1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="utf-8">
|
5 | <meta name="viewport" content="width=device-width,initial-scale=1">
|
6 | <title>packages/@interactjs/actions/drop/plugin.ts - Documentation</title>
|
7 |
|
8 | <script src="scripts/prettify/prettify.js"></script>
|
9 | <script src="scripts/prettify/lang-css.js"></script>
|
10 | |
11 |
|
12 |
|
13 | <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
14 | <link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
15 | <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
16 | </head>
|
17 | <body>
|
18 |
|
19 | <input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
20 | <label for="nav-trigger" class="navicon-button x">
|
21 | <div class="navicon"></div>
|
22 | </label>
|
23 |
|
24 | <label for="nav-trigger" class="overlay"></label>
|
25 |
|
26 | <nav>
|
27 | <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="DropEvent_DropEvent.html">DropEvent</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Interactable.html">Interactable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#context">context</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#deltaSource">deltaSource</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#draggable">draggable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#dropCheck">dropCheck</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#dropzone">dropzone</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#fire">fire</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#gesturable">gesturable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#getRect">getRect</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#off">off</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#on">on</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#origin">origin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#rectChecker">rectChecker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#reflow">reflow</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#resizable">resizable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#set">set</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#unset">unset</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="InteractEvent_InteractEvent.html">InteractEvent</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Interaction_Interaction.html">Interaction</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="module.exports_module.exports.html">exports</a></span></li><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-interact.html">interact</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.isSet">isSet</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.maxInteractions">maxInteractions</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.pointerMoveTolerance">pointerMoveTolerance</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.stop">stop</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.supportsPointerEvent">supportsPointerEvent</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.supportsTouch">supportsTouch</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.use">use</a></span></li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-modifiers_aspectRatio.html">modifiers/aspectRatio</a></span></li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-modifiers_snapEdges.html">modifiers/snapEdges</a></span></li><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#interact">interact</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#preventDefault">preventDefault</a></span></li>
|
28 | </nav>
|
29 |
|
30 | <div class="code-col-bg"></div>
|
31 |
|
32 | <div id="main">
|
33 |
|
34 | <h1 class="page-title">packages/@interactjs/actions/drop/plugin.ts</h1>
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | <section>
|
43 | <article>
|
44 | <pre class="prettyprint source linenums"><code>import type { EventPhase, InteractEvent } from '@interactjs/core/InteractEvent'
|
45 | import type { Interactable } from '@interactjs/core/Interactable'
|
46 | import type { Interaction, DoPhaseArg } from '@interactjs/core/Interaction'
|
47 | import type { Scope, Plugin } from '@interactjs/core/scope'
|
48 | import type { DropzoneOptions, Element, PointerEventType, Rect } from '@interactjs/types/index'
|
49 | import * as domUtils from '@interactjs/utils/domUtils'
|
50 | import extend from '@interactjs/utils/extend'
|
51 | import getOriginXY from '@interactjs/utils/getOriginXY'
|
52 | import is from '@interactjs/utils/is'
|
53 | import normalizeListeners from '@interactjs/utils/normalizeListeners'
|
54 | import * as pointerUtils from '@interactjs/utils/pointerUtils'
|
55 |
|
56 | import type { DragEvent } from '../drag/plugin'
|
57 | import drag from '../drag/plugin'
|
58 |
|
59 | import { DropEvent } from './DropEvent'
|
60 |
|
61 | export interface DropzoneMethod {
|
62 | (this: Interactable, options: DropzoneOptions | boolean): Interactable
|
63 | (): DropzoneOptions
|
64 | }
|
65 |
|
66 | declare module '@interactjs/core/Interactable' {
|
67 | interface Interactable {
|
68 | dropzone: DropzoneMethod
|
69 | dropCheck: (
|
70 | dragEvent: InteractEvent,
|
71 | event: PointerEventType,
|
72 | draggable: Interactable,
|
73 | draggableElement: Element,
|
74 | dropElemen: Element,
|
75 | rect: any,
|
76 | ) => boolean
|
77 | }
|
78 | }
|
79 |
|
80 | declare module '@interactjs/core/Interaction' {
|
81 | interface Interaction {
|
82 | dropState?: DropState
|
83 | }
|
84 | }
|
85 |
|
86 | declare module '@interactjs/core/InteractEvent' {
|
87 | interface InteractEvent {
|
88 | prevDropzone?: Interactable
|
89 | dropzone?: Interactable
|
90 | dragEnter?: Element
|
91 | dragLeave?: Element
|
92 | }
|
93 | }
|
94 |
|
95 | declare module '@interactjs/core/options' {
|
96 | interface ActionDefaults {
|
97 | drop: DropzoneOptions
|
98 | }
|
99 | }
|
100 |
|
101 | declare module '@interactjs/core/scope' {
|
102 | interface ActionMap {
|
103 | drop?: typeof drop
|
104 | }
|
105 |
|
106 | interface Scope {
|
107 | dynamicDrop?: boolean
|
108 | }
|
109 |
|
110 | interface SignalArgs {
|
111 | 'actions/drop:start': DropSignalArg
|
112 | 'actions/drop:move': DropSignalArg
|
113 | 'actions/drop:end': DropSignalArg
|
114 | }
|
115 | }
|
116 |
|
117 | declare module '@interactjs/core/InteractStatic' {
|
118 | interface InteractStatic {
|
119 | dynamicDrop: (this: InteractStatic, newValue?: boolean) => boolean | this
|
120 | }
|
121 | }
|
122 |
|
123 | interface DropSignalArg {
|
124 | interaction: Interaction<'drag'>
|
125 | dragEvent: DragEvent
|
126 | }
|
127 |
|
128 | export interface ActiveDrop {
|
129 | dropzone: Interactable
|
130 | element: Element
|
131 | rect: Rect
|
132 | }
|
133 |
|
134 | export interface DropState {
|
135 | cur: {
|
136 | // the dropzone a drag target might be dropped into
|
137 | dropzone: Interactable
|
138 | // the element at the time of checking
|
139 | element: Element
|
140 | }
|
141 | prev: {
|
142 | // the dropzone that was recently dragged away from
|
143 | dropzone: Interactable
|
144 | // the element at the time of checking
|
145 | element: Element
|
146 | }
|
147 | // wheather the potential drop was rejected from a listener
|
148 | rejected: boolean
|
149 | // the drop events related to the current drag event
|
150 | events: FiredDropEvents
|
151 | activeDrops: ActiveDrop[]
|
152 | }
|
153 |
|
154 | function install (scope: Scope) {
|
155 | const {
|
156 | actions,
|
157 | /** @lends module:interact */
|
158 | interactStatic: interact,
|
159 | /** @lends Interactable */
|
160 | Interactable,
|
161 | defaults,
|
162 | } = scope
|
163 |
|
164 | scope.usePlugin(drag)
|
165 |
|
166 | /**
|
167 | *
|
168 | * ```js
|
169 | * interact('.drop').dropzone({
|
170 | * accept: '.can-drop' || document.getElementById('single-drop'),
|
171 | * overlap: 'pointer' || 'center' || zeroToOne
|
172 | * }
|
173 | * ```
|
174 | *
|
175 | * Returns or sets whether draggables can be dropped onto this target to
|
176 | * trigger drop events
|
177 | *
|
178 | * Dropzones can receive the following events:
|
179 | * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
|
180 | * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
|
181 | * - `dragmove` when a draggable that has entered the dropzone is moved
|
182 | * - `drop` when a draggable is dropped into this dropzone
|
183 | *
|
184 | * Use the `accept` option to allow only elements that match the given CSS
|
185 | * selector or element. The value can be:
|
186 | *
|
187 | * - **an Element** - only that element can be dropped into this dropzone.
|
188 | * - **a string**, - the element being dragged must match it as a CSS selector.
|
189 | * - **`null`** - accept options is cleared - it accepts any element.
|
190 | *
|
191 | * Use the `overlap` option to set how drops are checked for. The allowed
|
192 | * values are:
|
193 | *
|
194 | * - `'pointer'`, the pointer must be over the dropzone (default)
|
195 | * - `'center'`, the draggable element's center must be over the dropzone
|
196 | * - a number from 0-1 which is the `(intersection area) / (draggable area)`.
|
197 | * e.g. `0.5` for drop to happen when half of the area of the draggable is
|
198 | * over the dropzone
|
199 | *
|
200 | * Use the `checker` option to specify a function to check if a dragged element
|
201 | * is over this Interactable.
|
202 | *
|
203 | * @param {boolean | object | null} [options] The new options to be set.
|
204 | * @return {object | Interactable} The current setting or this Interactable
|
205 | */
|
206 | Interactable.prototype.dropzone = function (this: Interactable, options?: DropzoneOptions | boolean) {
|
207 | return dropzoneMethod(this, options)
|
208 | } as Interactable['dropzone']
|
209 |
|
210 | /**
|
211 | * ```js
|
212 | * interact(target)
|
213 | * .dropChecker(function(dragEvent, // related dragmove or dragend event
|
214 | * event, // TouchEvent/PointerEvent/MouseEvent
|
215 | * dropped, // bool result of the default checker
|
216 | * dropzone, // dropzone Interactable
|
217 | * dropElement, // dropzone elemnt
|
218 | * draggable, // draggable Interactable
|
219 | * draggableElement) {// draggable element
|
220 | *
|
221 | * return dropped && event.target.hasAttribute('allow-drop')
|
222 | * }
|
223 | * ```
|
224 | */
|
225 | Interactable.prototype.dropCheck = function (
|
226 | this: Interactable,
|
227 | dragEvent,
|
228 | event,
|
229 | draggable,
|
230 | draggableElement,
|
231 | dropElement,
|
232 | rect,
|
233 | ) {
|
234 | return dropCheckMethod(this, dragEvent, event, draggable, draggableElement, dropElement, rect)
|
235 | }
|
236 |
|
237 | /**
|
238 | * Returns or sets whether the dimensions of dropzone elements are calculated
|
239 | * on every dragmove or only on dragstart for the default dropChecker
|
240 | *
|
241 | * @param {boolean} [newValue] True to check on each move. False to check only
|
242 | * before start
|
243 | * @return {boolean | interact} The current setting or interact
|
244 | */
|
245 | interact.dynamicDrop = function (newValue?: boolean) {
|
246 | if (is.bool(newValue)) {
|
247 | // if (dragging && scope.dynamicDrop !== newValue && !newValue) {
|
248 | // calcRects(dropzones)
|
249 | // }
|
250 |
|
251 | scope.dynamicDrop = newValue
|
252 |
|
253 | return interact
|
254 | }
|
255 | return scope.dynamicDrop
|
256 | }
|
257 |
|
258 | extend(actions.phaselessTypes, {
|
259 | dragenter: true,
|
260 | dragleave: true,
|
261 | dropactivate: true,
|
262 | dropdeactivate: true,
|
263 | dropmove: true,
|
264 | drop: true,
|
265 | })
|
266 | actions.methodDict.drop = 'dropzone'
|
267 |
|
268 | scope.dynamicDrop = false
|
269 |
|
270 | defaults.actions.drop = drop.defaults
|
271 | }
|
272 |
|
273 | function collectDrops ({ interactables }: Scope, draggableElement: Element) {
|
274 | const drops: ActiveDrop[] = []
|
275 |
|
276 | // collect all dropzones and their elements which qualify for a drop
|
277 | for (const dropzone of interactables.list) {
|
278 | if (!dropzone.options.drop.enabled) {
|
279 | continue
|
280 | }
|
281 |
|
282 | const accept = dropzone.options.drop.accept
|
283 |
|
284 | // test the draggable draggableElement against the dropzone's accept setting
|
285 | if (
|
286 | (is.element(accept) && accept !== draggableElement) ||
|
287 | (is.string(accept) && !domUtils.matchesSelector(draggableElement, accept)) ||
|
288 | (is.func(accept) && !accept({ dropzone, draggableElement }))
|
289 | ) {
|
290 | continue
|
291 | }
|
292 |
|
293 | // query for new elements if necessary
|
294 | const dropElements = (is.string(dropzone.target)
|
295 | ? dropzone._context.querySelectorAll(dropzone.target)
|
296 | : is.array(dropzone.target)
|
297 | ? dropzone.target
|
298 | : [dropzone.target]) as Element[]
|
299 |
|
300 | for (const dropzoneElement of dropElements) {
|
301 | if (dropzoneElement !== draggableElement) {
|
302 | drops.push({
|
303 | dropzone,
|
304 | element: dropzoneElement,
|
305 | rect: dropzone.getRect(dropzoneElement),
|
306 | })
|
307 | }
|
308 | }
|
309 | }
|
310 |
|
311 | return drops
|
312 | }
|
313 |
|
314 | function fireActivationEvents (activeDrops: ActiveDrop[], event: DropEvent) {
|
315 | // loop through all active dropzones and trigger event
|
316 | for (const { dropzone, element } of activeDrops.slice()) {
|
317 | event.dropzone = dropzone
|
318 |
|
319 | // set current element as event target
|
320 | event.target = element
|
321 | dropzone.fire(event)
|
322 | event.propagationStopped = event.immediatePropagationStopped = false
|
323 | }
|
324 | }
|
325 |
|
326 | // return a new array of possible drops. getActiveDrops should always be
|
327 | // called when a drag has just started or a drag event happens while
|
328 | // dynamicDrop is true
|
329 | function getActiveDrops (scope: Scope, dragElement: Element) {
|
330 | // get dropzones and their elements that could receive the draggable
|
331 | const activeDrops = collectDrops(scope, dragElement)
|
332 |
|
333 | for (const activeDrop of activeDrops) {
|
334 | activeDrop.rect = activeDrop.dropzone.getRect(activeDrop.element)
|
335 | }
|
336 |
|
337 | return activeDrops
|
338 | }
|
339 |
|
340 | function getDrop (
|
341 | { dropState, interactable: draggable, element: dragElement }: Partial<Interaction>,
|
342 | dragEvent,
|
343 | pointerEvent,
|
344 | ) {
|
345 | const validDrops = []
|
346 |
|
347 | // collect all dropzones and their elements which qualify for a drop
|
348 | for (const { dropzone, element: dropzoneElement, rect } of dropState.activeDrops) {
|
349 | validDrops.push(
|
350 | dropzone.dropCheck(dragEvent, pointerEvent, draggable, dragElement, dropzoneElement, rect)
|
351 | ? dropzoneElement
|
352 | : null,
|
353 | )
|
354 | }
|
355 |
|
356 | // get the most appropriate dropzone based on DOM depth and order
|
357 | const dropIndex = domUtils.indexOfDeepestElement(validDrops)
|
358 |
|
359 | return dropState.activeDrops[dropIndex] || null
|
360 | }
|
361 |
|
362 | function getDropEvents (interaction: Interaction, _pointerEvent, dragEvent: DragEvent) {
|
363 | const { dropState } = interaction
|
364 | const dropEvents = {
|
365 | enter: null,
|
366 | leave: null,
|
367 | activate: null,
|
368 | deactivate: null,
|
369 | move: null,
|
370 | drop: null,
|
371 | }
|
372 |
|
373 | if (dragEvent.type === 'dragstart') {
|
374 | dropEvents.activate = new DropEvent(dropState, dragEvent, 'dropactivate')
|
375 |
|
376 | dropEvents.activate.target = null
|
377 | dropEvents.activate.dropzone = null
|
378 | }
|
379 | if (dragEvent.type === 'dragend') {
|
380 | dropEvents.deactivate = new DropEvent(dropState, dragEvent, 'dropdeactivate')
|
381 |
|
382 | dropEvents.deactivate.target = null
|
383 | dropEvents.deactivate.dropzone = null
|
384 | }
|
385 |
|
386 | if (dropState.rejected) {
|
387 | return dropEvents
|
388 | }
|
389 |
|
390 | if (dropState.cur.element !== dropState.prev.element) {
|
391 | // if there was a previous dropzone, create a dragleave event
|
392 | if (dropState.prev.dropzone) {
|
393 | dropEvents.leave = new DropEvent(dropState, dragEvent, 'dragleave')
|
394 |
|
395 | dragEvent.dragLeave = dropEvents.leave.target = dropState.prev.element
|
396 | dragEvent.prevDropzone = dropEvents.leave.dropzone = dropState.prev.dropzone
|
397 | }
|
398 | // if dropzone is not null, create a dragenter event
|
399 | if (dropState.cur.dropzone) {
|
400 | dropEvents.enter = new DropEvent(dropState, dragEvent, 'dragenter')
|
401 |
|
402 | dragEvent.dragEnter = dropState.cur.element
|
403 | dragEvent.dropzone = dropState.cur.dropzone
|
404 | }
|
405 | }
|
406 |
|
407 | if (dragEvent.type === 'dragend' && dropState.cur.dropzone) {
|
408 | dropEvents.drop = new DropEvent(dropState, dragEvent, 'drop')
|
409 |
|
410 | dragEvent.dropzone = dropState.cur.dropzone
|
411 | dragEvent.relatedTarget = dropState.cur.element
|
412 | }
|
413 | if (dragEvent.type === 'dragmove' && dropState.cur.dropzone) {
|
414 | dropEvents.move = new DropEvent(dropState, dragEvent, 'dropmove')
|
415 |
|
416 | dropEvents.move.dragmove = dragEvent
|
417 | dragEvent.dropzone = dropState.cur.dropzone
|
418 | }
|
419 |
|
420 | return dropEvents
|
421 | }
|
422 |
|
423 | type FiredDropEvents = Partial<
|
424 | Record<'leave' | 'enter' | 'move' | 'drop' | 'activate' | 'deactivate', DropEvent>
|
425 | >
|
426 |
|
427 | function fireDropEvents (interaction: Interaction, events: FiredDropEvents) {
|
428 | const { dropState } = interaction
|
429 | const { activeDrops, cur, prev } = dropState
|
430 |
|
431 | if (events.leave) {
|
432 | prev.dropzone.fire(events.leave)
|
433 | }
|
434 | if (events.enter) {
|
435 | cur.dropzone.fire(events.enter)
|
436 | }
|
437 | if (events.move) {
|
438 | cur.dropzone.fire(events.move)
|
439 | }
|
440 | if (events.drop) {
|
441 | cur.dropzone.fire(events.drop)
|
442 | }
|
443 |
|
444 | if (events.deactivate) {
|
445 | fireActivationEvents(activeDrops, events.deactivate)
|
446 | }
|
447 |
|
448 | dropState.prev.dropzone = cur.dropzone
|
449 | dropState.prev.element = cur.element
|
450 | }
|
451 |
|
452 | function onEventCreated ({ interaction, iEvent, event }: DoPhaseArg<'drag', EventPhase>, scope: Scope) {
|
453 | if (iEvent.type !== 'dragmove' && iEvent.type !== 'dragend') {
|
454 | return
|
455 | }
|
456 |
|
457 | const { dropState } = interaction
|
458 |
|
459 | if (scope.dynamicDrop) {
|
460 | dropState.activeDrops = getActiveDrops(scope, interaction.element)
|
461 | }
|
462 |
|
463 | const dragEvent = iEvent
|
464 | const dropResult = getDrop(interaction, dragEvent, event)
|
465 |
|
466 | // update rejected status
|
467 | dropState.rejected =
|
468 | dropState.rejected &&
|
469 | !!dropResult &&
|
470 | dropResult.dropzone === dropState.cur.dropzone &&
|
471 | dropResult.element === dropState.cur.element
|
472 |
|
473 | dropState.cur.dropzone = dropResult && dropResult.dropzone
|
474 | dropState.cur.element = dropResult && dropResult.element
|
475 |
|
476 | dropState.events = getDropEvents(interaction, event, dragEvent)
|
477 | }
|
478 |
|
479 | function dropzoneMethod(interactable: Interactable): DropzoneOptions
|
480 | function dropzoneMethod(interactable: Interactable, options: DropzoneOptions | boolean): Interactable
|
481 | function dropzoneMethod (interactable: Interactable, options?: DropzoneOptions | boolean) {
|
482 | if (is.object(options)) {
|
483 | interactable.options.drop.enabled = options.enabled !== false
|
484 |
|
485 | if (options.listeners) {
|
486 | const normalized = normalizeListeners(options.listeners)
|
487 | // rename 'drop' to '' as it will be prefixed with 'drop'
|
488 | const corrected = Object.keys(normalized).reduce((acc, type) => {
|
489 | const correctedType = /^(enter|leave)/.test(type)
|
490 | ? `drag${type}`
|
491 | : /^(activate|deactivate|move)/.test(type)
|
492 | ? `drop${type}`
|
493 | : type
|
494 |
|
495 | acc[correctedType] = normalized[type]
|
496 |
|
497 | return acc
|
498 | }, {})
|
499 |
|
500 | interactable.off(interactable.options.drop.listeners)
|
501 | interactable.on(corrected)
|
502 | interactable.options.drop.listeners = corrected
|
503 | }
|
504 |
|
505 | if (is.func(options.ondrop)) {
|
506 | interactable.on('drop', options.ondrop)
|
507 | }
|
508 | if (is.func(options.ondropactivate)) {
|
509 | interactable.on('dropactivate', options.ondropactivate)
|
510 | }
|
511 | if (is.func(options.ondropdeactivate)) {
|
512 | interactable.on('dropdeactivate', options.ondropdeactivate)
|
513 | }
|
514 | if (is.func(options.ondragenter)) {
|
515 | interactable.on('dragenter', options.ondragenter)
|
516 | }
|
517 | if (is.func(options.ondragleave)) {
|
518 | interactable.on('dragleave', options.ondragleave)
|
519 | }
|
520 | if (is.func(options.ondropmove)) {
|
521 | interactable.on('dropmove', options.ondropmove)
|
522 | }
|
523 |
|
524 | if (/^(pointer|center)$/.test(options.overlap as string)) {
|
525 | interactable.options.drop.overlap = options.overlap
|
526 | } else if (is.number(options.overlap)) {
|
527 | interactable.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0)
|
528 | }
|
529 | if ('accept' in options) {
|
530 | interactable.options.drop.accept = options.accept
|
531 | }
|
532 | if ('checker' in options) {
|
533 | interactable.options.drop.checker = options.checker
|
534 | }
|
535 |
|
536 | return interactable
|
537 | }
|
538 |
|
539 | if (is.bool(options)) {
|
540 | interactable.options.drop.enabled = options
|
541 |
|
542 | return interactable
|
543 | }
|
544 |
|
545 | return interactable.options.drop
|
546 | }
|
547 |
|
548 | function dropCheckMethod (
|
549 | interactable: Interactable,
|
550 | dragEvent: InteractEvent,
|
551 | event: PointerEventType,
|
552 | draggable: Interactable,
|
553 | draggableElement: Element,
|
554 | dropElement: Element,
|
555 | rect: any,
|
556 | ) {
|
557 | let dropped = false
|
558 |
|
559 | // if the dropzone has no rect (eg. display: none)
|
560 | // call the custom dropChecker or just return false
|
561 | if (!(rect = rect || interactable.getRect(dropElement))) {
|
562 | return interactable.options.drop.checker
|
563 | ? interactable.options.drop.checker(
|
564 | dragEvent,
|
565 | event,
|
566 | dropped,
|
567 | interactable,
|
568 | dropElement,
|
569 | draggable,
|
570 | draggableElement,
|
571 | )
|
572 | : false
|
573 | }
|
574 |
|
575 | const dropOverlap = interactable.options.drop.overlap
|
576 |
|
577 | if (dropOverlap === 'pointer') {
|
578 | const origin = getOriginXY(draggable, draggableElement, 'drag')
|
579 | const page = pointerUtils.getPageXY(dragEvent)
|
580 |
|
581 | page.x += origin.x
|
582 | page.y += origin.y
|
583 |
|
584 | const horizontal = page.x > rect.left && page.x < rect.right
|
585 | const vertical = page.y > rect.top && page.y < rect.bottom
|
586 |
|
587 | dropped = horizontal && vertical
|
588 | }
|
589 |
|
590 | const dragRect = draggable.getRect(draggableElement)
|
591 |
|
592 | if (dragRect && dropOverlap === 'center') {
|
593 | const cx = dragRect.left + dragRect.width / 2
|
594 | const cy = dragRect.top + dragRect.height / 2
|
595 |
|
596 | dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom
|
597 | }
|
598 |
|
599 | if (dragRect && is.number(dropOverlap)) {
|
600 | const overlapArea =
|
601 | Math.max(0, Math.min(rect.right, dragRect.right) - Math.max(rect.left, dragRect.left)) *
|
602 | Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top, dragRect.top))
|
603 |
|
604 | const overlapRatio = overlapArea / (dragRect.width * dragRect.height)
|
605 |
|
606 | dropped = overlapRatio >= dropOverlap
|
607 | }
|
608 |
|
609 | if (interactable.options.drop.checker) {
|
610 | dropped = interactable.options.drop.checker(
|
611 | dragEvent,
|
612 | event,
|
613 | dropped,
|
614 | interactable,
|
615 | dropElement,
|
616 | draggable,
|
617 | draggableElement,
|
618 | )
|
619 | }
|
620 |
|
621 | return dropped
|
622 | }
|
623 |
|
624 | const drop: Plugin = {
|
625 | id: 'actions/drop',
|
626 | install,
|
627 | listeners: {
|
628 | 'interactions:before-action-start': ({ interaction }) => {
|
629 | if (interaction.prepared.name !== 'drag') {
|
630 | return
|
631 | }
|
632 |
|
633 | interaction.dropState = {
|
634 | cur: {
|
635 | dropzone: null,
|
636 | element: null,
|
637 | },
|
638 | prev: {
|
639 | dropzone: null,
|
640 | element: null,
|
641 | },
|
642 | rejected: null,
|
643 | events: null,
|
644 | activeDrops: [],
|
645 | }
|
646 | },
|
647 |
|
648 | 'interactions:after-action-start': (
|
649 | { interaction, event, iEvent: dragEvent }: DoPhaseArg<'drag', EventPhase>,
|
650 | scope,
|
651 | ) => {
|
652 | if (interaction.prepared.name !== 'drag') {
|
653 | return
|
654 | }
|
655 |
|
656 | const { dropState } = interaction
|
657 |
|
658 | // reset active dropzones
|
659 | dropState.activeDrops = null
|
660 | dropState.events = null
|
661 | dropState.activeDrops = getActiveDrops(scope, interaction.element)
|
662 | dropState.events = getDropEvents(interaction, event, dragEvent)
|
663 |
|
664 | if (dropState.events.activate) {
|
665 | fireActivationEvents(dropState.activeDrops, dropState.events.activate)
|
666 | scope.fire('actions/drop:start', { interaction, dragEvent })
|
667 | }
|
668 | },
|
669 |
|
670 | 'interactions:action-move': onEventCreated,
|
671 |
|
672 | 'interactions:after-action-move': (
|
673 | { interaction, iEvent: dragEvent }: DoPhaseArg<'drag', EventPhase>,
|
674 | scope,
|
675 | ) => {
|
676 | if (interaction.prepared.name !== 'drag') {
|
677 | return
|
678 | }
|
679 |
|
680 | fireDropEvents(interaction, interaction.dropState.events)
|
681 |
|
682 | scope.fire('actions/drop:move', { interaction, dragEvent })
|
683 | interaction.dropState.events = {}
|
684 | },
|
685 |
|
686 | 'interactions:action-end': (arg: DoPhaseArg<'drag', EventPhase>, scope) => {
|
687 | if (arg.interaction.prepared.name !== 'drag') {
|
688 | return
|
689 | }
|
690 |
|
691 | const { interaction, iEvent: dragEvent } = arg
|
692 |
|
693 | onEventCreated(arg, scope)
|
694 | fireDropEvents(interaction, interaction.dropState.events)
|
695 | scope.fire('actions/drop:end', { interaction, dragEvent })
|
696 | },
|
697 |
|
698 | 'interactions:stop': ({ interaction }) => {
|
699 | if (interaction.prepared.name !== 'drag') {
|
700 | return
|
701 | }
|
702 |
|
703 | const { dropState } = interaction
|
704 |
|
705 | if (dropState) {
|
706 | dropState.activeDrops = null
|
707 | dropState.events = null
|
708 | dropState.cur.dropzone = null
|
709 | dropState.cur.element = null
|
710 | dropState.prev.dropzone = null
|
711 | dropState.prev.element = null
|
712 | dropState.rejected = false
|
713 | }
|
714 | },
|
715 | },
|
716 | getActiveDrops,
|
717 | getDrop,
|
718 | getDropEvents,
|
719 | fireDropEvents,
|
720 | defaults: {
|
721 | enabled: false,
|
722 | accept: null,
|
723 | overlap: 'pointer',
|
724 | } as DropzoneOptions,
|
725 | }
|
726 |
|
727 | export default drop
|
728 | </code></pre>
|
729 | </article>
|
730 | </section>
|
731 |
|
732 |
|
733 |
|
734 |
|
735 | </div>
|
736 |
|
737 | <script>prettyPrint();</script>
|
738 | <script src="scripts/linenumber.js"></script>
|
739 | </body>
|
740 | </html>
|