@ram_28/kf-ai-sdk 1.0.16 → 1.0.19
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/api/client.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/{client-C15j4O5B.cjs → client-DgtkT50N.cjs} +1 -1
- package/dist/{client-CfvLiGfP.js → client-V-WzUb8H.js} +9 -5
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/context.d.ts +1 -1
- package/dist/components/hooks/useKanban/context.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +740 -740
- package/dist/kanban.cjs +1 -1
- package/dist/kanban.mjs +4 -2
- package/dist/{metadata-2FLBsFcf.cjs → metadata-0lZAfuTP.cjs} +1 -1
- package/dist/{metadata-DBcoDth-.js → metadata-B88D_pVS.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/docs/QUICK_REFERENCE.md +528 -0
- package/docs/useAuth.md +402 -0
- package/docs/useFilter.md +273 -0
- package/docs/useForm.md +629 -0
- package/docs/useKanban.md +421 -0
- package/docs/useTable.md +372 -0
- package/package.json +4 -2
- package/sdk/api/client.ts +7 -1
- package/sdk/components/hooks/useForm/useForm.ts +12 -2
- package/sdk/components/hooks/useKanban/context.ts +5 -3
package/docs/useTable.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# useTable
|
|
2
|
+
|
|
3
|
+
## Brief Description
|
|
4
|
+
|
|
5
|
+
- Provides complete table state management including data fetching, sorting, filtering, search, and pagination
|
|
6
|
+
- Integrates with `useFilter` for advanced filtering capabilities with nested condition groups
|
|
7
|
+
- Handles API interactions automatically using React Query for caching and background refetching
|
|
8
|
+
- Returns flattened state accessors (`sort.field`, `pagination.currentPage`) for easy component integration
|
|
9
|
+
|
|
10
|
+
## Type Reference
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
14
|
+
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
15
|
+
import type {
|
|
16
|
+
UseTableOptionsType,
|
|
17
|
+
UseTableReturnType,
|
|
18
|
+
ColumnDefinitionType,
|
|
19
|
+
} from "@ram_28/kf-ai-sdk/table/types";
|
|
20
|
+
import type {
|
|
21
|
+
ConditionType,
|
|
22
|
+
ConditionGroupType,
|
|
23
|
+
ConditionOperatorType,
|
|
24
|
+
ConditionGroupOperatorType,
|
|
25
|
+
FilterType,
|
|
26
|
+
UseFilterReturnType,
|
|
27
|
+
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
28
|
+
import type { ListResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
29
|
+
|
|
30
|
+
// Condition operators for comparing field values
|
|
31
|
+
type ConditionOperatorType =
|
|
32
|
+
| "EQ" | "NE" | "GT" | "GTE" | "LT" | "LTE"
|
|
33
|
+
| "Between" | "NotBetween"
|
|
34
|
+
| "IN" | "NIN"
|
|
35
|
+
| "Empty" | "NotEmpty"
|
|
36
|
+
| "Contains" | "NotContains"
|
|
37
|
+
| "MinLength" | "MaxLength";
|
|
38
|
+
|
|
39
|
+
// Group operators for combining conditions
|
|
40
|
+
type ConditionGroupOperatorType = "And" | "Or" | "Not";
|
|
41
|
+
|
|
42
|
+
// Column configuration
|
|
43
|
+
interface ColumnDefinitionType<T> {
|
|
44
|
+
fieldId: keyof T;
|
|
45
|
+
label?: string;
|
|
46
|
+
enableSorting?: boolean;
|
|
47
|
+
enableFiltering?: boolean;
|
|
48
|
+
transform?: (value: any, row: T) => React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Hook options
|
|
52
|
+
interface UseTableOptionsType<T> {
|
|
53
|
+
source: string;
|
|
54
|
+
columns: ColumnDefinitionType<T>[];
|
|
55
|
+
enableSorting?: boolean;
|
|
56
|
+
enableFiltering?: boolean;
|
|
57
|
+
enablePagination?: boolean;
|
|
58
|
+
initialState?: {
|
|
59
|
+
pagination?: { pageNo: number; pageSize: number };
|
|
60
|
+
sorting?: { field: keyof T; direction: "asc" | "desc" };
|
|
61
|
+
filters?: Array<ConditionType | ConditionGroupType>;
|
|
62
|
+
filterOperator?: ConditionGroupOperatorType;
|
|
63
|
+
};
|
|
64
|
+
onError?: (error: Error) => void;
|
|
65
|
+
onSuccess?: (data: T[]) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Hook return type
|
|
69
|
+
interface UseTableReturnType<T> {
|
|
70
|
+
// Data
|
|
71
|
+
rows: T[];
|
|
72
|
+
totalItems: number;
|
|
73
|
+
|
|
74
|
+
// Loading states
|
|
75
|
+
isLoading: boolean;
|
|
76
|
+
isFetching: boolean;
|
|
77
|
+
error: Error | null;
|
|
78
|
+
|
|
79
|
+
// Search
|
|
80
|
+
search: {
|
|
81
|
+
query: string;
|
|
82
|
+
setQuery: (value: string) => void;
|
|
83
|
+
clear: () => void;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Sort
|
|
87
|
+
sort: {
|
|
88
|
+
field: keyof T | null;
|
|
89
|
+
direction: "asc" | "desc" | null;
|
|
90
|
+
toggle: (field: keyof T) => void;
|
|
91
|
+
clear: () => void;
|
|
92
|
+
set: (field: keyof T, direction: "asc" | "desc") => void;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Filter (uses useFilter internally)
|
|
96
|
+
filter: UseFilterReturnType;
|
|
97
|
+
|
|
98
|
+
// Pagination
|
|
99
|
+
pagination: {
|
|
100
|
+
currentPage: number;
|
|
101
|
+
pageSize: number;
|
|
102
|
+
totalPages: number;
|
|
103
|
+
totalItems: number;
|
|
104
|
+
canGoNext: boolean;
|
|
105
|
+
canGoPrevious: boolean;
|
|
106
|
+
goToNext: () => void;
|
|
107
|
+
goToPrevious: () => void;
|
|
108
|
+
goToPage: (page: number) => void;
|
|
109
|
+
setPageSize: (size: number) => void;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Operations
|
|
113
|
+
refetch: () => Promise<ListResponseType<T>>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// API response structure
|
|
117
|
+
interface ListResponseType<T> {
|
|
118
|
+
Data: T[];
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Usage Example
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
126
|
+
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
127
|
+
import type {
|
|
128
|
+
UseTableOptionsType,
|
|
129
|
+
UseTableReturnType,
|
|
130
|
+
ColumnDefinitionType,
|
|
131
|
+
} from "@ram_28/kf-ai-sdk/table/types";
|
|
132
|
+
import type {
|
|
133
|
+
ConditionType,
|
|
134
|
+
ConditionGroupType,
|
|
135
|
+
ConditionOperatorType,
|
|
136
|
+
ConditionGroupOperatorType,
|
|
137
|
+
FilterType,
|
|
138
|
+
UseFilterReturnType,
|
|
139
|
+
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
140
|
+
import type { ListResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
141
|
+
import { Product, ProductType } from "../sources";
|
|
142
|
+
import { Roles } from "../sources/roles";
|
|
143
|
+
|
|
144
|
+
// Get the typed product for the Buyer role
|
|
145
|
+
type BuyerProduct = ProductType<typeof Roles.Buyer>;
|
|
146
|
+
|
|
147
|
+
function ProductsPage() {
|
|
148
|
+
// Instantiate the Product source with role
|
|
149
|
+
const product = new Product(Roles.Buyer);
|
|
150
|
+
|
|
151
|
+
// Column definitions with type safety
|
|
152
|
+
const columns: ColumnDefinitionType<BuyerProduct>[] = [
|
|
153
|
+
{ fieldId: "Title", label: "Name", enableSorting: true },
|
|
154
|
+
{
|
|
155
|
+
fieldId: "Price",
|
|
156
|
+
label: "Price",
|
|
157
|
+
enableSorting: true,
|
|
158
|
+
transform: (value) => `$${value.toFixed(2)}`,
|
|
159
|
+
},
|
|
160
|
+
{ fieldId: "Category", label: "Category", enableSorting: true, enableFiltering: true },
|
|
161
|
+
{ fieldId: "Stock", label: "Stock", enableSorting: true },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
// Hook configuration with full options
|
|
165
|
+
const options: UseTableOptionsType<BuyerProduct> = {
|
|
166
|
+
source: product._id, // Use the Business Object ID from the Product class
|
|
167
|
+
columns,
|
|
168
|
+
enableSorting: true,
|
|
169
|
+
enableFiltering: true,
|
|
170
|
+
enablePagination: true,
|
|
171
|
+
initialState: {
|
|
172
|
+
pagination: { pageNo: 1, pageSize: 10 },
|
|
173
|
+
sorting: { field: "_created_at", direction: "desc" },
|
|
174
|
+
filterOperator: "And",
|
|
175
|
+
},
|
|
176
|
+
onError: (error: Error) => console.error("Table error:", error.message),
|
|
177
|
+
onSuccess: (data: BuyerProduct[]) => console.log("Loaded", data.length, "products"),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const table: UseTableReturnType<BuyerProduct> = useTable<BuyerProduct>(options);
|
|
181
|
+
|
|
182
|
+
// Access filter functionality (UseFilterReturnType)
|
|
183
|
+
const filterState: UseFilterReturnType = table.filter;
|
|
184
|
+
|
|
185
|
+
// Add a filter with dynamic operator selection
|
|
186
|
+
const addFilter = (field: keyof BuyerProduct, operator: ConditionOperatorType, value: any) => {
|
|
187
|
+
table.filter.addCondition({
|
|
188
|
+
Operator: operator,
|
|
189
|
+
LHSField: field as string,
|
|
190
|
+
RHSValue: value,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Add a simple filter condition
|
|
195
|
+
const filterByCategory = (category: string) => {
|
|
196
|
+
table.filter.clearAllConditions();
|
|
197
|
+
addFilter("Category", "EQ", category);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Add a complex nested filter (Price > 100 OR Stock < 10)
|
|
201
|
+
const addComplexFilter = () => {
|
|
202
|
+
const groupId = table.filter.addConditionGroup("Or");
|
|
203
|
+
table.filter.addCondition({ Operator: "GT", LHSField: "Price", RHSValue: 100 }, groupId);
|
|
204
|
+
table.filter.addCondition({ Operator: "LT", LHSField: "Stock", RHSValue: 10 }, groupId);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Display active filters using type guards
|
|
208
|
+
const renderActiveFilters = () => (
|
|
209
|
+
<div className="active-filters">
|
|
210
|
+
{table.filter.items.map((item) => {
|
|
211
|
+
if (isCondition(item)) {
|
|
212
|
+
return (
|
|
213
|
+
<span key={item.id} className="filter-tag">
|
|
214
|
+
{item.LHSField} {item.Operator} {String(item.RHSValue)}
|
|
215
|
+
<button onClick={() => table.filter.removeCondition(item.id!)}>×</button>
|
|
216
|
+
</span>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (isConditionGroup(item)) {
|
|
220
|
+
return (
|
|
221
|
+
<span key={item.id} className="filter-group-tag">
|
|
222
|
+
{item.Operator} Group ({item.Condition.length} conditions)
|
|
223
|
+
<button onClick={() => table.filter.removeCondition(item.id!)}>×</button>
|
|
224
|
+
</span>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
})}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Toggle filter logic operator
|
|
233
|
+
const toggleFilterLogic = () => {
|
|
234
|
+
const next: ConditionGroupOperatorType = table.filter.operator === "And" ? "Or" : "And";
|
|
235
|
+
table.filter.setRootOperator(next);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Refetch data manually
|
|
239
|
+
const handleRefresh = async () => {
|
|
240
|
+
const response: ListResponseType<BuyerProduct> = await table.refetch();
|
|
241
|
+
console.log(`Refreshed: ${response.Data.length} items`);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Access filter payload for debugging
|
|
245
|
+
const getFilterPayload = (): FilterType | undefined => {
|
|
246
|
+
return table.filter.payload;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
if (table.isLoading) {
|
|
250
|
+
return <div>Loading products...</div>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (table.error) {
|
|
254
|
+
return <div>Error: {table.error.message}</div>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<div className="products-page">
|
|
259
|
+
{/* Search */}
|
|
260
|
+
<div className="search-bar">
|
|
261
|
+
<input
|
|
262
|
+
type="text"
|
|
263
|
+
placeholder="Search products..."
|
|
264
|
+
value={table.search.query}
|
|
265
|
+
onChange={(e) => table.search.setQuery(e.target.value)}
|
|
266
|
+
/>
|
|
267
|
+
<button onClick={table.search.clear}>Clear</button>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Filter Controls */}
|
|
271
|
+
<div className="filter-controls">
|
|
272
|
+
<button onClick={() => filterByCategory("Electronics")}>Electronics</button>
|
|
273
|
+
<button onClick={() => filterByCategory("Books")}>Books</button>
|
|
274
|
+
<button onClick={addComplexFilter}>Add Complex Filter</button>
|
|
275
|
+
<button onClick={toggleFilterLogic}>
|
|
276
|
+
Logic: {table.filter.operator}
|
|
277
|
+
</button>
|
|
278
|
+
{table.filter.hasConditions && (
|
|
279
|
+
<button onClick={() => table.filter.clearAllConditions()}>Clear All Filters</button>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Active Filters */}
|
|
284
|
+
{renderActiveFilters()}
|
|
285
|
+
|
|
286
|
+
{/* Table */}
|
|
287
|
+
<table>
|
|
288
|
+
<thead>
|
|
289
|
+
<tr>
|
|
290
|
+
{columns.map((col) => (
|
|
291
|
+
<th
|
|
292
|
+
key={String(col.fieldId)}
|
|
293
|
+
onClick={() => col.enableSorting && table.sort.toggle(col.fieldId)}
|
|
294
|
+
style={{ cursor: col.enableSorting ? "pointer" : "default" }}
|
|
295
|
+
>
|
|
296
|
+
{col.label || String(col.fieldId)}
|
|
297
|
+
{table.sort.field === col.fieldId && (
|
|
298
|
+
<span>{table.sort.direction === "asc" ? " ↑" : " ↓"}</span>
|
|
299
|
+
)}
|
|
300
|
+
</th>
|
|
301
|
+
))}
|
|
302
|
+
</tr>
|
|
303
|
+
</thead>
|
|
304
|
+
<tbody>
|
|
305
|
+
{table.rows.map((row: BuyerProduct) => (
|
|
306
|
+
<tr key={row._id}>
|
|
307
|
+
<td>{row.Title}</td>
|
|
308
|
+
<td>${row.Price.toFixed(2)}</td>
|
|
309
|
+
<td>{row.Category}</td>
|
|
310
|
+
<td>{row.Stock}</td>
|
|
311
|
+
</tr>
|
|
312
|
+
))}
|
|
313
|
+
</tbody>
|
|
314
|
+
</table>
|
|
315
|
+
|
|
316
|
+
{/* Pagination */}
|
|
317
|
+
<div className="pagination">
|
|
318
|
+
<button
|
|
319
|
+
onClick={table.pagination.goToPrevious}
|
|
320
|
+
disabled={!table.pagination.canGoPrevious}
|
|
321
|
+
>
|
|
322
|
+
Previous
|
|
323
|
+
</button>
|
|
324
|
+
<span>
|
|
325
|
+
Page {table.pagination.currentPage} of {table.pagination.totalPages}
|
|
326
|
+
({table.pagination.totalItems} total)
|
|
327
|
+
</span>
|
|
328
|
+
<button
|
|
329
|
+
onClick={table.pagination.goToNext}
|
|
330
|
+
disabled={!table.pagination.canGoNext}
|
|
331
|
+
>
|
|
332
|
+
Next
|
|
333
|
+
</button>
|
|
334
|
+
<select
|
|
335
|
+
value={table.pagination.pageSize}
|
|
336
|
+
onChange={(e) => table.pagination.setPageSize(Number(e.target.value))}
|
|
337
|
+
>
|
|
338
|
+
<option value={10}>10 per page</option>
|
|
339
|
+
<option value={25}>25 per page</option>
|
|
340
|
+
<option value={50}>50 per page</option>
|
|
341
|
+
</select>
|
|
342
|
+
<input
|
|
343
|
+
type="number"
|
|
344
|
+
min={1}
|
|
345
|
+
max={table.pagination.totalPages}
|
|
346
|
+
placeholder="Go to page"
|
|
347
|
+
onKeyDown={(e) => {
|
|
348
|
+
if (e.key === "Enter") {
|
|
349
|
+
table.pagination.goToPage(Number((e.target as HTMLInputElement).value));
|
|
350
|
+
}
|
|
351
|
+
}}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{/* Actions */}
|
|
356
|
+
<div className="table-actions">
|
|
357
|
+
<button onClick={handleRefresh} disabled={table.isFetching}>
|
|
358
|
+
{table.isFetching ? "Refreshing..." : "Refresh"}
|
|
359
|
+
</button>
|
|
360
|
+
<button onClick={() => table.sort.clear()}>Clear Sort</button>
|
|
361
|
+
<button onClick={() => table.sort.set("Price", "desc")}>Sort by Price (desc)</button>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
{/* Debug: Filter payload */}
|
|
365
|
+
<details>
|
|
366
|
+
<summary>Filter Payload (Debug)</summary>
|
|
367
|
+
<pre>{JSON.stringify(getFilterPayload(), null, 2)}</pre>
|
|
368
|
+
</details>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ram_28/kf-ai-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Type-safe, AI-driven SDK for building modern web applications with role-based access control",
|
|
5
5
|
"author": "Ramprasad",
|
|
6
6
|
"license": "MIT",
|
|
@@ -156,11 +156,13 @@
|
|
|
156
156
|
"types": "./dist/base-types.d.ts",
|
|
157
157
|
"default": "./dist/types.cjs"
|
|
158
158
|
}
|
|
159
|
-
}
|
|
159
|
+
},
|
|
160
|
+
"./docs/*": "./docs/*"
|
|
160
161
|
},
|
|
161
162
|
"files": [
|
|
162
163
|
"dist",
|
|
163
164
|
"sdk",
|
|
165
|
+
"docs",
|
|
164
166
|
"README.md",
|
|
165
167
|
"LICENSE"
|
|
166
168
|
],
|
package/sdk/api/client.ts
CHANGED
|
@@ -379,7 +379,13 @@ export function api<T = any>(bo_id: string): ResourceClient<T> {
|
|
|
379
379
|
);
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
|
|
382
|
+
const json = await response.json();
|
|
383
|
+
// API returns {"Data":{"_id":"..."},"ValidationFailures":[]}
|
|
384
|
+
// Extract _id from Data wrapper and return flat structure
|
|
385
|
+
return {
|
|
386
|
+
...json.Data,
|
|
387
|
+
_id: json.Data._id,
|
|
388
|
+
};
|
|
383
389
|
},
|
|
384
390
|
|
|
385
391
|
// ============================================================
|
|
@@ -81,6 +81,9 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
81
81
|
// This allows us to detect changes since the last draft, not since form init
|
|
82
82
|
const lastSyncedValuesRef = useRef<Partial<T> | null>(null);
|
|
83
83
|
|
|
84
|
+
// Track if draft creation has started (prevents duplicate calls in React strict mode)
|
|
85
|
+
const draftCreationStartedRef = useRef(false);
|
|
86
|
+
|
|
84
87
|
// Stable callback ref to prevent dependency loops
|
|
85
88
|
const onSchemaErrorRef = useRef(onSchemaError);
|
|
86
89
|
|
|
@@ -209,11 +212,15 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
209
212
|
operation !== "create" ||
|
|
210
213
|
!schemaConfig ||
|
|
211
214
|
!enabled ||
|
|
212
|
-
draftId
|
|
215
|
+
draftId ||
|
|
216
|
+
draftCreationStartedRef.current // Prevent duplicate calls in React strict mode
|
|
213
217
|
) {
|
|
214
218
|
return;
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
// Mark as started immediately to prevent duplicate calls
|
|
222
|
+
draftCreationStartedRef.current = true;
|
|
223
|
+
|
|
217
224
|
const createInitialDraft = async () => {
|
|
218
225
|
setIsCreatingDraft(true);
|
|
219
226
|
setDraftError(null);
|
|
@@ -244,13 +251,16 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
244
251
|
} catch (error) {
|
|
245
252
|
console.error("Failed to create initial draft:", error);
|
|
246
253
|
setDraftError(error as Error);
|
|
254
|
+
// Reset the ref on error so it can be retried
|
|
255
|
+
draftCreationStartedRef.current = false;
|
|
247
256
|
} finally {
|
|
248
257
|
setIsCreatingDraft(false);
|
|
249
258
|
}
|
|
250
259
|
};
|
|
251
260
|
|
|
252
261
|
createInitialDraft();
|
|
253
|
-
}, [isInteractiveMode, operation, schemaConfig, enabled, draftId, source
|
|
262
|
+
}, [isInteractiveMode, operation, schemaConfig, enabled, draftId, source]);
|
|
263
|
+
// Note: rhfForm removed from deps - we use ref pattern to avoid dependency loops
|
|
254
264
|
|
|
255
265
|
// ============================================================
|
|
256
266
|
// COMPUTED FIELD DEPENDENCY TRACKING AND OPTIMIZATION
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
|
-
import { UseKanbanReturnType } from "./types";
|
|
2
|
+
import type { UseKanbanReturnType } from "./types";
|
|
3
3
|
|
|
4
|
-
export const KanbanContext = createContext<UseKanbanReturnType<any> | null>(
|
|
4
|
+
export const KanbanContext = createContext<UseKanbanReturnType<any> | null>(
|
|
5
|
+
null,
|
|
6
|
+
);
|
|
5
7
|
|
|
6
8
|
export function useKanbanContext<T extends Record<string, any> = any>() {
|
|
7
9
|
const context = useContext(KanbanContext);
|
|
8
10
|
if (!context) {
|
|
9
11
|
throw new Error(
|
|
10
|
-
"Kanban components must be used within a KanbanBoard component"
|
|
12
|
+
"Kanban components must be used within a KanbanBoard component",
|
|
11
13
|
);
|
|
12
14
|
}
|
|
13
15
|
return context as UseKanbanReturnType<T>;
|