-
Notifications
You must be signed in to change notification settings - Fork 82
/
get-droppable-over.ts
157 lines (134 loc) · 4.51 KB
/
get-droppable-over.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { Position, Rect } from 'css-box-model';
import type {
DroppableDimension,
DroppableDimensionMap,
DroppableId,
DraggableDimension,
Axis,
} from '../types';
import { toDroppableList } from './dimension-structures';
import isPositionInFrame from './visibility/is-position-in-frame';
import { distance, patch } from './position';
import isWithin from './is-within';
// https://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other
// https://silentmatt.com/rectangle-intersection/
function getHasOverlap(first: Rect, second: Rect): boolean {
return (
first.left < second.right &&
first.right > second.left &&
first.top < second.bottom &&
first.bottom > second.top
);
}
interface Args {
pageBorderBox: Rect;
draggable: DraggableDimension;
droppables: DroppableDimensionMap;
}
interface WithDistance {
distance: number;
id: DroppableId;
}
interface GetFurthestArgs {
pageBorderBox: Rect;
draggable: DraggableDimension;
candidates: DroppableDimension[];
}
function getFurthestAway({
pageBorderBox,
draggable,
candidates,
}: GetFurthestArgs): DroppableId | null {
// We are not comparing the center of the home list with the target list as it would
// give preference to giant lists
// We are measuring the distance from where the draggable started
// to where it is *hitting* the candidate
// Note: The hit point might technically not be in the bounds of the candidate
const startCenter: Position = draggable.page.borderBox.center;
const sorted: WithDistance[] = candidates
.map((candidate: DroppableDimension): WithDistance => {
const axis: Axis = candidate.axis;
const target: Position = patch(
candidate.axis.line,
// use the current center of the dragging item on the main axis
pageBorderBox.center[axis.line],
// use the center of the list on the cross axis
candidate.page.borderBox.center[axis.crossAxisLine],
);
return {
id: candidate.descriptor.id,
distance: distance(startCenter, target),
};
})
// largest value will be first
.sort((a: WithDistance, b: WithDistance) => b.distance - a.distance);
// just being safe
return sorted[0] ? sorted[0].id : null;
}
export default function getDroppableOver({
pageBorderBox,
draggable,
droppables,
}: Args): DroppableId | null {
// We know at this point that some overlap has to exist
const candidates: DroppableDimension[] = toDroppableList(droppables).filter(
(item: DroppableDimension): boolean => {
// Cannot be a candidate when disabled
if (!item.isEnabled) {
return false;
}
// Cannot be a candidate when there is no visible area
const active: Rect | null = item.subject.active;
if (!active) {
return false;
}
// Cannot be a candidate when dragging item is not over the droppable at all
if (!getHasOverlap(pageBorderBox, active)) {
return false;
}
// 1. Candidate if the center position is over a droppable
if (isPositionInFrame(active)(pageBorderBox.center)) {
return true;
}
// 2. Candidate if an edge is over the cross axis half way point
// 3. Candidate if dragging item is totally over droppable on cross axis
const axis: Axis = item.axis;
const childCenter: number = active.center[axis.crossAxisLine];
const crossAxisStart: number = pageBorderBox[axis.crossAxisStart];
const crossAxisEnd: number = pageBorderBox[axis.crossAxisEnd];
const isContained = isWithin(
active[axis.crossAxisStart],
active[axis.crossAxisEnd],
);
const isStartContained: boolean = isContained(crossAxisStart);
const isEndContained: boolean = isContained(crossAxisEnd);
// Dragging item is totally covering the active area
if (!isStartContained && !isEndContained) {
return true;
}
/**
* edges must go beyond the center line in order to avoid
* cases were both conditions are satisfied.
*/
if (isStartContained) {
return crossAxisStart < childCenter;
}
return crossAxisEnd > childCenter;
},
);
if (!candidates.length) {
return null;
}
// Only one candidate - use that!
if (candidates.length === 1) {
return candidates[0].descriptor.id;
}
// Multiple options returned
// Should only occur with really large items
// Going to use fallback: distance from home
return getFurthestAway({
pageBorderBox,
draggable,
candidates,
});
}