@codella-software/react 2.2.26 → 2.3.0

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/dist/index.cjs CHANGED
@@ -1,79 +1,311 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const react = require("react");
4
3
  const utils = require("@codella-software/utils");
4
+ const react = require("react");
5
5
  const jsxRuntime = require("react/jsx-runtime");
6
6
  const liveUpdates = require("@codella-software/utils/live-updates");
7
7
  const richContent = require("@codella-software/utils/rich-content");
8
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
+ }
9
43
  function useFiltersAndSort(options) {
10
- const { service } = options;
11
- if (!service) {
12
- 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
+ );
13
49
  }
14
- const [state, setState] = react.useState(() => {
15
- return service.getState();
16
- });
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);
17
60
  react.useEffect(() => {
18
- if (!service || !service.state$) {
19
- 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
+ }
20
81
  }
21
- const subscription = service.state$.subscribe((newState) => {
22
- 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);
23
101
  });
24
- return () => subscription.unsubscribe();
25
- }, [service]);
26
- const callbacks = react.useMemo(() => ({
27
- setFilter: (key, value) => {
28
- service.setFilter(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 });
29
118
  },
30
- clearFilter: (key) => {
31
- service.clearFilter(key);
119
+ [svc]
120
+ );
121
+ const setFilters = react.useCallback(
122
+ (filters) => {
123
+ svc.setFilters(filters);
32
124
  },
33
- clearAllFilters: () => {
34
- service.clearAllFilters();
125
+ [svc]
126
+ );
127
+ const clearFilter = react.useCallback(
128
+ (key) => {
129
+ svc.removeFilter(key);
35
130
  },
36
- setSort: (field, direction) => {
37
- service.setSort(field, direction);
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;
38
145
  },
39
- toggleSort: (field) => {
40
- service.toggleSort(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);
41
154
  },
42
- setPage: (page) => {
43
- service.setPage(page);
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;
44
164
  },
45
- setPageSize: (size) => {
46
- service.setPageSize(size);
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);
47
176
  },
48
- nextPage: () => {
49
- service.nextPage();
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
+ const currentPage = svc.getCurrentState().pagination.page;
188
+ svc.setPagination(currentPage, size);
50
189
  },
51
- prevPage: () => {
52
- service.prevPage();
53
- }
54
- }), [service]);
55
- const memoizedReturn = react.useMemo(() => ({
56
- filters: state.filters,
57
- sortBy: state.sortBy,
58
- sortDirection: state.sortDirection,
59
- page: state.page,
60
- pageSize: state.pageSize,
61
- ...callbacks
62
- }), [state, callbacks]);
63
- return memoizedReturn;
190
+ [svc, debug]
191
+ );
192
+ const nextPage = react.useCallback(() => {
193
+ svc.setPagination(svc.getCurrentState().pagination.page + 1);
194
+ }, [svc]);
195
+ const prevPage = react.useCallback(() => {
196
+ const newPage = Math.max(0, svc.getCurrentState().pagination.page - 1);
197
+ svc.setPagination(newPage);
198
+ }, [svc]);
199
+ const firstPage = react.useCallback(() => {
200
+ svc.setPagination(0);
201
+ }, [svc]);
202
+ const setQuery = react.useCallback(
203
+ (query) => {
204
+ svc.setQuery(query);
205
+ },
206
+ [svc]
207
+ );
208
+ const clearQuery = react.useCallback(() => {
209
+ svc.setQuery("");
210
+ }, [svc]);
211
+ const reset = react.useCallback(() => {
212
+ svc.reset();
213
+ }, [svc]);
214
+ const clearStorageAndReset = react.useCallback(() => {
215
+ svc.clearStorage();
216
+ svc.reset();
217
+ }, [svc]);
218
+ const getService = react.useCallback(() => svc, [svc]);
219
+ const activeFilterCount = react.useMemo(
220
+ () => countActiveFilters(state.filters, defaultFiltersRef.current),
221
+ [state.filters]
222
+ );
223
+ const isFirstPage = state.pagination.page === 0;
224
+ const offset = state.pagination.page * state.pagination.limit;
225
+ return react.useMemo(
226
+ () => {
227
+ var _a, _b;
228
+ return {
229
+ // State
230
+ filters: state.filters,
231
+ sortBy: ((_a = state.sort) == null ? void 0 : _a.fieldName) ?? null,
232
+ sortDirection: ((_b = state.sort) == null ? void 0 : _b.direction) ?? "asc",
233
+ page: state.pagination.page,
234
+ pageSize: state.pagination.limit,
235
+ query: state.query,
236
+ state,
237
+ // Filter actions
238
+ setFilter,
239
+ setFilters,
240
+ clearFilter,
241
+ clearAllFilters,
242
+ hasFilter,
243
+ hasAnyFilters,
244
+ activeFilterCount,
245
+ // Sort actions
246
+ setSort,
247
+ toggleSort: setSort,
248
+ clearSort,
249
+ isSortedBy,
250
+ // Pagination actions
251
+ setPage,
252
+ setPageSize,
253
+ nextPage,
254
+ prevPage,
255
+ firstPage,
256
+ isFirstPage,
257
+ offset,
258
+ // Query actions
259
+ setQuery,
260
+ clearQuery,
261
+ // General actions
262
+ reset,
263
+ clearStorageAndReset,
264
+ storageKey: svc.getStorageKey(),
265
+ // Service access
266
+ getService
267
+ };
268
+ },
269
+ [
270
+ state,
271
+ setFilter,
272
+ setFilters,
273
+ clearFilter,
274
+ clearAllFilters,
275
+ hasFilter,
276
+ hasAnyFilters,
277
+ activeFilterCount,
278
+ setSort,
279
+ clearSort,
280
+ isSortedBy,
281
+ setPage,
282
+ setPageSize,
283
+ nextPage,
284
+ prevPage,
285
+ firstPage,
286
+ isFirstPage,
287
+ offset,
288
+ setQuery,
289
+ clearQuery,
290
+ reset,
291
+ clearStorageAndReset,
292
+ svc,
293
+ getService
294
+ ]
295
+ );
64
296
  }
65
297
  function useFormBuilder(options) {
66
298
  const { builder } = options;
67
299
  if (!builder) {
68
300
  throw new Error("useFormBuilder: builder is required");
69
301
  }
70
- const [state, setState] = react.useState(() => builder.getState());
302
+ const [state, setState] = react.useState(() => builder.currentState);
71
303
  const [isSubmitting, setIsSubmitting] = react.useState(false);
72
304
  react.useEffect(() => {
73
- if (!builder || !builder.stateChanged$) {
305
+ if (!builder || !builder.state$) {
74
306
  return;
75
307
  }
76
- const subscription = builder.stateChanged$.subscribe((newState) => {
308
+ const subscription = builder.state$.subscribe((newState) => {
77
309
  setState(newState);
78
310
  });
79
311
  return () => subscription.unsubscribe();
@@ -82,8 +314,10 @@ function useFormBuilder(options) {
82
314
  values: state.values,
83
315
  errors: state.errors,
84
316
  touched: state.touched,
85
- currentStep: state.currentStep,
86
- totalSteps: state.totalSteps,
317
+ currentStep: 0,
318
+ // Multi-step requires MultiStepFormBuilder
319
+ totalSteps: 1,
320
+ // Multi-step requires MultiStepFormBuilder
87
321
  isSubmitting,
88
322
  isValid: Object.keys(state.errors).length === 0,
89
323
  setFieldValue: async (field, value) => {
@@ -98,9 +332,9 @@ function useFormBuilder(options) {
98
332
  submit: async () => {
99
333
  setIsSubmitting(true);
100
334
  try {
101
- const result = await builder.submit();
335
+ await builder.submit();
102
336
  setIsSubmitting(false);
103
- return result;
337
+ return null;
104
338
  } catch (error) {
105
339
  setIsSubmitting(false);
106
340
  throw error;
@@ -110,14 +344,10 @@ function useFormBuilder(options) {
110
344
  builder.reset();
111
345
  },
112
346
  nextStep: () => {
113
- if (builder.nextStep) {
114
- builder.nextStep();
115
- }
347
+ console.warn("nextStep() is only available on MultiStepFormBuilder");
116
348
  },
117
349
  prevStep: () => {
118
- if (builder.prevStep) {
119
- builder.prevStep();
120
- }
350
+ console.warn("prevStep() is only available on MultiStepFormBuilder");
121
351
  }
122
352
  }), [builder, state, isSubmitting]);
123
353
  return memoizedReturn;
@@ -360,91 +590,10 @@ function useRichContent(options = {}) {
360
590
  setSelection
361
591
  };
362
592
  }
363
- function useTableService(options) {
364
- const { config, data } = options;
365
- const builder = react.useMemo(() => {
366
- const tb = new utils.TableBuilder(config);
367
- tb.setData(data);
368
- return tb;
369
- }, [config]);
370
- const [state, setState] = react.useState(() => builder.getState());
371
- const [selectedRows, setSelectedRows] = react.useState([]);
372
- const [allSelected, setAllSelected] = react.useState(false);
373
- react.useEffect(() => {
374
- if (!builder || !builder.stateChanged$) {
375
- return;
376
- }
377
- const subscription = builder.stateChanged$.subscribe((newState) => {
378
- setState(newState);
379
- });
380
- return () => subscription.unsubscribe();
381
- }, [builder]);
382
- react.useEffect(() => {
383
- builder.setData(data);
384
- }, [builder, data]);
385
- const memoizedMethods = react.useMemo(() => ({
386
- toggleRowSelection: (rowId) => {
387
- setSelectedRows(
388
- (prev) => prev.includes(rowId) ? prev.filter((id) => id !== rowId) : [...prev, rowId]
389
- );
390
- },
391
- toggleAllSelection: () => {
392
- if (allSelected) {
393
- setSelectedRows([]);
394
- setAllSelected(false);
395
- } else {
396
- setSelectedRows(state.rows.map((row) => row.id || String(row)));
397
- setAllSelected(true);
398
- }
399
- },
400
- clearSelection: () => {
401
- setSelectedRows([]);
402
- setAllSelected(false);
403
- },
404
- setSort: (field, direction) => {
405
- builder.setSort(field, direction);
406
- },
407
- toggleSort: (field) => {
408
- builder.toggleSort(field);
409
- },
410
- setFilter: (field, value) => {
411
- builder.setFilter(field, value);
412
- },
413
- clearFilter: (field) => {
414
- builder.clearFilter(field);
415
- },
416
- clearAllFilters: () => {
417
- builder.clearAllFilters();
418
- },
419
- setPage: (page) => {
420
- builder.setPage(page);
421
- },
422
- setPageSize: (size) => {
423
- builder.setPageSize(size);
424
- },
425
- nextPage: () => {
426
- builder.nextPage();
427
- },
428
- prevPage: () => {
429
- builder.prevPage();
430
- }
431
- }), [builder, allSelected, state.rows]);
432
- const memoizedReturn = react.useMemo(() => ({
433
- rows: state.rows,
434
- selectedRows,
435
- allSelected,
436
- currentPage: state.currentPage,
437
- pageSize: state.pageSize,
438
- totalPages: state.totalPages,
439
- totalItems: state.totalItems,
440
- hasNextPage: state.hasNextPage,
441
- hasPrevPage: state.hasPrevPage,
442
- sortBy: state.sortBy,
443
- sortDirection: state.sortDirection,
444
- filters: state.filters,
445
- ...memoizedMethods
446
- }), [state, selectedRows, allSelected, memoizedMethods]);
447
- return memoizedReturn;
593
+ function useTableService() {
594
+ throw new Error(
595
+ "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"
596
+ );
448
597
  }
449
598
  const TabsContext = react.createContext(void 0);
450
599
  function TabsProvider({ config, children }) {