|
import Vue from 'vue'; |
|
import { VNodeDirective } from 'vue/types/vnode'; |
|
|
|
declare global { |
|
interface HTMLElement { |
|
_draggingListeners?: Array<() => void>; |
|
} |
|
} |
|
|
|
interface DraggingHandlers { |
|
enter?: EventListener; |
|
leave?: EventListener; |
|
over?: EventListener; |
|
drop?: EventListener; |
|
} |
|
|
|
interface DraggingDirective extends VNodeDirective { |
|
value?: EventListener | DraggingHandlers; |
|
} |
|
|
|
let count = 0; |
|
let dragging = false; |
|
let cancelImmediate = noOp; |
|
|
|
Vue.directive( 'dragging', { |
|
inserted( element: HTMLElement, binding: DraggingDirective ): void { |
|
const dragEnter: EventListener = ( event ) => { |
|
event.preventDefault(); |
|
|
|
const handler: EventListener = ( |
|
typeof binding.value === 'function' |
|
? binding.value |
|
: binding.value!.enter |
|
) as EventListener; |
|
|
|
if ( count === 0 ) { |
|
dragging = true; |
|
|
|
handler( event ); |
|
} |
|
|
|
++count; |
|
}; |
|
|
|
const dragLeave: EventListener = ( event ) => { |
|
event.preventDefault(); |
|
|
|
const handler: EventListener = ( |
|
typeof binding.value === 'function' |
|
? binding.value |
|
: binding.value!.leave |
|
) as EventListener; |
|
|
|
cancelImmediate = setImmediate( () => { |
|
--count; |
|
|
|
if ( count === 0 ) { |
|
dragging = false; |
|
|
|
handler( event ); |
|
} |
|
} ); |
|
}; |
|
|
|
const dragOver: EventListener = ( event: Event ) => { |
|
event.preventDefault(); |
|
( event as DragEvent ).dataTransfer!.dropEffect = 'copy'; |
|
|
|
const handler: EventListener | undefined = |
|
typeof binding.value === 'function' |
|
? binding.value |
|
: binding.value!.leave; |
|
|
|
if ( handler ) { |
|
handler( event ); |
|
} |
|
}; |
|
|
|
const drop: EventListener = ( event ) => { |
|
event.preventDefault(); |
|
|
|
cancelImmediate(); |
|
|
|
if ( count > 0 ) { |
|
count = 0; |
|
dragging = false; |
|
} |
|
|
|
const handler: EventListener | undefined = |
|
typeof binding.value === 'function' |
|
? binding.value |
|
: binding.value!.leave; |
|
|
|
if ( handler ) { |
|
handler( event ); |
|
} |
|
}; |
|
|
|
element._draggingListeners = [ |
|
createEventListener( 'dragenter', dragEnter ), |
|
createEventListener( 'dragleave', dragLeave ), |
|
createEventListener( 'dragover', dragOver ), |
|
createEventListener( 'drop', drop ), |
|
]; |
|
}, |
|
|
|
unbind( element: HTMLElement ) { |
|
if ( !element._draggingListeners ) { |
|
return; |
|
} |
|
|
|
// Remove all listeners |
|
element._draggingListeners.forEach( fn => fn() ); |
|
}, |
|
} ); |
|
|
|
function createEventListener<K extends keyof DocumentEventMap>( |
|
type: K, |
|
listener: ( this: Document, event: DocumentEventMap[K] ) => any, options?: boolean | AddEventListenerOptions, |
|
) { |
|
document.addEventListener( type, listener, options ); |
|
|
|
return () => document.removeEventListener( |
|
type, |
|
listener, |
|
options, |
|
); |
|
} |
|
|
|
function noOp(): void { |
|
} |
|
|
|
function setImmediate( callback: ( ...args: any[] ) => void, ...args: any[] ) { |
|
let cancelled = false; |
|
|
|
Promise.resolve().then( () => cancelled || callback( ...args ) ); |
|
|
|
return () => { |
|
cancelled = true; |
|
}; |
|
} |