@ackplus/react-tanstack-data-table
Version:
A powerful React data table component built with MUI and TanStack Table
1,059 lines • 79.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataTable = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
* Main DataTable Component
*
* A comprehensive, highly customizable data table component built with:
* - Material-UI (MUI) for styling
* - TanStack Table for table logic
* - TypeScript for type safety
*/
const material_1 = require("@mui/material");
const react_table_1 = require("@tanstack/react-table");
// Import custom features
const column_filter_feature_1 = require("./features/column-filter.feature");
const features_1 = require("./features");
const react_virtual_1 = require("@tanstack/react-virtual");
const react_1 = __importStar(require("react"));
// Import from new organized structure
const data_table_context_1 = require("./contexts/data-table-context");
const utils_1 = require("./utils");
const debounced_fetch_utils_1 = require("./utils/debounced-fetch.utils");
const slot_helpers_1 = require("./utils/slot-helpers");
const headers_1 = require("./components/headers");
const pagination_1 = require("./components/pagination");
const rows_1 = require("./components/rows");
const toolbar_1 = require("./components/toolbar");
const special_columns_utils_1 = require("./utils/special-columns.utils");
// Static default initial state - defined outside component
const DEFAULT_INITIAL_STATE = {
sorting: [],
pagination: {
pageIndex: 0,
pageSize: 10,
},
selectionState: { ids: [], type: 'include' },
globalFilter: '',
expanded: {},
columnOrder: [],
columnPinning: {
left: [],
right: [],
},
columnVisibility: {},
columnSizing: {},
columnFilter: {
filters: [],
logic: 'AND',
pendingFilters: [],
pendingLogic: 'AND',
},
};
/**
* Main DataTable component with all features
*/
exports.DataTable = (0, react_1.forwardRef)(function DataTable({ initialState, columns, data = [], totalRow = 0, idKey = 'id', extraFilter = null, footerFilter = null,
// Data management mode (MUI DataGrid style)
dataMode = 'client', initialLoadData = true, onFetchData, onDataStateChange,
// Selection props
enableRowSelection = false, enableMultiRowSelection = true, selectMode = 'page', isRowSelectable, onSelectionChange,
// Row click props
onRowClick, selectOnRowClick = false,
// Bulk action props
enableBulkActions = false, bulkActions,
// Column resizing props
enableColumnResizing = false, columnResizeMode = 'onChange', onColumnSizingChange,
// Column ordering props
enableColumnDragging = false, onColumnDragEnd,
// Column pinning props
enableColumnPinning = false, onColumnPinningChange,
// Column visibility props
onColumnVisibilityChange, enableColumnVisibility = true,
// Expandable rows props
enableExpanding = false, getRowCanExpand, renderSubComponent,
// Pagination props
enablePagination = false, paginationMode = 'client',
// Filtering props
enableGlobalFilter = true, enableColumnFilter = false, filterMode = 'client',
// Sorting props
enableSorting = true, sortingMode = 'client', onSortingChange, exportFilename = 'export', onExportProgress, onExportComplete, onExportError, onServerExport, onExportCancel,
// Styling props
enableHover = true, enableStripes = false, tableProps = {}, fitToScreen = false, tableSize: initialTableSize = 'medium',
// Sticky header/footer props
enableStickyHeaderOrFooter = false, maxHeight = '400px',
// Virtualization props
enableVirtualization = false, estimateRowHeight = 52,
// Toolbar props
enableTableSizeControl = true, enableExport = false, enableReset = true,
// Loading and empty states
loading = false, emptyMessage = 'No data available', skeletonRows = 5,
// Column filters props
onColumnFiltersChange, onPaginationChange, onGlobalFilterChange,
// Slots
slots = {}, slotProps = {},
// Logging
logging, }, ref) {
// Convert mode-based props to boolean flags for internal use
const isServerMode = dataMode === 'server';
const isServerPagination = paginationMode === 'server' || isServerMode;
const isServerFiltering = filterMode === 'server' || isServerMode;
const isServerSorting = sortingMode === 'server' || isServerMode;
const logger = (0, react_1.useMemo)(() => (0, utils_1.createLogger)('DataTable', logging), [logging]);
(0, react_1.useEffect)(() => {
if (logger.isLevelEnabled('info')) {
logger.info('mounted', {
dataMode,
paginationMode,
filterMode,
sortingMode,
});
}
return () => {
if (logger.isLevelEnabled('info')) {
logger.info('unmounted');
}
};
}, [logger, dataMode, paginationMode, filterMode, sortingMode]);
// -------------------------------
// Memoized values (grouped together)
// -------------------------------
const initialStateConfig = (0, react_1.useMemo)(() => {
const config = {
...DEFAULT_INITIAL_STATE,
...initialState,
};
if (logger.isLevelEnabled('info')) {
logger.info('initialStateConfig', { config });
}
return config;
}, [initialState, logger]);
const initialSelectionState = (0, react_1.useMemo)(() => {
return initialStateConfig.selectionState || DEFAULT_INITIAL_STATE.selectionState;
}, [initialStateConfig.selectionState]);
// -------------------------------
// State hooks (grouped together)
// -------------------------------
// const [fetchLoading, setFetchLoading] = useState(false);
const [sorting, setSorting] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.sorting) || DEFAULT_INITIAL_STATE.sorting);
const [pagination, setPagination] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.pagination) || DEFAULT_INITIAL_STATE.pagination);
const [globalFilter, setGlobalFilter] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.globalFilter) || DEFAULT_INITIAL_STATE.globalFilter);
const [selectionState, setSelectionState] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.selectionState) || DEFAULT_INITIAL_STATE.selectionState);
const [columnFilter, setColumnFilter] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.columnFilter) || DEFAULT_INITIAL_STATE.columnFilter);
const [expanded, setExpanded] = (0, react_1.useState)({});
const [tableSize, setTableSize] = (0, react_1.useState)(initialTableSize || 'medium');
const [columnOrder, setColumnOrder] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.columnOrder) || DEFAULT_INITIAL_STATE.columnOrder);
const [columnPinning, setColumnPinning] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.columnPinning) || DEFAULT_INITIAL_STATE.columnPinning);
const [columnVisibility, setColumnVisibility] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.columnVisibility) || DEFAULT_INITIAL_STATE.columnVisibility);
const [columnSizing, setColumnSizing] = (0, react_1.useState)((initialState === null || initialState === void 0 ? void 0 : initialState.columnSizing) || DEFAULT_INITIAL_STATE.columnSizing);
const [serverData, setServerData] = (0, react_1.useState)(null);
const [serverTotal, setServerTotal] = (0, react_1.useState)(0);
const [exportController, setExportController] = (0, react_1.useState)(null);
// -------------------------------
// Ref hooks (grouped together)
// -------------------------------
const tableContainerRef = (0, react_1.useRef)(null);
const internalApiRef = (0, react_1.useRef)(null);
const { debouncedFetch, isLoading: fetchLoading } = (0, debounced_fetch_utils_1.useDebouncedFetch)(onFetchData);
const tableData = (0, react_1.useMemo)(() => serverData ? serverData : data, [serverData, data]);
const tableTotalRow = (0, react_1.useMemo)(() => serverData ? serverTotal : totalRow || data.length, [serverData, serverTotal, totalRow, data]);
const tableLoading = (0, react_1.useMemo)(() => onFetchData ? (loading || fetchLoading) : loading, [onFetchData, loading, fetchLoading]);
const enhancedColumns = (0, react_1.useMemo)(() => {
let columnsMap = [...columns];
if (enableExpanding) {
const expandingColumnMap = (0, special_columns_utils_1.createExpandingColumn)({
...((slotProps === null || slotProps === void 0 ? void 0 : slotProps.expandColumn) && typeof slotProps.expandColumn === 'object' ? slotProps.expandColumn : {}),
});
columnsMap = [expandingColumnMap, ...columnsMap];
}
if (enableRowSelection) {
const selectionColumnMap = (0, special_columns_utils_1.createSelectionColumn)({
...((slotProps === null || slotProps === void 0 ? void 0 : slotProps.selectionColumn) && typeof slotProps.selectionColumn === 'object' ? slotProps.selectionColumn : {}),
multiSelect: enableMultiRowSelection,
});
columnsMap = [selectionColumnMap, ...columnsMap];
}
const enhancedColumns = (0, utils_1.withIdsDeep)(columnsMap);
if (logger.isLevelEnabled('info')) {
logger.info('enhancedColumns', { enhancedColumns });
}
return enhancedColumns;
}, [columns, enableExpanding, enableRowSelection, logger, slotProps.expandColumn, slotProps.selectionColumn, enableMultiRowSelection]);
const isExporting = (0, react_1.useMemo)(() => exportController !== null, [exportController]);
const isSomeRowsSelected = (0, react_1.useMemo)(() => {
if (!enableBulkActions || !enableRowSelection)
return false;
if (selectionState.type === 'exclude') {
return selectionState.ids.length < tableTotalRow;
}
else {
return selectionState.ids.length > 0;
}
}, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
const selectedRowCount = (0, react_1.useMemo)(() => {
if (!enableBulkActions || !enableRowSelection)
return 0;
if (selectionState.type === 'exclude') {
return tableTotalRow - selectionState.ids.length;
}
else {
return selectionState.ids.length;
}
}, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
// -------------------------------
// Callback hooks (grouped together)
// -------------------------------
const fetchData = (0, react_1.useCallback)(async (overrides = {}) => {
var _a, _b;
if (!onFetchData) {
if (logger.isLevelEnabled('debug')) {
logger.debug('onFetchData not provided, skipping fetch', { overrides, columnFilter, sorting, pagination });
}
return;
}
const filters = {
globalFilter,
pagination,
columnFilter,
sorting,
...overrides,
};
if (logger.isLevelEnabled('info')) {
logger.info('Requesting data', { filters });
}
try {
const result = await debouncedFetch(filters);
if (logger.isLevelEnabled('info')) {
logger.info('Fetch resolved', {
rows: (_b = (_a = result === null || result === void 0 ? void 0 : result.data) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
total: result === null || result === void 0 ? void 0 : result.total,
});
}
if ((result === null || result === void 0 ? void 0 : result.data) && (result === null || result === void 0 ? void 0 : result.total) !== undefined) {
setServerData(result.data);
setServerTotal(result.total);
}
else if (logger.isLevelEnabled('warn')) {
logger.warn('Fetch handler returned unexpected shape', result);
}
return result;
}
catch (error) {
logger.error('Fetch failed', error);
throw error;
}
}, [
onFetchData,
globalFilter,
pagination,
columnFilter,
sorting,
debouncedFetch,
logger,
]);
const tableStateChange = (0, react_1.useCallback)((overrides = {}) => {
if (!onDataStateChange) {
if (logger.isLevelEnabled('debug')) {
logger.debug('No onDataStateChange handler registered; skipping state update notification', { overrides });
}
return;
}
const currentState = {
globalFilter,
columnFilter,
sorting,
pagination,
columnOrder,
columnPinning,
columnVisibility,
columnSizing,
...overrides,
};
if (logger.isLevelEnabled('debug')) {
logger.debug('Emitting tableStateChange', currentState);
}
onDataStateChange === null || onDataStateChange === void 0 ? void 0 : onDataStateChange(currentState);
}, [
onDataStateChange,
globalFilter,
columnFilter,
sorting,
pagination,
columnOrder,
columnPinning,
columnVisibility,
columnSizing,
logger,
]);
const handleSelectionStateChange = (0, react_1.useCallback)((updaterOrValue) => {
setSelectionState((prevState) => {
const newSelectionState = typeof updaterOrValue === 'function'
? updaterOrValue(prevState)
: updaterOrValue;
setTimeout(() => {
if (onSelectionChange) {
onSelectionChange(newSelectionState);
}
if (onDataStateChange) {
tableStateChange({ selectionState: newSelectionState });
}
}, 0);
return newSelectionState;
});
}, [onSelectionChange, onDataStateChange, tableStateChange]);
const handleColumnFilterStateChange = (0, react_1.useCallback)((filterState) => {
if (!filterState || typeof filterState !== 'object')
return;
setColumnFilter(filterState);
if (onColumnFiltersChange) {
setTimeout(() => onColumnFiltersChange(filterState), 0);
}
if (onDataStateChange) {
setTimeout(() => tableStateChange({ columnFilter: filterState }), 0);
}
}, [onColumnFiltersChange, onDataStateChange, tableStateChange]);
const resetPageToFirst = (0, react_1.useCallback)(() => {
if (logger.isLevelEnabled('info')) {
logger.info('Resetting to first page due to state change', {
previousPageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
});
}
const newPagination = { pageIndex: 0, pageSize: pagination.pageSize };
setPagination(newPagination);
onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(newPagination);
return newPagination;
}, [pagination, logger, onPaginationChange]);
const handleSortingChange = (0, react_1.useCallback)((updaterOrValue) => {
let newSorting = typeof updaterOrValue === 'function'
? updaterOrValue(sorting)
: updaterOrValue;
newSorting = newSorting.filter((sort) => sort.id);
setSorting(newSorting);
onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(newSorting);
if (logger.isLevelEnabled('debug')) {
logger.debug('Sorting change applied', {
sorting: newSorting,
serverMode: isServerMode,
serverSorting: isServerSorting,
});
}
if (isServerMode || isServerSorting) {
const pagination = resetPageToFirst();
if (logger.isLevelEnabled('debug')) {
logger.debug('Sorting change triggered server fetch', { pagination, sorting: newSorting });
}
tableStateChange({ sorting: newSorting, pagination });
fetchData({
sorting: newSorting,
pagination,
});
}
else if (onDataStateChange) {
const pagination = resetPageToFirst();
setTimeout(() => {
if (logger.isLevelEnabled('debug')) {
logger.debug('Sorting change notified client state change', { pagination, sorting: newSorting });
}
tableStateChange({ sorting: newSorting, pagination });
}, 0);
}
}, [sorting, onSortingChange, logger, isServerMode, isServerSorting, onDataStateChange, resetPageToFirst, tableStateChange, fetchData]);
const handleColumnOrderChange = (0, react_1.useCallback)((updatedColumnOrder) => {
const newColumnOrder = typeof updatedColumnOrder === 'function'
? updatedColumnOrder(columnOrder)
: updatedColumnOrder;
setColumnOrder(newColumnOrder);
if (onColumnDragEnd) {
onColumnDragEnd(newColumnOrder);
}
}, [onColumnDragEnd, columnOrder]);
const handleColumnPinningChange = (0, react_1.useCallback)((updatedColumnPinning) => {
const newColumnPinning = typeof updatedColumnPinning === 'function'
? updatedColumnPinning(columnPinning)
: updatedColumnPinning;
setColumnPinning(newColumnPinning);
if (onColumnPinningChange) {
onColumnPinningChange(newColumnPinning);
}
}, [onColumnPinningChange, columnPinning]);
// Column visibility change handler - same pattern as column order
const handleColumnVisibilityChange = (0, react_1.useCallback)((updater) => {
const newVisibility = typeof updater === 'function'
? updater(columnVisibility)
: updater;
setColumnVisibility(newVisibility);
if (onColumnVisibilityChange) {
setTimeout(() => {
onColumnVisibilityChange(newVisibility);
}, 0);
}
if (onDataStateChange) {
setTimeout(() => {
tableStateChange({ columnVisibility: newVisibility });
}, 0);
}
}, [onColumnVisibilityChange, onDataStateChange, tableStateChange, columnVisibility]);
// Column sizing change handler - same pattern as column order
const handleColumnSizingChange = (0, react_1.useCallback)((updater) => {
const newSizing = typeof updater === 'function'
? updater(columnSizing)
: updater;
setColumnSizing(newSizing);
if (onColumnSizingChange) {
setTimeout(() => {
onColumnSizingChange(newSizing);
}, 0);
}
if (onDataStateChange) {
setTimeout(() => {
tableStateChange({ columnSizing: newSizing });
}, 0);
}
}, [onColumnSizingChange, onDataStateChange, tableStateChange, columnSizing]);
const handlePaginationChange = (0, react_1.useCallback)((updater) => {
const newPagination = typeof updater === 'function' ? updater(pagination) : updater;
if (logger.isLevelEnabled('debug')) {
logger.debug('Pagination change requested', {
previous: pagination,
next: newPagination,
serverSide: isServerMode || isServerPagination,
});
}
// Update pagination state
setPagination(newPagination);
onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(newPagination);
if (logger.isLevelEnabled('debug')) {
logger.debug('Pagination state updated', newPagination);
}
// Notify state change and fetch data if needed
if (isServerMode || isServerPagination) {
setTimeout(() => {
if (logger.isLevelEnabled('debug')) {
logger.debug('Notifying server-side pagination change', newPagination);
}
tableStateChange({ pagination: newPagination });
fetchData({ pagination: newPagination });
}, 0);
}
else if (onDataStateChange) {
setTimeout(() => {
if (logger.isLevelEnabled('debug')) {
logger.debug('Notifying client-side pagination change', newPagination);
}
tableStateChange({ pagination: newPagination });
}, 0);
}
}, [
pagination,
isServerMode,
isServerPagination,
onDataStateChange,
fetchData,
tableStateChange,
logger,
onPaginationChange,
]);
const handleGlobalFilterChange = (0, react_1.useCallback)((updaterOrValue) => {
const newFilter = typeof updaterOrValue === 'function'
? updaterOrValue(globalFilter)
: updaterOrValue;
setGlobalFilter(newFilter);
if (logger.isLevelEnabled('debug')) {
logger.debug('Global filter change applied', {
value: newFilter,
serverMode: isServerMode,
serverFiltering: isServerFiltering,
});
}
if (isServerMode || isServerFiltering) {
const pagination = resetPageToFirst();
setTimeout(() => {
if (logger.isLevelEnabled('debug')) {
logger.debug('Global filter change triggering server fetch', {
pagination,
value: newFilter,
});
}
tableStateChange({ globalFilter: newFilter, pagination });
fetchData({ globalFilter: newFilter, pagination });
}, 0);
}
else if (onDataStateChange) {
const pagination = resetPageToFirst();
setTimeout(() => {
if (logger.isLevelEnabled('debug')) {
logger.debug('Global filter change notifying client listeners', {
pagination,
value: newFilter,
});
}
tableStateChange({ globalFilter: newFilter, pagination });
}, 0);
}
onGlobalFilterChange === null || onGlobalFilterChange === void 0 ? void 0 : onGlobalFilterChange(newFilter);
}, [globalFilter, logger, isServerMode, isServerFiltering, onDataStateChange, onGlobalFilterChange, resetPageToFirst, tableStateChange, fetchData]);
const onColumnFilterChangeHandler = (0, react_1.useCallback)((updater) => {
const currentState = columnFilter;
const newState = typeof updater === 'function'
? updater(currentState)
: updater;
const legacyFilterState = {
filters: newState.filters,
logic: newState.logic,
pendingFilters: newState.pendingFilters,
pendingLogic: newState.pendingLogic
};
handleColumnFilterStateChange(legacyFilterState);
}, [columnFilter, handleColumnFilterStateChange]);
const onColumnFilterApplyHandler = (0, react_1.useCallback)((appliedState) => {
const pagination = resetPageToFirst();
if (isServerFiltering) {
tableStateChange({
columnFilter: appliedState,
pagination,
});
fetchData({
columnFilter: appliedState,
pagination,
});
}
else if (onDataStateChange) {
setTimeout(() => tableStateChange({ columnFilter: appliedState, pagination }), 0);
}
setTimeout(() => {
onColumnFiltersChange === null || onColumnFiltersChange === void 0 ? void 0 : onColumnFiltersChange(appliedState);
}, 0);
}, [resetPageToFirst, isServerFiltering, onDataStateChange, tableStateChange, fetchData, onColumnFiltersChange]);
// -------------------------------
// Table creation (after callbacks/memo)
// -------------------------------
const table = (0, react_table_1.useReactTable)({
_features: [column_filter_feature_1.ColumnFilterFeature, features_1.SelectionFeature],
data: tableData,
columns: enhancedColumns,
// Use merged initial state so built-in reset helpers align with our controlled state defaults
initialState: initialStateConfig,
state: {
...(enableSorting ? { sorting } : {}),
...(enablePagination ? { pagination } : {}),
...(enableGlobalFilter ? { globalFilter } : {}),
...(enableExpanding ? { expanded } : {}),
...(enableColumnDragging ? { columnOrder } : {}),
...(enableColumnPinning ? { columnPinning } : {}),
...(enableColumnVisibility ? { columnVisibility } : {}),
...(enableColumnResizing ? { columnSizing } : {}),
...(enableColumnFilter ? { columnFilter } : {}),
...(enableRowSelection ? { selectionState } : {}),
},
// Selection options (same pattern as column filter)
// Add custom features
selectMode: selectMode,
enableAdvanceSelection: !!enableRowSelection,
isRowSelectable: isRowSelectable,
...(enableRowSelection ? { onSelectionStateChange: handleSelectionStateChange } : {}),
// Column filter
enableAdvanceColumnFilter: enableColumnFilter,
onColumnFilterChange: onColumnFilterChangeHandler, // Handle column filters change
onColumnFilterApply: onColumnFilterApplyHandler, // Handle when filters are actually applied
...(enableSorting ? { onSortingChange: handleSortingChange } : {}),
...(enablePagination ? { onPaginationChange: handlePaginationChange } : {}),
...(enableGlobalFilter ? { onGlobalFilterChange: handleGlobalFilterChange } : {}),
...(enableExpanding ? { onExpandedChange: setExpanded } : {}),
...(enableColumnDragging ? { onColumnOrderChange: handleColumnOrderChange } : {}),
...(enableColumnPinning ? { onColumnPinningChange: handleColumnPinningChange } : {}),
...(enableColumnVisibility ? { onColumnVisibilityChange: handleColumnVisibilityChange } : {}),
...(enableColumnResizing ? { onColumnSizingChange: handleColumnSizingChange } : {}),
// Row model
getCoreRowModel: (0, react_table_1.getCoreRowModel)(),
...(enableSorting ? { getSortedRowModel: (0, react_table_1.getSortedRowModel)() } : {}),
...(enableColumnFilter || enableGlobalFilter ? { getFilteredRowModel: (0, column_filter_feature_1.getCombinedFilteredRowModel)() } : {}),
...(enablePagination ? { getPaginationRowModel: (0, react_table_1.getPaginationRowModel)() } : {}),
// Sorting
enableSorting: enableSorting,
manualSorting: isServerSorting,
// Filtering
manualFiltering: isServerFiltering,
// Column resizing
enableColumnResizing: enableColumnResizing,
columnResizeMode: columnResizeMode,
// Column pinning
enableColumnPinning: enableColumnPinning,
// Expanding
...(enableExpanding ? { getRowCanExpand: getRowCanExpand } : {}),
// Pagination
manualPagination: isServerPagination,
autoResetPageIndex: false, // Prevent automatic page reset on state changes
// pageCount: enablePagination ? Math.ceil(tableTotalRow / pagination.pageSize) : -1,
rowCount: enablePagination ? (tableTotalRow !== null && tableTotalRow !== void 0 ? tableTotalRow : tableData.length) : tableData.length,
// Row ID
getRowId: (row, index) => (0, utils_1.generateRowId)(row, index, idKey),
// Debug
debugAll: false, // Disabled for production
});
// Compute width after table is created so column resizing is safe and reflects changes
const tableWidth = (0, react_1.useMemo)(() => {
if (fitToScreen) {
return '100%';
}
if (enableColumnResizing) {
return table.getCenterTotalSize();
}
return '100%';
}, [fitToScreen, enableColumnResizing, table]);
const tableStyle = (0, react_1.useMemo)(() => ({
width: tableWidth,
minWidth: '100%',
}), [tableWidth]);
// -------------------------------
// Virtualization and row memo
// -------------------------------
// Note: globalFilter is needed in dependencies to trigger recalculation when filter changes
// The table object is stable, so we need to depend on the filter state directly
const rows = (0, react_1.useMemo)(() => {
const rowModel = table.getRowModel();
return (rowModel === null || rowModel === void 0 ? void 0 : rowModel.rows) || [];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [table, tableData, globalFilter, enableGlobalFilter, enableColumnFilter, enablePagination]);
const rowVirtualizer = (0, react_virtual_1.useVirtualizer)({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => estimateRowHeight,
overscan: 10,
enabled: enableVirtualization && !enablePagination && rows.length > 0,
});
// -------------------------------
// Callbacks (after table creation)
// -------------------------------
const handleColumnReorder = (0, react_1.useCallback)((draggedColumnId, targetColumnId) => {
const currentOrder = columnOrder.length > 0 ? columnOrder : enhancedColumns.map((col, index) => {
if (col.id)
return col.id;
const anyCol = col;
if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
return anyCol.accessorKey;
}
return `column_${index}`;
});
const draggedIndex = currentOrder.indexOf(draggedColumnId);
const targetIndex = currentOrder.indexOf(targetColumnId);
if (draggedIndex === -1 || targetIndex === -1)
return;
const newOrder = [...currentOrder];
newOrder.splice(draggedIndex, 1);
newOrder.splice(targetIndex, 0, draggedColumnId);
handleColumnOrderChange(newOrder);
}, [columnOrder, enhancedColumns, handleColumnOrderChange]);
// -------------------------------
// Effects (after callbacks)
// -------------------------------
(0, react_1.useEffect)(() => {
if (initialLoadData && onFetchData) {
if (logger.isLevelEnabled('info')) {
logger.info('Initial data load triggered', { initialLoadData });
}
fetchData();
}
else if (logger.isLevelEnabled('debug')) {
logger.debug('Skipping initial data load', {
initialLoadData,
hasOnFetchData: !!onFetchData
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
(0, react_1.useEffect)(() => {
if (enableColumnDragging && columnOrder.length === 0) {
const initialOrder = enhancedColumns.map((col, index) => {
if (col.id)
return col.id;
const anyCol = col;
if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
return anyCol.accessorKey;
}
return `column_${index}`;
});
setColumnOrder(initialOrder);
}
}, [enableColumnDragging, enhancedColumns, columnOrder.length]);
const dataTableApi = (0, react_1.useMemo)(() => ({
table: {
getTable: () => table,
},
// Column Management
columnVisibility: {
showColumn: (columnId) => {
var _a;
(_a = table.getColumn(columnId)) === null || _a === void 0 ? void 0 : _a.toggleVisibility(true);
},
hideColumn: (columnId) => {
var _a;
(_a = table.getColumn(columnId)) === null || _a === void 0 ? void 0 : _a.toggleVisibility(false);
},
toggleColumn: (columnId) => {
var _a;
(_a = table.getColumn(columnId)) === null || _a === void 0 ? void 0 : _a.toggleVisibility();
},
showAllColumns: () => {
table.toggleAllColumnsVisible(true);
},
hideAllColumns: () => {
table.toggleAllColumnsVisible(false);
},
resetColumnVisibility: () => {
const initialVisibility = initialStateConfig.columnVisibility || {};
table.setColumnVisibility(initialVisibility);
// Manually trigger handler to ensure callbacks are called
handleColumnVisibilityChange(initialVisibility);
},
},
// Column Ordering
columnOrdering: {
setColumnOrder: (columnOrder) => {
table.setColumnOrder(columnOrder);
},
moveColumn: (columnId, toIndex) => {
const currentOrder = table.getState().columnOrder || [];
const currentIndex = currentOrder.indexOf(columnId);
if (currentIndex === -1)
return;
const newOrder = [...currentOrder];
newOrder.splice(currentIndex, 1);
newOrder.splice(toIndex, 0, columnId);
table.setColumnOrder(newOrder);
},
resetColumnOrder: () => {
const initialOrder = enhancedColumns.map((col, index) => {
if (col.id)
return col.id;
const anyCol = col;
if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
return anyCol.accessorKey;
}
return `column_${index}`;
});
table.setColumnOrder(initialOrder);
// Manually trigger handler to ensure callbacks are called
handleColumnOrderChange(initialOrder);
},
},
// Column Pinning
columnPinning: {
pinColumnLeft: (columnId) => {
const currentPinning = table.getState().columnPinning;
const newPinning = { ...currentPinning };
// Remove from right if exists
newPinning.right = (newPinning.right || []).filter(id => id !== columnId);
// Add to left if not exists
newPinning.left = [...(newPinning.left || []).filter(id => id !== columnId), columnId];
table.setColumnPinning(newPinning);
},
pinColumnRight: (columnId) => {
const currentPinning = table.getState().columnPinning;
const newPinning = { ...currentPinning };
// Remove from left if exists
newPinning.left = (newPinning.left || []).filter(id => id !== columnId);
// Add to right if not exists - prepend to beginning (appears rightmost to leftmost)
// First column pinned appears rightmost, second appears to its left, etc.
newPinning.right = [columnId, ...(newPinning.right || []).filter(id => id !== columnId)];
table.setColumnPinning(newPinning);
},
unpinColumn: (columnId) => {
const currentPinning = table.getState().columnPinning;
const newPinning = {
left: (currentPinning.left || []).filter(id => id !== columnId),
right: (currentPinning.right || []).filter(id => id !== columnId),
};
table.setColumnPinning(newPinning);
},
setPinning: (pinning) => {
table.setColumnPinning(pinning);
},
resetColumnPinning: () => {
const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
table.setColumnPinning(initialPinning);
// Manually trigger handler to ensure callbacks are called
handleColumnPinningChange(initialPinning);
},
},
// Column Resizing
columnResizing: {
resizeColumn: (columnId, width) => {
// Use table's setColumnSizing method
const currentSizing = table.getState().columnSizing;
table.setColumnSizing({
...currentSizing,
[columnId]: width,
});
},
autoSizeColumn: (columnId) => {
var _a;
// TanStack doesn't have built-in auto-size, so reset to default
(_a = table.getColumn(columnId)) === null || _a === void 0 ? void 0 : _a.resetSize();
},
autoSizeAllColumns: () => {
const initialSizing = initialStateConfig.columnSizing || {};
table.setColumnSizing(initialSizing);
// Manually trigger handler to ensure callbacks are called
handleColumnSizingChange(initialSizing);
},
resetColumnSizing: () => {
const initialSizing = initialStateConfig.columnSizing || {};
table.setColumnSizing(initialSizing);
// Manually trigger handler to ensure callbacks are called
handleColumnSizingChange(initialSizing);
},
},
// Filtering
filtering: {
setGlobalFilter: (filter) => {
table.setGlobalFilter(filter);
},
clearGlobalFilter: () => {
table.setGlobalFilter('');
},
setColumnFilters: (filters) => {
handleColumnFilterStateChange(filters);
},
addColumnFilter: (columnId, operator, value) => {
const newFilter = {
id: `filter_${Date.now()}`,
columnId,
operator,
value,
};
const columnFilter = table.getState().columnFilter;
const currentFilters = columnFilter.filters || [];
const newFilters = [...currentFilters, newFilter];
handleColumnFilterStateChange({
filters: newFilters,
logic: columnFilter.logic,
pendingFilters: columnFilter.pendingFilters || [],
pendingLogic: columnFilter.pendingLogic || 'AND',
});
if (logger.isLevelEnabled('debug')) {
logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, newFilters);
}
},
removeColumnFilter: (filterId) => {
const columnFilter = table.getState().columnFilter;
const currentFilters = columnFilter.filters || [];
const newFilters = currentFilters.filter((f) => f.id !== filterId);
handleColumnFilterStateChange({
filters: newFilters,
logic: columnFilter.logic,
pendingFilters: columnFilter.pendingFilters || [],
pendingLogic: columnFilter.pendingLogic || 'AND',
});
if (logger.isLevelEnabled('debug')) {
logger.debug(`Removing column filter ${filterId}`, newFilters);
}
},
clearAllFilters: () => {
table.setGlobalFilter('');
handleColumnFilterStateChange({
filters: [],
logic: 'AND',
pendingFilters: [],
pendingLogic: 'AND',
});
},
resetFilters: () => {
handleColumnFilterStateChange({
filters: [],
logic: 'AND',
pendingFilters: [],
pendingLogic: 'AND',
});
if (logger.isLevelEnabled('debug')) {
logger.debug('Resetting filters');
}
},
},
// Sorting
sorting: {
setSorting: (sortingState) => {
table.setSorting(sortingState);
if (logger.isLevelEnabled('debug')) {
logger.debug(`Setting sorting`, sortingState);
}
},
sortColumn: (columnId, direction) => {
const column = table.getColumn(columnId);
if (!column)
return;
if (direction === false) {
column.clearSorting();
}
else {
column.toggleSorting(direction === 'desc');
}
},
clearSorting: () => {
table.setSorting([]);
// Manually trigger handler to ensure callbacks are called
handleSortingChange([]);
},
resetSorting: () => {
const initialSorting = initialStateConfig.sorting || [];
table.setSorting(initialSorting);
// Manually trigger handler to ensure callbacks are called
handleSortingChange(initialSorting);
},
},
// Pagination
pagination: {
goToPage: (pageIndex) => {
table.setPageIndex(pageIndex);
if (logger.isLevelEnabled('debug')) {
logger.debug(`Going to page ${pageIndex}`);
}
},
nextPage: () => {
table.nextPage();
if (logger.isLevelEnabled('debug')) {
logger.debug('Next page');
}
},
previousPage: () => {
table.previousPage();
if (logger.isLevelEnabled('debug')) {
logger.debug('Previous page');
}
},
setPageSize: (pageSize) => {
table.setPageSize(pageSize);
if (logger.isLevelEnabled('debug')) {
logger.debug(`Setting page size to ${pageSize}`);
}
},
goToFirstPage: () => {
table.setPageIndex(0);
if (logger.isLevelEnabled('debug')) {
logger.debug('Going to first page');
}
},
goToLastPage: () => {
const pageCount = table.getPageCount();
if (pageCount > 0) {
table.setPageIndex(pageCount - 1);
if (logger.isLevelEnabled('debug')) {
logger.debug(`Going to last page ${pageCount - 1}`);
}
}
},
resetPagination: () => {
const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
table.setPagination(initialPagination);
// Manually trigger handler to ensure callbacks are called
handlePaginationChange(initialPagination);
},
},
// Access via table methods: table.selectRow(), table.getIsRowSelected(), etc.
selection: {
selectRow: (rowId) => { var _a; return (_a = table.selectRow) === null || _a === void 0 ? void 0 : _a.call(table, rowId); },
deselectRow: (rowId) => { var _a; return (_a = table.deselectRow) === null || _a === void 0 ? void 0 : _a.call(table, rowId); },
toggleRowSelection: (rowId) => { var _a; return (_a = table.toggleRowSelected) === null || _a === void 0 ? void 0 : _a.call(table, rowId); },
selectAll: () => { var _a; return (_a = table.selectAll) === null || _a === void 0 ? void 0 : _a.call(table); },
deselectAll: () => { var _a; return (_a = table.deselectAll) === null || _a === void 0 ? void 0 : _a.call(table); },
toggleSelectAll: () => { var _a; return (_a = table.toggleAllRowsSelected) === null || _a === void 0 ? void 0 : _a.call(table); },
getSelectionState: () => { var _a; return ((_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table)) || { ids: [], type: 'include' }; },
getSelectedRows: () => table.getSelectedRows(),
getSelectedCount: () => table.getSelectedCount(),
isRowSelected: (rowId) => table.getIsRowSelected(rowId) || false,
},
// Data Management
data: {
refresh: (resetPagination = false) => {
var _a, _b, _c;
const filters = table.getState();
const pagination = {
pageIndex: resetPagination ? 0 : ((_a = initialStateConfig.pagination) === null || _a === void 0 ? void 0 : _a.pageIndex) || 0,
pageSize: ((_b = filters.pagination) === null || _b === void 0 ? void 0 : _b.pageSize) || ((_c = initialStateConfig.pagination) === null || _c === void 0 ? void 0 : _c.pageSize) || 10,
};
const allState = table.getState();
setPagination(pagination);
onDataStateChange === null || onDataStateChange === void 0 ? void 0 : onDataStateChange({ ...allState, pagination });
fetchData === null || fetchData === void 0 ? void 0 : fetchData({ pagination });
if (logger.isLevelEnabled('debug')) {
logger.debug('Refreshing data using Ref', { pagination, allState });
}
},
reload: () => {
const allState = table.getState();
onDataStateChange === null || onDataStateChange === void 0 ? void 0 : onDataStateChange(allState);
onFetchData === null || onFetchData === void 0 ? void 0 : onFetchData({});
if (logger.isLevelEnabled('debug')) {
logger.info('Reloading data', allState);
}
},
// Data CRUD operations
getAllData: () => {
var _a;
return ((_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map(row => row.original)) || [];
},
getRowData: (rowId) => {
var _a, _b;
return (_b = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.find(row => String(row.original[idKey]) === rowId)) === null || _b === void 0 ? void 0 : _b.original;
},
getRowByIndex: (index) => {
var _a, _b;
return (_b = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a[index]) === null || _b === void 0 ? void 0 : _b.original;
},
updateRow: (rowId, updates) => {
var _a;
const newData = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map(row => String(row.original[idKey]) === rowId
? {
...row.original,
...updates,
}
: row.original);
setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData || []);
if (logger.isLevelEnabled('debug')) {
logger.debug(`Updating row ${rowId}`, updates);
}
},
updateRowByIndex: (index, updates) => {
var _a;
const newData = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map(row => row.original);
if (newData === null || newData === void 0 ? void 0 : newData[index]) {
newData[index] = {
...newData[index],
...updates,
};
setServerData(newData);
if (logger.isLevelEnabled('debug'))