@codella-software/react 2.2.27 → 2.3.1

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,73 +1,158 @@
1
- import { FiltersAndSortService } from '@codella-software/utils';
1
+ import { FiltersAndSortService, FilterAndSortState, SortDirection } from '@codella-software/utils';
2
+ /**
3
+ * Configuration for creating a new FiltersAndSortService instance
4
+ */
5
+ export interface FiltersAndSortConfig<T extends Record<string, any> = Record<string, any>> {
6
+ /** Unique storage key for persistence */
7
+ storageKey: string;
8
+ /** Whether to persist state to storage (default: true) */
9
+ persistToStorage?: boolean;
10
+ /** Initial state override */
11
+ initialState?: Partial<FilterAndSortState<T>>;
12
+ /** Default filter values used when clearing filters */
13
+ defaultFilters?: T;
14
+ /** Skip hydrating state from storage on init */
15
+ skipStorageHydration?: boolean;
16
+ /** Debounce time for storage persistence in ms (default: 300) */
17
+ storageDebounceMs?: number;
18
+ /** Optional storage key prefix */
19
+ storageKeyPrefix?: string;
20
+ }
2
21
  /**
3
22
  * Hook options for useFiltersAndSort
4
23
  */
5
- export interface UseFiltersAndSortOptions {
6
- /** FiltersAndSortService instance */
7
- service: FiltersAndSortService;
24
+ export interface UseFiltersAndSortOptions<T extends Record<string, any> = Record<string, any>> {
25
+ /** An existing FiltersAndSortService instance to use */
26
+ service?: FiltersAndSortService<T>;
27
+ /** Configuration to create a new service instance (mutually exclusive with service) */
28
+ config?: FiltersAndSortConfig<T>;
29
+ /** Enable debug logging */
30
+ debug?: boolean;
31
+ /** Custom equality function for state comparison */
32
+ isEqual?: (prev: FilterAndSortState<T>, next: FilterAndSortState<T>) => boolean;
8
33
  }
9
34
  /**
10
35
  * Hook return value for useFiltersAndSort
11
36
  */
12
- export interface UseFiltersAndSortReturn {
37
+ export interface UseFiltersAndSortReturn<T extends Record<string, any> = Record<string, any>> {
13
38
  /** Current filter state */
14
- filters: Record<string, any>;
39
+ filters: T;
15
40
  /** Current sort field */
16
41
  sortBy: string | null;
17
42
  /** Current sort direction */
18
- sortDirection: 'asc' | 'desc';
43
+ sortDirection: SortDirection;
19
44
  /** Current page (0-based) */
20
45
  page: number;
21
46
  /** Page size */
22
47
  pageSize: number;
23
48
  /** Current search query */
24
49
  query: string;
25
- /** Set filter */
26
- setFilter: (key: string, value: any) => void;
27
- /** Clear specific filter */
28
- clearFilter: (key: string) => void;
29
- /** Clear all filters */
50
+ /** Full state object for advanced use cases */
51
+ state: FilterAndSortState<T>;
52
+ /** Set a single filter value */
53
+ setFilter: <K extends keyof T>(key: K, value: T[K]) => void;
54
+ /** Set multiple filters at once */
55
+ setFilters: (filters: Partial<T>) => void;
56
+ /** Clear a specific filter */
57
+ clearFilter: <K extends keyof T>(key: K) => void;
58
+ /** Clear all filters (resets to defaultFilters) */
30
59
  clearAllFilters: () => void;
60
+ /** Check if a specific filter is active */
61
+ hasFilter: <K extends keyof T>(key: K) => boolean;
62
+ /** Check if any filters are active */
63
+ hasAnyFilters: () => boolean;
64
+ /** Get count of active filters */
65
+ activeFilterCount: number;
31
66
  /** Set/toggle sort (auto-toggles between asc -> desc -> none) */
32
67
  setSort: (field: string) => void;
33
68
  /** Toggle sort direction for field (alias for setSort) */
34
69
  toggleSort: (field: string) => void;
70
+ /** Clear current sort */
71
+ clearSort: () => void;
72
+ /** Check if a field is currently sorted */
73
+ isSortedBy: (field: string) => boolean;
35
74
  /** Set page */
36
75
  setPage: (page: number) => void;
37
- /** Set page size */
76
+ /** Set page size (resets to page 0) */
38
77
  setPageSize: (size: number) => void;
78
+ /** Set both page and pageSize atomically */
79
+ setPagination: (page: number, size?: number) => void;
39
80
  /** Go to next page */
40
81
  nextPage: () => void;
41
82
  /** Go to previous page */
42
83
  prevPage: () => void;
84
+ /** Go to first page */
85
+ firstPage: () => void;
86
+ /** Check if on first page */
87
+ isFirstPage: boolean;
88
+ /** Calculate offset for API requests */
89
+ offset: number;
43
90
  /** Set search query */
44
91
  setQuery: (query: string) => void;
92
+ /** Clear search query */
93
+ clearQuery: () => void;
45
94
  /** Reset to initial state */
46
95
  reset: () => void;
96
+ /** Clear storage and reset */
97
+ clearStorageAndReset: () => void;
98
+ /** Get the storage key being used */
99
+ storageKey: string;
100
+ /** Direct access to the underlying service (for advanced use cases) */
101
+ getService: () => FiltersAndSortService<T>;
47
102
  }
48
103
  /**
49
- * React hook that wraps FiltersAndSortService
104
+ * React hook that wraps FiltersAndSortService with enhanced functionality
50
105
  *
51
- * @param options - Hook options including FiltersAndSortService instance
106
+ * @typeParam T - The filters object type
107
+ * @param options - Hook options including service instance or config
52
108
  * @returns Filters and sort state with control methods
53
109
  *
54
110
  * @example
55
111
  * ```tsx
56
- * const service = new FiltersAndSortService({ storageKey: 'my-filters' });
112
+ * // Option 1: Create service externally (recommended for sharing between components)
113
+ * const service = new FiltersAndSortService<MyFilters>({ storageKey: 'my-filters' });
57
114
  *
58
115
  * function MyTable() {
59
- * const filters = useFiltersAndSort({ service });
116
+ * const {
117
+ * filters,
118
+ * setFilter,
119
+ * clearAllFilters,
120
+ * hasAnyFilters,
121
+ * activeFilterCount
122
+ * } = useFiltersAndSort({ service });
123
+ *
60
124
  * return (
61
125
  * <div>
62
126
  * <input
63
- * value={filters.filters.name || ''}
64
- * onChange={(e) => filters.setFilter('name', e.target.value)}
127
+ * value={filters.name || ''}
128
+ * onChange={(e) => setFilter('name', e.target.value)}
65
129
  * />
66
- * <button onClick={() => filters.clearAllFilters()}>Clear</button>
130
+ * {hasAnyFilters() && (
131
+ * <button onClick={clearAllFilters}>
132
+ * Clear ({activeFilterCount})
133
+ * </button>
134
+ * )}
67
135
  * </div>
68
136
  * );
69
137
  * }
138
+ *
139
+ * // Option 2: Let the hook create the service (simpler for single-component use)
140
+ * function SimpleTable() {
141
+ * const filters = useFiltersAndSort({
142
+ * config: {
143
+ * storageKey: 'simple-table',
144
+ * defaultFilters: { status: 'all' }
145
+ * }
146
+ * });
147
+ * // ...
148
+ * }
70
149
  * ```
71
150
  */
72
- export declare function useFiltersAndSort(options: UseFiltersAndSortOptions): UseFiltersAndSortReturn;
151
+ export declare function useFiltersAndSort<T extends Record<string, any> = Record<string, any>>(options: UseFiltersAndSortOptions<T>): UseFiltersAndSortReturn<T>;
152
+ /**
153
+ * Convenience hook for creating a filters service with simpler API
154
+ * Equivalent to useFiltersAndSort({ config: ... })
155
+ */
156
+ export declare function useFilters<T extends Record<string, any> = Record<string, any>>(storageKey: string, options?: Omit<FiltersAndSortConfig<T>, 'storageKey'>): UseFiltersAndSortReturn<T>;
157
+ export type { FilterAndSortState, SortDirection };
73
158
  //# sourceMappingURL=useFiltersAndSort.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFiltersAndSort.d.ts","sourceRoot":"","sources":["../../src/filters-and-sort/useFiltersAndSort.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAA2B,MAAM,yBAAyB,CAAA;AAGxF;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,qCAAqC;IACrC,OAAO,EAAE,qBAAqB,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,yBAAyB;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,6BAA6B;IAC7B,aAAa,EAAE,KAAK,GAAG,MAAM,CAAA;IAC7B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB;IACjB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAA;IAC5C,4BAA4B;IAC5B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,wBAAwB;IACxB,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,iEAAiE;IACjE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,0DAA0D;IAC1D,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,eAAe;IACf,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,oBAAoB;IACpB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,sBAAsB;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,uBAAuB;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CAkFzB"}
1
+ {"version":3,"file":"useFiltersAndSort.d.ts","sourceRoot":"","sources":["../../src/filters-and-sort/useFiltersAndSort.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,77 +1,317 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const utils = require("@codella-software/utils");
3
4
  const react = require("react");
4
5
  const jsxRuntime = require("react/jsx-runtime");
5
6
  const liveUpdates = require("@codella-software/utils/live-updates");
6
7
  const richContent = require("@codella-software/utils/rich-content");
7
8
  const tabs = require("@codella-software/utils/tabs");
9
+ function defaultIsEqual(prev, next) {
10
+ if (prev === next) return true;
11
+ if (prev.query !== next.query) return false;
12
+ if (prev.pagination.page !== next.pagination.page) return false;
13
+ if (prev.pagination.limit !== next.pagination.limit) return false;
14
+ const prevSort = prev.sort;
15
+ const nextSort = next.sort;
16
+ if ((prevSort == null ? void 0 : prevSort.fieldName) !== (nextSort == null ? void 0 : nextSort.fieldName)) return false;
17
+ if ((prevSort == null ? void 0 : prevSort.direction) !== (nextSort == null ? void 0 : nextSort.direction)) return false;
18
+ const prevFilters = prev.filters;
19
+ const nextFilters = next.filters;
20
+ const prevKeys = Object.keys(prevFilters);
21
+ const nextKeys = Object.keys(nextFilters);
22
+ if (prevKeys.length !== nextKeys.length) return false;
23
+ for (const key of prevKeys) {
24
+ if (prevFilters[key] !== nextFilters[key]) return false;
25
+ }
26
+ return true;
27
+ }
28
+ function countActiveFilters(filters, defaultFilters) {
29
+ let count = 0;
30
+ const keys = Object.keys(filters);
31
+ for (const key of keys) {
32
+ const value = filters[key];
33
+ const defaultValue = defaultFilters == null ? void 0 : defaultFilters[key];
34
+ const isDefault = value === defaultValue;
35
+ const isEmpty = value === null || value === void 0 || value === "";
36
+ const isEmptyArray = Array.isArray(value) && value.length === 0;
37
+ if (!isEmpty && !isEmptyArray && !isDefault) {
38
+ count++;
39
+ }
40
+ }
41
+ return count;
42
+ }
8
43
  function useFiltersAndSort(options) {
9
- const { service } = options;
10
- if (!service) {
11
- throw new Error("useFiltersAndSort: service is required");
44
+ const { service: externalService, config, debug = false, isEqual = defaultIsEqual } = options;
45
+ if (!externalService && !config) {
46
+ throw new Error(
47
+ "useFiltersAndSort: either `service` or `config` must be provided. Pass an existing FiltersAndSortService instance via `service`, or provide a `config` object to create a new service."
48
+ );
12
49
  }
13
- const [state, setState] = react.useState(() => {
14
- return service.getCurrentState();
15
- });
50
+ if (externalService && config) {
51
+ if (debug) {
52
+ console.warn(
53
+ "useFiltersAndSort: both `service` and `config` were provided. The `service` will be used and `config` will be ignored."
54
+ );
55
+ }
56
+ }
57
+ const ownsServiceRef = react.useRef(false);
58
+ const serviceRef = react.useRef(null);
59
+ const isEqualRef = react.useRef(isEqual);
16
60
  react.useEffect(() => {
17
- if (!service) {
18
- return;
61
+ isEqualRef.current = isEqual;
62
+ }, [isEqual]);
63
+ if (!serviceRef.current) {
64
+ if (externalService) {
65
+ serviceRef.current = externalService;
66
+ ownsServiceRef.current = false;
67
+ } else if (config) {
68
+ serviceRef.current = new utils.FiltersAndSortService({
69
+ storageKey: config.storageKey,
70
+ persistToStorage: config.persistToStorage,
71
+ initialState: config.initialState,
72
+ defaultFilters: config.defaultFilters,
73
+ skipStorageHydration: config.skipStorageHydration,
74
+ storageDebounceMs: config.storageDebounceMs,
75
+ storageKeyPrefix: config.storageKeyPrefix
76
+ });
77
+ ownsServiceRef.current = true;
78
+ if (debug) {
79
+ console.log(`useFiltersAndSort: created service with key "${config.storageKey}"`);
80
+ }
19
81
  }
20
- const subscription = service.getState().subscribe((newState) => {
21
- setState(newState);
82
+ }
83
+ const svc = serviceRef.current;
84
+ const defaultFiltersRef = react.useRef(config == null ? void 0 : config.defaultFilters);
85
+ const subscribe = react.useCallback(
86
+ (onStoreChange) => {
87
+ const subscription = svc.getState().subscribe(() => {
88
+ onStoreChange();
89
+ });
90
+ return () => subscription.unsubscribe();
91
+ },
92
+ [svc]
93
+ );
94
+ const getSnapshot = react.useCallback(() => svc.getCurrentState(), [svc]);
95
+ const getServerSnapshot = react.useCallback(() => svc.getDefaultState(), [svc]);
96
+ const state = react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
97
+ react.useEffect(() => {
98
+ if (!debug) return;
99
+ const sub = svc.getState().subscribe((newState) => {
100
+ console.log(`useFiltersAndSort [${svc.getStorageKey()}]:`, newState);
22
101
  });
23
- return () => subscription.unsubscribe();
24
- }, [service]);
25
- const callbacks = react.useMemo(() => ({
26
- setFilter: (key, value) => {
27
- service.setFilters({ [key]: value });
102
+ return () => sub.unsubscribe();
103
+ }, [svc, debug]);
104
+ react.useEffect(() => {
105
+ return () => {
106
+ if (ownsServiceRef.current && serviceRef.current) {
107
+ if (debug) {
108
+ console.log(`useFiltersAndSort: destroying owned service "${serviceRef.current.getStorageKey()}"`);
109
+ }
110
+ serviceRef.current.destroy();
111
+ serviceRef.current = null;
112
+ }
113
+ };
114
+ }, [debug]);
115
+ const setFilter = react.useCallback(
116
+ (key, value) => {
117
+ svc.setFilters({ [key]: value });
28
118
  },
29
- clearFilter: (key) => {
30
- service.removeFilter(key);
119
+ [svc]
120
+ );
121
+ const setFilters = react.useCallback(
122
+ (filters) => {
123
+ svc.setFilters(filters);
124
+ },
125
+ [svc]
126
+ );
127
+ const clearFilter = react.useCallback(
128
+ (key) => {
129
+ svc.removeFilter(key);
31
130
  },
32
- clearAllFilters: () => {
33
- service.clearFilters();
131
+ [svc]
132
+ );
133
+ const clearAllFilters = react.useCallback(() => {
134
+ svc.clearFilters();
135
+ }, [svc]);
136
+ const hasFilter = react.useCallback(
137
+ (key) => {
138
+ var _a;
139
+ const value = svc.getCurrentState().filters[key];
140
+ const defaultValue = (_a = defaultFiltersRef.current) == null ? void 0 : _a[key];
141
+ if (value === defaultValue) return false;
142
+ if (value === null || value === void 0 || value === "") return false;
143
+ if (Array.isArray(value) && value.length === 0) return false;
144
+ return true;
34
145
  },
35
- setSort: (field) => {
36
- service.setSort(field);
146
+ [svc]
147
+ );
148
+ const hasAnyFilters = react.useCallback(() => {
149
+ return countActiveFilters(svc.getCurrentState().filters, defaultFiltersRef.current) > 0;
150
+ }, [svc]);
151
+ const setSort = react.useCallback(
152
+ (field) => {
153
+ svc.setSort(field);
37
154
  },
38
- toggleSort: (field) => {
39
- service.setSort(field);
155
+ [svc]
156
+ );
157
+ const clearSort = react.useCallback(() => {
158
+ svc.setSort("");
159
+ }, [svc]);
160
+ const isSortedBy = react.useCallback(
161
+ (field) => {
162
+ var _a;
163
+ return ((_a = svc.getCurrentState().sort) == null ? void 0 : _a.fieldName) === field;
40
164
  },
41
- setPage: (page) => {
42
- service.setPagination(page);
165
+ [svc]
166
+ );
167
+ const setPage = react.useCallback(
168
+ (page) => {
169
+ if (page < 0) {
170
+ if (debug) {
171
+ console.warn("useFiltersAndSort: setPage called with negative value, clamping to 0");
172
+ }
173
+ page = 0;
174
+ }
175
+ svc.setPagination(page);
43
176
  },
44
- setPageSize: (size) => {
45
- const currentPage = state.pagination.page;
46
- service.setPagination(currentPage, size);
177
+ [svc, debug]
178
+ );
179
+ const setPageSize = react.useCallback(
180
+ (size) => {
181
+ if (size < 1) {
182
+ if (debug) {
183
+ console.warn("useFiltersAndSort: setPageSize called with value < 1, clamping to 1");
184
+ }
185
+ size = 1;
186
+ }
187
+ svc.setPagination(0, size);
47
188
  },
48
- nextPage: () => {
49
- service.setPagination(state.pagination.page + 1);
189
+ [svc, debug]
190
+ );
191
+ const setPagination = react.useCallback(
192
+ (page, size) => {
193
+ if (page < 0) {
194
+ if (debug) {
195
+ console.warn("useFiltersAndSort: setPagination called with negative page, clamping to 0");
196
+ }
197
+ page = 0;
198
+ }
199
+ if (size !== void 0 && size < 1) {
200
+ if (debug) {
201
+ console.warn("useFiltersAndSort: setPagination called with size < 1, clamping to 1");
202
+ }
203
+ size = 1;
204
+ }
205
+ svc.setPagination(page, size);
50
206
  },
51
- prevPage: () => {
52
- const newPage = Math.max(0, state.pagination.page - 1);
53
- service.setPagination(newPage);
207
+ [svc, debug]
208
+ );
209
+ const nextPage = react.useCallback(() => {
210
+ svc.setPagination(svc.getCurrentState().pagination.page + 1);
211
+ }, [svc]);
212
+ const prevPage = react.useCallback(() => {
213
+ const newPage = Math.max(0, svc.getCurrentState().pagination.page - 1);
214
+ svc.setPagination(newPage);
215
+ }, [svc]);
216
+ const firstPage = react.useCallback(() => {
217
+ svc.setPagination(0);
218
+ }, [svc]);
219
+ const setQuery = react.useCallback(
220
+ (query) => {
221
+ svc.setQuery(query);
54
222
  },
55
- setQuery: (query) => {
56
- service.setQuery(query);
223
+ [svc]
224
+ );
225
+ const clearQuery = react.useCallback(() => {
226
+ svc.setQuery("");
227
+ }, [svc]);
228
+ const reset = react.useCallback(() => {
229
+ svc.reset();
230
+ }, [svc]);
231
+ const clearStorageAndReset = react.useCallback(() => {
232
+ svc.clearStorage();
233
+ svc.reset();
234
+ }, [svc]);
235
+ const getService = react.useCallback(() => svc, [svc]);
236
+ const activeFilterCount = react.useMemo(
237
+ () => countActiveFilters(state.filters, defaultFiltersRef.current),
238
+ [state.filters]
239
+ );
240
+ const isFirstPage = state.pagination.page === 0;
241
+ const offset = state.pagination.page * state.pagination.limit;
242
+ return react.useMemo(
243
+ () => {
244
+ var _a, _b;
245
+ return {
246
+ // State
247
+ filters: state.filters,
248
+ sortBy: ((_a = state.sort) == null ? void 0 : _a.fieldName) ?? null,
249
+ sortDirection: ((_b = state.sort) == null ? void 0 : _b.direction) ?? "asc",
250
+ page: state.pagination.page,
251
+ pageSize: state.pagination.limit,
252
+ query: state.query,
253
+ state,
254
+ // Filter actions
255
+ setFilter,
256
+ setFilters,
257
+ clearFilter,
258
+ clearAllFilters,
259
+ hasFilter,
260
+ hasAnyFilters,
261
+ activeFilterCount,
262
+ // Sort actions
263
+ setSort,
264
+ toggleSort: setSort,
265
+ clearSort,
266
+ isSortedBy,
267
+ // Pagination actions
268
+ setPage,
269
+ setPageSize,
270
+ setPagination,
271
+ nextPage,
272
+ prevPage,
273
+ firstPage,
274
+ isFirstPage,
275
+ offset,
276
+ // Query actions
277
+ setQuery,
278
+ clearQuery,
279
+ // General actions
280
+ reset,
281
+ clearStorageAndReset,
282
+ storageKey: svc.getStorageKey(),
283
+ // Service access
284
+ getService
285
+ };
57
286
  },
58
- reset: () => {
59
- service.reset();
60
- }
61
- }), [service, state.pagination.page]);
62
- const memoizedReturn = react.useMemo(() => {
63
- var _a, _b;
64
- return {
65
- filters: state.filters,
66
- sortBy: ((_a = state.sort) == null ? void 0 : _a.fieldName) || null,
67
- sortDirection: ((_b = state.sort) == null ? void 0 : _b.direction) || "asc",
68
- page: state.pagination.page,
69
- pageSize: state.pagination.limit,
70
- query: state.query,
71
- ...callbacks
72
- };
73
- }, [state, callbacks]);
74
- return memoizedReturn;
287
+ [
288
+ state,
289
+ setFilter,
290
+ setFilters,
291
+ clearFilter,
292
+ clearAllFilters,
293
+ hasFilter,
294
+ hasAnyFilters,
295
+ activeFilterCount,
296
+ setSort,
297
+ clearSort,
298
+ isSortedBy,
299
+ setPage,
300
+ setPageSize,
301
+ setPagination,
302
+ nextPage,
303
+ prevPage,
304
+ firstPage,
305
+ isFirstPage,
306
+ offset,
307
+ setQuery,
308
+ clearQuery,
309
+ reset,
310
+ clearStorageAndReset,
311
+ svc,
312
+ getService
313
+ ]
314
+ );
75
315
  }
76
316
  function useFormBuilder(options) {
77
317
  const { builder } = options;
@@ -369,11 +609,6 @@ function useRichContent(options = {}) {
369
609
  setSelection
370
610
  };
371
611
  }
372
- function useTableService() {
373
- throw new Error(
374
- "useTableService is deprecated. TableBuilder is a configuration builder, not a stateful service. Use FiltersAndSortService with useFiltersAndSort hook for reactive state management. See documentation: https://github.com/CodellaSoftware/codella-utils"
375
- );
376
- }
377
612
  const TabsContext = react.createContext(void 0);
378
613
  function TabsProvider({ config, children }) {
379
614
  const [service] = react.useState(() => new tabs.TabsService(config));
@@ -449,7 +684,6 @@ exports.useLiveUpdates = useLiveUpdates;
449
684
  exports.useRichContent = useRichContent;
450
685
  exports.useSetActiveTab = useSetActiveTab;
451
686
  exports.useTabChange = useTabChange;
452
- exports.useTableService = useTableService;
453
687
  exports.useTabs = useTabs;
454
688
  exports.useTabsContext = useTabsContext;
455
689
  exports.useTabsService = useTabsService;