@archbase/kanban 4.0.37

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 (95) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +630 -0
  3. package/dist/archbase-kanban-4.0.37.tgz +0 -0
  4. package/dist/components/Card/Card.d.ts +26 -0
  5. package/dist/components/Card/index.d.ts +1 -0
  6. package/dist/components/CardSkeleton/CardSkeleton.d.ts +9 -0
  7. package/dist/components/CardSkeleton/index.d.ts +2 -0
  8. package/dist/components/Column/Column.d.ts +47 -0
  9. package/dist/components/Column/index.d.ts +1 -0
  10. package/dist/components/ColumnAdder/ColumnAdder.d.ts +6 -0
  11. package/dist/components/ColumnAdder/index.d.ts +1 -0
  12. package/dist/components/ColumnContent/ColumnContent.d.ts +24 -0
  13. package/dist/components/ColumnContent/index.d.ts +1 -0
  14. package/dist/components/ColumnHeader/ColumnHeader.d.ts +10 -0
  15. package/dist/components/ColumnHeader/index.d.ts +1 -0
  16. package/dist/components/DefaultCard/DefaultCard.d.ts +3 -0
  17. package/dist/components/DefaultCard/index.d.ts +1 -0
  18. package/dist/components/GenericItem/GenericItem.d.ts +26 -0
  19. package/dist/components/GenericItem/index.d.ts +1 -0
  20. package/dist/components/Kanban.d.ts +3 -0
  21. package/dist/components/index.d.ts +2 -0
  22. package/dist/components/types.d.ts +124 -0
  23. package/dist/context/KanbanContext.d.ts +5 -0
  24. package/dist/global/dnd/dropManager.d.ts +16 -0
  25. package/dist/global/dnd/useCardDnd.d.ts +22 -0
  26. package/dist/global/dnd/useColumnDnd.d.ts +29 -0
  27. package/dist/index.css +1 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +1041 -0
  30. package/dist/utils/columnsUtils.d.ts +4 -0
  31. package/dist/utils/getPrefix.d.ts +2 -0
  32. package/dist/utils/getSharedProps.d.ts +8 -0
  33. package/dist/utils/infinite-scroll.d.ts +4 -0
  34. package/dist/utils/mergeRefs.d.ts +5 -0
  35. package/dist/utils/scroll.d.ts +2 -0
  36. package/package.json +66 -0
  37. package/src/common/_index.scss +0 -0
  38. package/src/components/Card/Card.tsx +215 -0
  39. package/src/components/Card/_Card.scss +11 -0
  40. package/src/components/Card/_index.scss +1 -0
  41. package/src/components/Card/index.ts +1 -0
  42. package/src/components/CardSkeleton/CardSkeleton.tsx +45 -0
  43. package/src/components/CardSkeleton/_CardSkeleton.scss +245 -0
  44. package/src/components/CardSkeleton/_index.scss +1 -0
  45. package/src/components/CardSkeleton/index.ts +2 -0
  46. package/src/components/Column/Column.tsx +246 -0
  47. package/src/components/Column/_Column.scss +44 -0
  48. package/src/components/Column/_index.scss +1 -0
  49. package/src/components/Column/index.ts +1 -0
  50. package/src/components/ColumnAdder/ColumnAdder.tsx +18 -0
  51. package/src/components/ColumnAdder/_ColumnAdder.scss +0 -0
  52. package/src/components/ColumnAdder/_index.scss +1 -0
  53. package/src/components/ColumnAdder/index.ts +1 -0
  54. package/src/components/ColumnContent/ColumnContent.tsx +238 -0
  55. package/src/components/ColumnContent/_ColumnContent.scss +23 -0
  56. package/src/components/ColumnContent/_index.scss +1 -0
  57. package/src/components/ColumnContent/index.ts +1 -0
  58. package/src/components/ColumnHeader/ColumnHeader.tsx +43 -0
  59. package/src/components/ColumnHeader/_ColumnHeader.scss +26 -0
  60. package/src/components/ColumnHeader/_index.scss +1 -0
  61. package/src/components/ColumnHeader/index.ts +1 -0
  62. package/src/components/DefaultCard/DefaultCard.tsx +9 -0
  63. package/src/components/DefaultCard/_DefaultCard.scss +14 -0
  64. package/src/components/DefaultCard/_index.scss +1 -0
  65. package/src/components/DefaultCard/index.ts +1 -0
  66. package/src/components/GenericItem/GenericItem.tsx +132 -0
  67. package/src/components/GenericItem/_GenericItem.scss +4 -0
  68. package/src/components/GenericItem/_index.scss +1 -0
  69. package/src/components/GenericItem/index.ts +1 -0
  70. package/src/components/Kanban.tsx +102 -0
  71. package/src/components/_Kanban.scss +8 -0
  72. package/src/components/_index.scss +9 -0
  73. package/src/components/index.ts +2 -0
  74. package/src/components/types.ts +155 -0
  75. package/src/context/KanbanContext.tsx +19 -0
  76. package/src/global/_index.scss +3 -0
  77. package/src/global/assets/styles/abstracts/_breakpoints.scss +71 -0
  78. package/src/global/assets/styles/abstracts/_colors.scss +85 -0
  79. package/src/global/assets/styles/abstracts/_functions.scss +3 -0
  80. package/src/global/assets/styles/abstracts/_index.scss +5 -0
  81. package/src/global/assets/styles/abstracts/_mixins.scss +49 -0
  82. package/src/global/assets/styles/abstracts/_padding.scss +1 -0
  83. package/src/global/assets/styles/abstracts/_variables.scss +8 -0
  84. package/src/global/assets/styles/base/_index.scss +34 -0
  85. package/src/global/dnd/dropManager.ts +301 -0
  86. package/src/global/dnd/useCardDnd.tsx +225 -0
  87. package/src/global/dnd/useColumnDnd.tsx +231 -0
  88. package/src/global/theme-default.scss +137 -0
  89. package/src/index.ts +6 -0
  90. package/src/utils/columnsUtils.ts +19 -0
  91. package/src/utils/getPrefix.ts +7 -0
  92. package/src/utils/getSharedProps.ts +18 -0
  93. package/src/utils/infinite-scroll.ts +18 -0
  94. package/src/utils/mergeRefs.ts +23 -0
  95. package/src/utils/scroll.ts +34 -0
@@ -0,0 +1,34 @@
1
+ @use "../abstracts" as *;
2
+
3
+ @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700,wght@0,100..900;1,100..900&display=swap");
4
+
5
+ * {
6
+ font-family: "Raleway", sans-serif;
7
+ margin: 0;
8
+ padding: 0;
9
+ box-sizing: border-box;
10
+ outline: none;
11
+ border: none;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ }
17
+
18
+ html {
19
+ scroll-behavior: smooth;
20
+ scroll-padding-top: 5rem;
21
+ text-size-adjust: none;
22
+ }
23
+
24
+ a,
25
+ button {
26
+ cursor: pointer;
27
+ }
28
+
29
+ a {
30
+ @include transition(opacity, 0.2s, ease-in-out);
31
+ &:active {
32
+ opacity: 0.8;
33
+ }
34
+ }
@@ -0,0 +1,301 @@
1
+ import {
2
+ BoardData,
3
+ BoardItem,
4
+ DropColumnParams,
5
+ DropParams,
6
+ } from "@/components";
7
+ import { extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
8
+ import { reorderWithEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/util/reorder-with-edge";
9
+
10
+ const isCardData = (data: Record<string, unknown>) => data?.type === "card";
11
+ const isColumnData = (data: Record<string, unknown>) => data?.type === "column";
12
+
13
+ const getTasksByColumnId = (columnId: string, dataSource: BoardData) => {
14
+ const tasks: BoardItem[] = [];
15
+ const column = dataSource[columnId];
16
+ if (!column) return tasks;
17
+ const parentTasks = column.children;
18
+ parentTasks?.forEach((taskId) => {
19
+ const task = dataSource[taskId];
20
+ if (task) tasks.push(task);
21
+ });
22
+ return tasks;
23
+ };
24
+
25
+ export const handleCardDrop = ({
26
+ source,
27
+ location,
28
+ columns,
29
+ dataSource,
30
+ onCardMove,
31
+ onColumnMove,
32
+ }: DropParams) => {
33
+ // ── Column drop ───────────────────────────────────────────────────
34
+ if (isColumnData(source.data)) {
35
+ if (!onColumnMove) return;
36
+
37
+ const innerMost = location.current.dropTargets[0];
38
+ if (!innerMost) return;
39
+
40
+ const dropTargetData = innerMost.data;
41
+ if (!isColumnData(dropTargetData)) return;
42
+
43
+ const sourceColumnId = source.data.columnId as string;
44
+ const targetColumnId = dropTargetData.columnId as string;
45
+ if (sourceColumnId === targetColumnId) return;
46
+
47
+ const sourceIndex = columns.findIndex((c) => c.id === sourceColumnId);
48
+ const targetIndex = columns.findIndex((c) => c.id === targetColumnId);
49
+ if (sourceIndex === -1 || targetIndex === -1) return;
50
+
51
+ const closestEdge = extractClosestEdge(dropTargetData);
52
+ const reordered = reorderWithEdge({
53
+ axis: "horizontal",
54
+ list: columns,
55
+ startIndex: sourceIndex,
56
+ indexOfTarget: targetIndex,
57
+ closestEdgeOfTarget: closestEdge,
58
+ });
59
+
60
+ onColumnMove({
61
+ columnId: sourceColumnId,
62
+ fromIndex: sourceIndex,
63
+ toIndex: reordered.findIndex((c) => c.id === sourceColumnId),
64
+ });
65
+
66
+ return;
67
+ }
68
+
69
+ if (!isCardData(source.data)) return;
70
+
71
+ const draggingCardId = source.data.itemId;
72
+ const sourceColumnId = source.data.columnId;
73
+
74
+ const innerMost = location.current.dropTargets[0];
75
+ if (!innerMost) return;
76
+
77
+ const dropTargetData = innerMost.data;
78
+
79
+ // Case 1: Dropping on another card
80
+ if (dropTargetData["card-drop-target"]) {
81
+ const targetColumnId = dropTargetData.columnId;
82
+ const targetCardId = dropTargetData.itemId;
83
+ const closestEdge = extractClosestEdge(dropTargetData);
84
+
85
+ // Find the cards involved
86
+ const sourceColumn = dataSource[sourceColumnId];
87
+ const targetColumn = dataSource[targetColumnId];
88
+
89
+ if (!sourceColumn || !targetColumn) return;
90
+
91
+ // For same column reordering
92
+ if (sourceColumnId === targetColumnId) {
93
+ // Get all tasks in the column
94
+ const allTasks = getTasksByColumnId(targetColumnId, dataSource);
95
+
96
+ // Find source and target indices
97
+ const sourceIndex = allTasks.findIndex(
98
+ (task) => task.id === draggingCardId,
99
+ );
100
+ const targetIndex = allTasks.findIndex(
101
+ (task) => task.id === targetCardId,
102
+ );
103
+
104
+ if (sourceIndex === -1 || targetIndex === -1) return;
105
+
106
+ // No change needed if same position
107
+ if (sourceIndex === targetIndex) return;
108
+
109
+ // Use reorderWithEdge to get the correct new order
110
+ const reordered = reorderWithEdge({
111
+ axis: "vertical",
112
+ list: allTasks,
113
+ startIndex: sourceIndex,
114
+ indexOfTarget: targetIndex,
115
+ closestEdgeOfTarget: closestEdge,
116
+ });
117
+
118
+ // Calculate the new position based on the reordered array
119
+ const newPosition = reordered.findIndex(
120
+ (task) => task.id === draggingCardId,
121
+ );
122
+
123
+ // Find tasks above and below the insertion point
124
+ const taskAbove = newPosition > 0 ? reordered[newPosition - 1] : null;
125
+ const taskBelow =
126
+ newPosition < reordered.length - 1 ? reordered[newPosition + 1] : null;
127
+
128
+ // Call the provided onCardMove handler
129
+ if (onCardMove) {
130
+ onCardMove({
131
+ cardId: draggingCardId,
132
+ fromColumnId: sourceColumnId,
133
+ toColumnId: targetColumnId,
134
+ taskAbove: taskAbove?.id,
135
+ taskBelow: taskBelow?.id,
136
+ position: newPosition,
137
+ });
138
+ }
139
+ }
140
+ // Moving between columns
141
+ else {
142
+ // Get all tasks in the target column
143
+ const allTargetTasks = getTasksByColumnId(targetColumnId, dataSource);
144
+
145
+ // Find the target task index
146
+ const targetIndex = allTargetTasks.findIndex(
147
+ (task) => task.id === targetCardId,
148
+ );
149
+ if (targetIndex === -1) return;
150
+ const finalIndex =
151
+ closestEdge === "bottom" ? targetIndex + 1 : targetIndex;
152
+
153
+ // Get the dragging task from source column
154
+ const draggingTask = getTasksByColumnId(sourceColumnId, dataSource).find(
155
+ (task) => task.id === draggingCardId,
156
+ );
157
+ if (!draggingTask) return;
158
+
159
+ // Create a temporary array with the dragging task inserted at the target position
160
+ const tempArray = [...allTargetTasks];
161
+ tempArray.splice(finalIndex, 0, draggingTask);
162
+
163
+ // Use reorderWithEdge to get the correct final position
164
+ const reordered = reorderWithEdge({
165
+ axis: "vertical",
166
+ list: tempArray,
167
+ startIndex: finalIndex,
168
+ indexOfTarget: finalIndex,
169
+ closestEdgeOfTarget: closestEdge,
170
+ });
171
+
172
+ // Calculate the new position based on the reordered array
173
+ const newPosition = reordered.findIndex(
174
+ (task) => task.id === draggingCardId,
175
+ );
176
+
177
+ const taskAbove = newPosition > 0 ? reordered[newPosition - 1] : null;
178
+ const taskBelow =
179
+ newPosition < reordered.length - 1 ? reordered[newPosition + 1] : null;
180
+
181
+ if (onCardMove) {
182
+ onCardMove({
183
+ cardId: draggingCardId,
184
+ fromColumnId: sourceColumnId,
185
+ toColumnId: targetColumnId,
186
+ taskAbove: taskAbove?.id,
187
+ taskBelow: taskBelow?.id,
188
+ position: newPosition,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ // Case 2: Dropping directly on a column (not on a card)
194
+ else if (isColumnData(dropTargetData)) {
195
+ const targetColumnId = dropTargetData.columnId;
196
+ const targetTasks = getTasksByColumnId(targetColumnId, dataSource);
197
+ const taskAbove =
198
+ targetTasks.length > 0 ? targetTasks[targetTasks.length - 1] : null;
199
+
200
+ if (onCardMove) {
201
+ onCardMove({
202
+ cardId: draggingCardId,
203
+ fromColumnId: sourceColumnId,
204
+ toColumnId: targetColumnId,
205
+ taskAbove: taskAbove?.id,
206
+ taskBelow: null,
207
+ position: targetTasks.length,
208
+ });
209
+ }
210
+ }
211
+ };
212
+
213
+ export const dropColumnHandler = (
214
+ drop: DropColumnParams,
215
+ dataSource: BoardData,
216
+ ) => {
217
+ const { columnId, toIndex } = drop;
218
+ const newDataSource = { ...dataSource };
219
+ const rootChildren = [...(newDataSource["root"]?.children || [])];
220
+ const currentIndex = rootChildren.indexOf(columnId);
221
+
222
+ if (currentIndex === -1) return newDataSource;
223
+
224
+ rootChildren.splice(currentIndex, 1);
225
+ rootChildren.splice(toIndex, 0, columnId);
226
+ if (newDataSource["root"]) {
227
+ newDataSource["root"].children = rootChildren;
228
+ }
229
+
230
+ return newDataSource;
231
+ };
232
+
233
+ export const dropHandler = (
234
+ drop: {
235
+ cardId: string;
236
+ fromColumnId: string;
237
+ toColumnId: string;
238
+ taskAbove: string | null;
239
+ taskBelow: string | null;
240
+ },
241
+ dataSource: BoardData,
242
+ updateDroppedItem?: (targetColumn: BoardItem, droppedItem: any) => any,
243
+ updateDestinationColumn?: (targetColumn: BoardItem) => any,
244
+ updateSourceColumn?: (sourceColumn: BoardItem) => any,
245
+ ) => {
246
+ const { cardId, fromColumnId, toColumnId, taskAbove, taskBelow } = drop;
247
+
248
+ const newDataSource = { ...dataSource };
249
+
250
+ if (newDataSource[fromColumnId]?.children) {
251
+ newDataSource[fromColumnId].children = newDataSource[
252
+ fromColumnId
253
+ ].children.filter((id: string) => id !== cardId);
254
+ if (
255
+ newDataSource[fromColumnId]?.totalItems !== undefined &&
256
+ newDataSource[fromColumnId].totalItems > 0
257
+ )
258
+ newDataSource[fromColumnId].totalItems--;
259
+ }
260
+
261
+ if (updateSourceColumn && newDataSource[fromColumnId])
262
+ newDataSource[fromColumnId] = updateSourceColumn(
263
+ newDataSource[fromColumnId],
264
+ );
265
+
266
+ const targetChildren = newDataSource[toColumnId]?.children || [];
267
+ let insertIndex = 0;
268
+
269
+ if (taskAbove) insertIndex = targetChildren.indexOf(taskAbove) + 1;
270
+ else if (taskBelow) insertIndex = targetChildren.indexOf(taskBelow);
271
+
272
+ const alreadyExistInTarget = targetChildren.includes(cardId);
273
+
274
+ if (
275
+ newDataSource[toColumnId]?.totalItems !== undefined &&
276
+ !alreadyExistInTarget
277
+ )
278
+ newDataSource[toColumnId].totalItems++;
279
+
280
+ if (updateDroppedItem && newDataSource[cardId]) {
281
+ const updatedItem = updateDroppedItem?.(
282
+ newDataSource[toColumnId],
283
+ newDataSource[cardId],
284
+ );
285
+ newDataSource[cardId] = updatedItem || newDataSource[cardId];
286
+ }
287
+
288
+ if (newDataSource[toColumnId]?.children) {
289
+ newDataSource[toColumnId].children = newDataSource[
290
+ toColumnId
291
+ ]?.children?.filter((id: string) => id !== cardId);
292
+ newDataSource[toColumnId].children.splice(insertIndex, 0, cardId);
293
+ }
294
+
295
+ if (updateDestinationColumn && newDataSource[toColumnId])
296
+ newDataSource[toColumnId] = updateDestinationColumn(
297
+ newDataSource[toColumnId],
298
+ );
299
+
300
+ return newDataSource;
301
+ };
@@ -0,0 +1,225 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { BoardItem, DndState } from "@/components/types";
3
+ import {
4
+ draggable,
5
+ dropTargetForElements,
6
+ } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
7
+ import {
8
+ attachClosestEdge,
9
+ extractClosestEdge,
10
+ Edge,
11
+ } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
12
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
13
+ import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
14
+ import { preserveOffsetOnSource } from "@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source";
15
+ import { useKanbanContext } from "@/context/KanbanContext";
16
+
17
+ export type TaskCardState =
18
+ | {
19
+ type: "idle";
20
+ }
21
+ | {
22
+ type: "is-dragging";
23
+ }
24
+ | {
25
+ type: "is-dragging-and-left-self";
26
+ }
27
+ | {
28
+ type: "is-over";
29
+ dragging: DOMRect;
30
+ closestEdge: Edge;
31
+ }
32
+ | {
33
+ type: "preview";
34
+ container: HTMLElement;
35
+ dragging: DOMRect;
36
+ };
37
+
38
+ const idle: TaskCardState = { type: "idle" };
39
+
40
+ // Custom hook to handle all drag and drop logic
41
+ export const useCardDnd = (
42
+ data: BoardItem = {} as BoardItem,
43
+ column: BoardItem = {} as BoardItem,
44
+ index: number,
45
+ isDraggable: boolean,
46
+ onCardDndStateChange?: (info: DndState) => void
47
+ ) => {
48
+ const { viewOnly } = useKanbanContext();
49
+ const outerRef = useRef<HTMLDivElement>(null);
50
+ const innerRef = useRef<HTMLDivElement>(null);
51
+ const [state, setState] = useState<TaskCardState>(idle);
52
+
53
+ // Memoize initial data to prevent recreating on each render
54
+ const getInitialData = useCallback(
55
+ () => ({
56
+ type: "card",
57
+ itemId: data?.id,
58
+ columnId: column?.id,
59
+ index,
60
+ isDraggable,
61
+ parentId: data.parentId,
62
+ rect: innerRef.current?.getBoundingClientRect() || null,
63
+ }),
64
+ [data?.id, column?.id, index, isDraggable, data?.parentId]
65
+ );
66
+
67
+ const getDropTargetData = useCallback(
68
+ ({ input, element }) => {
69
+ const cardData = {
70
+ type: "card",
71
+ "card-drop-target": true,
72
+ itemId: data.id,
73
+ columnId: column.id,
74
+ index,
75
+ isDraggable,
76
+ parentId: data.parentId,
77
+ };
78
+
79
+ return attachClosestEdge(cardData, {
80
+ input,
81
+ element,
82
+ allowedEdges: ["top", "bottom"],
83
+ });
84
+ },
85
+ [data?.id, column?.id, index, isDraggable, data?.parentId]
86
+ );
87
+
88
+ // Optimize the drop check to avoid recalculating on every drag move
89
+ const canDrop = useCallback(
90
+ (args) => {
91
+ const sourceData = args.source.data;
92
+ if (sourceData.itemId === data.parentId) return false;
93
+ return sourceData.isDraggable;
94
+ },
95
+ [data?.id, data?.parentId]
96
+ );
97
+
98
+ // Drag and drop event handlers
99
+ const handleGenerateDragPreview = useCallback(
100
+ ({ nativeSetDragImage, location }) => {
101
+ setCustomNativeDragPreview({
102
+ nativeSetDragImage,
103
+ getOffset: preserveOffsetOnSource({
104
+ element: innerRef.current!,
105
+ input: location.current.input,
106
+ }),
107
+ render({ container }) {
108
+ const rect = innerRef.current!.getBoundingClientRect();
109
+ setState({
110
+ type: "preview",
111
+ container,
112
+ dragging: rect,
113
+ });
114
+ },
115
+ });
116
+ },
117
+ []
118
+ );
119
+
120
+ const handleDragStart = useCallback(() => {
121
+ setState({ type: "is-dragging" });
122
+ }, []);
123
+
124
+ const handleDrop = useCallback(() => {
125
+ setState(idle);
126
+ }, []);
127
+
128
+ const handleDragEnter = useCallback(
129
+ ({ source, self }) => {
130
+ if (source.data.type !== "card") return;
131
+ if (source.data.itemId === data.id) return;
132
+
133
+ const closestEdge = extractClosestEdge(self.data);
134
+ if (!closestEdge) return;
135
+
136
+ setState({
137
+ type: "is-over",
138
+ dragging: source.data.rect as DOMRect,
139
+ closestEdge,
140
+ });
141
+ },
142
+ [data?.id]
143
+ );
144
+
145
+ const handleDrag = useCallback(
146
+ ({ source, self }) => {
147
+ if (source.data.type !== "card") return;
148
+ if (source.data.itemId === data.id) return;
149
+
150
+ const closestEdge = extractClosestEdge(self.data);
151
+ if (!closestEdge) return;
152
+
153
+ setState({
154
+ type: "is-over",
155
+ dragging: source.data.rect as DOMRect,
156
+ closestEdge,
157
+ });
158
+ },
159
+ [data?.id]
160
+ );
161
+
162
+ const handleDragLeave = useCallback(
163
+ ({ source }) => {
164
+ if (source.data.type !== "card") return;
165
+
166
+ if (source.data.itemId === data?.id) {
167
+ setState({ type: "is-dragging-and-left-self" });
168
+ return;
169
+ }
170
+
171
+ setState(idle);
172
+ },
173
+ [data.id]
174
+ );
175
+
176
+ // Setup drag and drop effects
177
+ useEffect(() => {
178
+ const outer = outerRef.current;
179
+ const inner = innerRef.current;
180
+
181
+ if (!outer || !inner) return;
182
+
183
+ return combine(
184
+ draggable({
185
+ element: inner,
186
+ getInitialData,
187
+ onGenerateDragPreview: handleGenerateDragPreview,
188
+ onDragStart: handleDragStart,
189
+ onDrop: handleDrop,
190
+ canDrag: () => isDraggable && !viewOnly,
191
+ }),
192
+ dropTargetForElements({
193
+ element: outer,
194
+ canDrop,
195
+ getIsSticky: () => true,
196
+ getData: getDropTargetData,
197
+ onDragEnter: handleDragEnter,
198
+ onDrag: handleDrag,
199
+ onDragLeave: handleDragLeave,
200
+ onDrop: handleDrop,
201
+ })
202
+ );
203
+ }, [
204
+ getInitialData,
205
+ handleGenerateDragPreview,
206
+ handleDragStart,
207
+ handleDrop,
208
+ isDraggable,
209
+ canDrop,
210
+ getDropTargetData,
211
+ handleDragEnter,
212
+ handleDrag,
213
+ handleDragLeave,
214
+ ]);
215
+
216
+ useEffect(() => {
217
+ onCardDndStateChange?.({ state, card: data, column });
218
+ }, [state, onCardDndStateChange]);
219
+
220
+ return {
221
+ outerRef,
222
+ innerRef,
223
+ state,
224
+ };
225
+ };