@noya-app/noya-designsystem 0.1.57 → 0.1.59
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +15 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.js +300 -253
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +789 -743
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/validateDropIndicator.test.ts +64 -31
- package/src/components/sorting/SharedDragProvider.tsx +79 -25
- package/src/components/sorting/Sortable.tsx +19 -26
- package/src/components/sorting/sorting.ts +32 -63
- package/src/utils/moveTreeItem.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@dnd-kit/core": "6.3.1",
|
|
24
24
|
"@dnd-kit/sortable": "10.0.0",
|
|
25
|
-
"@noya-app/noya-colorpicker": "0.1.
|
|
25
|
+
"@noya-app/noya-colorpicker": "0.1.23",
|
|
26
26
|
"@noya-app/noya-utils": "0.1.5",
|
|
27
27
|
"@noya-app/noya-geometry": "0.1.11",
|
|
28
28
|
"@noya-app/noya-icons": "0.1.10",
|
|
@@ -1,88 +1,121 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
defaultAcceptsDrop,
|
|
4
|
+
MoveDragItemParameters,
|
|
4
5
|
validateDropIndicator,
|
|
5
6
|
} from "../components/sorting/sorting";
|
|
6
7
|
|
|
7
8
|
describe("defaultAcceptsDrop", () => {
|
|
8
9
|
it("should return false if the active item is the same as the over item", () => {
|
|
9
|
-
const result = defaultAcceptsDrop(
|
|
10
|
+
const result = defaultAcceptsDrop({
|
|
11
|
+
sourceIndex: 0,
|
|
12
|
+
targetIndex: 0,
|
|
13
|
+
position: "inside",
|
|
14
|
+
sourceListId: "",
|
|
15
|
+
targetListId: "",
|
|
16
|
+
});
|
|
10
17
|
expect(result).toBe(false);
|
|
11
18
|
});
|
|
12
19
|
|
|
13
20
|
it("should return false for 'inside' position", () => {
|
|
14
|
-
const result = defaultAcceptsDrop(
|
|
21
|
+
const result = defaultAcceptsDrop({
|
|
22
|
+
sourceIndex: 0,
|
|
23
|
+
targetIndex: 1,
|
|
24
|
+
position: "inside",
|
|
25
|
+
sourceListId: "",
|
|
26
|
+
targetListId: "",
|
|
27
|
+
});
|
|
15
28
|
expect(result).toBe(false);
|
|
16
29
|
});
|
|
17
30
|
|
|
18
31
|
it("should return false when dropping above the same item", () => {
|
|
19
|
-
const result = defaultAcceptsDrop(
|
|
32
|
+
const result = defaultAcceptsDrop({
|
|
33
|
+
sourceIndex: 0,
|
|
34
|
+
targetIndex: 0,
|
|
35
|
+
position: "above",
|
|
36
|
+
sourceListId: "",
|
|
37
|
+
targetListId: "",
|
|
38
|
+
});
|
|
20
39
|
expect(result).toBe(false);
|
|
21
40
|
});
|
|
22
41
|
|
|
23
42
|
it("should return false when dropping below the same item", () => {
|
|
24
|
-
const result = defaultAcceptsDrop(
|
|
43
|
+
const result = defaultAcceptsDrop({
|
|
44
|
+
sourceIndex: 0,
|
|
45
|
+
targetIndex: 0,
|
|
46
|
+
position: "below",
|
|
47
|
+
sourceListId: "",
|
|
48
|
+
targetListId: "",
|
|
49
|
+
});
|
|
25
50
|
expect(result).toBe(false);
|
|
26
51
|
});
|
|
27
52
|
|
|
28
53
|
it("should return false when dropping above the item below the source", () => {
|
|
29
|
-
const result = defaultAcceptsDrop(
|
|
54
|
+
const result = defaultAcceptsDrop({
|
|
55
|
+
sourceIndex: 0,
|
|
56
|
+
targetIndex: 1,
|
|
57
|
+
position: "above",
|
|
58
|
+
sourceListId: "",
|
|
59
|
+
targetListId: "",
|
|
60
|
+
});
|
|
30
61
|
expect(result).toBe(false);
|
|
31
62
|
});
|
|
32
63
|
|
|
33
64
|
it("should return false when dropping below the item above the source", () => {
|
|
34
|
-
const result = defaultAcceptsDrop(
|
|
65
|
+
const result = defaultAcceptsDrop({
|
|
66
|
+
sourceIndex: 1,
|
|
67
|
+
targetIndex: 0,
|
|
68
|
+
position: "below",
|
|
69
|
+
sourceListId: "",
|
|
70
|
+
targetListId: "",
|
|
71
|
+
});
|
|
35
72
|
expect(result).toBe(false);
|
|
36
73
|
});
|
|
37
74
|
|
|
38
75
|
it("should return true when dropping above a valid target", () => {
|
|
39
|
-
const result = defaultAcceptsDrop(
|
|
76
|
+
const result = defaultAcceptsDrop({
|
|
77
|
+
sourceIndex: 0,
|
|
78
|
+
targetIndex: 2,
|
|
79
|
+
position: "above",
|
|
80
|
+
sourceListId: "",
|
|
81
|
+
targetListId: "",
|
|
82
|
+
});
|
|
40
83
|
expect(result).toBe(true);
|
|
41
84
|
});
|
|
42
85
|
|
|
43
86
|
it("should return true when dropping below a valid target", () => {
|
|
44
|
-
const result = defaultAcceptsDrop(
|
|
87
|
+
const result = defaultAcceptsDrop({
|
|
88
|
+
sourceIndex: 2,
|
|
89
|
+
targetIndex: 0,
|
|
90
|
+
position: "below",
|
|
91
|
+
sourceListId: "",
|
|
92
|
+
targetListId: "",
|
|
93
|
+
});
|
|
45
94
|
expect(result).toBe(true);
|
|
46
95
|
});
|
|
47
96
|
});
|
|
48
97
|
|
|
49
98
|
describe("validateDropIndicator", () => {
|
|
50
|
-
const mockAcceptsDrop = (
|
|
51
|
-
sourceIndex: number,
|
|
52
|
-
targetIndex: number,
|
|
53
|
-
position: "above" | "below" | "inside"
|
|
54
|
-
) => {
|
|
99
|
+
const mockAcceptsDrop = (parameters: MoveDragItemParameters) => {
|
|
55
100
|
// Mock that only allows dropping inside
|
|
56
|
-
if (position === "inside") return true;
|
|
101
|
+
if (parameters.position === "inside") return true;
|
|
57
102
|
return false;
|
|
58
103
|
};
|
|
59
104
|
|
|
60
|
-
const mockAcceptsDropAboveBelow = (
|
|
61
|
-
sourceIndex: number,
|
|
62
|
-
targetIndex: number,
|
|
63
|
-
position: "above" | "below" | "inside"
|
|
64
|
-
) => {
|
|
105
|
+
const mockAcceptsDropAboveBelow = (parameters: MoveDragItemParameters) => {
|
|
65
106
|
// Mock that only allows dropping above/below
|
|
66
|
-
if (position === "inside") return false;
|
|
107
|
+
if (parameters.position === "inside") return false;
|
|
67
108
|
return true;
|
|
68
109
|
};
|
|
69
110
|
|
|
70
|
-
const mockAcceptsAll = (
|
|
71
|
-
sourceIndex: number,
|
|
72
|
-
targetIndex: number,
|
|
73
|
-
position: "above" | "below" | "inside"
|
|
74
|
-
) => {
|
|
111
|
+
const mockAcceptsAll = (_: MoveDragItemParameters) => {
|
|
75
112
|
// Mock that allows all drop positions
|
|
76
113
|
return true;
|
|
77
114
|
};
|
|
78
115
|
|
|
79
|
-
const mockAcceptsAboveAndInside = (
|
|
80
|
-
sourceIndex: number,
|
|
81
|
-
targetIndex: number,
|
|
82
|
-
position: "above" | "below" | "inside"
|
|
83
|
-
) => {
|
|
116
|
+
const mockAcceptsAboveAndInside = (parameters: MoveDragItemParameters) => {
|
|
84
117
|
// Mock that allows dropping above and inside, but not below
|
|
85
|
-
return position !== "below";
|
|
118
|
+
return parameters.position !== "below";
|
|
86
119
|
};
|
|
87
120
|
|
|
88
121
|
const keys = ["item1", "item2", "item3"];
|
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
useSensor,
|
|
14
14
|
useSensors,
|
|
15
15
|
} from "@dnd-kit/core";
|
|
16
|
-
import { Point } from "@noya-app/noya-geometry";
|
|
16
|
+
import { Point, rectContainsPoint, unionRects } from "@noya-app/noya-geometry";
|
|
17
|
+
import { windowsOf } from "@noya-app/noya-utils";
|
|
18
|
+
import { useStableCallback } from "@noya-app/react-utils";
|
|
17
19
|
import * as React from "react";
|
|
18
20
|
import { useMemo } from "react";
|
|
19
21
|
import { createPortal } from "react-dom";
|
|
@@ -27,15 +29,23 @@ import {
|
|
|
27
29
|
SharedDragProps,
|
|
28
30
|
useDragRegistrationManager,
|
|
29
31
|
} from "./DragRegistration";
|
|
30
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
DragItem,
|
|
34
|
+
DragState,
|
|
35
|
+
MoveDragItemHandler,
|
|
36
|
+
MoveDragItemParameters,
|
|
37
|
+
RelativeDropPosition,
|
|
38
|
+
} from "./sorting";
|
|
31
39
|
|
|
32
40
|
type SharedDragProviderProps = {
|
|
33
41
|
children: React.ReactNode;
|
|
42
|
+
onMoveItem?: MoveDragItemHandler;
|
|
34
43
|
sharedDragProps?: SharedDragProps;
|
|
35
44
|
};
|
|
36
45
|
|
|
37
46
|
export function SharedDragProvider({
|
|
38
47
|
children,
|
|
48
|
+
onMoveItem,
|
|
39
49
|
sharedDragProps = {
|
|
40
50
|
dragRegistrationContext: DragRegistrationContext,
|
|
41
51
|
activeDragContext: ActiveDragContext,
|
|
@@ -56,6 +66,8 @@ export function SharedDragProvider({
|
|
|
56
66
|
indicator: null,
|
|
57
67
|
});
|
|
58
68
|
|
|
69
|
+
// console.log("dragState", dragState.target);
|
|
70
|
+
|
|
59
71
|
const { registerList, unregisterList, registeredLists } =
|
|
60
72
|
useDragRegistrationManager();
|
|
61
73
|
|
|
@@ -116,6 +128,8 @@ export function SharedDragProvider({
|
|
|
116
128
|
[registeredLists, dragState.source]
|
|
117
129
|
);
|
|
118
130
|
|
|
131
|
+
const stableOnMoveItem = useStableCallback(onMoveItem);
|
|
132
|
+
|
|
119
133
|
const handleDragEnd = React.useCallback(
|
|
120
134
|
(event: DragEndEvent) => {
|
|
121
135
|
const { active, over } = event;
|
|
@@ -148,15 +162,24 @@ export function SharedDragProvider({
|
|
|
148
162
|
|
|
149
163
|
if (!sourceList || !targetList) return;
|
|
150
164
|
|
|
151
|
-
|
|
152
|
-
dropTarget.source.index,
|
|
153
|
-
dropTarget.target.index,
|
|
154
|
-
dropTarget.indicator,
|
|
155
|
-
dropTarget.source.listId,
|
|
156
|
-
dropTarget.target.listId
|
|
157
|
-
|
|
165
|
+
const parameters: MoveDragItemParameters = {
|
|
166
|
+
sourceIndex: dropTarget.source.index,
|
|
167
|
+
targetIndex: dropTarget.target.index,
|
|
168
|
+
position: dropTarget.indicator,
|
|
169
|
+
sourceListId: dropTarget.source.listId,
|
|
170
|
+
targetListId: dropTarget.target.listId,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Both lists have the opportunity to handle the move
|
|
174
|
+
sourceList.onMoveItem(parameters);
|
|
175
|
+
|
|
176
|
+
if (dropTarget.target.listId !== dropTarget.source.listId) {
|
|
177
|
+
targetList.onMoveItem(parameters);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stableOnMoveItem?.(parameters);
|
|
158
181
|
},
|
|
159
|
-
[registeredLists, dragState.source]
|
|
182
|
+
[registeredLists, dragState.source, stableOnMoveItem]
|
|
160
183
|
);
|
|
161
184
|
|
|
162
185
|
const contextValue: DragRegistrationContextType = React.useMemo(
|
|
@@ -176,7 +199,7 @@ export function SharedDragProvider({
|
|
|
176
199
|
);
|
|
177
200
|
|
|
178
201
|
const collisionDetection = useMemo(() => {
|
|
179
|
-
return getItemFirstCollisionDetection(
|
|
202
|
+
return getItemFirstCollisionDetection(registeredLists);
|
|
180
203
|
}, [registeredLists]);
|
|
181
204
|
|
|
182
205
|
return (
|
|
@@ -239,13 +262,13 @@ export function withDragProvider<T extends object>(
|
|
|
239
262
|
|
|
240
263
|
// Custom collision detection that prioritizes items over containers
|
|
241
264
|
export const getItemFirstCollisionDetection =
|
|
242
|
-
(
|
|
265
|
+
(registeredLists: Map<string, RegisteredList>): CollisionDetection =>
|
|
243
266
|
(parameters) => {
|
|
244
267
|
const pointerCollisions = pointerWithin(parameters);
|
|
245
268
|
|
|
246
269
|
if (pointerCollisions.length > 0) {
|
|
247
270
|
const itemCollisions = pointerCollisions.filter((collision) => {
|
|
248
|
-
for (const listId of
|
|
271
|
+
for (const listId of registeredLists.keys()) {
|
|
249
272
|
if (collision.id === listId) {
|
|
250
273
|
return false;
|
|
251
274
|
}
|
|
@@ -259,11 +282,42 @@ export const getItemFirstCollisionDetection =
|
|
|
259
282
|
}
|
|
260
283
|
}
|
|
261
284
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
285
|
+
const listCollision = pointerCollisions.at(0);
|
|
286
|
+
const nearestCenters = closestCenter(parameters);
|
|
287
|
+
|
|
288
|
+
// Handle a pointer between items by checking if the pointer is within
|
|
289
|
+
// a window of two items.
|
|
290
|
+
if (parameters.pointerCoordinates && listCollision) {
|
|
291
|
+
const list = registeredLists.get(listCollision.id as string);
|
|
292
|
+
|
|
293
|
+
if (list) {
|
|
294
|
+
const pairs = windowsOf(list.keys, 2);
|
|
295
|
+
|
|
296
|
+
for (const pair of pairs) {
|
|
297
|
+
const clientRects = pair.map(
|
|
298
|
+
(key) => parameters.droppableRects.get(key)!
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const merged = unionRects(
|
|
302
|
+
...clientRects.map((rect) => ({
|
|
303
|
+
x: rect.left,
|
|
304
|
+
y: rect.top,
|
|
305
|
+
width: rect.width,
|
|
306
|
+
height: rect.height,
|
|
307
|
+
}))
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (rectContainsPoint(merged, parameters.pointerCoordinates)) {
|
|
311
|
+
return nearestCenters.filter((center) =>
|
|
312
|
+
pair.some((id) => id === center.id)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// This handles empty lists
|
|
320
|
+
return pointerCollisions;
|
|
267
321
|
};
|
|
268
322
|
|
|
269
323
|
function findDropTarget({
|
|
@@ -327,13 +381,13 @@ function findDropTarget({
|
|
|
327
381
|
|
|
328
382
|
// Dropping on the list container itself
|
|
329
383
|
if (over.id === targetList.id) {
|
|
330
|
-
const canDrop = targetList.acceptsDrop(
|
|
331
|
-
source.index,
|
|
332
|
-
Math.max(0, targetList.keys.length - 1), // Use last valid index or 0 for empty lists
|
|
333
|
-
"below",
|
|
334
|
-
sourceList.id,
|
|
335
|
-
targetList.id
|
|
336
|
-
);
|
|
384
|
+
const canDrop = targetList.acceptsDrop({
|
|
385
|
+
sourceIndex: source.index,
|
|
386
|
+
targetIndex: Math.max(0, targetList.keys.length - 1), // Use last valid index or 0 for empty lists
|
|
387
|
+
position: "below",
|
|
388
|
+
sourceListId: sourceList.id,
|
|
389
|
+
targetListId: targetList.id,
|
|
390
|
+
});
|
|
337
391
|
|
|
338
392
|
if (canDrop) {
|
|
339
393
|
return {
|
|
@@ -69,10 +69,9 @@ function useSortableDropIndicator({
|
|
|
69
69
|
|
|
70
70
|
if (!source || !target) return;
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
: undefined;
|
|
72
|
+
if (target.listId === listId && target.itemId === itemId) {
|
|
73
|
+
return activeDrag.indicator ?? undefined;
|
|
74
|
+
}
|
|
76
75
|
|
|
77
76
|
const targetParentIndex =
|
|
78
77
|
target.index === -1 ? undefined : getDropTargetParentIndex?.(target.index);
|
|
@@ -80,31 +79,31 @@ function useSortableDropIndicator({
|
|
|
80
79
|
const isValidParent =
|
|
81
80
|
targetParentIndex === undefined || targetParentIndex === -1
|
|
82
81
|
? false
|
|
83
|
-
: acceptsDrop(
|
|
84
|
-
source.index,
|
|
85
|
-
targetParentIndex,
|
|
86
|
-
"inside",
|
|
87
|
-
source.listId,
|
|
88
|
-
target.listId
|
|
89
|
-
);
|
|
82
|
+
: acceptsDrop({
|
|
83
|
+
sourceIndex: source.index,
|
|
84
|
+
targetIndex: targetParentIndex,
|
|
85
|
+
position: "inside",
|
|
86
|
+
sourceListId: source.listId,
|
|
87
|
+
targetListId: target.listId,
|
|
88
|
+
});
|
|
90
89
|
|
|
91
90
|
const overIdAcceptsDrop =
|
|
92
91
|
target.index === -1
|
|
93
92
|
? undefined
|
|
94
|
-
: acceptsDrop(
|
|
95
|
-
source.index,
|
|
96
|
-
target.index,
|
|
97
|
-
"inside",
|
|
98
|
-
source.listId,
|
|
99
|
-
target.listId
|
|
100
|
-
);
|
|
93
|
+
: acceptsDrop({
|
|
94
|
+
sourceIndex: source.index,
|
|
95
|
+
targetIndex: target.index,
|
|
96
|
+
position: "inside",
|
|
97
|
+
sourceListId: source.listId,
|
|
98
|
+
targetListId: target.listId,
|
|
99
|
+
});
|
|
101
100
|
|
|
102
101
|
const thisItemIsTargetParent = targetParentIndex === index;
|
|
103
102
|
|
|
104
|
-
const
|
|
103
|
+
const showIndicatorOnParent =
|
|
105
104
|
!overIdAcceptsDrop && isValidParent && thisItemIsTargetParent;
|
|
106
105
|
|
|
107
|
-
return
|
|
106
|
+
return showIndicatorOnParent ? "inside" : undefined;
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
interface RootProps {
|
|
@@ -189,12 +188,6 @@ function SortableRoot_({
|
|
|
189
188
|
|
|
190
189
|
const { setNodeRef: setDroppableRef } = useDroppable({
|
|
191
190
|
id,
|
|
192
|
-
// Make the container droppable only when the list is empty
|
|
193
|
-
// This avoids conflicts with individual sortable items.
|
|
194
|
-
// This isn't strictly necessary since we also use a custom collision detection
|
|
195
|
-
// that prioritizes items over containers, but it helps with dropping into
|
|
196
|
-
// the best position (either above or below, rather than always below).
|
|
197
|
-
disabled: keys.length > 0,
|
|
198
191
|
});
|
|
199
192
|
|
|
200
193
|
// Register this list with the global drag system
|
|
@@ -3,29 +3,19 @@ import * as React from "react";
|
|
|
3
3
|
|
|
4
4
|
export type RelativeDropPosition = "above" | "below" | "inside";
|
|
5
5
|
|
|
6
|
-
export type
|
|
7
|
-
sourceIndex: number
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
targetListId: string
|
|
20
|
-
) => boolean;
|
|
21
|
-
|
|
22
|
-
export type MoveDragItemHandler = (
|
|
23
|
-
sourceIndex: number,
|
|
24
|
-
targetIndex: number,
|
|
25
|
-
position: RelativeDropPosition,
|
|
26
|
-
sourceListId: string,
|
|
27
|
-
targetListId: string
|
|
28
|
-
) => void;
|
|
6
|
+
export type MoveDragItemParameters = {
|
|
7
|
+
sourceIndex: number;
|
|
8
|
+
sourceListId: string;
|
|
9
|
+
targetIndex: number;
|
|
10
|
+
targetListId: string;
|
|
11
|
+
position: RelativeDropPosition;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DropValidator = (parameters: MoveDragItemParameters) => boolean;
|
|
15
|
+
|
|
16
|
+
export type AcceptsDrop = (parameters: MoveDragItemParameters) => boolean;
|
|
17
|
+
|
|
18
|
+
export type MoveDragItemHandler = (parameters: MoveDragItemParameters) => void;
|
|
29
19
|
|
|
30
20
|
export type AcceptsFromList = ({
|
|
31
21
|
sourceListId,
|
|
@@ -54,11 +44,11 @@ export const normalizeListTargetIndex = (
|
|
|
54
44
|
return position === "above" ? index : index + 1;
|
|
55
45
|
};
|
|
56
46
|
|
|
57
|
-
export const defaultAcceptsDrop: DropValidator = (
|
|
47
|
+
export const defaultAcceptsDrop: DropValidator = ({
|
|
58
48
|
sourceIndex,
|
|
59
49
|
targetIndex,
|
|
60
|
-
position
|
|
61
|
-
) => {
|
|
50
|
+
position,
|
|
51
|
+
}) => {
|
|
62
52
|
if (position === "inside") return false;
|
|
63
53
|
|
|
64
54
|
const normalized = normalizeListTargetIndex(targetIndex, position);
|
|
@@ -120,13 +110,13 @@ export function validateDropIndicator({
|
|
|
120
110
|
if (overIndex === -1) return undefined;
|
|
121
111
|
if (activeIndex === -1 && sourceListId === targetListId) return undefined;
|
|
122
112
|
|
|
123
|
-
const acceptsDropInside = acceptsDrop(
|
|
124
|
-
activeIndex,
|
|
125
|
-
overIndex,
|
|
126
|
-
"inside",
|
|
113
|
+
const acceptsDropInside = acceptsDrop({
|
|
114
|
+
sourceIndex: activeIndex,
|
|
115
|
+
targetIndex: overIndex,
|
|
116
|
+
position: "inside",
|
|
127
117
|
sourceListId,
|
|
128
|
-
targetListId
|
|
129
|
-
);
|
|
118
|
+
targetListId,
|
|
119
|
+
});
|
|
130
120
|
|
|
131
121
|
// Drop into the middle third if possible
|
|
132
122
|
if (
|
|
@@ -142,27 +132,14 @@ export function validateDropIndicator({
|
|
|
142
132
|
offsetStart
|
|
143
133
|
);
|
|
144
134
|
|
|
145
|
-
// For moving within the same list, we are more strict about the drop position.
|
|
146
|
-
// The source item must actually be contained in the target element.
|
|
147
|
-
// Otherwise the user may unintentionally drop the item into target,
|
|
148
|
-
// since it’s difficult to select no target at all.
|
|
149
|
-
// When dropping into a different list, the user definitely wants the item within that list,
|
|
150
|
-
// and it also handles the empty list case.
|
|
151
|
-
if (
|
|
152
|
-
!isContained(elementStart, elementSize, offsetStart) &&
|
|
153
|
-
sourceListId === targetListId
|
|
154
|
-
) {
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
135
|
// Drop above or below if possible
|
|
159
|
-
const acceptedHalf = acceptsDrop(
|
|
160
|
-
activeIndex,
|
|
161
|
-
overIndex,
|
|
162
|
-
containingHalf,
|
|
136
|
+
const acceptedHalf = acceptsDrop({
|
|
137
|
+
sourceIndex: activeIndex,
|
|
138
|
+
targetIndex: overIndex,
|
|
139
|
+
position: containingHalf,
|
|
163
140
|
sourceListId,
|
|
164
|
-
targetListId
|
|
165
|
-
);
|
|
141
|
+
targetListId,
|
|
142
|
+
});
|
|
166
143
|
|
|
167
144
|
return acceptedHalf
|
|
168
145
|
? containingHalf
|
|
@@ -183,16 +160,6 @@ function isContainedInMiddleThird(
|
|
|
183
160
|
);
|
|
184
161
|
}
|
|
185
162
|
|
|
186
|
-
function isContained(
|
|
187
|
-
elementStart: number,
|
|
188
|
-
elementSize: number,
|
|
189
|
-
offsetStart: number
|
|
190
|
-
): boolean {
|
|
191
|
-
return (
|
|
192
|
-
offsetStart >= elementStart && offsetStart <= elementStart + elementSize
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
163
|
function getContainingHalf(
|
|
197
164
|
elementStart: number,
|
|
198
165
|
elementSize: number,
|
|
@@ -202,14 +169,16 @@ function getContainingHalf(
|
|
|
202
169
|
return "below";
|
|
203
170
|
}
|
|
204
171
|
|
|
172
|
+
export const dragItemKeySeparator = "-~-";
|
|
173
|
+
|
|
205
174
|
export function createDragItemKey(listId: string, itemId: UniqueIdentifier) {
|
|
206
|
-
return `${listId}
|
|
175
|
+
return `${listId}${dragItemKeySeparator}${itemId}`;
|
|
207
176
|
}
|
|
208
177
|
|
|
209
178
|
export function parseDragItemKey(key: string): {
|
|
210
179
|
listId: string;
|
|
211
180
|
itemId: UniqueIdentifier;
|
|
212
181
|
} {
|
|
213
|
-
const [listId, itemId] = key.split(
|
|
182
|
+
const [listId, itemId] = key.split(dragItemKeySeparator);
|
|
214
183
|
return { listId, itemId };
|
|
215
184
|
}
|
|
@@ -88,13 +88,13 @@ export function acceptsDrop({
|
|
|
88
88
|
return false;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
return defaultAcceptsDrop(
|
|
92
|
-
sourceIndexPath.at(-1)!,
|
|
93
|
-
targetIndexPath.at(-1)!,
|
|
91
|
+
return defaultAcceptsDrop({
|
|
92
|
+
sourceIndex: sourceIndexPath.at(-1)!,
|
|
93
|
+
targetIndex: targetIndexPath.at(-1)!,
|
|
94
94
|
position,
|
|
95
|
-
"",
|
|
96
|
-
""
|
|
97
|
-
);
|
|
95
|
+
sourceListId: "",
|
|
96
|
+
targetListId: "",
|
|
97
|
+
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
return true;
|