UNPKG

@ackplus/react-tanstack-data-table

Version:

A powerful React data table component built with MUI and TanStack Table

1,059 lines 79.4 kB
"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'))