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

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,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
+ }