@ackplus/react-tanstack-data-table 1.1.17 → 1.1.18

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
- import { IconButtonProps, SxProps } from '@mui/material';
2
- import { ReactElement } from 'react';
1
+ import { type IconButtonProps, type SxProps } from "@mui/material";
2
+ import { type ReactElement } from "react";
3
3
  export interface ColumnFilterControlProps {
4
4
  title?: string;
5
5
  titleSx?: SxProps;
@@ -9,6 +9,7 @@ export interface ColumnFilterControlProps {
9
9
  clearButtonProps?: any;
10
10
  applyButtonProps?: any;
11
11
  addButtonProps?: any;
12
+ deleteButtonProps?: any;
12
13
  logicSelectProps?: any;
13
14
  [key: string]: any;
14
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"column-filter-control.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/toolbar/column-filter-control.tsx"],"names":[],"mappings":"AACA,OAAO,EAYH,eAAe,EACf,OAAO,EACV,MAAM,eAAe,CAAC;AACvB,OAAc,EAAmC,YAAY,EAAE,MAAM,OAAO,CAAC;AAc7E,MAAM,WAAW,wBAAwB;IAErC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,wBAA6B,GAAG,YAAY,CAyUtF"}
1
+ {"version":3,"file":"column-filter-control.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/toolbar/column-filter-control.tsx"],"names":[],"mappings":"AACA,OAAO,EAYH,KAAK,eAAe,EACpB,KAAK,OAAO,EACf,MAAM,eAAe,CAAC;AACvB,OAAc,EAMV,KAAK,YAAY,EACpB,MAAM,OAAO,CAAC;AAWf,MAAM,WAAW,wBAAwB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,GAAG,CAAC;IAEjB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,iBAAiB,CAAC,EAAE,GAAG,CAAC;IACxB,gBAAgB,CAAC,EAAE,GAAG,CAAC;IAEvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAiBD,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,wBAA6B,GAAG,YAAY,CA6WtF"}
@@ -45,43 +45,49 @@ const column_helpers_1 = require("../../utils/column-helpers");
45
45
  const slot_helpers_1 = require("../../utils/slot-helpers");
46
46
  const filters_1 = require("../filters");
47
47
  const filter_value_input_1 = require("../filters/filter-value-input");
48
+ /**
49
+ * Small helper component to sync MenuDropdown open state to parent state
50
+ * WITHOUT calling hooks inside render-prop callback.
51
+ */
52
+ function OpenStateSync({ open, onChange, }) {
53
+ (0, react_1.useEffect)(() => onChange(open), [open, onChange]);
54
+ return null;
55
+ }
48
56
  function ColumnFilterControl(props = {}) {
49
57
  var _a, _b, _c;
50
58
  const { table, slots, slotProps } = (0, data_table_context_1.useDataTableContext)();
51
- // Extract slot-specific props with enhanced merging
52
- const iconSlotProps = (0, slot_helpers_1.extractSlotProps)(slotProps, 'filterIcon');
53
- const FilterIconSlot = (0, slot_helpers_1.getSlotComponent)(slots, 'filterIcon', icons_material_1.FilterList);
54
- // Use the custom feature state from the table - now using pending filters for UI
59
+ const iconSlotProps = (0, slot_helpers_1.extractSlotProps)(slotProps, "filterIcon");
60
+ const FilterIconSlot = (0, slot_helpers_1.getSlotComponent)(slots, "filterIcon", icons_material_1.FilterList);
61
+ const [isMenuOpen, setIsMenuOpen] = (0, react_1.useState)(false);
62
+ const didAutoAddRef = (0, react_1.useRef)(false);
55
63
  const filterState = ((_a = table === null || table === void 0 ? void 0 : table.getColumnFilterState) === null || _a === void 0 ? void 0 : _a.call(table)) || {
56
64
  filters: [],
57
- logic: 'AND',
65
+ logic: "AND",
58
66
  pendingFilters: [],
59
- pendingLogic: 'AND'
67
+ pendingLogic: "AND",
60
68
  };
61
- // Use pending filters for the UI (draft state)
62
- const filters = filterState.pendingFilters;
63
- const filterLogic = filterState.pendingLogic;
64
- // Active filters are the actual applied filters
69
+ const filters = filterState.pendingFilters || [];
70
+ const filterLogic = (filterState.pendingLogic || "AND");
65
71
  const activeFiltersCount = ((_c = (_b = table === null || table === void 0 ? void 0 : table.getActiveColumnFilters) === null || _b === void 0 ? void 0 : _b.call(table)) === null || _c === void 0 ? void 0 : _c.length) || 0;
66
72
  const filterableColumns = (0, react_1.useMemo)(() => {
67
- return table === null || table === void 0 ? void 0 : table.getAllLeafColumns().filter(column => (0, column_helpers_1.isColumnFilterable)(column));
73
+ return (table === null || table === void 0 ? void 0 : table.getAllLeafColumns().filter((column) => (0, column_helpers_1.isColumnFilterable)(column))) || [];
68
74
  }, [table]);
75
+ const getOperatorsForColumn = (0, react_1.useCallback)((columnId) => {
76
+ const column = filterableColumns.find((col) => col.id === columnId);
77
+ const type = (0, column_helpers_1.getColumnType)(column);
78
+ return filters_1.FILTER_OPERATORS[type] || filters_1.FILTER_OPERATORS.text;
79
+ }, [filterableColumns]);
69
80
  const addFilter = (0, react_1.useCallback)((columnId, operator) => {
70
81
  var _a, _b;
71
- // If no column specified, use empty (user will select)
72
- // If column specified, get its appropriate default operator
73
- let defaultOperator = operator || '';
82
+ let defaultOperator = operator || "";
74
83
  if (columnId && !operator) {
75
- const column = filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.find(col => col.id === columnId);
84
+ const column = filterableColumns.find((col) => col.id === columnId);
76
85
  const columnType = (0, column_helpers_1.getColumnType)(column);
77
86
  const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
78
- defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || 'contains';
87
+ defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || "contains";
79
88
  }
80
- (_b = table === null || table === void 0 ? void 0 : table.addPendingColumnFilter) === null || _b === void 0 ? void 0 : _b.call(table, columnId || '', defaultOperator, '');
89
+ (_b = table === null || table === void 0 ? void 0 : table.addPendingColumnFilter) === null || _b === void 0 ? void 0 : _b.call(table, columnId || "", defaultOperator, "");
81
90
  }, [table, filterableColumns]);
82
- const handleAddFilter = (0, react_1.useCallback)(() => {
83
- addFilter();
84
- }, [addFilter]);
85
91
  const updateFilter = (0, react_1.useCallback)((filterId, updates) => {
86
92
  var _a;
87
93
  (_a = table === null || table === void 0 ? void 0 : table.updatePendingColumnFilter) === null || _a === void 0 ? void 0 : _a.call(table, filterId, updates);
@@ -91,117 +97,110 @@ function ColumnFilterControl(props = {}) {
91
97
  (_a = table === null || table === void 0 ? void 0 : table.removePendingColumnFilter) === null || _a === void 0 ? void 0 : _a.call(table, filterId);
92
98
  }, [table]);
93
99
  const clearAllFilters = (0, react_1.useCallback)((closeDialog) => {
94
- var _a;
95
- // Clear all pending filters
96
- (_a = table === null || table === void 0 ? void 0 : table.clearAllPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
97
- // Immediately apply the clear (which will clear active filters too)
100
+ // Defer all work to avoid long-running click handler (prevents "[Violation] 'click' handler took Xms")
98
101
  setTimeout(() => {
99
102
  var _a;
100
- (_a = table === null || table === void 0 ? void 0 : table.applyPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
101
- // Close dialog if callback provided
102
- if (closeDialog) {
103
- closeDialog();
104
- }
103
+ (_a = table === null || table === void 0 ? void 0 : table.resetColumnFilter) === null || _a === void 0 ? void 0 : _a.call(table);
104
+ // Prevent auto-add effect from adding a row when it sees empty state after clear
105
+ didAutoAddRef.current = true;
106
+ closeDialog === null || closeDialog === void 0 ? void 0 : closeDialog();
105
107
  }, 0);
106
108
  }, [table]);
107
- // Handle filter logic change (AND/OR)
108
109
  const handleLogicChange = (0, react_1.useCallback)((newLogic) => {
109
110
  var _a;
110
111
  (_a = table === null || table === void 0 ? void 0 : table.setPendingFilterLogic) === null || _a === void 0 ? void 0 : _a.call(table, newLogic);
111
112
  }, [table]);
112
- // Apply all pending filters
113
113
  const applyFilters = (0, react_1.useCallback)(() => {
114
114
  var _a;
115
115
  (_a = table === null || table === void 0 ? void 0 : table.applyPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
116
116
  }, [table]);
117
- // Handle apply button click
118
117
  const handleApplyFilters = (0, react_1.useCallback)((closeDialog) => {
119
- applyFilters();
120
- closeDialog();
118
+ // Defer so click handler returns immediately (prevents "[Violation] 'click' handler took Xms")
119
+ setTimeout(() => {
120
+ applyFilters();
121
+ closeDialog();
122
+ }, 0);
121
123
  }, [applyFilters]);
122
- const getOperatorsForColumn = (0, react_1.useCallback)((columnId) => {
123
- const column = filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.find(col => col.id === columnId);
124
- const type = (0, column_helpers_1.getColumnType)(column);
125
- return filters_1.FILTER_OPERATORS[type] || filters_1.FILTER_OPERATORS.text;
126
- }, [filterableColumns]);
127
- // Handle column selection change
128
124
  const handleColumnChange = (0, react_1.useCallback)((filterId, newColumnId, currentFilter) => {
129
125
  var _a;
130
- const newColumn = filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.find(col => col.id === newColumnId);
126
+ const newColumn = filterableColumns.find((col) => col.id === newColumnId);
131
127
  const columnType = (0, column_helpers_1.getColumnType)(newColumn);
132
128
  const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
133
- // Only reset operator if current operator is not valid for new column type
134
- const currentOperatorValid = operators.some(op => op.value === currentFilter.operator);
135
- const newOperator = currentOperatorValid ? currentFilter.operator : ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || '';
129
+ const currentOperatorValid = operators.some((op) => op.value === currentFilter.operator);
130
+ const newOperator = currentOperatorValid ? currentFilter.operator : ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || "";
136
131
  updateFilter(filterId, {
137
132
  columnId: newColumnId,
138
133
  operator: newOperator,
139
- // Keep the current value unless operator is empty/notEmpty
140
- value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
134
+ value: ["isEmpty", "isNotEmpty"].includes(newOperator) ? "" : currentFilter.value,
141
135
  });
142
136
  }, [filterableColumns, updateFilter]);
143
- // Handle operator selection change
144
137
  const handleOperatorChange = (0, react_1.useCallback)((filterId, newOperator, currentFilter) => {
145
138
  updateFilter(filterId, {
146
139
  operator: newOperator,
147
- // Only reset value if operator is empty/notEmpty, otherwise preserve it
148
- value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
140
+ value: ["isEmpty", "isNotEmpty"].includes(newOperator) ? "" : currentFilter.value,
149
141
  });
150
142
  }, [updateFilter]);
151
- // Handle filter value change
152
143
  const handleFilterValueChange = (0, react_1.useCallback)((filterId, value) => {
153
144
  updateFilter(filterId, { value });
154
145
  }, [updateFilter]);
155
- // Handle filter removal
156
- const handleRemoveFilter = (0, react_1.useCallback)((filterId) => {
157
- removeFilter(filterId);
158
- }, [removeFilter]);
159
- // Count pending filters that are ready to apply (have column, operator, and value OR are empty/notEmpty operators)
160
- const pendingFiltersCount = filters.filter(f => {
161
- if (!f.columnId || !f.operator)
162
- return false;
163
- // For empty/notEmpty operators, no value is needed
164
- if (['isEmpty', 'isNotEmpty'].includes(f.operator))
165
- return true;
166
- // For other operators, value is required
167
- return f.value && f.value.toString().trim() !== '';
168
- }).length;
169
- // Check if we need to show "Clear Applied Filters" button
146
+ const pendingReadyCount = (0, react_1.useMemo)(() => {
147
+ return filters.filter((f) => {
148
+ if (!f.columnId || !f.operator)
149
+ return false;
150
+ if (["isEmpty", "isNotEmpty"].includes(f.operator))
151
+ return true;
152
+ return f.value != null && String(f.value).trim() !== "";
153
+ }).length;
154
+ }, [filters]);
170
155
  const hasAppliedFilters = activeFiltersCount > 0;
171
- // Determine if there are pending changes that can be applied
172
- const hasPendingChanges = pendingFiltersCount > 0 || (filters.length === 0 && hasAppliedFilters);
173
- // Auto-add default filter when opening if no filters exist AND no applied filters
156
+ const hasPendingChanges = pendingReadyCount > 0 || (filters.length === 0 && hasAppliedFilters);
157
+ // Auto-add only once per open. If menu opened with existing filters, mark as processed so
158
+ // "Clear All" doesn't cause a new row to be auto-added when state becomes empty.
174
159
  (0, react_1.useEffect)(() => {
175
160
  var _a;
176
- if (filters.length === 0 && filterableColumns && (filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.length) > 0 && activeFiltersCount === 0) {
177
- const firstColumn = filterableColumns[0];
178
- const columnType = (0, column_helpers_1.getColumnType)(firstColumn);
179
- const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
180
- const defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || 'contains';
181
- // Add default filter with first column and its first operator
182
- addFilter(firstColumn === null || firstColumn === void 0 ? void 0 : firstColumn.id, defaultOperator);
161
+ if (!isMenuOpen) {
162
+ didAutoAddRef.current = false;
163
+ return;
164
+ }
165
+ if (didAutoAddRef.current)
166
+ return;
167
+ if (!filterableColumns.length) {
168
+ didAutoAddRef.current = true;
169
+ return;
183
170
  }
184
- }, [filters.length, filterableColumns, addFilter, activeFiltersCount]);
185
- // Merge all props for maximum flexibility
186
- const mergedProps = (0, slot_helpers_1.mergeSlotProps)({
187
- // Default props
188
- size: 'small',
189
- sx: { flexShrink: 0 },
190
- }, (slotProps === null || slotProps === void 0 ? void 0 : slotProps.columnFilterControl) || {}, props);
191
- return ((0, jsx_runtime_1.jsx)(menu_dropdown_1.MenuDropdown, { anchor: ((0, jsx_runtime_1.jsx)(material_1.Badge, { badgeContent: activeFiltersCount > 0 ? activeFiltersCount : 0, color: "primary", invisible: activeFiltersCount === 0, ...mergedProps.badgeProps, children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { ...mergedProps, children: (0, jsx_runtime_1.jsx)(FilterIconSlot, { ...iconSlotProps }) }) })), children: ({ handleClose }) => ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: {
171
+ if (filters.length > 0 || activeFiltersCount > 0) {
172
+ // Already have filters this session; mark processed so clear won't re-trigger auto-add
173
+ didAutoAddRef.current = true;
174
+ return;
175
+ }
176
+ const firstColumn = filterableColumns[0];
177
+ const columnType = (0, column_helpers_1.getColumnType)(firstColumn);
178
+ const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
179
+ const defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || "contains";
180
+ didAutoAddRef.current = true;
181
+ addFilter(firstColumn.id, defaultOperator);
182
+ }, [isMenuOpen, filterableColumns, filters.length, activeFiltersCount, addFilter]);
183
+ // Merge props but do NOT spread non-icon props onto IconButton
184
+ const mergedProps = (0, slot_helpers_1.mergeSlotProps)({ size: "small", sx: { flexShrink: 0 } }, (slotProps === null || slotProps === void 0 ? void 0 : slotProps.columnFilterControl) || {}, props);
185
+ const { badgeProps, menuSx, title, titleSx, logicSelectProps, clearButtonProps, applyButtonProps, addButtonProps, deleteButtonProps, iconButtonProps, ...iconButtonRestProps } = mergedProps;
186
+ return ((0, jsx_runtime_1.jsx)(menu_dropdown_1.MenuDropdown, { anchor: ({ isOpen }) => ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: "inline-flex" }, children: [(0, jsx_runtime_1.jsx)(OpenStateSync, { open: isOpen, onChange: setIsMenuOpen }), (0, jsx_runtime_1.jsx)(material_1.Badge, { badgeContent: activeFiltersCount > 0 ? activeFiltersCount : 0, color: "primary", invisible: activeFiltersCount === 0, ...badgeProps, children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { ...iconButtonRestProps, ...iconButtonProps, children: (0, jsx_runtime_1.jsx)(FilterIconSlot, { ...iconSlotProps }) }) })] })), children: ({ handleClose }) => ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: {
192
187
  p: 2,
193
188
  minWidth: 400,
194
189
  maxWidth: 600,
195
- ...mergedProps.menuSx,
196
- }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "subtitle2", sx: {
190
+ ...(menuSx || {}),
191
+ }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "subtitle2", sx: {
197
192
  mb: 1,
198
- ...mergedProps.titleSx,
199
- }, children: mergedProps.title || 'Column Filters' }), (0, jsx_runtime_1.jsx)(material_1.Divider, { sx: { mb: 2 } }), filters.length > 1 && ((0, jsx_runtime_1.jsx)(material_1.Box, { sx: { mb: 2 }, children: (0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Logic" }), (0, jsx_runtime_1.jsxs)(material_1.Select, { value: filterLogic, label: "Logic", onChange: (e) => handleLogicChange(e.target.value), ...mergedProps.logicSelectProps, children: [(0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: "AND", children: "AND" }), (0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: "OR", children: "OR" })] })] }) })), (0, jsx_runtime_1.jsx)(material_1.Stack, { spacing: 2, sx: { mb: 2 }, children: filters.map((filter) => {
200
- const selectedColumn = filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.find(col => col.id === filter.columnId);
193
+ ...(titleSx || {}),
194
+ }, children: title || "Column Filters" }), (0, jsx_runtime_1.jsx)(material_1.Divider, { sx: { mb: 2 } }), filters.length > 1 && ((0, jsx_runtime_1.jsx)(material_1.Box, { sx: { mb: 2 }, children: (0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Logic" }), (0, jsx_runtime_1.jsxs)(material_1.Select, { value: filterLogic, label: "Logic", onChange: (e) => handleLogicChange(e.target.value), ...logicSelectProps, children: [(0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: "AND", children: "AND" }), (0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: "OR", children: "OR" })] })] }) })), (0, jsx_runtime_1.jsx)(material_1.Stack, { spacing: 2, sx: { mb: 2 }, children: filters.map((filter) => {
195
+ const selectedColumn = filterableColumns.find((col) => col.id === filter.columnId);
201
196
  const operators = filter.columnId ? getOperatorsForColumn(filter.columnId) : [];
202
- const needsValue = !['isEmpty', 'isNotEmpty'].includes(filter.operator);
203
- return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [(0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Column" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.columnId || '', label: "Column", onChange: (e) => handleColumnChange(filter.id, e.target.value, filter), children: filterableColumns === null || filterableColumns === void 0 ? void 0 : filterableColumns.map(column => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: column.id, children: typeof column.columnDef.header === 'string'
197
+ const needsValue = !["isEmpty", "isNotEmpty"].includes(filter.operator);
198
+ return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [(0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Column" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.columnId || "", label: "Column", onChange: (e) => handleColumnChange(filter.id, e.target.value, filter), children: filterableColumns.map((column) => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: column.id, children: typeof column.columnDef.header === "string"
204
199
  ? column.columnDef.header
205
- : column.id }, column.id))) })] }), (0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Operator" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.operator || '', label: "Operator", onChange: (e) => handleOperatorChange(filter.id, e.target.value, filter), disabled: !filter.columnId, children: operators.map(op => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: op.value, children: op.label }, op.value))) })] }), needsValue && selectedColumn && ((0, jsx_runtime_1.jsx)(filter_value_input_1.FilterValueInput, { filter: filter, column: selectedColumn, onValueChange: (value) => handleFilterValueChange(filter.id, value) })), (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", onClick: () => handleRemoveFilter(filter.id), color: "error", ...mergedProps.deleteButtonProps, children: (0, jsx_runtime_1.jsx)(icons_1.DeleteIcon, { fontSize: "small" }) })] }, filter.id));
206
- }) }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", size: "small", startIcon: (0, jsx_runtime_1.jsx)(icons_1.AddIcon, {}), onClick: handleAddFilter, disabled: !filterableColumns || filterableColumns.length === 0, sx: { mb: 2 }, ...mergedProps.addButtonProps, children: "Add Filter" }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", children: [hasAppliedFilters && ((0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", size: "small", onClick: () => clearAllFilters(handleClose), color: "error", ...mergedProps.clearButtonProps, children: "Clear All" })), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", size: "small", onClick: () => handleApplyFilters(handleClose), disabled: !hasPendingChanges, ...mergedProps.applyButtonProps, children: "Apply" })] })] })) }));
200
+ : column.id }, column.id))) })] }), (0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 120 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Operator" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.operator || "", label: "Operator", onChange: (e) => handleOperatorChange(filter.id, e.target.value, filter), disabled: !filter.columnId, children: operators.map((op) => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: op.value, children: op.label }, op.value))) })] }), needsValue && selectedColumn && ((0, jsx_runtime_1.jsx)(filter_value_input_1.FilterValueInput, { filter: filter, column: selectedColumn, onValueChange: (value) => handleFilterValueChange(filter.id, value) })), (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", onClick: () => removeFilter(filter.id), color: "error", ...deleteButtonProps, children: (0, jsx_runtime_1.jsx)(icons_1.DeleteIcon, { fontSize: "small" }) })] }, filter.id));
201
+ }) }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", size: "small", startIcon: (0, jsx_runtime_1.jsx)(icons_1.AddIcon, {}), onClick: () => addFilter(), disabled: filterableColumns.length === 0, sx: { mb: 2 }, ...addButtonProps, children: "Add Filter" }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", children: [hasAppliedFilters && ((0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", size: "small", onClick: (e) => {
202
+ e.preventDefault();
203
+ e.stopPropagation();
204
+ clearAllFilters(handleClose);
205
+ }, color: "error", ...clearButtonProps, children: "Clear All" })), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", size: "small", onClick: () => handleApplyFilters(() => handleClose === null || handleClose === void 0 ? void 0 : handleClose()), disabled: !hasPendingChanges, ...applyButtonProps, children: "Apply" })] })] })) }));
207
206
  }
@@ -1 +1 @@
1
- {"version":3,"file":"table-refresh-control.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/toolbar/table-refresh-control.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,YAAY,EAAe,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAuB,eAAe,EAAoB,MAAM,eAAe,CAAC;AAKvF,MAAM,WAAW,wBAAwB;IACrC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,GAAG,CAAC;IAEnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,qCAAqC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,gDAAgD;IAChD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,wBAA6B,GAAG,YAAY,CAkCtF"}
1
+ {"version":3,"file":"table-refresh-control.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/toolbar/table-refresh-control.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,YAAY,EAAe,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAuB,eAAe,EAAoB,MAAM,eAAe,CAAC;AAKvF,MAAM,WAAW,wBAAwB;IACrC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,GAAG,CAAC;IAEnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,qCAAqC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,gDAAgD;IAChD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,wBAA6B,GAAG,YAAY,CAsCtF"}
@@ -57,5 +57,7 @@ function TableRefreshControl(props = {}) {
57
57
  disabled: !!props.loading,
58
58
  sx: { flexShrink: 0 },
59
59
  }, refreshIconSlotProps, props.iconButtonProps || {});
60
- return ((0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: "Refresh data", ...props.tooltipProps, children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { ...mergedIconButtonProps, children: props.loading && props.showSpinnerWhileLoading ? ((0, jsx_runtime_1.jsx)(material_1.CircularProgress, { size: 16 })) : ((0, jsx_runtime_1.jsx)(RefreshIconSlot, { ...refreshIconSlotProps })) }) }));
60
+ // Wrap in span so when IconButton is disabled (loading), the tooltip still
61
+ // receives pointer events and closes on mouse leave (disabled elements don't fire them).
62
+ return ((0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: "Refresh data", ...props.tooltipProps, children: (0, jsx_runtime_1.jsx)("span", { style: { display: 'inline-flex' }, children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { ...mergedIconButtonProps, children: props.loading && props.showSpinnerWhileLoading ? ((0, jsx_runtime_1.jsx)(material_1.CircularProgress, { size: 16 })) : ((0, jsx_runtime_1.jsx)(RefreshIconSlot, { ...refreshIconSlotProps })) }) }) }));
61
63
  }
@@ -28,13 +28,14 @@ declare module '@tanstack/react-table' {
28
28
  onColumnFilterApply?: (state: ColumnFilterState) => void;
29
29
  }
30
30
  interface Table<TData extends RowData> {
31
- setColumnFilterState: (updater: Updater<ColumnFilterState>) => void;
31
+ setColumnFilterState: (updater: Updater<ColumnFilterState> | ColumnFilterState) => void;
32
32
  addPendingColumnFilter: (columnId: string, operator: string, value: any) => void;
33
33
  updatePendingColumnFilter: (filterId: string, updates: Partial<ColumnFilterRule>) => void;
34
34
  removePendingColumnFilter: (filterId: string) => void;
35
35
  clearAllPendingColumnFilters: () => void;
36
36
  setPendingFilterLogic: (logic: 'AND' | 'OR') => void;
37
37
  applyPendingColumnFilters: () => void;
38
+ resetColumnFilter: () => void;
38
39
  addColumnFilter: (columnId: string, operator: string, value: any) => void;
39
40
  updateColumnFilter: (filterId: string, updates: Partial<ColumnFilterRule>) => void;
40
41
  removeColumnFilter: (filterId: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"column-filter.feature.d.ts","sourceRoot":"","sources":["../../../src/lib/features/column-filter.feature.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACH,KAAK,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EAGP,QAAQ,EAGX,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI9D,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAChC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAErE,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC5D;AAGD,OAAO,QAAQ,uBAAuB,CAAC;IACnC,UAAU,UAAU;QAChB,YAAY,EAAE,iBAAiB,CAAC;KACnC;IAED,UAAU,oBAAoB,CAAC,KAAK,SAAS,OAAO;QAChD,yBAAyB,CAAC,EAAE,OAAO,CAAC;QACpC,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QACrE,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;KAC5D;IAED,UAAU,KAAK,CAAC,KAAK,SAAS,OAAO;QACjC,oBAAoB,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAGpE,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;QACjF,yBAAyB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QAC1F,yBAAyB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;QACtD,4BAA4B,EAAE,MAAM,IAAI,CAAC;QACzC,qBAAqB,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC;QAGrD,yBAAyB,EAAE,MAAM,IAAI,CAAC;QAGtC,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;QAC1E,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QACnF,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/C,qBAAqB,EAAE,MAAM,IAAI,CAAC;QAClC,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC;QAG9C,sBAAsB,EAAE,MAAM,gBAAgB,EAAE,CAAC;QACjD,uBAAuB,EAAE,MAAM,gBAAgB,EAAE,CAAC;QAClD,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;KACjD;CACJ;AA8BD,eAAO,MAAM,mBAAmB,EAAE,YAAY,CAAC,GAAG,CAqLjD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,0BAA0B,CACtC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,gBAAgB,EAAE,EAC3B,KAAK,GAAE,KAAK,GAAG,IAAY,GAC5B,OAAO,CA2BT;AAED,eAAO,MAAM,2BAA2B,GAAI,KAAK,QACrC,OAAO,KAAK,CAAC,KAAK,CAAC,WAAS,QAAQ,CAAC,KAAK,CAwCrD,CAAC"}
1
+ {"version":3,"file":"column-filter.feature.d.ts","sourceRoot":"","sources":["../../../src/lib/features/column-filter.feature.ts"],"names":[],"mappings":"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"}
@@ -115,6 +115,20 @@ exports.ColumnFilterFeature = {
115
115
  pendingFilters: [],
116
116
  }));
117
117
  };
118
+ table.resetColumnFilter = () => {
119
+ var _a, _b;
120
+ if (!table.options.enableAdvanceColumnFilter)
121
+ return;
122
+ const newState = {
123
+ pendingFilters: [],
124
+ pendingLogic: 'AND',
125
+ filters: [],
126
+ logic: 'AND',
127
+ };
128
+ table.setColumnFilterState(newState);
129
+ // Notify engine so it can reset pagination and refetch (server mode)
130
+ (_b = (_a = table.options).onColumnFilterApply) === null || _b === void 0 ? void 0 : _b.call(_a, newState);
131
+ };
118
132
  table.setPendingFilterLogic = (logic) => {
119
133
  if (!table.options.enableAdvanceColumnFilter)
120
134
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"use-data-table-engine.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/use-data-table-engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-data-table-engine.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/use-data-table-engine.ts"],"names":[],"mappings":"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"}
@@ -381,7 +381,10 @@ function useDataTableEngine(props) {
381
381
  lastServerKeyRef.current = serverKey;
382
382
  const delay = (_a = nextFetchDelayRef.current) !== null && _a !== void 0 ? _a : 0;
383
383
  nextFetchDelayRef.current = 0; // reset after using
384
- void fetchData({}, { delay, meta: { reason: "stateChange" } });
384
+ const timeoutId = setTimeout(() => {
385
+ void fetchData({}, { delay, meta: { reason: "stateChange" } });
386
+ }, 0);
387
+ return () => clearTimeout(timeoutId);
385
388
  }, [serverKey, fetchData]);
386
389
  // columnFilter apply handler stays explicit (button), but you can also auto-fetch on change if needed
387
390
  const handleColumnFilterChangeHandler = (0, react_1.useCallback)((updater, isApply = false) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ackplus/react-tanstack-data-table",
3
3
  "type": "commonjs",
4
- "version": "1.1.17",
4
+ "version": "1.1.18",
5
5
  "description": "A powerful React data table component built with MUI and TanStack Table",
6
6
  "keywords": [
7
7
  "react",
@@ -1,4 +1,4 @@
1
- import { FilterList } from '@mui/icons-material';
1
+ import { FilterList } from "@mui/icons-material";
2
2
  import {
3
3
  Box,
4
4
  MenuItem,
@@ -11,242 +11,298 @@ import {
11
11
  IconButton,
12
12
  Divider,
13
13
  Badge,
14
- IconButtonProps,
15
- SxProps,
16
- } from '@mui/material';
17
- import React, { useMemo, useCallback, useEffect, ReactElement } from 'react';
18
-
19
- import { MenuDropdown } from '../droupdown/menu-dropdown';
20
- import { useDataTableContext } from '../../contexts/data-table-context';
21
- import {
22
- AddIcon,
23
- DeleteIcon,
24
- } from '../../icons';
25
- import { getColumnType, isColumnFilterable } from '../../utils/column-helpers';
26
- import { getSlotComponent, mergeSlotProps, extractSlotProps } from '../../utils/slot-helpers';
27
- import { FILTER_OPERATORS } from '../filters';
28
- import { FilterValueInput } from '../filters/filter-value-input';
29
- import { ColumnFilterRule } from '../../features';
14
+ type IconButtonProps,
15
+ type SxProps,
16
+ } from "@mui/material";
17
+ import React, {
18
+ useMemo,
19
+ useCallback,
20
+ useEffect,
21
+ useRef,
22
+ useState,
23
+ type ReactElement,
24
+ } from "react";
25
+
26
+ import { MenuDropdown } from "../droupdown/menu-dropdown";
27
+ import { useDataTableContext } from "../../contexts/data-table-context";
28
+ import { AddIcon, DeleteIcon } from "../../icons";
29
+ import { getColumnType, isColumnFilterable } from "../../utils/column-helpers";
30
+ import { getSlotComponent, mergeSlotProps, extractSlotProps } from "../../utils/slot-helpers";
31
+ import { FILTER_OPERATORS } from "../filters";
32
+ import { FilterValueInput } from "../filters/filter-value-input";
33
+ import type { ColumnFilterRule } from "../../features";
30
34
 
31
35
  export interface ColumnFilterControlProps {
32
- // Allow full customization of any prop
33
36
  title?: string;
34
37
  titleSx?: SxProps;
35
38
  menuSx?: SxProps;
39
+
36
40
  iconButtonProps?: IconButtonProps;
37
41
  badgeProps?: any;
42
+
38
43
  clearButtonProps?: any;
39
44
  applyButtonProps?: any;
40
45
  addButtonProps?: any;
46
+ deleteButtonProps?: any;
41
47
  logicSelectProps?: any;
48
+
42
49
  [key: string]: any;
43
50
  }
44
51
 
52
+ /**
53
+ * Small helper component to sync MenuDropdown open state to parent state
54
+ * WITHOUT calling hooks inside render-prop callback.
55
+ */
56
+ function OpenStateSync({
57
+ open,
58
+ onChange,
59
+ }: {
60
+ open: boolean;
61
+ onChange: (open: boolean) => void;
62
+ }) {
63
+ useEffect(() => onChange(open), [open, onChange]);
64
+ return null;
65
+ }
66
+
45
67
  export function ColumnFilterControl(props: ColumnFilterControlProps = {}): ReactElement {
46
68
  const { table, slots, slotProps } = useDataTableContext();
47
-
48
- // Extract slot-specific props with enhanced merging
49
- const iconSlotProps = extractSlotProps(slotProps, 'filterIcon');
50
-
51
- const FilterIconSlot = getSlotComponent(slots, 'filterIcon', FilterList);
52
-
53
- // Use the custom feature state from the table - now using pending filters for UI
54
- const filterState = table?.getColumnFilterState?.() || {
55
- filters: [],
56
- logic: 'AND',
57
- pendingFilters: [],
58
- pendingLogic: 'AND'
59
- };
60
-
61
- // Use pending filters for the UI (draft state)
62
- const filters = filterState.pendingFilters;
63
- const filterLogic = filterState.pendingLogic;
64
-
65
- // Active filters are the actual applied filters
69
+
70
+ const iconSlotProps = extractSlotProps(slotProps, "filterIcon");
71
+ const FilterIconSlot = getSlotComponent(slots, "filterIcon", FilterList);
72
+
73
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
74
+ const didAutoAddRef = useRef(false);
75
+
76
+ const filterState =
77
+ table?.getColumnFilterState?.() || ({
78
+ filters: [],
79
+ logic: "AND",
80
+ pendingFilters: [],
81
+ pendingLogic: "AND",
82
+ } as any);
83
+
84
+ const filters: ColumnFilterRule[] = filterState.pendingFilters || [];
85
+ const filterLogic: "AND" | "OR" = (filterState.pendingLogic || "AND") as any;
86
+
66
87
  const activeFiltersCount = table?.getActiveColumnFilters?.()?.length || 0;
67
88
 
68
89
  const filterableColumns = useMemo(() => {
69
- return table?.getAllLeafColumns()
70
- .filter(column => isColumnFilterable(column));
90
+ return table?.getAllLeafColumns().filter((column: any) => isColumnFilterable(column)) || [];
71
91
  }, [table]);
72
92
 
73
- const addFilter = useCallback((columnId?: string, operator?: string) => {
74
- // If no column specified, use empty (user will select)
75
- // If column specified, get its appropriate default operator
76
- let defaultOperator = operator || '';
93
+ const getOperatorsForColumn = useCallback(
94
+ (columnId: string) => {
95
+ const column = filterableColumns.find((col: any) => col.id === columnId);
96
+ const type = getColumnType(column as any);
97
+ return FILTER_OPERATORS[type as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
98
+ },
99
+ [filterableColumns]
100
+ );
77
101
 
78
- if (columnId && !operator) {
79
- const column = filterableColumns?.find(col => col.id === columnId);
80
- const columnType = getColumnType(column as any);
81
- const operators = FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
82
- defaultOperator = operators[0]?.value || 'contains';
83
- }
102
+ const addFilter = useCallback(
103
+ (columnId?: string, operator?: string) => {
104
+ let defaultOperator = operator || "";
84
105
 
85
- table?.addPendingColumnFilter?.(columnId || '', defaultOperator, '');
86
- }, [table, filterableColumns]);
106
+ if (columnId && !operator) {
107
+ const column = filterableColumns.find((col: any) => col.id === columnId);
108
+ const columnType = getColumnType(column as any);
109
+ const operators =
110
+ FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
111
+ defaultOperator = operators[0]?.value || "contains";
112
+ }
87
113
 
88
- const handleAddFilter = useCallback(() => {
89
- addFilter();
90
- }, [addFilter]);
114
+ table?.addPendingColumnFilter?.(columnId || "", defaultOperator, "");
115
+ },
116
+ [table, filterableColumns]
117
+ );
91
118
 
92
- const updateFilter = useCallback((filterId: string, updates: Partial<ColumnFilterRule>) => {
93
- table?.updatePendingColumnFilter?.(filterId, updates);
94
- }, [table]);
119
+ const updateFilter = useCallback(
120
+ (filterId: string, updates: Partial<ColumnFilterRule>) => {
121
+ table?.updatePendingColumnFilter?.(filterId, updates);
122
+ },
123
+ [table]
124
+ );
95
125
 
96
- const removeFilter = useCallback((filterId: string) => {
97
- table?.removePendingColumnFilter?.(filterId);
98
- }, [table]);
126
+ const removeFilter = useCallback(
127
+ (filterId: string) => {
128
+ table?.removePendingColumnFilter?.(filterId);
129
+ },
130
+ [table]
131
+ );
99
132
 
100
133
  const clearAllFilters = useCallback((closeDialog?: () => void) => {
101
- // Clear all pending filters
102
- table?.clearAllPendingColumnFilters?.();
103
- // Immediately apply the clear (which will clear active filters too)
134
+ // Defer all work to avoid long-running click handler (prevents "[Violation] 'click' handler took Xms")
104
135
  setTimeout(() => {
105
- table?.applyPendingColumnFilters?.();
106
- // Close dialog if callback provided
107
- if (closeDialog) {
108
- closeDialog();
109
- }
136
+ table?.resetColumnFilter?.();
137
+ // Prevent auto-add effect from adding a row when it sees empty state after clear
138
+ didAutoAddRef.current = true;
139
+ closeDialog?.();
110
140
  }, 0);
111
141
  }, [table]);
112
142
 
113
- // Handle filter logic change (AND/OR)
114
- const handleLogicChange = useCallback((newLogic: 'AND' | 'OR') => {
115
- table?.setPendingFilterLogic?.(newLogic);
116
- }, [table]);
143
+ const handleLogicChange = useCallback(
144
+ (newLogic: "AND" | "OR") => {
145
+ table?.setPendingFilterLogic?.(newLogic);
146
+ },
147
+ [table]
148
+ );
117
149
 
118
- // Apply all pending filters
119
150
  const applyFilters = useCallback(() => {
120
151
  table?.applyPendingColumnFilters?.();
121
152
  }, [table]);
122
153
 
123
- // Handle apply button click
124
- const handleApplyFilters = useCallback((closeDialog: () => void) => {
125
- applyFilters();
126
- closeDialog();
127
- }, [applyFilters]);
128
-
129
- const getOperatorsForColumn = useCallback((columnId: string) => {
130
- const column = filterableColumns?.find(col => col.id === columnId);
131
- const type = getColumnType(column as any);
132
- return FILTER_OPERATORS[type as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
133
- }, [filterableColumns]);
134
-
135
- // Handle column selection change
136
- const handleColumnChange = useCallback((filterId: string, newColumnId: string, currentFilter: ColumnFilterRule) => {
137
- const newColumn = filterableColumns?.find(col => col.id === newColumnId);
138
- const columnType = getColumnType(newColumn as any);
139
- const operators = FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
140
-
141
- // Only reset operator if current operator is not valid for new column type
142
- const currentOperatorValid = operators.some(op => op.value === currentFilter.operator);
143
- const newOperator = currentOperatorValid ? currentFilter.operator : operators[0]?.value || '';
144
-
145
- updateFilter(filterId, {
146
- columnId: newColumnId,
147
- operator: newOperator,
148
- // Keep the current value unless operator is empty/notEmpty
149
- value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
150
- });
151
- }, [filterableColumns, updateFilter]);
152
-
153
- // Handle operator selection change
154
- const handleOperatorChange = useCallback((filterId: string, newOperator: string, currentFilter: ColumnFilterRule) => {
155
- updateFilter(filterId, {
156
- operator: newOperator,
157
- // Only reset value if operator is empty/notEmpty, otherwise preserve it
158
- value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
159
- });
160
- }, [updateFilter]);
161
-
162
- // Handle filter value change
163
- const handleFilterValueChange = useCallback((filterId: string, value: any) => {
164
- updateFilter(filterId, { value });
165
- }, [updateFilter]);
166
-
167
- // Handle filter removal
168
- const handleRemoveFilter = useCallback((filterId: string) => {
169
- removeFilter(filterId);
170
- }, [removeFilter]);
171
-
172
- // Count pending filters that are ready to apply (have column, operator, and value OR are empty/notEmpty operators)
173
- const pendingFiltersCount = filters.filter(f => {
174
- if (!f.columnId || !f.operator) return false;
175
- // For empty/notEmpty operators, no value is needed
176
- if (['isEmpty', 'isNotEmpty'].includes(f.operator)) return true;
177
- // For other operators, value is required
178
- return f.value && f.value.toString().trim() !== '';
179
- }).length;
180
-
181
- // Check if we need to show "Clear Applied Filters" button
182
- const hasAppliedFilters = activeFiltersCount > 0;
154
+ const handleApplyFilters = useCallback(
155
+ (closeDialog: () => void) => {
156
+ // Defer so click handler returns immediately (prevents "[Violation] 'click' handler took Xms")
157
+ setTimeout(() => {
158
+ applyFilters();
159
+ closeDialog();
160
+ }, 0);
161
+ },
162
+ [applyFilters]
163
+ );
183
164
 
184
- // Determine if there are pending changes that can be applied
185
- const hasPendingChanges = pendingFiltersCount > 0 || (filters.length === 0 && hasAppliedFilters);
165
+ const handleColumnChange = useCallback(
166
+ (filterId: string, newColumnId: string, currentFilter: ColumnFilterRule) => {
167
+ const newColumn = filterableColumns.find((col: any) => col.id === newColumnId);
168
+ const columnType = getColumnType(newColumn as any);
169
+ const operators =
170
+ FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
171
+
172
+ const currentOperatorValid = operators.some((op) => op.value === currentFilter.operator);
173
+ const newOperator = currentOperatorValid ? currentFilter.operator : operators[0]?.value || "";
174
+
175
+ updateFilter(filterId, {
176
+ columnId: newColumnId,
177
+ operator: newOperator,
178
+ value: ["isEmpty", "isNotEmpty"].includes(newOperator) ? "" : currentFilter.value,
179
+ });
180
+ },
181
+ [filterableColumns, updateFilter]
182
+ );
183
+
184
+ const handleOperatorChange = useCallback(
185
+ (filterId: string, newOperator: string, currentFilter: ColumnFilterRule) => {
186
+ updateFilter(filterId, {
187
+ operator: newOperator,
188
+ value: ["isEmpty", "isNotEmpty"].includes(newOperator) ? "" : currentFilter.value,
189
+ });
190
+ },
191
+ [updateFilter]
192
+ );
193
+
194
+ const handleFilterValueChange = useCallback(
195
+ (filterId: string, value: any) => {
196
+ updateFilter(filterId, { value });
197
+ },
198
+ [updateFilter]
199
+ );
200
+
201
+ const pendingReadyCount = useMemo(() => {
202
+ return filters.filter((f) => {
203
+ if (!f.columnId || !f.operator) return false;
204
+ if (["isEmpty", "isNotEmpty"].includes(f.operator)) return true;
205
+ return f.value != null && String(f.value).trim() !== "";
206
+ }).length;
207
+ }, [filters]);
208
+
209
+ const hasAppliedFilters = activeFiltersCount > 0;
210
+ const hasPendingChanges = pendingReadyCount > 0 || (filters.length === 0 && hasAppliedFilters);
186
211
 
187
- // Auto-add default filter when opening if no filters exist AND no applied filters
212
+ // Auto-add only once per open. If menu opened with existing filters, mark as processed so
213
+ // "Clear All" doesn't cause a new row to be auto-added when state becomes empty.
188
214
  useEffect(() => {
189
- if (filters.length === 0 && filterableColumns && filterableColumns?.length > 0 && activeFiltersCount === 0) {
190
- const firstColumn = filterableColumns[0];
191
- const columnType = getColumnType(firstColumn as any);
192
- const operators = FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
193
- const defaultOperator = operators[0]?.value || 'contains';
194
- // Add default filter with first column and its first operator
195
- addFilter(firstColumn?.id, defaultOperator);
215
+ if (!isMenuOpen) {
216
+ didAutoAddRef.current = false;
217
+ return;
196
218
  }
197
- }, [filters.length, filterableColumns, addFilter, activeFiltersCount]);
219
+ if (didAutoAddRef.current) return;
220
+ if (!filterableColumns.length) {
221
+ didAutoAddRef.current = true;
222
+ return;
223
+ }
224
+ if (filters.length > 0 || activeFiltersCount > 0) {
225
+ // Already have filters this session; mark processed so clear won't re-trigger auto-add
226
+ didAutoAddRef.current = true;
227
+ return;
228
+ }
229
+
230
+ const firstColumn = filterableColumns[0];
231
+ const columnType = getColumnType(firstColumn as any);
232
+ const operators =
233
+ FILTER_OPERATORS[columnType as keyof typeof FILTER_OPERATORS] || FILTER_OPERATORS.text;
234
+ const defaultOperator = operators[0]?.value || "contains";
235
+
236
+ didAutoAddRef.current = true;
237
+ addFilter(firstColumn.id, defaultOperator);
238
+ }, [isMenuOpen, filterableColumns, filters.length, activeFiltersCount, addFilter]);
198
239
 
199
- // Merge all props for maximum flexibility
240
+ // Merge props but do NOT spread non-icon props onto IconButton
200
241
  const mergedProps = mergeSlotProps(
201
- {
202
- // Default props
203
- size: 'small',
204
- sx: { flexShrink: 0 },
205
- },
242
+ { size: "small", sx: { flexShrink: 0 } },
206
243
  slotProps?.columnFilterControl || {},
207
244
  props
208
245
  );
209
246
 
247
+ const {
248
+ badgeProps,
249
+ menuSx,
250
+ title,
251
+ titleSx,
252
+ logicSelectProps,
253
+ clearButtonProps,
254
+ applyButtonProps,
255
+ addButtonProps,
256
+ deleteButtonProps,
257
+ iconButtonProps,
258
+ ...iconButtonRestProps
259
+ } = mergedProps;
260
+
210
261
  return (
211
262
  <MenuDropdown
212
- anchor={(
213
- <Badge
214
- badgeContent={activeFiltersCount > 0 ? activeFiltersCount : 0}
215
- color="primary"
216
- invisible={activeFiltersCount === 0}
217
- {...mergedProps.badgeProps}
218
- >
219
- <IconButton
220
- {...mergedProps}
263
+ anchor={({ isOpen }) => (
264
+ <Box sx={{ display: "inline-flex" }}>
265
+ {/* sync dropdown open state to our local state */}
266
+ <OpenStateSync open={isOpen} onChange={setIsMenuOpen} />
267
+
268
+ <Badge
269
+ badgeContent={activeFiltersCount > 0 ? activeFiltersCount : 0}
270
+ color="primary"
271
+ invisible={activeFiltersCount === 0}
272
+ {...badgeProps}
221
273
  >
222
- <FilterIconSlot
223
- {...iconSlotProps}
224
- />
225
- </IconButton>
226
- </Badge>
274
+ <IconButton
275
+ {...(iconButtonRestProps as IconButtonProps)}
276
+ {...(iconButtonProps as IconButtonProps)}
277
+ >
278
+ <FilterIconSlot {...iconSlotProps} />
279
+ </IconButton>
280
+ </Badge>
281
+ </Box>
227
282
  )}
228
283
  >
229
- {({ handleClose }: { handleClose: () => void }) => (
284
+ {({ handleClose }: { handleClose: (event?: any) => void }) => (
230
285
  <Box
231
286
  sx={{
232
287
  p: 2,
233
288
  minWidth: 400,
234
289
  maxWidth: 600,
235
- ...mergedProps.menuSx,
290
+ ...(menuSx || {}),
236
291
  }}
292
+ onClick={(e) => e.stopPropagation()}
237
293
  >
238
294
  <Typography
239
295
  variant="subtitle2"
240
296
  sx={{
241
297
  mb: 1,
242
- ...mergedProps.titleSx,
298
+ ...(titleSx || {}),
243
299
  }}
244
300
  >
245
- {mergedProps.title || 'Column Filters'}
301
+ {title || "Column Filters"}
246
302
  </Typography>
303
+
247
304
  <Divider sx={{ mb: 2 }} />
248
305
 
249
- {/* Filter Logic Selection */}
250
306
  {filters.length > 1 && (
251
307
  <Box sx={{ mb: 2 }}>
252
308
  <FormControl size="small" sx={{ minWidth: 120 }}>
@@ -254,8 +310,8 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
254
310
  <Select
255
311
  value={filterLogic}
256
312
  label="Logic"
257
- onChange={(e) => handleLogicChange(e.target.value as 'AND' | 'OR')}
258
- {...mergedProps.logicSelectProps}
313
+ onChange={(e) => handleLogicChange(e.target.value as "AND" | "OR")}
314
+ {...logicSelectProps}
259
315
  >
260
316
  <MenuItem value="AND">AND</MenuItem>
261
317
  <MenuItem value="OR">OR</MenuItem>
@@ -264,26 +320,26 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
264
320
  </Box>
265
321
  )}
266
322
 
267
- {/* Filter Rules */}
268
323
  <Stack spacing={2} sx={{ mb: 2 }}>
269
324
  {filters.map((filter) => {
270
- const selectedColumn = filterableColumns?.find(col => col.id === filter.columnId);
325
+ const selectedColumn = filterableColumns.find((col: any) => col.id === filter.columnId);
271
326
  const operators = filter.columnId ? getOperatorsForColumn(filter.columnId) : [];
272
- const needsValue = !['isEmpty', 'isNotEmpty'].includes(filter.operator);
327
+ const needsValue = !["isEmpty", "isNotEmpty"].includes(filter.operator);
273
328
 
274
329
  return (
275
330
  <Stack key={filter.id} direction="row" spacing={1} alignItems="center">
276
- {/* Column Selection */}
277
331
  <FormControl size="small" sx={{ minWidth: 120 }}>
278
332
  <InputLabel>Column</InputLabel>
279
333
  <Select
280
- value={filter.columnId || ''}
334
+ value={filter.columnId || ""}
281
335
  label="Column"
282
- onChange={(e) => handleColumnChange(filter.id, e.target.value, filter)}
336
+ onChange={(e) =>
337
+ handleColumnChange(filter.id, e.target.value as string, filter)
338
+ }
283
339
  >
284
- {filterableColumns?.map(column => (
340
+ {filterableColumns.map((column: any) => (
285
341
  <MenuItem key={column.id} value={column.id}>
286
- {typeof column.columnDef.header === 'string'
342
+ {typeof column.columnDef.header === "string"
287
343
  ? column.columnDef.header
288
344
  : column.id}
289
345
  </MenuItem>
@@ -291,16 +347,17 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
291
347
  </Select>
292
348
  </FormControl>
293
349
 
294
- {/* Operator Selection */}
295
350
  <FormControl size="small" sx={{ minWidth: 120 }}>
296
351
  <InputLabel>Operator</InputLabel>
297
352
  <Select
298
- value={filter.operator || ''}
353
+ value={filter.operator || ""}
299
354
  label="Operator"
300
- onChange={(e) => handleOperatorChange(filter.id, e.target.value, filter)}
355
+ onChange={(e) =>
356
+ handleOperatorChange(filter.id, e.target.value as string, filter)
357
+ }
301
358
  disabled={!filter.columnId}
302
359
  >
303
- {operators.map(op => (
360
+ {operators.map((op: any) => (
304
361
  <MenuItem key={op.value} value={op.value}>
305
362
  {op.label}
306
363
  </MenuItem>
@@ -308,7 +365,6 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
308
365
  </Select>
309
366
  </FormControl>
310
367
 
311
- {/* Value Input */}
312
368
  {needsValue && selectedColumn && (
313
369
  <FilterValueInput
314
370
  filter={filter}
@@ -317,52 +373,54 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
317
373
  />
318
374
  )}
319
375
 
320
- {/* Remove Filter Button */}
321
- <IconButton
322
- size="small"
323
- onClick={() => handleRemoveFilter(filter.id)}
324
- color="error"
325
- {...mergedProps.deleteButtonProps}
326
- >
327
- <DeleteIcon fontSize="small" />
328
- </IconButton>
376
+ <IconButton
377
+ size="small"
378
+ onClick={() => removeFilter(filter.id)}
379
+ color="error"
380
+ {...deleteButtonProps}
381
+ >
382
+ <DeleteIcon fontSize="small" />
383
+ </IconButton>
329
384
  </Stack>
330
385
  );
331
386
  })}
332
387
  </Stack>
333
388
 
334
- {/* Add Filter Button */}
335
- <Button
336
- variant="outlined"
337
- size="small"
338
- startIcon={<AddIcon />}
339
- onClick={handleAddFilter}
340
- disabled={!filterableColumns || filterableColumns.length === 0}
341
- sx={{ mb: 2 }}
342
- {...mergedProps.addButtonProps}
343
- >
344
- Add Filter
345
- </Button>
346
-
347
- {/* Action Buttons */}
389
+ <Button
390
+ variant="outlined"
391
+ size="small"
392
+ startIcon={<AddIcon />}
393
+ onClick={() => addFilter()}
394
+ disabled={filterableColumns.length === 0}
395
+ sx={{ mb: 2 }}
396
+ {...addButtonProps}
397
+ >
398
+ Add Filter
399
+ </Button>
400
+
348
401
  <Stack direction="row" spacing={1} justifyContent="flex-end">
349
402
  {hasAppliedFilters && (
350
403
  <Button
351
404
  variant="outlined"
352
405
  size="small"
353
- onClick={() => clearAllFilters(handleClose)}
406
+ onClick={(e) => {
407
+ e.preventDefault();
408
+ e.stopPropagation();
409
+ clearAllFilters(handleClose);
410
+ }}
354
411
  color="error"
355
- {...mergedProps.clearButtonProps}
412
+ {...clearButtonProps}
356
413
  >
357
414
  Clear All
358
415
  </Button>
359
416
  )}
417
+
360
418
  <Button
361
419
  variant="contained"
362
420
  size="small"
363
- onClick={() => handleApplyFilters(handleClose)}
421
+ onClick={() => handleApplyFilters(() => handleClose?.())}
364
422
  disabled={!hasPendingChanges}
365
- {...mergedProps.applyButtonProps}
423
+ {...applyButtonProps}
366
424
  >
367
425
  Apply
368
426
  </Button>
@@ -371,4 +429,4 @@ export function ColumnFilterControl(props: ColumnFilterControlProps = {}): React
371
429
  )}
372
430
  </MenuDropdown>
373
431
  );
374
- }
432
+ }
@@ -44,15 +44,19 @@ export function TableRefreshControl(props: TableRefreshControlProps = {}): React
44
44
  props.iconButtonProps || {}
45
45
  );
46
46
 
47
+ // Wrap in span so when IconButton is disabled (loading), the tooltip still
48
+ // receives pointer events and closes on mouse leave (disabled elements don't fire them).
47
49
  return (
48
50
  <Tooltip title="Refresh data" {...props.tooltipProps}>
49
- <IconButton {...mergedIconButtonProps}>
50
- {props.loading && props.showSpinnerWhileLoading ? (
51
- <CircularProgress size={16} />
52
- ) : (
53
- <RefreshIconSlot {...refreshIconSlotProps} />
54
- )}
55
- </IconButton>
51
+ <span style={{ display: 'inline-flex' }}>
52
+ <IconButton {...mergedIconButtonProps}>
53
+ {props.loading && props.showSpinnerWhileLoading ? (
54
+ <CircularProgress size={16} />
55
+ ) : (
56
+ <RefreshIconSlot {...refreshIconSlotProps} />
57
+ )}
58
+ </IconButton>
59
+ </span>
56
60
  </Tooltip>
57
61
  );
58
62
  }
@@ -49,7 +49,7 @@ declare module '@tanstack/react-table' {
49
49
  }
50
50
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
51
  interface Table<TData extends RowData> {
52
- setColumnFilterState: (updater: Updater<ColumnFilterState>) => void;
52
+ setColumnFilterState: (updater: Updater<ColumnFilterState> | ColumnFilterState) => void;
53
53
 
54
54
  // Pending filter methods (for draft state)
55
55
  addPendingColumnFilter: (columnId: string, operator: string, value: any) => void;
@@ -60,6 +60,7 @@ declare module '@tanstack/react-table' {
60
60
 
61
61
  // Apply pending filters to active filters
62
62
  applyPendingColumnFilters: () => void;
63
+ resetColumnFilter: () => void;
63
64
 
64
65
  // Legacy methods (for backward compatibility)
65
66
  addColumnFilter: (columnId: string, operator: string, value: any) => void;
@@ -187,6 +188,19 @@ export const ColumnFilterFeature: TableFeature<any> = {
187
188
  }));
188
189
  };
189
190
 
191
+ table.resetColumnFilter = () => {
192
+ if (!table.options.enableAdvanceColumnFilter) return;
193
+ const newState: ColumnFilterState = {
194
+ pendingFilters: [],
195
+ pendingLogic: 'AND',
196
+ filters: [],
197
+ logic: 'AND',
198
+ };
199
+ table.setColumnFilterState(newState);
200
+ // Notify engine so it can reset pagination and refetch (server mode)
201
+ table.options.onColumnFilterApply?.(newState);
202
+ };
203
+
190
204
  table.setPendingFilterLogic = (logic: 'AND' | 'OR') => {
191
205
  if (!table.options.enableAdvanceColumnFilter) return;
192
206
  table.setColumnFilterState((old) => ({
@@ -628,7 +628,10 @@ export function useDataTableEngine<T extends Record<string, any>>(
628
628
  const delay = nextFetchDelayRef.current ?? 0;
629
629
  nextFetchDelayRef.current = 0; // reset after using
630
630
 
631
- void fetchData({}, { delay, meta: { reason: "stateChange" } });
631
+ const timeoutId = setTimeout(() => {
632
+ void fetchData({}, { delay, meta: { reason: "stateChange" } });
633
+ }, 0);
634
+ return () => clearTimeout(timeoutId);
632
635
  }, [serverKey, fetchData]);
633
636
 
634
637
  // columnFilter apply handler stays explicit (button), but you can also auto-fetch on change if needed