react-restyle-components 0.4.50 → 0.4.51
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.
- package/lib/src/core-components/src/components/Table/Table.js +93 -18
- package/lib/src/core-components/src/components/Table/filters.d.ts +203 -10
- package/lib/src/core-components/src/components/Table/filters.js +319 -203
- package/lib/src/core-components/src/components/Table/index.d.ts +2 -2
- package/lib/src/core-components/src/components/Table/index.js +1 -1
- package/lib/src/core-components/src/components/Table/types.d.ts +10 -3
- package/lib/src/core-components/src/tc.global.css +18 -1
- package/lib/src/core-components/src/tc.module.css +3 -1
- package/package.json +1 -1
|
@@ -49,6 +49,7 @@ const EmptyIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stro
|
|
|
49
49
|
const RefreshIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M23 4v6h-6M1 20v-6h6", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
50
50
|
const PrintIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M6 9V2h12v7M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("rect", { x: "6", y: "14", width: "12", height: "8" })] }));
|
|
51
51
|
const ErrorIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("path", { d: "M12 8v4M12 16h.01", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
52
|
+
const tableFilterCache = new Map();
|
|
52
53
|
export const Table = forwardRef(function TableComponent(props, ref) {
|
|
53
54
|
const { id, data, columns, keyField = '_id', loading = false, loadingIndicator, pagination = true, paginationConfig, totalSize, remote = true, defaultSort, sort: controlledSort, filterable = false, defaultFilters, filters: controlledFilters, defaultShowFilters = true, showFilters: controlledShowFilters, onShowFiltersChange, showFilterToggle = true, searchable = true, searchPlaceholder = 'Search...', defaultSearchValue = '', searchValue: controlledSearchValue, searchDebounce = 300, rowSelection, expandable, editMode = 'dblclick', showEditIcon = false, onCellEdit, exportable = true, exportFileName = 'table_export', exportFormat = 'csv', columnToggle = false, isFieldSelector = true, bordered = true, striped = false, hover = true, compact = false, cellPadding, stickyHeader = false, maxHeight, rowClassName, rowStyle, classNames = {}, styles = {}, className, style, emptyText = 'No data available', onChange, onPageChange, onSortChange, onFilterChange, onSearch, onRowClick, onRowDoubleClick, onClearFilters, toolbar, hideToolbar = false, showFooter = false, caption,
|
|
54
55
|
// Quick configuration props
|
|
@@ -62,7 +63,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
62
63
|
// Toolbar customization
|
|
63
64
|
toolbarPosition = 'top', toolbarLeft, toolbarRight, toolbarCenter,
|
|
64
65
|
// Refresh
|
|
65
|
-
refreshable = false, onRefresh,
|
|
66
|
+
refreshable = false, onRefresh, isForceReload,
|
|
66
67
|
// Print
|
|
67
68
|
printable = false, onPrint,
|
|
68
69
|
// Size
|
|
@@ -84,8 +85,15 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
84
85
|
// Resolve aliases
|
|
85
86
|
const resolvedExportable = isExport ?? exportable;
|
|
86
87
|
const resolvedExportFileName = fileName ?? exportFileName;
|
|
88
|
+
// Read cached filter/search state (survives remounts when parent changes React key)
|
|
89
|
+
const cachedFilterState = tableFilterCache.get(id);
|
|
87
90
|
// Auto-detect remote mode: if totalSize <= data.length, use client-side (remote=false)
|
|
91
|
+
// Exception: if onFilter is provided, always use remote mode (server handles filtering)
|
|
88
92
|
const resolvedRemote = useMemo(() => {
|
|
93
|
+
// If onFilter callback is provided, data is server-filtered - never re-filter client-side
|
|
94
|
+
if (onFilter) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
89
97
|
// If totalSize is provided and is <= data.length, all data is loaded - use client-side
|
|
90
98
|
if (totalSize !== undefined &&
|
|
91
99
|
totalSize > 0 &&
|
|
@@ -94,7 +102,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
94
102
|
}
|
|
95
103
|
// Otherwise use the prop value (default: true)
|
|
96
104
|
return remote;
|
|
97
|
-
}, [remote, totalSize, data.length]);
|
|
105
|
+
}, [remote, totalSize, data.length, onFilter]);
|
|
98
106
|
// Handle isSelectRow shorthand
|
|
99
107
|
const resolvedRowSelection = isSelectRow
|
|
100
108
|
? {
|
|
@@ -146,8 +154,12 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
146
154
|
const tableRef = useRef(null);
|
|
147
155
|
const columnToggleRef = useRef(null);
|
|
148
156
|
const prevSelectionCountRef = useRef(0);
|
|
149
|
-
//
|
|
150
|
-
|
|
157
|
+
// Cache filter component references per dataField to keep stable React element types.
|
|
158
|
+
// Without this, factory functions like TextFilter({...}) create a new component ref
|
|
159
|
+
// on every render, causing React to unmount/remount the filter and lose typed text.
|
|
160
|
+
const filterComponentCacheRef = useRef(new Map());
|
|
161
|
+
// State (use cached values if available to survive remounts from key changes)
|
|
162
|
+
const [internalSearchValue, setInternalSearchValue] = useState(cachedFilterState?.searchValue ?? defaultSearchValue);
|
|
151
163
|
const [internalShowFilters, setInternalShowFilters] = useState(defaultShowFilters);
|
|
152
164
|
const [columnToggleOpen, setColumnToggleOpen] = useState(false);
|
|
153
165
|
const [columnSearch, setColumnSearch] = useState('');
|
|
@@ -179,6 +191,13 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
179
191
|
// Add new columns at the end
|
|
180
192
|
const added = columns.filter((c) => !existingDataFields.has(c.dataField));
|
|
181
193
|
setReorderedColumns([...preserved, ...added]);
|
|
194
|
+
// Clean up filter component cache for removed columns
|
|
195
|
+
const newFieldSet = new Set(columns.map((c) => c.dataField));
|
|
196
|
+
for (const key of filterComponentCacheRef.current.keys()) {
|
|
197
|
+
if (!newFieldSet.has(key)) {
|
|
198
|
+
filterComponentCacheRef.current.delete(key);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
182
201
|
}
|
|
183
202
|
else {
|
|
184
203
|
// Same columns but maybe different data - update column data while preserving order
|
|
@@ -226,34 +245,53 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
226
245
|
// Clear cache when editing target changes
|
|
227
246
|
editorRendererCacheRef.current.clear();
|
|
228
247
|
}, [editingCell]);
|
|
248
|
+
// Force reload: when isForceReload toggles, trigger onRefresh
|
|
249
|
+
const prevForceReloadRef = useRef(isForceReload);
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (prevForceReloadRef.current !== isForceReload &&
|
|
252
|
+
isForceReload !== undefined) {
|
|
253
|
+
prevForceReloadRef.current = isForceReload;
|
|
254
|
+
onRefresh?.();
|
|
255
|
+
}
|
|
256
|
+
}, [isForceReload, onRefresh]);
|
|
229
257
|
const searchValue = controlledSearchValue ?? internalSearchValue;
|
|
230
258
|
const debouncedSearchValue = useTableDebounce(searchValue, searchDebounce);
|
|
231
259
|
// Sort state
|
|
232
260
|
const { sort, handleSort } = useSortState(defaultSort, controlledSort);
|
|
233
|
-
// Filter state
|
|
234
|
-
const { filters, setFilter, clearFilters } = useFilterState(defaultFilters, controlledFilters);
|
|
261
|
+
// Filter state (prefer cached filters over defaultFilters to survive remounts)
|
|
262
|
+
const { filters, setFilter, clearFilters } = useFilterState(cachedFilterState?.filters ?? defaultFilters, controlledFilters);
|
|
235
263
|
// Track if onFilter should be called (only after user interaction)
|
|
236
264
|
const shouldCallOnFilter = useRef(false);
|
|
237
265
|
const filterTypeRef = useRef('filter');
|
|
238
266
|
const onFilterRef = useRef(onFilter);
|
|
239
267
|
// Track last called values to prevent duplicate calls
|
|
240
268
|
const lastOnFilterCallRef = useRef(null);
|
|
241
|
-
// Track focused filter field to restore focus after data changes
|
|
242
|
-
const focusedFilterFieldRef = useRef(null);
|
|
269
|
+
// Track focused filter field to restore focus after data changes (or remounts)
|
|
270
|
+
const focusedFilterFieldRef = useRef(cachedFilterState?.focusedField ?? null);
|
|
243
271
|
const tableContainerRef = useRef(null);
|
|
272
|
+
// Helper: save current filter/search/focus state to module-level cache
|
|
273
|
+
const saveFilterCache = useCallback((updates) => {
|
|
274
|
+
const current = tableFilterCache.get(id) || {
|
|
275
|
+
filters: {},
|
|
276
|
+
searchValue: '',
|
|
277
|
+
focusedField: null,
|
|
278
|
+
};
|
|
279
|
+
tableFilterCache.set(id, { ...current, ...updates });
|
|
280
|
+
}, [id]);
|
|
244
281
|
// Clear focus ref when clicking outside the table
|
|
245
282
|
useEffect(() => {
|
|
246
283
|
const handleClickOutside = (event) => {
|
|
247
284
|
if (tableContainerRef.current &&
|
|
248
285
|
!tableContainerRef.current.contains(event.target)) {
|
|
249
286
|
focusedFilterFieldRef.current = null;
|
|
287
|
+
saveFilterCache({ focusedField: null });
|
|
250
288
|
}
|
|
251
289
|
};
|
|
252
290
|
document.addEventListener('mousedown', handleClickOutside);
|
|
253
291
|
return () => {
|
|
254
292
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
255
293
|
};
|
|
256
|
-
}, []);
|
|
294
|
+
}, [saveFilterCache]);
|
|
257
295
|
// Restore filter focus after data changes
|
|
258
296
|
useEffect(() => {
|
|
259
297
|
const restoreFocus = () => {
|
|
@@ -283,7 +321,18 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
283
321
|
};
|
|
284
322
|
}, [data]);
|
|
285
323
|
// Pagination state
|
|
286
|
-
const { page, pageSize, totalPages, goToPage, goToNextPage, goToPrevPage, goToFirstPage, goToLastPage, changePageSize, } = usePaginationState(resolvedPaginationConfig?.page || 0, resolvedPaginationConfig?.pageSize || 10, totalSize ?? data.length);
|
|
324
|
+
const { page, pageSize, totalPages, goToPage, goToNextPage, goToPrevPage, goToFirstPage, goToLastPage, changePageSize, setPage, } = usePaginationState(resolvedPaginationConfig?.page || 0, resolvedPaginationConfig?.pageSize || 10, totalSize ?? data.length);
|
|
325
|
+
// Reset page to 0 when data changes (e.g., after server-side onFilter response)
|
|
326
|
+
// This ensures new data is visible without re-triggering onFilter.
|
|
327
|
+
const prevDataRef = useRef(data);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (prevDataRef.current !== data) {
|
|
330
|
+
prevDataRef.current = data;
|
|
331
|
+
if (page !== 0) {
|
|
332
|
+
setPage(0);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}, [data, page, setPage]);
|
|
287
336
|
// Row selection
|
|
288
337
|
const { selectedKeys, isSelected, toggleRow, toggleAll, isAllSelected, isIndeterminate, } = useRowSelection(data, resolvedRowSelection?.keyField || keyField, resolvedRowSelection?.mode || 'none', resolvedRowSelection?.selectedRowKeys, resolvedRowSelection?.getCheckboxProps);
|
|
289
338
|
// Row expansion
|
|
@@ -421,7 +470,9 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
421
470
|
shouldCallOnFilter.current = true;
|
|
422
471
|
filterTypeRef.current = 'search';
|
|
423
472
|
onChange?.({ type: 'search', search: value });
|
|
424
|
-
|
|
473
|
+
// Persist to cache
|
|
474
|
+
saveFilterCache({ searchValue: value });
|
|
475
|
+
}, [resetEditingState, onSearch, onChange, saveFilterCache]);
|
|
425
476
|
// Handle sort
|
|
426
477
|
const handleSortClick = useCallback((column) => {
|
|
427
478
|
if (!column.sort)
|
|
@@ -441,7 +492,12 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
441
492
|
shouldCallOnFilter.current = true;
|
|
442
493
|
filterTypeRef.current = 'filter';
|
|
443
494
|
onChange?.({ type: 'filter', filters: newFilters });
|
|
444
|
-
|
|
495
|
+
// Persist to cache (including focused field)
|
|
496
|
+
saveFilterCache({
|
|
497
|
+
filters: newFilters,
|
|
498
|
+
focusedField: focusedFilterFieldRef.current,
|
|
499
|
+
});
|
|
500
|
+
}, [filters, setFilter, onFilterChange, onChange, resetEditingState, saveFilterCache]);
|
|
445
501
|
// Handle clear all filters
|
|
446
502
|
const handleClearFilters = useCallback(() => {
|
|
447
503
|
resetEditingState();
|
|
@@ -453,7 +509,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
453
509
|
onChange?.({ type: 'filter', filters: {} });
|
|
454
510
|
// Reset the tracking ref so subsequent filters can trigger onFilter
|
|
455
511
|
lastOnFilterCallRef.current = null;
|
|
512
|
+
// Clear the cache so remounts also start fresh
|
|
513
|
+
tableFilterCache.delete(id);
|
|
456
514
|
}, [
|
|
515
|
+
id,
|
|
457
516
|
clearFilters,
|
|
458
517
|
onClearFilters,
|
|
459
518
|
onFilterChange,
|
|
@@ -951,6 +1010,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
951
1010
|
return (_jsxs(Toolbar, { className: classNames.toolbar, style: styles.toolbar, children: [_jsxs(ToolbarGroup, { children: [toolbarLeft, searchable && (_jsxs(SearchInput, { children: [_jsx(SearchIcon, {}), _jsx("input", { type: "text", value: searchValue, onChange: (e) => handleSearchChange(e.target.value), onFocus: () => {
|
|
952
1011
|
// Clear column filter focus when global search is focused
|
|
953
1012
|
focusedFilterFieldRef.current = null;
|
|
1013
|
+
saveFilterCache({ focusedField: null });
|
|
954
1014
|
}, placeholder: searchPlaceholder })] })), searchable && (_jsx(ToolbarButton, { onClick: () => handleSearchChange(''), disabled: !searchValue, style: { opacity: searchValue ? 1 : 0.6 }, children: "Clear" })), _jsx(ToolbarButton, { onClick: handleClearFilters, disabled: !hasFilters, style: { opacity: hasFilters ? 1 : 0.6 }, children: "Clear all filters" }), resolvedExportable && hideExcelSheet !== true && (_jsxs(ToolbarButton, { onClick: handleExport, children: [_jsx(DownloadIcon, {}), exportFormat === 'excel' ? 'Export Excel' : 'Export CSV'] })), showFilterToggle && isFieldSelector && (_jsxs("div", { ref: columnToggleRef, style: { position: 'relative' }, children: [_jsx(Tooltip, { content: "Show/Hide Columns", position: "bottom", children: _jsx(ToolbarButton, { "$active": columnToggleOpen, onClick: () => setColumnToggleOpen(!columnToggleOpen), "aria-label": "Toggle column visibility", style: { padding: '0 8px' }, children: _jsx(FilterIcon, {}) }) }), columnToggleOpen && (_jsxs(ColumnTogglePanel, { children: [_jsxs(ColumnToggleHeader, { children: [_jsx("span", { children: "Show/Hide Columns" }), _jsx("button", { onClick: () => setColumnToggleOpen(false), children: _jsx(CloseIcon, {}) })] }), _jsx(ColumnToggleSearch, { children: _jsx("input", { type: "text", value: columnSearch, onChange: (e) => setColumnSearch(e.target.value), placeholder: "Search columns..." }) }), _jsxs(ColumnToggleList, { children: [_jsxs(ColumnToggleItem, { style: {
|
|
955
1015
|
borderBottom: '1px solid #e5e7eb',
|
|
956
1016
|
paddingBottom: 8,
|
|
@@ -1168,14 +1228,26 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
1168
1228
|
column.filter === true ||
|
|
1169
1229
|
column.filterComponent ||
|
|
1170
1230
|
column.filterRenderer;
|
|
1171
|
-
// Get the filter component
|
|
1172
|
-
|
|
1173
|
-
|
|
1231
|
+
// Get the filter component (use cached reference to prevent remounting)
|
|
1232
|
+
let FilterComponent = null;
|
|
1233
|
+
if (hasColumnFilter) {
|
|
1234
|
+
const cacheKey = column.dataField;
|
|
1235
|
+
const resolved = (typeof column.filter === 'function'
|
|
1174
1236
|
? column.filter
|
|
1175
1237
|
: null) ||
|
|
1176
1238
|
column.filterComponent ||
|
|
1177
|
-
getFilterComponent(column.filterType || 'text')
|
|
1178
|
-
|
|
1239
|
+
getFilterComponent(column.filterType || 'text');
|
|
1240
|
+
const cached = filterComponentCacheRef.current.get(cacheKey);
|
|
1241
|
+
if (cached && cached.displayName === resolved.displayName) {
|
|
1242
|
+
FilterComponent = cached;
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
filterComponentCacheRef.current.set(cacheKey, resolved);
|
|
1246
|
+
FilterComponent = resolved;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
// Non-null reference for JSX rendering (guarded by showFilter check below)
|
|
1250
|
+
const ResolvedFilter = FilterComponent;
|
|
1179
1251
|
const onFilter = (value) => handleFilterChange(column.dataField, value);
|
|
1180
1252
|
// Get resized column style
|
|
1181
1253
|
const resizeStyle = resizable
|
|
@@ -1304,7 +1376,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
1304
1376
|
}, onClick: (e) => e.stopPropagation(), children: column.filterRenderer ? (column.filterRenderer(onFilter, column)) : (_jsx("div", { onFocusCapture: () => {
|
|
1305
1377
|
focusedFilterFieldRef.current =
|
|
1306
1378
|
column.dataField;
|
|
1307
|
-
|
|
1379
|
+
saveFilterCache({
|
|
1380
|
+
focusedField: column.dataField,
|
|
1381
|
+
});
|
|
1382
|
+
}, "data-filter-wrapper": column.dataField, style: { width: '100%' }, children: _jsx(ResolvedFilter, { column: column, value: filters[column.dataField], onChange: onFilter, onClear: () => handleFilterChange(column.dataField, null) }, `filter-${column.dataField}`) })) }), showSort && (_jsx("div", { style: {
|
|
1308
1383
|
minWidth: 40,
|
|
1309
1384
|
minHeight: 24,
|
|
1310
1385
|
display: 'flex',
|
|
@@ -61,11 +61,8 @@ export interface NumberFilterOptions {
|
|
|
61
61
|
allowDecimal?: boolean;
|
|
62
62
|
/** Callback to get filter instance for external control */
|
|
63
63
|
getFilter?: (filter: NumberFilterInstance) => void;
|
|
64
|
-
/** Callback when filter value changes */
|
|
65
|
-
onFilter?: (value:
|
|
66
|
-
number: string;
|
|
67
|
-
comparator: string;
|
|
68
|
-
} | null) => void;
|
|
64
|
+
/** Callback when filter value changes - passes string value */
|
|
65
|
+
onFilter?: (value: string | null) => void;
|
|
69
66
|
/** Input ID */
|
|
70
67
|
id?: string;
|
|
71
68
|
/** Disabled state */
|
|
@@ -88,7 +85,7 @@ export interface NumberFilterInstance {
|
|
|
88
85
|
setValue: (value: {
|
|
89
86
|
number: string;
|
|
90
87
|
comparator: string;
|
|
91
|
-
} | null) => void;
|
|
88
|
+
} | string | null) => void;
|
|
92
89
|
/** Clear the filter */
|
|
93
90
|
clear: () => void;
|
|
94
91
|
}
|
|
@@ -193,7 +190,9 @@ export interface DateFilterInstance {
|
|
|
193
190
|
* // As a component (for direct usage)
|
|
194
191
|
* <TextFilter column={column} value={value} onChange={onChange} />
|
|
195
192
|
*/
|
|
196
|
-
export declare function TextFilter(options: TextFilterOptions): React.FC<TableFilterProps
|
|
193
|
+
export declare function TextFilter(options: TextFilterOptions): React.FC<TableFilterProps> & {
|
|
194
|
+
props: TextFilterOptions;
|
|
195
|
+
};
|
|
197
196
|
export declare function TextFilter(props: TableFilterProps): React.ReactElement;
|
|
198
197
|
/**
|
|
199
198
|
* Number filter - can be used as a component or factory function
|
|
@@ -207,7 +206,9 @@ export declare function TextFilter(props: TableFilterProps): React.ReactElement;
|
|
|
207
206
|
* getFilter: (filter) => { myFilterRef = filter; },
|
|
208
207
|
* })
|
|
209
208
|
*/
|
|
210
|
-
export declare function NumberFilter(options: NumberFilterOptions): React.FC<TableFilterProps
|
|
209
|
+
export declare function NumberFilter(options: NumberFilterOptions): React.FC<TableFilterProps> & {
|
|
210
|
+
props: NumberFilterOptions;
|
|
211
|
+
};
|
|
211
212
|
export declare function NumberFilter(props: TableFilterProps): React.ReactElement;
|
|
212
213
|
/**
|
|
213
214
|
* Date filter - can be used as a component or factory function
|
|
@@ -220,7 +221,9 @@ export declare function NumberFilter(props: TableFilterProps): React.ReactElemen
|
|
|
220
221
|
* getFilter: (filter) => { myFilterRef = filter; },
|
|
221
222
|
* })
|
|
222
223
|
*/
|
|
223
|
-
export declare function DateFilter(options: DateFilterOptions): React.FC<TableFilterProps
|
|
224
|
+
export declare function DateFilter(options: DateFilterOptions): React.FC<TableFilterProps> & {
|
|
225
|
+
props: DateFilterOptions;
|
|
226
|
+
};
|
|
224
227
|
export declare function DateFilter(props: TableFilterProps): React.ReactElement;
|
|
225
228
|
/**
|
|
226
229
|
* Select filter - can be used as a component or factory function
|
|
@@ -233,8 +236,198 @@ export declare function DateFilter(props: TableFilterProps): React.ReactElement;
|
|
|
233
236
|
* getFilter: (filter) => { myFilterRef = filter; },
|
|
234
237
|
* })
|
|
235
238
|
*/
|
|
236
|
-
export declare function SelectFilter(options: SelectFilterOptions): React.FC<TableFilterProps
|
|
239
|
+
export declare function SelectFilter(options: SelectFilterOptions): React.FC<TableFilterProps> & {
|
|
240
|
+
props: SelectFilterOptions;
|
|
241
|
+
};
|
|
237
242
|
export declare function SelectFilter(props: TableFilterProps): React.ReactElement;
|
|
243
|
+
/**
|
|
244
|
+
* Custom filter options for factory function pattern
|
|
245
|
+
* Allows rendering any custom filter component while integrating with Table's filter system
|
|
246
|
+
*/
|
|
247
|
+
export interface CustomFilterOptions<T = any> {
|
|
248
|
+
/** Custom render function for the filter UI (optional if using column.filterRenderer) */
|
|
249
|
+
render?: (props: CustomFilterRenderProps<T>) => React.ReactNode;
|
|
250
|
+
/** Placeholder text (passed to render props) */
|
|
251
|
+
placeholder?: string;
|
|
252
|
+
/** CSS class name(s) for the filter */
|
|
253
|
+
className?: string;
|
|
254
|
+
/** Inline style for the filter */
|
|
255
|
+
style?: React.CSSProperties;
|
|
256
|
+
/** Default value for the filter */
|
|
257
|
+
defaultValue?: T;
|
|
258
|
+
/** Debounce delay in milliseconds */
|
|
259
|
+
delay?: number;
|
|
260
|
+
/** Callback to get filter instance for external control */
|
|
261
|
+
getFilter?: (filter: CustomFilterInstance<T>) => void;
|
|
262
|
+
/** Callback when filter value changes */
|
|
263
|
+
onFilter?: (value: T | null) => void;
|
|
264
|
+
/** Custom filter function for data filtering */
|
|
265
|
+
filterFunction?: (cellValue: any, filterValue: T, row: any) => boolean;
|
|
266
|
+
/** Input ID */
|
|
267
|
+
id?: string;
|
|
268
|
+
/** Disabled state */
|
|
269
|
+
disabled?: boolean;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Props passed to custom filter render function
|
|
273
|
+
*/
|
|
274
|
+
export interface CustomFilterRenderProps<T = any> {
|
|
275
|
+
/** Current filter value */
|
|
276
|
+
value: T | null;
|
|
277
|
+
/** Callback to update filter value */
|
|
278
|
+
onChange: (value: T | null) => void;
|
|
279
|
+
/** Column configuration */
|
|
280
|
+
column: any;
|
|
281
|
+
/** Clear the filter */
|
|
282
|
+
clear: () => void;
|
|
283
|
+
/** Placeholder text from options */
|
|
284
|
+
placeholder?: string;
|
|
285
|
+
/** CSS class name from options */
|
|
286
|
+
className?: string;
|
|
287
|
+
/** Style from options */
|
|
288
|
+
style?: React.CSSProperties;
|
|
289
|
+
/** Input ID from options */
|
|
290
|
+
id?: string;
|
|
291
|
+
/** Disabled state from options */
|
|
292
|
+
disabled?: boolean;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Custom filter instance returned by getFilter callback
|
|
296
|
+
*/
|
|
297
|
+
export interface CustomFilterInstance<T = any> {
|
|
298
|
+
/** Current filter value */
|
|
299
|
+
value: T | null;
|
|
300
|
+
/** Set filter value programmatically */
|
|
301
|
+
setValue: (value: T | null) => void;
|
|
302
|
+
/** Clear the filter */
|
|
303
|
+
clear: () => void;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Custom filter - allows rendering any custom filter component
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* // Basic custom filter with input
|
|
310
|
+
* filter: CustomFilter({
|
|
311
|
+
* render: ({ value, onChange }) => (
|
|
312
|
+
* <input
|
|
313
|
+
* type="text"
|
|
314
|
+
* value={value || ''}
|
|
315
|
+
* onChange={(e) => onChange(e.target.value || null)}
|
|
316
|
+
* placeholder="Custom filter..."
|
|
317
|
+
* />
|
|
318
|
+
* ),
|
|
319
|
+
* })
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // Using render props (placeholder, className, etc.)
|
|
323
|
+
* filter: CustomFilter({
|
|
324
|
+
* placeholder: 'Search...',
|
|
325
|
+
* className: 'my-custom-input',
|
|
326
|
+
* render: ({ value, onChange, placeholder, className }) => (
|
|
327
|
+
* <input
|
|
328
|
+
* type="text"
|
|
329
|
+
* value={value || ''}
|
|
330
|
+
* onChange={(e) => onChange(e.target.value || null)}
|
|
331
|
+
* placeholder={placeholder}
|
|
332
|
+
* className={className}
|
|
333
|
+
* />
|
|
334
|
+
* ),
|
|
335
|
+
* })
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* // Using with column.filterRenderer (simple options pattern)
|
|
339
|
+
* // This allows using existing filter components with custom configuration
|
|
340
|
+
* {
|
|
341
|
+
* dataField: 'picture',
|
|
342
|
+
* text: 'Picture',
|
|
343
|
+
* filter: CustomFilter({
|
|
344
|
+
* placeholder: 'Picture',
|
|
345
|
+
* getFilter: (filter) => {
|
|
346
|
+
* pictureFilterRef.current = filter;
|
|
347
|
+
* },
|
|
348
|
+
* }),
|
|
349
|
+
* filterRenderer: (onFilter, column) => (
|
|
350
|
+
* <NumberFilter onFilter={onFilter} column={column} />
|
|
351
|
+
* ),
|
|
352
|
+
* }
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* // Custom range filter
|
|
356
|
+
* filter: CustomFilter({
|
|
357
|
+
* render: ({ value, onChange }) => (
|
|
358
|
+
* <div style={{ display: 'flex', gap: 4 }}>
|
|
359
|
+
* <input
|
|
360
|
+
* type="number"
|
|
361
|
+
* placeholder="Min"
|
|
362
|
+
* value={value?.min || ''}
|
|
363
|
+
* onChange={(e) => onChange({ ...value, min: e.target.value })}
|
|
364
|
+
* />
|
|
365
|
+
* <input
|
|
366
|
+
* type="number"
|
|
367
|
+
* placeholder="Max"
|
|
368
|
+
* value={value?.max || ''}
|
|
369
|
+
* onChange={(e) => onChange({ ...value, max: e.target.value })}
|
|
370
|
+
* />
|
|
371
|
+
* </div>
|
|
372
|
+
* ),
|
|
373
|
+
* filterFunction: (cellValue, filterValue) => {
|
|
374
|
+
* if (!filterValue) return true;
|
|
375
|
+
* const { min, max } = filterValue;
|
|
376
|
+
* const num = Number(cellValue);
|
|
377
|
+
* if (min && num < Number(min)) return false;
|
|
378
|
+
* if (max && num > Number(max)) return false;
|
|
379
|
+
* return true;
|
|
380
|
+
* },
|
|
381
|
+
* })
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* // Custom multi-select filter with checkboxes
|
|
385
|
+
* filter: CustomFilter({
|
|
386
|
+
* render: ({ value, onChange }) => {
|
|
387
|
+
* const selected = value || [];
|
|
388
|
+
* const options = ['Active', 'Inactive', 'Pending'];
|
|
389
|
+
* return (
|
|
390
|
+
* <div>
|
|
391
|
+
* {options.map(opt => (
|
|
392
|
+
* <label key={opt}>
|
|
393
|
+
* <input
|
|
394
|
+
* type="checkbox"
|
|
395
|
+
* checked={selected.includes(opt)}
|
|
396
|
+
* onChange={(e) => {
|
|
397
|
+
* if (e.target.checked) {
|
|
398
|
+
* onChange([...selected, opt]);
|
|
399
|
+
* } else {
|
|
400
|
+
* onChange(selected.filter(s => s !== opt));
|
|
401
|
+
* }
|
|
402
|
+
* }}
|
|
403
|
+
* />
|
|
404
|
+
* {opt}
|
|
405
|
+
* </label>
|
|
406
|
+
* ))}
|
|
407
|
+
* </div>
|
|
408
|
+
* );
|
|
409
|
+
* },
|
|
410
|
+
* filterFunction: (cellValue, filterValue) => {
|
|
411
|
+
* if (!filterValue?.length) return true;
|
|
412
|
+
* return filterValue.includes(cellValue);
|
|
413
|
+
* },
|
|
414
|
+
* })
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* // External control with getFilter
|
|
418
|
+
* filter: CustomFilter({
|
|
419
|
+
* placeholder: 'Custom...',
|
|
420
|
+
* getFilter: (filter) => {
|
|
421
|
+
* customFilterRef.current = filter;
|
|
422
|
+
* // filter.value - get current value
|
|
423
|
+
* // filter.setValue(newValue) - set value programmatically
|
|
424
|
+
* // filter.clear() - clear the filter
|
|
425
|
+
* },
|
|
426
|
+
* })
|
|
427
|
+
*/
|
|
428
|
+
export declare function CustomFilter<T = any>(options: CustomFilterOptions<T>): React.FC<TableFilterProps> & {
|
|
429
|
+
props: CustomFilterOptions<T>;
|
|
430
|
+
};
|
|
238
431
|
/**
|
|
239
432
|
* Get filter component based on type
|
|
240
433
|
*/
|