@ram_28/kf-ai-sdk 1.0.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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +840 -0
  3. package/dist/api/client.d.ts +78 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/datetime.d.ts +21 -0
  6. package/dist/api/datetime.d.ts.map +1 -0
  7. package/dist/api/index.d.ts +7 -0
  8. package/dist/api/index.d.ts.map +1 -0
  9. package/dist/api/metadata.d.ts +75 -0
  10. package/dist/api/metadata.d.ts.map +1 -0
  11. package/dist/components/hooks/index.d.ts +8 -0
  12. package/dist/components/hooks/index.d.ts.map +1 -0
  13. package/dist/components/hooks/useFilter/index.d.ts +5 -0
  14. package/dist/components/hooks/useFilter/index.d.ts.map +1 -0
  15. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +33 -0
  16. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +1 -0
  17. package/dist/components/hooks/useFilter/types.d.ts +137 -0
  18. package/dist/components/hooks/useFilter/types.d.ts.map +1 -0
  19. package/dist/components/hooks/useFilter/useFilter.d.ts +3 -0
  20. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -0
  21. package/dist/components/hooks/useFilter/validation.utils.d.ts +38 -0
  22. package/dist/components/hooks/useFilter/validation.utils.d.ts.map +1 -0
  23. package/dist/components/hooks/useForm/apiClient.d.ts +71 -0
  24. package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -0
  25. package/dist/components/hooks/useForm/expressionValidator.utils.d.ts +28 -0
  26. package/dist/components/hooks/useForm/expressionValidator.utils.d.ts.map +1 -0
  27. package/dist/components/hooks/useForm/index.d.ts +6 -0
  28. package/dist/components/hooks/useForm/index.d.ts.map +1 -0
  29. package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts +88 -0
  30. package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts.map +1 -0
  31. package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts +28 -0
  32. package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts.map +1 -0
  33. package/dist/components/hooks/useForm/schemaParser.utils.d.ts +29 -0
  34. package/dist/components/hooks/useForm/schemaParser.utils.d.ts.map +1 -0
  35. package/dist/components/hooks/useForm/types.d.ts +412 -0
  36. package/dist/components/hooks/useForm/types.d.ts.map +1 -0
  37. package/dist/components/hooks/useForm/useForm.d.ts +3 -0
  38. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -0
  39. package/dist/components/hooks/useKanban/apiClient.d.ts +99 -0
  40. package/dist/components/hooks/useKanban/apiClient.d.ts.map +1 -0
  41. package/dist/components/hooks/useKanban/context.d.ts +4 -0
  42. package/dist/components/hooks/useKanban/context.d.ts.map +1 -0
  43. package/dist/components/hooks/useKanban/dragDropManager.d.ts +27 -0
  44. package/dist/components/hooks/useKanban/dragDropManager.d.ts.map +1 -0
  45. package/dist/components/hooks/useKanban/index.d.ts +6 -0
  46. package/dist/components/hooks/useKanban/index.d.ts.map +1 -0
  47. package/dist/components/hooks/useKanban/types.d.ts +438 -0
  48. package/dist/components/hooks/useKanban/types.d.ts.map +1 -0
  49. package/dist/components/hooks/useKanban/useKanban.d.ts +3 -0
  50. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -0
  51. package/dist/components/hooks/useKanban/useKanbanSimple.d.ts +62 -0
  52. package/dist/components/hooks/useKanban/useKanbanSimple.d.ts.map +1 -0
  53. package/dist/components/hooks/useTable/index.d.ts +3 -0
  54. package/dist/components/hooks/useTable/index.d.ts.map +1 -0
  55. package/dist/components/hooks/useTable/types.d.ts +107 -0
  56. package/dist/components/hooks/useTable/types.d.ts.map +1 -0
  57. package/dist/components/hooks/useTable/useTable.d.ts +8 -0
  58. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -0
  59. package/dist/components/index.d.ts +3 -0
  60. package/dist/components/index.d.ts.map +1 -0
  61. package/dist/components/ui/index.d.ts +2 -0
  62. package/dist/components/ui/index.d.ts.map +1 -0
  63. package/dist/components/ui/kanban/Kanban.d.ts +12 -0
  64. package/dist/components/ui/kanban/Kanban.d.ts.map +1 -0
  65. package/dist/components/ui/kanban/index.d.ts +2 -0
  66. package/dist/components/ui/kanban/index.d.ts.map +1 -0
  67. package/dist/index.cjs +45 -0
  68. package/dist/index.d.ts +5 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.mjs +6522 -0
  71. package/dist/types/base-fields.d.ts +182 -0
  72. package/dist/types/base-fields.d.ts.map +1 -0
  73. package/dist/types/common.d.ts +238 -0
  74. package/dist/types/common.d.ts.map +1 -0
  75. package/dist/types/index.d.ts +3 -0
  76. package/dist/types/index.d.ts.map +1 -0
  77. package/dist/utils/cn.d.ts +7 -0
  78. package/dist/utils/cn.d.ts.map +1 -0
  79. package/dist/utils/formatting.d.ts +52 -0
  80. package/dist/utils/formatting.d.ts.map +1 -0
  81. package/dist/utils/index.d.ts +3 -0
  82. package/dist/utils/index.d.ts.map +1 -0
  83. package/package.json +98 -0
  84. package/sdk/api/client.ts +447 -0
  85. package/sdk/api/datetime.ts +33 -0
  86. package/sdk/api/index.ts +61 -0
  87. package/sdk/api/metadata.ts +148 -0
  88. package/sdk/components/hooks/index.ts +34 -0
  89. package/sdk/components/hooks/useFilter/index.ts +37 -0
  90. package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +298 -0
  91. package/sdk/components/hooks/useFilter/types.ts +158 -0
  92. package/sdk/components/hooks/useFilter/useFilter.llm.txt +497 -0
  93. package/sdk/components/hooks/useFilter/useFilter.ts +494 -0
  94. package/sdk/components/hooks/useFilter/validation.utils.ts +401 -0
  95. package/sdk/components/hooks/useForm/apiClient.ts +441 -0
  96. package/sdk/components/hooks/useForm/expressionValidator.utils.ts +444 -0
  97. package/sdk/components/hooks/useForm/index.ts +64 -0
  98. package/sdk/components/hooks/useForm/optimizedExpressionValidator.utils.ts +482 -0
  99. package/sdk/components/hooks/useForm/ruleClassifier.utils.ts +424 -0
  100. package/sdk/components/hooks/useForm/schemaParser.utils.ts +519 -0
  101. package/sdk/components/hooks/useForm/types.ts +630 -0
  102. package/sdk/components/hooks/useForm/useForm.llm.txt +340 -0
  103. package/sdk/components/hooks/useForm/useForm.ts +821 -0
  104. package/sdk/components/hooks/useKanban/apiClient.ts +494 -0
  105. package/sdk/components/hooks/useKanban/context.ts +14 -0
  106. package/sdk/components/hooks/useKanban/dragDropManager.ts +529 -0
  107. package/sdk/components/hooks/useKanban/index.ts +63 -0
  108. package/sdk/components/hooks/useKanban/types.ts +606 -0
  109. package/sdk/components/hooks/useKanban/useKanban.llm.txt +482 -0
  110. package/sdk/components/hooks/useKanban/useKanban.ts +725 -0
  111. package/sdk/components/hooks/useKanban/useKanbanSimple.ts +389 -0
  112. package/sdk/components/hooks/useTable/index.ts +5 -0
  113. package/sdk/components/hooks/useTable/types.ts +154 -0
  114. package/sdk/components/hooks/useTable/useTable.llm.txt +344 -0
  115. package/sdk/components/hooks/useTable/useTable.ts +413 -0
  116. package/sdk/components/index.ts +15 -0
  117. package/sdk/components/ui/index.ts +2 -0
  118. package/sdk/components/ui/kanban/Kanban.tsx +134 -0
  119. package/sdk/components/ui/kanban/index.ts +11 -0
  120. package/sdk/index.ts +13 -0
  121. package/sdk/types/base-fields.ts +221 -0
  122. package/sdk/types/common.ts +306 -0
  123. package/sdk/types/index.ts +5 -0
  124. package/sdk/utils/cn.ts +10 -0
  125. package/sdk/utils/formatting.ts +212 -0
  126. package/sdk/utils/index.ts +5 -0
@@ -0,0 +1,529 @@
1
+ // ============================================================
2
+ // KANBAN DRAG & DROP MANAGER
3
+ // ============================================================
4
+ // Complete drag & drop functionality with mouse, touch, and keyboard support
5
+ // Includes accessibility features and auto-scrolling
6
+
7
+ import { useState, useCallback, useRef, useEffect } from "react";
8
+ import type {
9
+ KanbanCard,
10
+ KanbanColumn,
11
+ DragDropState,
12
+ DragDropManager
13
+ } from "./types";
14
+
15
+ // ============================================================
16
+ // CONFIGURATION CONSTANTS
17
+ // ============================================================
18
+
19
+ const AUTO_SCROLL_THRESHOLD = 50;
20
+ const AUTO_SCROLL_SPEED = 5;
21
+ const TOUCH_MOVE_THRESHOLD = 5;
22
+
23
+ // ============================================================
24
+ // DRAG & DROP HOOK
25
+ // ============================================================
26
+
27
+ export function useDragDropManager<T>({
28
+ onCardMove,
29
+ onError,
30
+ columns,
31
+ announceMove
32
+ }: {
33
+ onCardMove?: (card: KanbanCard<T>, fromColumnId: string, toColumnId: string) => void;
34
+ onError?: (error: Error) => void;
35
+ columns: KanbanColumn<T>[];
36
+ announceMove?: (card: KanbanCard<T>, fromColumn: string, toColumn: string) => void;
37
+ }): DragDropManager<T> {
38
+
39
+ // ============================================================
40
+ // STATE MANAGEMENT
41
+ // ============================================================
42
+
43
+ const [dragState, setDragState] = useState<DragDropState<T>>({
44
+ isDragging: false,
45
+ draggedCard: null,
46
+ dragOverColumn: null,
47
+ dragOverPosition: null,
48
+ dragSourceColumn: null
49
+ });
50
+
51
+ // Refs for managing drag operations
52
+ const dragImageRef = useRef<HTMLElement | null>(null);
53
+ const autoScrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
54
+ const touchStartRef = useRef<{ x: number; y: number; card: KanbanCard<T> } | null>(null);
55
+
56
+ // ============================================================
57
+ // UTILITY FUNCTIONS
58
+ // ============================================================
59
+
60
+ const resetState = useCallback(() => {
61
+ setDragState({
62
+ isDragging: false,
63
+ draggedCard: null,
64
+ dragOverColumn: null,
65
+ dragOverPosition: null,
66
+ dragSourceColumn: null
67
+ });
68
+
69
+ // Clear any auto-scroll timeout
70
+ if (autoScrollTimeoutRef.current) {
71
+ clearTimeout(autoScrollTimeoutRef.current);
72
+ autoScrollTimeoutRef.current = null;
73
+ }
74
+ }, []);
75
+
76
+ const findColumnById = useCallback((columnId: string): KanbanColumn<T> | null => {
77
+ return columns.find(col => col._id === columnId) || null;
78
+ }, [columns]);
79
+
80
+ const findCardById = useCallback((cardId: string): { card: KanbanCard<T>; column: KanbanColumn<T> } | null => {
81
+ for (const column of columns) {
82
+ const card = column.cards.find(c => c._id === cardId);
83
+ if (card) {
84
+ return { card, column };
85
+ }
86
+ }
87
+ return null;
88
+ }, [columns]);
89
+
90
+ // ============================================================
91
+ // MOUSE DRAG HANDLERS
92
+ // ============================================================
93
+
94
+ const handleDragStart = useCallback((event: DragEvent, card: KanbanCard<T>) => {
95
+ try {
96
+ const cardElement = event.target as HTMLElement;
97
+ const sourceColumn = findColumnById(card.columnId);
98
+
99
+ if (!sourceColumn) {
100
+ throw new Error("Source column not found");
101
+ }
102
+
103
+ // Set up drag data
104
+ event.dataTransfer?.setData("text/plain", card._id);
105
+ event.dataTransfer?.setData("application/json", JSON.stringify(card));
106
+
107
+ // Create drag image from the actual card element
108
+ const dragImage = cardElement.cloneNode(true) as HTMLElement;
109
+ dragImage.style.cssText = `
110
+ position: fixed;
111
+ top: -1000px;
112
+ left: -1000px;
113
+ opacity: 0.9;
114
+ transform: rotate(-2deg) scale(0.95);
115
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2);
116
+ pointer-events: none;
117
+ z-index: 9999;
118
+ max-width: 300px;
119
+ background: white;
120
+ border-radius: 8px;
121
+ `;
122
+ document.body.appendChild(dragImage);
123
+ dragImageRef.current = dragImage;
124
+
125
+ if (event.dataTransfer) {
126
+ event.dataTransfer.setDragImage(dragImage, event.offsetX || 50, event.offsetY || 20);
127
+ }
128
+
129
+ // Update state
130
+ setDragState({
131
+ isDragging: true,
132
+ draggedCard: card,
133
+ dragOverColumn: null,
134
+ dragOverPosition: null,
135
+ dragSourceColumn: sourceColumn._id
136
+ });
137
+
138
+ // Accessibility announcement
139
+ announceMove?.(card, sourceColumn.title, "being moved");
140
+
141
+ } catch (error) {
142
+ onError?.(error instanceof Error ? error : new Error("Drag start failed"));
143
+ resetState();
144
+ }
145
+ }, [findColumnById, onError, resetState, announceMove]);
146
+
147
+ const handleDragOver = useCallback((event: DragEvent, columnId?: string) => {
148
+ event.preventDefault();
149
+
150
+ if (!dragState.isDragging || !dragState.draggedCard) return;
151
+
152
+ const column = columnId ? findColumnById(columnId) : null;
153
+
154
+ setDragState(prev => ({
155
+ ...prev,
156
+ dragOverColumn: column?._id || null,
157
+ dragOverPosition: null // Will be calculated based on mouse position
158
+ }));
159
+
160
+ // Auto-scroll logic
161
+ handleAutoScroll(event);
162
+
163
+ }, [dragState.isDragging, dragState.draggedCard, findColumnById]);
164
+
165
+ const handleDrop = useCallback((event: DragEvent, columnId: string) => {
166
+ event.preventDefault();
167
+
168
+ if (!dragState.isDragging || !dragState.draggedCard) return;
169
+
170
+ try {
171
+ const card = dragState.draggedCard;
172
+ const sourceColumnId = dragState.dragSourceColumn!;
173
+ const targetColumn = findColumnById(columnId);
174
+ const sourceColumn = findColumnById(sourceColumnId);
175
+
176
+ if (!targetColumn || !sourceColumn) {
177
+ throw new Error("Target or source column not found");
178
+ }
179
+
180
+ // Don't do anything if dropping in the same position
181
+ if (sourceColumnId === columnId) {
182
+ resetState();
183
+ return;
184
+ }
185
+
186
+ // Check if column has a limit
187
+ if (targetColumn.limit && targetColumn.cards.length >= targetColumn.limit) {
188
+ throw new Error(`Column "${targetColumn.title}" has reached its limit of ${targetColumn.limit} cards`);
189
+ }
190
+
191
+ // Call the move handler
192
+ onCardMove?.(card, sourceColumnId, columnId);
193
+
194
+ // Accessibility announcement
195
+ announceMove?.(card, sourceColumn.title, targetColumn.title);
196
+
197
+ } catch (error) {
198
+ onError?.(error instanceof Error ? error : new Error("Drop failed"));
199
+ } finally {
200
+ resetState();
201
+ }
202
+ }, [dragState, findColumnById, onCardMove, onError, resetState, announceMove]);
203
+
204
+ const handleDragEnd = useCallback(() => {
205
+ // Clean up drag image
206
+ if (dragImageRef.current) {
207
+ document.body.removeChild(dragImageRef.current);
208
+ dragImageRef.current = null;
209
+ }
210
+
211
+ resetState();
212
+ }, [resetState]);
213
+
214
+ // ============================================================
215
+ // AUTO-SCROLL FUNCTIONALITY
216
+ // ============================================================
217
+
218
+ const handleAutoScroll = useCallback((event: DragEvent) => {
219
+ const container = (event.target as HTMLElement).closest('.kanban-container');
220
+ if (!container) return;
221
+
222
+ const rect = container.getBoundingClientRect();
223
+ const mouseX = event.clientX - rect.left;
224
+ const containerWidth = rect.width;
225
+
226
+ let scrollDirection = 0;
227
+
228
+ // Check if near left edge
229
+ if (mouseX < AUTO_SCROLL_THRESHOLD) {
230
+ scrollDirection = -AUTO_SCROLL_SPEED;
231
+ }
232
+ // Check if near right edge
233
+ else if (mouseX > containerWidth - AUTO_SCROLL_THRESHOLD) {
234
+ scrollDirection = AUTO_SCROLL_SPEED;
235
+ }
236
+
237
+ if (scrollDirection !== 0) {
238
+ // Clear existing timeout
239
+ if (autoScrollTimeoutRef.current) {
240
+ clearTimeout(autoScrollTimeoutRef.current);
241
+ }
242
+
243
+ // Start auto-scroll
244
+ const scroll = () => {
245
+ container.scrollLeft += scrollDirection;
246
+ autoScrollTimeoutRef.current = setTimeout(scroll, 16); // ~60fps
247
+ };
248
+
249
+ autoScrollTimeoutRef.current = setTimeout(scroll, 100);
250
+ } else {
251
+ // Stop auto-scroll
252
+ if (autoScrollTimeoutRef.current) {
253
+ clearTimeout(autoScrollTimeoutRef.current);
254
+ autoScrollTimeoutRef.current = null;
255
+ }
256
+ }
257
+ }, []);
258
+
259
+ // ============================================================
260
+ // KEYBOARD NAVIGATION
261
+ // ============================================================
262
+
263
+ const handleKeyDown = useCallback((event: KeyboardEvent, card: KanbanCard<T>) => {
264
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Enter', 'Space', 'Escape'].includes(event.key)) {
265
+ return;
266
+ }
267
+
268
+ event.preventDefault();
269
+
270
+ const cardData = findCardById(card._id);
271
+ if (!cardData) return;
272
+
273
+ const { column: currentColumn } = cardData;
274
+ const currentColumnIndex = columns.findIndex(col => col._id === currentColumn._id);
275
+
276
+ try {
277
+ switch (event.key) {
278
+ case 'ArrowLeft':
279
+ // Move to previous column
280
+ if (currentColumnIndex > 0) {
281
+ const targetColumn = columns[currentColumnIndex - 1];
282
+ onCardMove?.(card, currentColumn._id, targetColumn._id);
283
+ announceMove?.(card, currentColumn.title, targetColumn.title);
284
+ }
285
+ break;
286
+
287
+ case 'ArrowRight':
288
+ // Move to next column
289
+ if (currentColumnIndex < columns.length - 1) {
290
+ const targetColumn = columns[currentColumnIndex + 1];
291
+ onCardMove?.(card, currentColumn._id, targetColumn._id);
292
+ announceMove?.(card, currentColumn.title, targetColumn.title);
293
+ }
294
+ break;
295
+
296
+ case 'ArrowUp':
297
+ // Move up within column (future enhancement)
298
+ announceMove?.(card, "current position", "position above");
299
+ break;
300
+
301
+ case 'ArrowDown':
302
+ // Move down within column (future enhancement)
303
+ announceMove?.(card, "current position", "position below");
304
+ break;
305
+
306
+ case 'Enter':
307
+ case 'Space':
308
+ // Start drag mode (future enhancement for keyboard-only drag)
309
+ announceMove?.(card, currentColumn.title, "drag mode");
310
+ break;
311
+
312
+ case 'Escape':
313
+ // Cancel drag mode
314
+ announceMove?.(card, "drag mode", "cancelled");
315
+ break;
316
+ }
317
+ } catch (error) {
318
+ onError?.(error instanceof Error ? error : new Error("Keyboard navigation failed"));
319
+ }
320
+ }, [findCardById, columns, onCardMove, onError, announceMove]);
321
+
322
+ // ============================================================
323
+ // TOUCH HANDLERS
324
+ // ============================================================
325
+
326
+ const handleTouchStart = useCallback((event: TouchEvent, card: KanbanCard<T>) => {
327
+ const touch = event.touches[0];
328
+ touchStartRef.current = {
329
+ x: touch.clientX,
330
+ y: touch.clientY,
331
+ card
332
+ };
333
+ }, []);
334
+
335
+ const handleTouchMove = useCallback((event: TouchEvent) => {
336
+ if (!touchStartRef.current) return;
337
+
338
+ const touch = event.touches[0];
339
+ const deltaX = Math.abs(touch.clientX - touchStartRef.current.x);
340
+ const deltaY = Math.abs(touch.clientY - touchStartRef.current.y);
341
+
342
+ // Check if moved enough to start drag
343
+ if (deltaX > TOUCH_MOVE_THRESHOLD || deltaY > TOUCH_MOVE_THRESHOLD) {
344
+ const card = touchStartRef.current.card;
345
+ const sourceColumn = findColumnById(card.columnId);
346
+
347
+ if (!sourceColumn) return;
348
+
349
+ setDragState({
350
+ isDragging: true,
351
+ draggedCard: card,
352
+ dragOverColumn: null,
353
+ dragOverPosition: null,
354
+ dragSourceColumn: sourceColumn._id
355
+ });
356
+
357
+ announceMove?.(card, sourceColumn.title, "being moved");
358
+ }
359
+ }, [findColumnById, announceMove]);
360
+
361
+ const handleTouchEnd = useCallback((event: TouchEvent) => {
362
+ if (!dragState.isDragging || !dragState.draggedCard || !touchStartRef.current) {
363
+ touchStartRef.current = null;
364
+ return;
365
+ }
366
+
367
+ try {
368
+ // Find the element under the touch point
369
+ const touch = event.changedTouches[0];
370
+ const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
371
+ const columnElement = elementBelow?.closest('[data-column-id]') as HTMLElement;
372
+
373
+ if (columnElement) {
374
+ const columnId = columnElement.dataset.columnId!;
375
+ const card = dragState.draggedCard;
376
+ const sourceColumnId = dragState.dragSourceColumn!;
377
+ const targetColumn = findColumnById(columnId);
378
+ const sourceColumn = findColumnById(sourceColumnId);
379
+
380
+ if (targetColumn && sourceColumn && sourceColumnId !== columnId) {
381
+ // Check column limit
382
+ if (targetColumn.limit && targetColumn.cards.length >= targetColumn.limit) {
383
+ throw new Error(`Column "${targetColumn.title}" has reached its limit of ${targetColumn.limit} cards`);
384
+ }
385
+
386
+ onCardMove?.(card, sourceColumnId, columnId);
387
+ announceMove?.(card, sourceColumn.title, targetColumn.title);
388
+ }
389
+ }
390
+ } catch (error) {
391
+ onError?.(error instanceof Error ? error : new Error("Touch drop failed"));
392
+ } finally {
393
+ touchStartRef.current = null;
394
+ resetState();
395
+ }
396
+ }, [dragState, findColumnById, onCardMove, onError, resetState, announceMove]);
397
+
398
+ // ============================================================
399
+ // ACCESSIBILITY ANNOUNCEMENTS
400
+ // ============================================================
401
+
402
+ const defaultAnnounceMove = useCallback((card: KanbanCard<T>, fromColumn: string, toColumn: string) => {
403
+ const message = `Moved "${card.title}" from ${fromColumn} to ${toColumn}`;
404
+
405
+ // Create live region announcement
406
+ const announcement = document.createElement('div');
407
+ announcement.setAttribute('aria-live', 'polite');
408
+ announcement.setAttribute('aria-atomic', 'true');
409
+ announcement.className = 'sr-only';
410
+ announcement.textContent = message;
411
+
412
+ document.body.appendChild(announcement);
413
+
414
+ // Remove after announcement
415
+ setTimeout(() => {
416
+ document.body.removeChild(announcement);
417
+ }, 1000);
418
+
419
+ // Also log for debugging
420
+ console.log(`Kanban: ${message}`);
421
+ }, []);
422
+
423
+ // ============================================================
424
+ // CLEANUP ON UNMOUNT
425
+ // ============================================================
426
+
427
+ useEffect(() => {
428
+ return () => {
429
+ // Cleanup on unmount
430
+ if (autoScrollTimeoutRef.current) {
431
+ clearTimeout(autoScrollTimeoutRef.current);
432
+ }
433
+
434
+ if (dragImageRef.current && document.body.contains(dragImageRef.current)) {
435
+ document.body.removeChild(dragImageRef.current);
436
+ }
437
+ };
438
+ }, []);
439
+
440
+ // ============================================================
441
+ // RETURN INTERFACE
442
+ // ============================================================
443
+
444
+ return {
445
+ // State
446
+ isDragging: dragState.isDragging,
447
+ draggedCard: dragState.draggedCard,
448
+ dragOverColumn: dragState.dragOverColumn,
449
+ dragOverPosition: dragState.dragOverPosition,
450
+ dragSourceColumn: dragState.dragSourceColumn,
451
+
452
+ // Handlers
453
+ handleDragStart,
454
+ handleDragOver,
455
+ handleDrop,
456
+ handleDragEnd,
457
+ handleKeyDown,
458
+ handleTouchStart,
459
+ handleTouchMove,
460
+ handleTouchEnd,
461
+
462
+ // Utilities
463
+ announceMove: announceMove || defaultAnnounceMove,
464
+ reset: resetState
465
+ };
466
+ }
467
+
468
+ // ============================================================
469
+ // UTILITY FUNCTIONS FOR DRAG & DROP
470
+ // ============================================================
471
+
472
+ /**
473
+ * Calculate drop position based on mouse coordinates
474
+ */
475
+ export function calculateDropPosition<T>(
476
+ event: DragEvent,
477
+ column: KanbanColumn<T>,
478
+ cardHeight = 80
479
+ ): number {
480
+ const columnElement = (event.target as HTMLElement).closest('.kanban-column');
481
+ if (!columnElement) return column.cards.length;
482
+
483
+ const columnRect = columnElement.getBoundingClientRect();
484
+ const relativeY = event.clientY - columnRect.top;
485
+
486
+ // Calculate which position in the column this corresponds to
487
+ const headerHeight = 60; // Approximate column header height
488
+ const cardY = relativeY - headerHeight;
489
+
490
+ if (cardY <= 0) return 0;
491
+
492
+ const position = Math.floor(cardY / cardHeight);
493
+ return Math.min(position, column.cards.length);
494
+ }
495
+
496
+ /**
497
+ * Add visual feedback classes to elements during drag
498
+ */
499
+ export function addDragFeedback(element: HTMLElement, type: 'source' | 'target') {
500
+ element.classList.add(`kanban-drag-${type}`);
501
+ }
502
+
503
+ /**
504
+ * Remove visual feedback classes
505
+ */
506
+ export function removeDragFeedback(element: HTMLElement) {
507
+ element.classList.remove('kanban-drag-source', 'kanban-drag-target');
508
+ }
509
+
510
+ /**
511
+ * Check if a column can accept a drop
512
+ */
513
+ export function canAcceptDrop<T>(
514
+ column: KanbanColumn<T>,
515
+ draggedCard: KanbanCard<T>
516
+ ): { canDrop: boolean; reason?: string } {
517
+ // Check if column has a limit
518
+ if (column.limit && column.cards.length >= column.limit && draggedCard.columnId !== column._id) {
519
+ return {
520
+ canDrop: false,
521
+ reason: `Column "${column.title}" has reached its limit of ${column.limit} cards`
522
+ };
523
+ }
524
+
525
+ // Add more validation rules here as needed
526
+ // e.g., check card type compatibility, user permissions, etc.
527
+
528
+ return { canDrop: true };
529
+ }
@@ -0,0 +1,63 @@
1
+ // ============================================================
2
+ // KANBAN SDK - Main Export
3
+ // ============================================================
4
+
5
+ export { useKanban } from './useKanban';
6
+ export { KanbanContext, useKanbanContext } from './context';
7
+
8
+ export type {
9
+ // Core interfaces
10
+ UseKanbanOptions,
11
+ UseKanbanReturn,
12
+ KanbanCard,
13
+ KanbanColumn,
14
+ ColumnConfig,
15
+ ColumnDefinition,
16
+
17
+ // Operations
18
+ CardOperations,
19
+ SearchOperations,
20
+ FilterOperations,
21
+
22
+ // Drag & Drop
23
+ DragDropState,
24
+ DragDropHandlers,
25
+ DragDropManager,
26
+
27
+ // API Types
28
+ CardApiResponse,
29
+ ColumnApiResponse,
30
+ MoveCardRequest,
31
+ ReorderRequest,
32
+ BulkCardUpdateRequest,
33
+ BulkColumnUpdateRequest,
34
+
35
+ // Validation
36
+ ValidationResult,
37
+ CardValidationContext,
38
+ ColumnValidationContext,
39
+
40
+ // Events
41
+ KanbanEventType,
42
+ KanbanEvent,
43
+
44
+ // Utility Types
45
+ ExtractCardType,
46
+ PartialBy,
47
+ CreateCardInput,
48
+ CreateColumnInput,
49
+ } from './types';
50
+
51
+ // Export utility functions for advanced use cases
52
+ export {
53
+ mergeCardsIntoColumns,
54
+ calculateCardPosition,
55
+ calculateColumnPosition,
56
+ normalizePositions,
57
+ handleKanbanApiError,
58
+ validateApiResponse
59
+ } from './apiClient';
60
+
61
+ export {
62
+ useDragDropManager
63
+ } from './dragDropManager';