react-restyle-components 0.4.49 → 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.
Files changed (27) hide show
  1. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.d.ts +3 -0
  2. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.js +237 -0
  3. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.d.ts +2 -0
  4. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.js +1 -0
  5. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.d.ts +29 -0
  6. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.js +1 -0
  7. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.d.ts +3 -0
  8. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.js +42 -0
  9. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.d.ts +3 -0
  10. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.js +349 -0
  11. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.d.ts +2 -0
  12. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.js +1 -0
  13. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.d.ts +75 -0
  14. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.js +1 -0
  15. package/lib/src/core-components/src/components/ProgressStepper/index.d.ts +4 -0
  16. package/lib/src/core-components/src/components/ProgressStepper/index.js +2 -0
  17. package/lib/src/core-components/src/components/Table/Table.js +102 -25
  18. package/lib/src/core-components/src/components/Table/filters.d.ts +203 -10
  19. package/lib/src/core-components/src/components/Table/filters.js +319 -203
  20. package/lib/src/core-components/src/components/Table/index.d.ts +2 -2
  21. package/lib/src/core-components/src/components/Table/index.js +1 -1
  22. package/lib/src/core-components/src/components/Table/types.d.ts +13 -4
  23. package/lib/src/core-components/src/components/index.d.ts +1 -0
  24. package/lib/src/core-components/src/components/index.js +1 -0
  25. package/lib/src/core-components/src/tc.global.css +1 -252
  26. package/lib/src/core-components/src/tc.module.css +1 -1
  27. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export * from './ProgressStepper';
2
+ export type { ProgressStepperOrientation, ProgressStepperSize, ProgressStepperStepState, ProgressStepperIndicatorType, ProgressStepperStep, ProgressStepperLineItem, ProgressStepperProps, } from './types';
@@ -0,0 +1 @@
1
+ export * from './ProgressStepper';
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ export type ProgressStepperOrientation = 'horizontal' | 'vertical';
3
+ export type ProgressStepperSize = 'small' | 'medium' | 'large';
4
+ export type ProgressStepperStepState = 'incomplete' | 'inprogress' | 'complete' | 'warning' | 'error';
5
+ /**
6
+ * Indicator type for each step:
7
+ * - 'dot': Simple circle dot
8
+ * - 'number': Step number display
9
+ * - 'icon': Custom icon only
10
+ * - 'iconCircle': Icon inside a circle
11
+ * - 'check': Checkmark icon (auto for complete state)
12
+ */
13
+ export type ProgressStepperIndicatorType = 'dot' | 'number' | 'icon' | 'iconCircle' | 'check';
14
+ export interface ProgressStepperStep {
15
+ /** Unique identifier for the step */
16
+ id?: string;
17
+ /** Current state of the step */
18
+ state: ProgressStepperStepState;
19
+ /** Title text for the step */
20
+ stepTitle?: string;
21
+ /** Subtitle text for the step */
22
+ stepSubTitle?: string;
23
+ /** Link URL for the step */
24
+ stepLinkHref?: string;
25
+ /** Link text for the step */
26
+ stepLinkText?: string;
27
+ /** Tag text to display */
28
+ tagText?: string;
29
+ /** Tag variant */
30
+ tagVariant?: 'success' | 'error' | 'warning' | 'info' | 'neutral';
31
+ /** Sub-steps for partial progress */
32
+ subSteps?: ProgressStepperStep[];
33
+ /** Custom icon for the step (React node) */
34
+ icon?: React.ReactNode;
35
+ /** Icon source URL (alternative to icon prop) */
36
+ iconSrc?: string;
37
+ /** Step number override (for number indicator type) */
38
+ stepNumber?: number;
39
+ }
40
+ export interface ProgressStepperLineItem extends ProgressStepperStep {
41
+ /** Index of the step in the list */
42
+ index: number;
43
+ /** Total number of steps */
44
+ stepsAmount: number;
45
+ /** Size of the step indicator */
46
+ size: ProgressStepperSize;
47
+ /** Orientation of the stepper */
48
+ orientation: ProgressStepperOrientation;
49
+ /** Whether to use compact spacing */
50
+ isPacked?: boolean;
51
+ /** Type of indicator to display */
52
+ indicatorType: ProgressStepperIndicatorType;
53
+ /** Primary color for active/complete states */
54
+ activeColor?: string;
55
+ /** Color for incomplete state */
56
+ inactiveColor?: string;
57
+ }
58
+ export interface ProgressStepperProps extends React.HTMLAttributes<HTMLUListElement> {
59
+ /** Array of steps to display */
60
+ steps: ProgressStepperStep[];
61
+ /** Orientation of the progress stepper */
62
+ orientation?: ProgressStepperOrientation;
63
+ /** Size of the step indicators */
64
+ size?: ProgressStepperSize;
65
+ /** Whether to use compact vertical spacing */
66
+ isPacked?: boolean;
67
+ /** Type of indicator to display for all steps */
68
+ indicatorType?: ProgressStepperIndicatorType;
69
+ /** Primary color for active/complete states */
70
+ activeColor?: string;
71
+ /** Color for incomplete state */
72
+ inactiveColor?: string;
73
+ /** Custom class name */
74
+ className?: string;
75
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ProgressBar';
2
+ export * from './ProgressStepper';
3
+ export type { ProgressBarProps } from './ProgressBar/types';
4
+ export type { ProgressStepperOrientation, ProgressStepperSize, ProgressStepperStepState, ProgressStepperIndicatorType, ProgressStepperStep, ProgressStepperLineItem, ProgressStepperProps, } from './ProgressStepper/types';
@@ -0,0 +1,2 @@
1
+ export * from './ProgressBar';
2
+ export * from './ProgressStepper';
@@ -49,8 +49,9 @@ 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
- 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, 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
+ 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
55
56
  isDelete = false, isEditModify, isUpdate, isExport, isSelectRow, getNonSelectableRows, nonSelectableStyle, isView = false, fileName, hideExcelSheet = false,
56
57
  // Quick callbacks
@@ -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
- // State
150
- const [internalSearchValue, setInternalSearchValue] = useState(defaultSearchValue);
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
- }, [resetEditingState, onSearch, onChange]);
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
- }, [filters, setFilter, onFilterChange, onChange, resetEditingState]);
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,7 +1010,8 @@ 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;
954
- }, 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 && (_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: {
1013
+ saveFilterCache({ focusedField: null });
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,
957
1017
  marginBottom: 4,
@@ -1011,7 +1071,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
1011
1071
  }
1012
1072
  setToggleDraggingColumn(null);
1013
1073
  setToggleDragOverColumn(null);
1014
- }, children: [_jsx("input", { type: "checkbox", checked: !isColumnHidden(column.dataField), onChange: () => toggleColumn(column.dataField), onClick: (e) => e.stopPropagation() }), _jsx("span", { children: column.text }), reorderable && (_jsx(ColumnToggleDragHandle, { "$isDragging": toggleDraggingColumn === column.dataField, title: "Drag to reorder" }))] }, column.dataField)))] })] }))] }))] }), toolbarCenter, _jsxs(ToolbarGroup, { children: [refreshable && (_jsxs(ToolbarButton, { onClick: onRefresh, children: [_jsx(RefreshIcon, {}), "Refresh"] })), printable && (_jsxs(ToolbarButton, { onClick: onPrint, children: [_jsx(PrintIcon, {}), "Print"] })), columnToggle && (_jsxs("div", { ref: !showFilterToggle ? columnToggleRef : undefined, style: { position: 'relative' }, children: [_jsxs(ToolbarButton, { "$active": columnToggleOpen, onClick: () => setColumnToggleOpen(!columnToggleOpen), children: [_jsx(ColumnsIcon, {}), "Columns"] }), columnToggleOpen && (_jsxs(ColumnTogglePanel, { children: [_jsxs(ColumnToggleHeader, { children: [_jsx("span", { children: "Toggle 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..." }) }), _jsx(ColumnToggleList, { children: filteredToggleColumns.map((column, index) => (_jsxs(ColumnToggleItem, { "$reorderable": reorderable, "$isDragging": toggleDraggingColumn === column.dataField, "$isDragOver": toggleDragOverColumn === column.dataField, draggable: reorderable, onDragStart: (e) => {
1074
+ }, children: [_jsx("input", { type: "checkbox", checked: !isColumnHidden(column.dataField), onChange: () => toggleColumn(column.dataField), onClick: (e) => e.stopPropagation() }), _jsx("span", { children: column.text }), reorderable && (_jsx(ColumnToggleDragHandle, { "$isDragging": toggleDraggingColumn === column.dataField, title: "Drag to reorder" }))] }, column.dataField)))] })] }))] }))] }), toolbarCenter, _jsxs(ToolbarGroup, { children: [refreshable && (_jsxs(ToolbarButton, { onClick: onRefresh, children: [_jsx(RefreshIcon, {}), "Refresh"] })), printable && (_jsxs(ToolbarButton, { onClick: onPrint, children: [_jsx(PrintIcon, {}), "Print"] })), columnToggle && isFieldSelector && (_jsxs("div", { ref: !showFilterToggle ? columnToggleRef : undefined, style: { position: 'relative' }, children: [_jsxs(ToolbarButton, { "$active": columnToggleOpen, onClick: () => setColumnToggleOpen(!columnToggleOpen), children: [_jsx(ColumnsIcon, {}), "Columns"] }), columnToggleOpen && (_jsxs(ColumnTogglePanel, { children: [_jsxs(ColumnToggleHeader, { children: [_jsx("span", { children: "Toggle 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..." }) }), _jsx(ColumnToggleList, { children: filteredToggleColumns.map((column, index) => (_jsxs(ColumnToggleItem, { "$reorderable": reorderable, "$isDragging": toggleDraggingColumn === column.dataField, "$isDragOver": toggleDragOverColumn === column.dataField, draggable: reorderable, onDragStart: (e) => {
1015
1075
  if (!reorderable)
1016
1076
  return;
1017
1077
  setToggleDraggingColumn(column.dataField);
@@ -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
- const FilterComponent = hasColumnFilter
1173
- ? (typeof column.filter === 'function'
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
- : null;
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
- }, "data-filter-wrapper": column.dataField, style: { width: '100%' }, children: _jsx(FilterComponent, { column: column, value: filters[column.dataField], onChange: onFilter, onClear: () => handleFilterChange(column.dataField, null) }, `filter-${column.dataField}`) })) }), showSort && (_jsx("div", { style: {
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',
@@ -1419,10 +1494,12 @@ export const Table = forwardRef(function TableComponent(props, ref) {
1419
1494
  // When disabled: false or not disabled, no styles should be applied
1420
1495
  const isRowDisabled = !!checkboxProps?.disabled;
1421
1496
  const disabledStyle = isRowDisabled
1422
- ? nonSelectableStyle || {
1423
- backgroundColor: '#f3f4f6',
1424
- opacity: 0.7,
1425
- }
1497
+ ? typeof nonSelectableStyle === 'function'
1498
+ ? nonSelectableStyle(row, rowIndex)
1499
+ : nonSelectableStyle || {
1500
+ backgroundColor: '#f3f4f6',
1501
+ opacity: 0.7,
1502
+ }
1426
1503
  : undefined;
1427
1504
  return (_jsxs(React.Fragment, { children: [_jsxs(TableRow, { "$selected": rowSelected, "$clickable": !!onRowClick ||
1428
1505
  resolvedRowSelection?.mode === 'single' ||
@@ -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
  */