1 | const actions = require('./base');
|
2 | const utils = require('../utils');
|
3 | const scope = require('../scope');
|
4 |
|
5 | const interact = require('../interact');
|
6 | const InteractEvent = require('../InteractEvent');
|
7 |
|
8 | const Interactable = require('../Interactable');
|
9 | const Interaction = require('../Interaction');
|
10 | const defaultOptions = require('../defaultOptions');
|
11 |
|
12 | const drop = {
|
13 | defaults: {
|
14 | enabled: false,
|
15 | accept : null,
|
16 | overlap: 'pointer',
|
17 | },
|
18 | };
|
19 |
|
20 | let dynamicDrop = false;
|
21 |
|
22 | Interaction.signals.on('action-start', function ({ interaction, event }) {
|
23 | if (interaction.prepared.name !== 'drag') { return; }
|
24 |
|
25 |
|
26 | interaction.activeDrops.dropzones = [];
|
27 | interaction.activeDrops.elements = [];
|
28 | interaction.activeDrops.rects = [];
|
29 |
|
30 | interaction.dropEvents = null;
|
31 |
|
32 | if (!interaction.dynamicDrop) {
|
33 | setActiveDrops(interaction.activeDrops, interaction.element);
|
34 | }
|
35 |
|
36 | const dragEvent = interaction.prevEvent;
|
37 | const dropEvents = getDropEvents(interaction, event, dragEvent);
|
38 |
|
39 | if (dropEvents.activate) {
|
40 | fireActiveDrops(interaction.activeDrops, dropEvents.activate);
|
41 | }
|
42 | });
|
43 |
|
44 | InteractEvent.signals.on('new', function ({ interaction, iEvent, event }) {
|
45 | if (iEvent.type !== 'dragmove' && iEvent.type !== 'dragend') { return; }
|
46 |
|
47 | const draggableElement = interaction.element;
|
48 | const dragEvent = iEvent;
|
49 | const dropResult = getDrop(dragEvent, event, draggableElement);
|
50 |
|
51 | interaction.dropTarget = dropResult.dropzone;
|
52 | interaction.dropElement = dropResult.element;
|
53 |
|
54 | interaction.dropEvents = getDropEvents(interaction, event, dragEvent);
|
55 | });
|
56 |
|
57 | Interaction.signals.on('action-move', function ({ interaction }) {
|
58 | if (interaction.prepared.name !== 'drag') { return; }
|
59 |
|
60 | fireDropEvents(interaction, interaction.dropEvents);
|
61 | });
|
62 |
|
63 | Interaction.signals.on('action-end', function ({ interaction }) {
|
64 | if (interaction.prepared.name === 'drag') {
|
65 | fireDropEvents(interaction, interaction.dropEvents);
|
66 | }
|
67 | });
|
68 |
|
69 | Interaction.signals.on('stop-drag', function ({ interaction }) {
|
70 | interaction.activeDrops = {
|
71 | dropzones: null,
|
72 | elements: null,
|
73 | rects: null,
|
74 | };
|
75 |
|
76 | interaction.dropEvents = null;
|
77 | });
|
78 |
|
79 | function collectDrops (activeDrops, element) {
|
80 | const drops = [];
|
81 | const elements = [];
|
82 |
|
83 |
|
84 | for (const current of scope.interactables) {
|
85 | if (!current.options.drop.enabled) { continue; }
|
86 |
|
87 | const accept = current.options.drop.accept;
|
88 |
|
89 |
|
90 | if ((utils.is.element(accept) && accept !== element)
|
91 | || (utils.is.string(accept)
|
92 | && !utils.matchesSelector(element, accept))) {
|
93 |
|
94 | continue;
|
95 | }
|
96 |
|
97 |
|
98 | const dropElements = utils.is.string(current.target)
|
99 | ? current._context.querySelectorAll(current.target)
|
100 | : [current.target];
|
101 |
|
102 | for (const currentElement of dropElements) {
|
103 | if (currentElement !== element) {
|
104 | drops.push(current);
|
105 | elements.push(currentElement);
|
106 | }
|
107 | }
|
108 | }
|
109 |
|
110 | return {
|
111 | elements,
|
112 | dropzones: drops,
|
113 | };
|
114 | }
|
115 |
|
116 | function fireActiveDrops (activeDrops, event) {
|
117 | let prevElement;
|
118 |
|
119 |
|
120 | for (let i = 0; i < activeDrops.dropzones.length; i++) {
|
121 | const current = activeDrops.dropzones[i];
|
122 | const currentElement = activeDrops.elements [i];
|
123 |
|
124 |
|
125 | if (currentElement !== prevElement) {
|
126 |
|
127 | event.target = currentElement;
|
128 | current.fire(event);
|
129 | }
|
130 | prevElement = currentElement;
|
131 | }
|
132 | }
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | function setActiveDrops (activeDrops, dragElement) {
|
138 |
|
139 | const possibleDrops = collectDrops(activeDrops, dragElement);
|
140 |
|
141 | activeDrops.dropzones = possibleDrops.dropzones;
|
142 | activeDrops.elements = possibleDrops.elements;
|
143 | activeDrops.rects = [];
|
144 |
|
145 | for (let i = 0; i < activeDrops.dropzones.length; i++) {
|
146 | activeDrops.rects[i] = activeDrops.dropzones[i].getRect(activeDrops.elements[i]);
|
147 | }
|
148 | }
|
149 |
|
150 | function getDrop (dragEvent, event, dragElement) {
|
151 | const interaction = dragEvent.interaction;
|
152 | const validDrops = [];
|
153 |
|
154 | if (dynamicDrop) {
|
155 | setActiveDrops(interaction.activeDrops, dragElement);
|
156 | }
|
157 |
|
158 |
|
159 | for (let j = 0; j < interaction.activeDrops.dropzones.length; j++) {
|
160 | const current = interaction.activeDrops.dropzones[j];
|
161 | const currentElement = interaction.activeDrops.elements [j];
|
162 | const rect = interaction.activeDrops.rects [j];
|
163 |
|
164 | validDrops.push(current.dropCheck(dragEvent, event, interaction.target, dragElement, currentElement, rect)
|
165 | ? currentElement
|
166 | : null);
|
167 | }
|
168 |
|
169 |
|
170 | const dropIndex = utils.indexOfDeepestElement(validDrops);
|
171 |
|
172 | return {
|
173 | dropzone: interaction.activeDrops.dropzones[dropIndex] || null,
|
174 | element : interaction.activeDrops.elements [dropIndex] || null,
|
175 | };
|
176 | }
|
177 |
|
178 | function getDropEvents (interaction, pointerEvent, dragEvent) {
|
179 | const dropEvents = {
|
180 | enter : null,
|
181 | leave : null,
|
182 | activate : null,
|
183 | deactivate: null,
|
184 | move : null,
|
185 | drop : null,
|
186 | };
|
187 |
|
188 | const tmpl = {
|
189 | dragEvent,
|
190 | interaction,
|
191 | target : interaction.dropElement,
|
192 | dropzone : interaction.dropTarget,
|
193 | relatedTarget: dragEvent.target,
|
194 | draggable : dragEvent.interactable,
|
195 | timeStamp : dragEvent.timeStamp,
|
196 | };
|
197 |
|
198 | if (interaction.dropElement !== interaction.prevDropElement) {
|
199 |
|
200 | if (interaction.prevDropTarget) {
|
201 | dropEvents.leave = utils.extend({ type: 'dragleave' }, tmpl);
|
202 |
|
203 | dragEvent.dragLeave = dropEvents.leave.target = interaction.prevDropElement;
|
204 | dragEvent.prevDropzone = dropEvents.leave.dropzone = interaction.prevDropTarget;
|
205 | }
|
206 |
|
207 | if (interaction.dropTarget) {
|
208 | dropEvents.enter = {
|
209 | dragEvent,
|
210 | interaction,
|
211 | target : interaction.dropElement,
|
212 | dropzone : interaction.dropTarget,
|
213 | relatedTarget: dragEvent.target,
|
214 | draggable : dragEvent.interactable,
|
215 | timeStamp : dragEvent.timeStamp,
|
216 | type : 'dragenter',
|
217 | };
|
218 |
|
219 | dragEvent.dragEnter = interaction.dropElement;
|
220 | dragEvent.dropzone = interaction.dropTarget;
|
221 | }
|
222 | }
|
223 |
|
224 | if (dragEvent.type === 'dragend' && interaction.dropTarget) {
|
225 | dropEvents.drop = utils.extend({ type: 'drop' }, tmpl);
|
226 |
|
227 | dragEvent.dropzone = interaction.dropTarget;
|
228 | dragEvent.relatedTarget = interaction.dropElement;
|
229 | }
|
230 | if (dragEvent.type === 'dragstart') {
|
231 | dropEvents.activate = utils.extend({ type: 'dropactivate' }, tmpl);
|
232 |
|
233 | dropEvents.activate.target = null;
|
234 | dropEvents.activate.dropzone = null;
|
235 | }
|
236 | if (dragEvent.type === 'dragend') {
|
237 | dropEvents.deactivate = utils.extend({ type: 'dropdeactivate' }, tmpl);
|
238 |
|
239 | dropEvents.deactivate.target = null;
|
240 | dropEvents.deactivate.dropzone = null;
|
241 | }
|
242 | if (dragEvent.type === 'dragmove' && interaction.dropTarget) {
|
243 | dropEvents.move = utils.extend({
|
244 | dragmove : dragEvent,
|
245 | type : 'dropmove',
|
246 | }, tmpl);
|
247 |
|
248 | dragEvent.dropzone = interaction.dropTarget;
|
249 | }
|
250 |
|
251 | return dropEvents;
|
252 | }
|
253 |
|
254 | function fireDropEvents (interaction, dropEvents) {
|
255 | const {
|
256 | activeDrops,
|
257 | prevDropTarget,
|
258 | dropTarget,
|
259 | dropElement,
|
260 | } = interaction;
|
261 |
|
262 | if (dropEvents.leave) { prevDropTarget.fire(dropEvents.leave); }
|
263 | if (dropEvents.move ) { dropTarget.fire(dropEvents.move ); }
|
264 | if (dropEvents.enter) { dropTarget.fire(dropEvents.enter); }
|
265 | if (dropEvents.drop ) { dropTarget.fire(dropEvents.drop ); }
|
266 | if (dropEvents.deactivate) {
|
267 | fireActiveDrops(activeDrops, dropEvents.deactivate);
|
268 | }
|
269 |
|
270 | interaction.prevDropTarget = dropTarget;
|
271 | interaction.prevDropElement = dropElement;
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | Interactable.prototype.dropzone = function (options) {
|
328 | if (utils.is.object(options)) {
|
329 | this.options.drop.enabled = options.enabled === false? false: true;
|
330 |
|
331 | if (utils.is.function(options.ondrop) ) { this.events.ondrop = options.ondrop ; }
|
332 | if (utils.is.function(options.ondropactivate) ) { this.events.ondropactivate = options.ondropactivate ; }
|
333 | if (utils.is.function(options.ondropdeactivate)) { this.events.ondropdeactivate = options.ondropdeactivate; }
|
334 | if (utils.is.function(options.ondragenter) ) { this.events.ondragenter = options.ondragenter ; }
|
335 | if (utils.is.function(options.ondragleave) ) { this.events.ondragleave = options.ondragleave ; }
|
336 | if (utils.is.function(options.ondropmove) ) { this.events.ondropmove = options.ondropmove ; }
|
337 |
|
338 | if (/^(pointer|center)$/.test(options.overlap)) {
|
339 | this.options.drop.overlap = options.overlap;
|
340 | }
|
341 | else if (utils.is.number(options.overlap)) {
|
342 | this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
|
343 | }
|
344 | if ('accept' in options) {
|
345 | this.options.drop.accept = options.accept;
|
346 | }
|
347 | if ('checker' in options) {
|
348 | this.options.drop.checker = options.checker;
|
349 | }
|
350 |
|
351 |
|
352 | return this;
|
353 | }
|
354 |
|
355 | if (utils.is.bool(options)) {
|
356 | this.options.drop.enabled = options;
|
357 |
|
358 | if (!options) {
|
359 | this.ondragenter = this.ondragleave = this.ondrop
|
360 | = this.ondropactivate = this.ondropdeactivate = null;
|
361 | }
|
362 |
|
363 | return this;
|
364 | }
|
365 |
|
366 | return this.options.drop;
|
367 | };
|
368 |
|
369 | Interactable.prototype.dropCheck = function (dragEvent, event, draggable, draggableElement, dropElement, rect) {
|
370 | let dropped = false;
|
371 |
|
372 |
|
373 |
|
374 | if (!(rect = rect || this.getRect(dropElement))) {
|
375 | return (this.options.drop.checker
|
376 | ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
|
377 | : false);
|
378 | }
|
379 |
|
380 | const dropOverlap = this.options.drop.overlap;
|
381 |
|
382 | if (dropOverlap === 'pointer') {
|
383 | const origin = utils.getOriginXY(draggable, draggableElement, 'drag');
|
384 | const page = utils.getPageXY(dragEvent);
|
385 |
|
386 | page.x += origin.x;
|
387 | page.y += origin.y;
|
388 |
|
389 | const horizontal = (page.x > rect.left) && (page.x < rect.right);
|
390 | const vertical = (page.y > rect.top ) && (page.y < rect.bottom);
|
391 |
|
392 | dropped = horizontal && vertical;
|
393 | }
|
394 |
|
395 | const dragRect = draggable.getRect(draggableElement);
|
396 |
|
397 | if (dragRect && dropOverlap === 'center') {
|
398 | const cx = dragRect.left + dragRect.width / 2;
|
399 | const cy = dragRect.top + dragRect.height / 2;
|
400 |
|
401 | dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
|
402 | }
|
403 |
|
404 | if (dragRect && utils.is.number(dropOverlap)) {
|
405 | const overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
|
406 | * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top )));
|
407 |
|
408 | const overlapRatio = overlapArea / (dragRect.width * dragRect.height);
|
409 |
|
410 | dropped = overlapRatio >= dropOverlap;
|
411 | }
|
412 |
|
413 | if (this.options.drop.checker) {
|
414 | dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
|
415 | }
|
416 |
|
417 | return dropped;
|
418 | };
|
419 |
|
420 | Interactable.signals.on('unset', function ({ interactable }) {
|
421 | interactable.dropzone(false);
|
422 | });
|
423 |
|
424 | Interactable.settingsMethods.push('dropChecker');
|
425 |
|
426 | Interaction.signals.on('new', function (interaction) {
|
427 | interaction.dropTarget = null;
|
428 | interaction.dropElement = null;
|
429 | interaction.prevDropTarget = null;
|
430 | interaction.prevDropElement = null;
|
431 | interaction.dropEvents = null;
|
432 |
|
433 | interaction.activeDrops = {
|
434 | dropzones: [],
|
435 | elements : [],
|
436 | rects : [],
|
437 | };
|
438 |
|
439 | });
|
440 |
|
441 | Interaction.signals.on('stop', function ({ interaction }) {
|
442 | interaction.dropTarget = interaction.dropElement =
|
443 | interaction.prevDropTarget = interaction.prevDropElement = null;
|
444 | });
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 | interact.dynamicDrop = function (newValue) {
|
455 | if (utils.is.bool(newValue)) {
|
456 |
|
457 |
|
458 |
|
459 |
|
460 | dynamicDrop = newValue;
|
461 |
|
462 | return interact;
|
463 | }
|
464 | return dynamicDrop;
|
465 | };
|
466 |
|
467 | utils.merge(Interactable.eventTypes, [
|
468 | 'dragenter',
|
469 | 'dragleave',
|
470 | 'dropactivate',
|
471 | 'dropdeactivate',
|
472 | 'dropmove',
|
473 | 'drop',
|
474 | ]);
|
475 | actions.methodDict.drop = 'dropzone';
|
476 |
|
477 | defaultOptions.drop = drop.defaults;
|
478 |
|
479 | module.exports = drop;
|