@ram_28/kf-ai-sdk 1.0.5 → 1.0.8

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 (38) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/components/hooks/useFilter/index.d.ts +3 -4
  4. package/dist/components/hooks/useFilter/index.d.ts.map +1 -1
  5. package/dist/components/hooks/useFilter/types.d.ts +84 -127
  6. package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
  7. package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
  8. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
  9. package/dist/components/hooks/useKanban/index.d.ts +1 -1
  10. package/dist/components/hooks/useKanban/index.d.ts.map +1 -1
  11. package/dist/components/hooks/useKanban/types.d.ts +6 -49
  12. package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
  13. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
  14. package/dist/components/hooks/useTable/types.d.ts +5 -35
  15. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  16. package/dist/components/hooks/useTable/useTable.d.ts +0 -5
  17. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  18. package/dist/index.cjs +13 -13
  19. package/dist/index.mjs +2395 -2865
  20. package/dist/types/common.d.ts +35 -26
  21. package/dist/types/common.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/sdk/api/index.ts +7 -3
  24. package/sdk/components/hooks/useFilter/index.ts +19 -31
  25. package/sdk/components/hooks/useFilter/types.ts +157 -138
  26. package/sdk/components/hooks/useFilter/useFilter.ts +259 -414
  27. package/sdk/components/hooks/useKanban/index.ts +0 -1
  28. package/sdk/components/hooks/useKanban/types.ts +8 -71
  29. package/sdk/components/hooks/useKanban/useKanban.ts +14 -77
  30. package/sdk/components/hooks/useTable/types.ts +7 -63
  31. package/sdk/components/hooks/useTable/useTable.ts +13 -122
  32. package/sdk/types/common.ts +42 -26
  33. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +0 -33
  34. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +0 -1
  35. package/dist/components/hooks/useFilter/validation.utils.d.ts +0 -38
  36. package/dist/components/hooks/useFilter/validation.utils.d.ts.map +0 -1
  37. package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +0 -298
  38. package/sdk/components/hooks/useFilter/validation.utils.ts +0 -401
@@ -1,494 +1,339 @@
1
- import { useState, useCallback, useMemo, useEffect } from "react";
2
- import type { Filter, LogicalOperator } from "../../../types/common";
1
+ import { useState, useCallback, useMemo } from "react";
3
2
  import type {
4
- FilterConditionWithId,
5
- TypedFilterConditionInput,
6
- FieldDefinition,
7
- ValidationResult,
8
- ValidationError,
9
- FilterState,
10
- UseFilterOptions,
11
- UseFilterReturn,
12
- } from "./types";
3
+ Condition,
4
+ ConditionGroup,
5
+ ConditionGroupOperator,
6
+ Filter,
7
+ } from "../../../types/common";
8
+ import type { UseFilterOptions, UseFilterReturn } from "./types";
9
+ import { isConditionGroup } from "./types";
13
10
 
14
11
  // ============================================================
15
- // VALIDATION HELPERS
12
+ // HELPER FUNCTIONS
16
13
  // ============================================================
17
14
 
15
+ let idCounter = 0;
16
+
18
17
  /**
19
- * Generate a unique ID for conditions
18
+ * Generate a unique ID for conditions and groups
20
19
  */
21
20
  const generateId = (): string => {
22
- return crypto.randomUUID();
21
+ return `filter_${Date.now()}_${++idCounter}`;
23
22
  };
24
23
 
25
24
  /**
26
- * Helper to check if operator is a logical operator
25
+ * Ensure an item has an id
27
26
  */
28
- const isLogicalOperator = (operator: string): operator is LogicalOperator => {
29
- return operator === 'And' || operator === 'Or' || operator === 'Not';
27
+ const ensureId = <T extends Condition | ConditionGroup>(item: T): T => {
28
+ if (!item.id) {
29
+ return { ...item, id: generateId() };
30
+ }
31
+ return item;
30
32
  };
31
33
 
32
34
  /**
33
- * Validate a filter condition (supports both simple conditions and nested logical groups)
35
+ * Deep clone and ensure all items have ids
34
36
  */
35
- const validateFilterCondition = <T>(
36
- condition: Partial<FilterConditionWithId>,
37
- fieldDefinitions?: Record<keyof T, FieldDefinition>
38
- ): ValidationResult => {
39
- const errors: string[] = [];
40
-
41
- // Check required fields
42
- if (!condition.operator) {
43
- errors.push('Operator is required');
44
- }
45
-
46
- // Check if this is a logical operator (nested group)
47
- if (condition.operator && isLogicalOperator(condition.operator)) {
48
- // Validate logical group
49
- if (!condition.children || !Array.isArray(condition.children)) {
50
- errors.push('Logical operators require a children array');
51
- } else if (condition.children.length === 0) {
52
- errors.push('Logical operators require at least one child condition');
53
- } else if (condition.operator === 'Not' && condition.children.length > 1) {
54
- errors.push('Not operator can only have one child condition');
37
+ const cloneWithIds = (
38
+ items: Array<Condition | ConditionGroup>
39
+ ): Array<Condition | ConditionGroup> => {
40
+ return items.map((item) => {
41
+ const withId = ensureId(item);
42
+ if (isConditionGroup(withId)) {
43
+ return {
44
+ ...withId,
45
+ Condition: cloneWithIds(withId.Condition),
46
+ };
55
47
  }
48
+ return withId;
49
+ });
50
+ };
56
51
 
57
- // Recursively validate children
58
- if (condition.children && Array.isArray(condition.children)) {
59
- condition.children.forEach((child, index) => {
60
- const childValidation = validateFilterCondition(child, fieldDefinitions);
61
- if (!childValidation.isValid) {
62
- errors.push(...childValidation.errors.map(err => `Child ${index + 1}: ${err}`));
63
- }
64
- });
52
+ /**
53
+ * Strip id fields from items for API payload
54
+ */
55
+ const stripIds = (
56
+ items: Array<Condition | ConditionGroup>
57
+ ): Array<Condition | ConditionGroup> => {
58
+ return items.map((item) => {
59
+ if (isConditionGroup(item)) {
60
+ const { id, ...rest } = item;
61
+ return {
62
+ ...rest,
63
+ Condition: stripIds(item.Condition),
64
+ } as ConditionGroup;
65
65
  }
66
+ const { id, ...rest } = item;
67
+ return rest as Condition;
68
+ });
69
+ };
66
70
 
67
- // Logical operators should not have lhsField or rhsValue
68
- if (condition.lhsField) {
69
- errors.push('Logical operators should not have lhsField');
70
- }
71
- if (condition.rhsValue !== undefined) {
72
- errors.push('Logical operators should not have rhsValue');
73
- }
74
- } else {
75
- // Validate simple condition
76
- if (!condition.lhsField) {
77
- errors.push('Field is required for condition operators');
71
+ /**
72
+ * Find an item by id in a tree structure
73
+ */
74
+ const findById = (
75
+ items: Array<Condition | ConditionGroup>,
76
+ id: string
77
+ ): Condition | ConditionGroup | undefined => {
78
+ for (const item of items) {
79
+ if (item.id === id) {
80
+ return item;
78
81
  }
79
-
80
- // Validate operator-specific requirements
81
- if (condition.operator && condition.rhsValue !== undefined) {
82
- switch (condition.operator) {
83
- case 'Between':
84
- case 'NotBetween':
85
- if (!Array.isArray(condition.rhsValue) || condition.rhsValue.length !== 2) {
86
- errors.push('Between operators require an array of two values');
87
- }
88
- break;
89
- case 'IN':
90
- case 'NIN':
91
- if (!Array.isArray(condition.rhsValue) || condition.rhsValue.length === 0) {
92
- errors.push('IN/NIN operators require a non-empty array');
93
- }
94
- break;
95
- case 'Empty':
96
- case 'NotEmpty':
97
- // These operators don't need RHS values
98
- break;
99
- default:
100
- if (condition.rhsValue === null || condition.rhsValue === undefined || condition.rhsValue === '') {
101
- errors.push('Value is required for this operator');
102
- }
103
- break;
104
- }
82
+ if (isConditionGroup(item)) {
83
+ const found = findById(item.Condition, id);
84
+ if (found) return found;
105
85
  }
86
+ }
87
+ return undefined;
88
+ };
106
89
 
107
- // Field-specific validation
108
- if (fieldDefinitions && condition.lhsField && condition.operator) {
109
- const fieldDef = fieldDefinitions[condition.lhsField as keyof T];
110
- if (fieldDef) {
111
- // Check if operator is allowed for this field
112
- if (!fieldDef.allowedOperators.includes(condition.operator as any)) {
113
- errors.push(`Operator ${condition.operator} is not allowed for field ${condition.lhsField}`);
114
- }
115
-
116
- // Custom field validation
117
- if (fieldDef.validateValue && condition.rhsValue !== undefined) {
118
- const fieldValidation = fieldDef.validateValue(condition.rhsValue, condition.operator as any);
119
- if (!fieldValidation.isValid) {
120
- errors.push(...fieldValidation.errors);
121
- }
122
- }
123
- }
90
+ /**
91
+ * Find a parent group by child id
92
+ */
93
+ const findParentById = (
94
+ items: Array<Condition | ConditionGroup>,
95
+ childId: string,
96
+ parent: ConditionGroup | null = null
97
+ ): ConditionGroup | null => {
98
+ for (const item of items) {
99
+ if (item.id === childId) {
100
+ return parent;
124
101
  }
125
-
126
- // Simple conditions should not have children
127
- if (condition.children && condition.children.length > 0) {
128
- errors.push('Condition operators should not have children');
102
+ if (isConditionGroup(item)) {
103
+ const found = findParentById(item.Condition, childId, item);
104
+ if (found !== null) return found;
129
105
  }
130
106
  }
131
-
132
- return {
133
- isValid: errors.length === 0,
134
- errors
135
- };
107
+ return null;
136
108
  };
137
109
 
138
110
  /**
139
- * Convert a single FilterConditionWithId to API format (FilterCondition or FilterLogical)
111
+ * Update an item in the tree by id
140
112
  */
141
- const convertConditionToAPI = (condition: FilterConditionWithId): any => {
142
- // Check if this is a logical operator (nested group)
143
- if (isLogicalOperator(condition.operator)) {
144
- // Build nested logical filter
145
- return {
146
- Operator: condition.operator,
147
- Condition: (condition.children || [])
148
- .filter(child => child.isValid)
149
- .map(child => convertConditionToAPI(child))
150
- };
151
- } else {
152
- // Build simple condition
153
- return {
154
- Operator: condition.operator,
155
- LHSField: condition.lhsField,
156
- RHSValue: condition.rhsValue,
157
- RHSType: condition.rhsType || "Constant"
158
- };
159
- }
113
+ const updateInTree = (
114
+ items: Array<Condition | ConditionGroup>,
115
+ id: string,
116
+ updater: (item: Condition | ConditionGroup) => Condition | ConditionGroup
117
+ ): Array<Condition | ConditionGroup> => {
118
+ return items.map((item) => {
119
+ if (item.id === id) {
120
+ return updater(item);
121
+ }
122
+ if (isConditionGroup(item)) {
123
+ return {
124
+ ...item,
125
+ Condition: updateInTree(item.Condition, id, updater),
126
+ };
127
+ }
128
+ return item;
129
+ });
160
130
  };
161
131
 
162
132
  /**
163
- * Convert filter state to SDK Filter format
164
- * Supports both flat and nested filter structures
133
+ * Remove an item from the tree by id
165
134
  */
166
- const buildFilterPayload = (
167
- conditions: FilterConditionWithId[],
168
- logicalOperator: LogicalOperator
169
- ): Filter | undefined => {
170
- if (conditions.length === 0) {
171
- return undefined;
172
- }
173
-
174
- const validConditions = conditions.filter(c => c.isValid);
175
- if (validConditions.length === 0) {
176
- return undefined;
177
- }
135
+ const removeFromTree = (
136
+ items: Array<Condition | ConditionGroup>,
137
+ id: string
138
+ ): Array<Condition | ConditionGroup> => {
139
+ return items
140
+ .filter((item) => item.id !== id)
141
+ .map((item) => {
142
+ if (isConditionGroup(item)) {
143
+ return {
144
+ ...item,
145
+ Condition: removeFromTree(item.Condition, id),
146
+ };
147
+ }
148
+ return item;
149
+ });
150
+ };
178
151
 
179
- return {
180
- Operator: logicalOperator,
181
- Condition: validConditions.map(c => convertConditionToAPI(c))
182
- };
152
+ /**
153
+ * Add an item to a specific parent in the tree
154
+ */
155
+ const addToParent = (
156
+ items: Array<Condition | ConditionGroup>,
157
+ parentId: string,
158
+ newItem: Condition | ConditionGroup
159
+ ): Array<Condition | ConditionGroup> => {
160
+ return items.map((item) => {
161
+ if (item.id === parentId && isConditionGroup(item)) {
162
+ return {
163
+ ...item,
164
+ Condition: [...item.Condition, newItem],
165
+ };
166
+ }
167
+ if (isConditionGroup(item)) {
168
+ return {
169
+ ...item,
170
+ Condition: addToParent(item.Condition, parentId, newItem),
171
+ };
172
+ }
173
+ return item;
174
+ });
183
175
  };
184
176
 
185
177
  // ============================================================
186
- // MAIN HOOK
178
+ // USE FILTER HOOK - Nested Filter Support
187
179
  // ============================================================
188
180
 
189
- export function useFilter<T = any>(
190
- options: UseFilterOptions<T> = {}
191
- ): UseFilterReturn<T> {
192
- // ============================================================
193
- // STATE MANAGEMENT
194
- // ============================================================
195
-
196
- const [filterState, setFilterState] = useState<FilterState>({
197
- logicalOperator: options.initialLogicalOperator || "And",
198
- conditions: options.initialConditions || []
199
- });
200
-
201
- // Store initial state for reset functionality
202
- const [initialState] = useState<FilterState>({
203
- logicalOperator: options.initialLogicalOperator || "And",
204
- conditions: options.initialConditions || []
205
- });
206
-
207
- // ============================================================
208
- // VALIDATION
209
- // ============================================================
210
-
211
- const validateCondition = useCallback((condition: Partial<FilterConditionWithId>): ValidationResult => {
212
- return validateFilterCondition(condition, options.fieldDefinitions);
213
- }, [options.fieldDefinitions]);
214
-
215
- const validateAllConditions = useCallback((): ValidationResult => {
216
- const allErrors: string[] = [];
181
+ export function useFilter(options: UseFilterOptions = {}): UseFilterReturn {
182
+ // Initialize items with ids
183
+ const [items, setItems] = useState<Array<Condition | ConditionGroup>>(() =>
184
+ cloneWithIds(options.initialConditions || [])
185
+ );
217
186
 
218
- filterState.conditions.forEach(condition => {
219
- const validation = validateCondition(condition);
220
- if (!validation.isValid) {
221
- allErrors.push(...validation.errors.map(err => `Condition ${condition.id}: ${err}`));
222
- }
223
- });
187
+ const [operator, setOperatorState] = useState<ConditionGroupOperator>(
188
+ options.initialOperator || "And"
189
+ );
224
190
 
191
+ // Build payload for API (strip ids)
192
+ const payload = useMemo((): Filter | undefined => {
193
+ if (items.length === 0) return undefined;
225
194
  return {
226
- isValid: allErrors.length === 0,
227
- errors: allErrors
195
+ Operator: operator,
196
+ Condition: stripIds(items),
228
197
  };
229
- }, [filterState.conditions, validateCondition]);
198
+ }, [items, operator]);
199
+
200
+ const hasConditions = items.length > 0;
230
201
 
231
202
  // ============================================================
232
- // CONDITION MANAGEMENT
203
+ // ADD OPERATIONS
233
204
  // ============================================================
234
205
 
235
- const addCondition = useCallback((condition: TypedFilterConditionInput<T>): string => {
206
+ const add = useCallback((condition: Omit<Condition, "id">): string => {
236
207
  const id = generateId();
237
- // Convert typed input to internal format (using unknown for type narrowing)
238
- const internalCondition = condition as unknown as Omit<FilterConditionWithId, 'id' | 'isValid'>;
239
- const validation = validateCondition(internalCondition);
208
+ const newCondition: Condition = { ...condition, id };
209
+ setItems((prev) => [...prev, newCondition]);
210
+ return id;
211
+ }, []);
240
212
 
241
- const newCondition: FilterConditionWithId = {
242
- ...internalCondition,
213
+ const addGroup = useCallback((groupOperator: ConditionGroupOperator): string => {
214
+ const id = generateId();
215
+ const newGroup: ConditionGroup = {
243
216
  id,
244
- isValid: validation.isValid,
245
- validationErrors: validation.errors
217
+ Operator: groupOperator,
218
+ Condition: [],
246
219
  };
247
-
248
- setFilterState(prev => ({
249
- ...prev,
250
- conditions: [...prev.conditions, newCondition]
251
- }));
252
-
253
- if (options.onConditionAdd) {
254
- options.onConditionAdd(newCondition);
255
- }
256
-
220
+ setItems((prev) => [...prev, newGroup]);
257
221
  return id;
258
- }, [validateCondition, options]);
259
-
260
- const updateCondition = useCallback((id: string, updates: Partial<TypedFilterConditionInput<T>>): boolean => {
261
- let found = false;
262
- // Convert typed input to internal format (using unknown for type narrowing)
263
- const internalUpdates = updates as unknown as Partial<FilterConditionWithId>;
264
-
265
- setFilterState(prev => ({
266
- ...prev,
267
- conditions: prev.conditions.map(condition => {
268
- if (condition.id === id) {
269
- found = true;
270
- const updatedCondition = { ...condition, ...internalUpdates };
271
- const validation = validateCondition(updatedCondition);
272
-
273
- const finalCondition = {
274
- ...updatedCondition,
275
- isValid: validation.isValid,
276
- validationErrors: validation.errors
277
- };
278
-
279
- if (options.onConditionUpdate) {
280
- options.onConditionUpdate(finalCondition);
281
- }
282
-
283
- return finalCondition;
284
- }
285
- return condition;
286
- })
287
- }));
288
-
289
- return found;
290
- }, [validateCondition, options]);
291
-
292
- const removeCondition = useCallback((id: string): boolean => {
293
- let found = false;
294
-
295
- setFilterState(prev => ({
296
- ...prev,
297
- conditions: prev.conditions.filter(condition => {
298
- if (condition.id === id) {
299
- found = true;
300
- if (options.onConditionRemove) {
301
- options.onConditionRemove(id);
302
- }
303
- return false;
304
- }
305
- return true;
306
- })
307
- }));
308
-
309
- return found;
310
- }, [options]);
311
-
312
- const clearConditions = useCallback(() => {
313
- setFilterState(prev => ({
314
- ...prev,
315
- conditions: []
316
- }));
317
222
  }, []);
318
223
 
319
- const getCondition = useCallback((id: string): FilterConditionWithId | undefined => {
320
- return filterState.conditions.find(condition => condition.id === id);
321
- }, [filterState.conditions]);
322
-
323
- // ============================================================
324
- // LOGICAL OPERATOR MANAGEMENT
325
- // ============================================================
326
-
327
- const setLogicalOperator = useCallback((operator: LogicalOperator) => {
328
- setFilterState(prev => ({
329
- ...prev,
330
- logicalOperator: operator
331
- }));
332
- }, []);
333
-
334
- // ============================================================
335
- // BULK OPERATIONS
336
- // ============================================================
224
+ const addTo = useCallback(
225
+ (parentId: string, condition: Omit<Condition, "id">): string => {
226
+ const id = generateId();
227
+ const newCondition: Condition = { ...condition, id };
228
+ setItems((prev) => addToParent(prev, parentId, newCondition));
229
+ return id;
230
+ },
231
+ []
232
+ );
337
233
 
338
- const setConditions = useCallback((conditions: FilterConditionWithId[]) => {
339
- const validatedConditions = conditions.map(condition => {
340
- const validation = validateCondition(condition);
341
- return {
342
- ...condition,
343
- isValid: validation.isValid,
344
- validationErrors: validation.errors
234
+ const addGroupTo = useCallback(
235
+ (parentId: string, groupOperator: ConditionGroupOperator): string => {
236
+ const id = generateId();
237
+ const newGroup: ConditionGroup = {
238
+ id,
239
+ Operator: groupOperator,
240
+ Condition: [],
345
241
  };
346
- });
347
-
348
- setFilterState(prev => ({
349
- ...prev,
350
- conditions: validatedConditions
351
- }));
352
- }, [validateCondition]);
353
-
354
- const replaceCondition = useCallback((id: string, newCondition: TypedFilterConditionInput<T>): boolean => {
355
- let found = false;
356
- // Convert typed input to internal format (using unknown for type narrowing)
357
- const internalCondition = newCondition as unknown as Omit<FilterConditionWithId, 'id' | 'isValid'>;
358
-
359
- setFilterState(prev => ({
360
- ...prev,
361
- conditions: prev.conditions.map(condition => {
362
- if (condition.id === id) {
363
- found = true;
364
- const validation = validateCondition(internalCondition);
365
- return {
366
- ...internalCondition,
367
- id,
368
- isValid: validation.isValid,
369
- validationErrors: validation.errors
370
- };
371
- }
372
- return condition;
373
- })
374
- }));
375
-
376
- return found;
377
- }, [validateCondition]);
242
+ setItems((prev) => addToParent(prev, parentId, newGroup));
243
+ return id;
244
+ },
245
+ []
246
+ );
378
247
 
379
248
  // ============================================================
380
- // STATE MANAGEMENT
249
+ // UPDATE OPERATIONS
381
250
  // ============================================================
382
251
 
383
- const exportState = useCallback((): FilterState => ({
384
- logicalOperator: filterState.logicalOperator,
385
- conditions: filterState.conditions.map(condition => ({ ...condition })) // Deep copy
386
- }), [filterState]);
387
-
388
- const importState = useCallback((state: FilterState) => {
389
- const validatedConditions = state.conditions.map(condition => {
390
- const validation = validateCondition(condition);
391
- return {
392
- ...condition,
393
- isValid: validation.isValid,
394
- validationErrors: validation.errors
395
- };
396
- });
397
-
398
- setFilterState({
399
- logicalOperator: state.logicalOperator,
400
- conditions: validatedConditions
401
- });
402
- }, [validateCondition]);
252
+ const update = useCallback(
253
+ (id: string, updates: Partial<Omit<Condition, "id">>): void => {
254
+ setItems((prev) =>
255
+ updateInTree(prev, id, (item) => {
256
+ if (!isConditionGroup(item)) {
257
+ return { ...item, ...updates };
258
+ }
259
+ return item;
260
+ })
261
+ );
262
+ },
263
+ []
264
+ );
403
265
 
404
- const resetToInitial = useCallback(() => {
405
- setFilterState({ ...initialState });
406
- }, [initialState]);
266
+ const updateOperator = useCallback(
267
+ (id: string, newOperator: ConditionGroupOperator): void => {
268
+ setItems((prev) =>
269
+ updateInTree(prev, id, (item) => {
270
+ if (isConditionGroup(item)) {
271
+ return { ...item, Operator: newOperator };
272
+ }
273
+ return item;
274
+ })
275
+ );
276
+ },
277
+ []
278
+ );
407
279
 
408
280
  // ============================================================
409
- // COMPUTED VALUES
281
+ // REMOVE & ACCESS
410
282
  // ============================================================
411
283
 
412
- const filterPayload = useMemo(() =>
413
- buildFilterPayload(filterState.conditions, filterState.logicalOperator),
414
- [filterState.conditions, filterState.logicalOperator]
415
- );
416
-
417
- const validationErrors = useMemo((): ValidationError[] => {
418
- const errors: ValidationError[] = [];
419
-
420
- filterState.conditions.forEach(condition => {
421
- if (!condition.isValid && condition.validationErrors) {
422
- condition.validationErrors.forEach(error => {
423
- errors.push({
424
- conditionId: condition.id,
425
- field: condition.lhsField || '', // Empty string for logical operators
426
- message: error
427
- });
428
- });
429
- }
430
- });
431
-
432
- return errors;
433
- }, [filterState.conditions]);
284
+ const remove = useCallback((id: string): void => {
285
+ setItems((prev) => removeFromTree(prev, id));
286
+ }, []);
434
287
 
435
- const isValid = useMemo(() =>
436
- filterState.conditions.every(condition => condition.isValid),
437
- [filterState.conditions]
288
+ const get = useCallback(
289
+ (id: string): Condition | ConditionGroup | undefined => {
290
+ return findById(items, id);
291
+ },
292
+ [items]
438
293
  );
439
294
 
440
- const getConditionCount = useCallback(() => filterState.conditions.length, [filterState.conditions]);
441
- const hasConditions = useMemo(() => filterState.conditions.length > 0, [filterState.conditions]);
442
- const canAddCondition = useMemo(() => true, []); // Can always add more conditions
443
-
444
295
  // ============================================================
445
- // VALIDATION ERROR CALLBACK
296
+ // UTILITY
446
297
  // ============================================================
447
298
 
448
- useEffect(() => {
449
- if (options.onValidationError && validationErrors.length > 0) {
450
- options.onValidationError(validationErrors);
451
- }
452
- }, [validationErrors, options]);
299
+ const clear = useCallback((): void => {
300
+ setItems([]);
301
+ }, []);
302
+
303
+ const setOperator = useCallback((op: ConditionGroupOperator): void => {
304
+ setOperatorState(op);
305
+ }, []);
453
306
 
454
307
  // ============================================================
455
- // RETURN OBJECT
308
+ // RETURN
456
309
  // ============================================================
457
310
 
458
311
  return {
459
- // Current state
460
- conditions: filterState.conditions,
461
- logicalOperator: filterState.logicalOperator,
462
- filterPayload,
463
- isValid,
464
- validationErrors,
465
-
466
- // Condition management
467
- addCondition,
468
- updateCondition,
469
- removeCondition,
470
- clearConditions,
471
- getCondition,
472
-
473
- // Logical operator management
474
- setLogicalOperator,
475
-
476
- // Bulk operations
477
- setConditions,
478
- replaceCondition,
479
-
480
- // Validation
481
- validateCondition,
482
- validateAllConditions,
483
-
484
- // State management
485
- exportState,
486
- importState,
487
- resetToInitial,
488
-
489
- // Utilities
490
- getConditionCount,
312
+ // State
313
+ operator,
314
+ items,
315
+ payload,
491
316
  hasConditions,
492
- canAddCondition
317
+
318
+ // Add operations
319
+ add,
320
+ addGroup,
321
+ addTo,
322
+ addGroupTo,
323
+
324
+ // Update operations
325
+ update,
326
+ updateOperator,
327
+
328
+ // Remove & access
329
+ remove,
330
+ get,
331
+
332
+ // Utility
333
+ clear,
334
+ setOperator,
335
+
336
+ // Legacy API
337
+ conditions: items,
493
338
  };
494
339
  }