UNPKG

ng-devui

Version:

DevUI components based on Angular

1 lines 257 kB
{"version":3,"file":"ng-devui-dragdrop.mjs","sources":["../../devui/dragdrop/shared/utils.ts","../../devui/dragdrop/touch-support/dragdrop-touch.ts","../../devui/dragdrop/services/drag-drop.service.ts","../../devui/dragdrop/shared/preserve-next-event-emitter.ts","../../devui/dragdrop/directives/drag-preview.component.ts","../../devui/dragdrop/directives/drag-preview.component.html","../../devui/dragdrop/directives/drag-preview.directive.ts","../../devui/dragdrop/directives/draggable.directive.ts","../../devui/dragdrop/directives/batch-draggable.directive.ts","../../devui/dragdrop/directives/drag-preivew-clone-from-domRef.component.ts","../../devui/dragdrop/services/drag-drop-desc-reg.service.ts","../../devui/dragdrop/services/drag-drop-descendant-sync.service.ts","../../devui/dragdrop/services/drag-drop-sync.service.ts","../../devui/dragdrop/directives/drag-sync.directive.ts","../../devui/dragdrop/directives/dragdrop-sync-box.directive.ts","../../devui/dragdrop/directives/drop-scroll-enhance.type.ts","../../devui/dragdrop/directives/drop-scroll-enhance.directive.ts","../../devui/dragdrop/directives/drop-scroll-enhance-side.directive.ts","../../devui/dragdrop/shared/drop-event.model.ts","../../devui/dragdrop/directives/droppable.directive.ts","../../devui/dragdrop/directives/drop-sort-sync.directive.ts","../../devui/dragdrop/directives/sortable.directive.ts","../../devui/dragdrop/drag-drop.module.ts","../../devui/dragdrop/ng-devui-dragdrop.ts"],"sourcesContent":["import { ElementRef } from '@angular/core';\r\n\r\nexport class Utils {\r\n /**\r\n * Polyfill for element.matches.\r\n * See: https://developer.mozilla.org/en/docs/Web/API/Element/matches#Polyfill\r\n * element\r\n */\r\n public static matches(element: any, selectorName: string): boolean {\r\n const proto: any = Element.prototype;\r\n const func =\r\n proto.matches ||\r\n proto.matchesSelector ||\r\n proto.mozMatchesSelector ||\r\n proto.msMatchesSelector ||\r\n proto.oMatchesSelector ||\r\n proto.webkitMatchesSelector ||\r\n function (s) {\r\n const matches = (this.document || this.ownerDocument).querySelectorAll(s);\r\n let i = matches.length;\r\n while (--i >= 0 && matches.item(i) !== this) {\r\n // do nothing\r\n }\r\n return i > -1;\r\n };\r\n\r\n return func.call(element, selectorName);\r\n }\r\n\r\n /**\r\n * Applies the specified css class on nativeElement\r\n * elementRef\r\n * className\r\n */\r\n public static addClass(elementRef: ElementRef | any, className: string) {\r\n if (className === undefined) {\r\n return;\r\n }\r\n const e = this.getElementWithValidClassList(elementRef);\r\n\r\n if (e) {\r\n e.classList.add(className);\r\n }\r\n }\r\n\r\n /**\r\n * Removes the specified class from nativeElement\r\n * elementRef\r\n * className\r\n */\r\n public static removeClass(elementRef: ElementRef | any, className: string) {\r\n if (className === undefined) {\r\n return;\r\n }\r\n const e = this.getElementWithValidClassList(elementRef);\r\n\r\n if (e) {\r\n e.classList.remove(className);\r\n }\r\n }\r\n\r\n /**\r\n * Gets element with valid classList\r\n *\r\n * elementRef\r\n * @returns ElementRef | null\r\n */\r\n private static getElementWithValidClassList(elementRef: ElementRef) {\r\n const e = elementRef instanceof ElementRef ? elementRef.nativeElement : elementRef;\r\n\r\n if (e.classList !== undefined && e.classList !== null) {\r\n return e;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public static slice(args, slice?, sliceEnd?) {\r\n const ret = [];\r\n let len = args.length;\r\n\r\n if (len === 0) {\r\n return ret;\r\n }\r\n\r\n const start = slice < 0 ? Math.max(0, slice + len) : slice || 0;\r\n\r\n if (sliceEnd !== undefined) {\r\n len = sliceEnd < 0 ? sliceEnd + len : sliceEnd;\r\n }\r\n\r\n while (len-- > start) {\r\n ret[len - start] = args[len];\r\n }\r\n return ret;\r\n }\r\n\r\n // 动态添加styles\r\n public static addElStyles(el: any, styles: any) {\r\n if (styles instanceof Object) {\r\n for (const s in styles) {\r\n if (Object.prototype.hasOwnProperty.call(styles, s)) {\r\n if (Array.isArray(styles[s])) {\r\n // 用于支持兼容渐退\r\n styles[s].forEach((val) => {\r\n el.style[s] = val;\r\n });\r\n } else {\r\n el.style[s] = styles[s];\r\n }\r\n }\r\n }\r\n }\r\n }\r\n public static dispatchEventToUnderElement(event: DragEvent, target?: HTMLElement, eventType?: string) {\r\n const up = target || <HTMLElement>event.target;\r\n up.style.display = 'none';\r\n const { x, y } = { x: event.clientX, y: event.clientY };\r\n const under = document.elementFromPoint(x, y);\r\n up.style.display = '';\r\n if (!under) {\r\n return event;\r\n }\r\n const ev = document.createEvent('DragEvent');\r\n ev.initMouseEvent(\r\n eventType || event.type,\r\n true,\r\n true,\r\n window,\r\n 0,\r\n event.screenX,\r\n event.screenY,\r\n event.clientX,\r\n event.clientY,\r\n event.ctrlKey,\r\n event.altKey,\r\n event.shiftKey,\r\n event.metaKey,\r\n event.button,\r\n event.relatedTarget\r\n );\r\n if (ev.dataTransfer !== null) {\r\n ev.dataTransfer.setData('text', '');\r\n ev.dataTransfer.effectAllowed = event.dataTransfer.effectAllowed;\r\n }\r\n setTimeout(() => {\r\n under.dispatchEvent(ev);\r\n }, 0);\r\n return event;\r\n }\r\n}\r\n","/**\r\n * 2020.03.23-Modified from https://github.com/Bernardo-Castilho/dragdroptouch, license: MIT,reason:Converting .js file to .ts file\r\n */\r\nexport class DragDropTouch {\r\n static readonly THRESHOLD = 5; // pixels to move before drag starts\r\n static readonly OPACITY = 0.5; // drag image opacity\r\n static readonly DBLCLICK = 500; // max ms between clicks in a double click\r\n static readonly DRAG_OVER_TIME = 300; // interval ms when drag over\r\n static readonly CTX_MENU = 900; // ms to hold before raising 'contextmenu' event\r\n static readonly IS_PRESS_HOLD_MODE = true; // decides of press & hold mode presence\r\n static readonly PRESS_HOLD_AWAIT = 400; // ms to wait before press & hold is detected\r\n static readonly PRESS_HOLD_MARGIN = 25; // pixels that finger might shiver while pressing\r\n static readonly PRESS_HOLD_THRESHOLD = 0; // pixels to move before drag starts\r\n static readonly DRAG_HANDLE_ATTR = 'data-drag-handle-selector';\r\n static readonly rmvAttrs = 'id,class,style,draggable'.split(',');\r\n static readonly kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(',');\r\n static readonly ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(',');\r\n\r\n private static instance: DragDropTouch = null;\r\n\r\n dataTransfer: DataTransfer;\r\n lastClick = 0;\r\n lastTouch: TouchEvent;\r\n // touched element\r\n lastTarget: HTMLElement;\r\n // touched draggble element\r\n dragSource: HTMLElement;\r\n ptDown: { x: number; y: number };\r\n isDragEnabled: boolean;\r\n isDropZone: boolean;\r\n pressHoldInterval;\r\n img;\r\n imgCustom;\r\n imgOffset;\r\n // for continual drag over event even touch point stop at a certain point for a while.\r\n dragoverTimer;\r\n // for bind touch move and touch end event to touch target incase virtual scroll cause\r\n // document no longer get capture/ bubble of touchmove event from dom removed from document tree\r\n touchTarget: EventTarget;\r\n touchmoveListener: EventListener;\r\n touchendListener: EventListener;\r\n listenerOpt: boolean | EventListenerOptions;\r\n\r\n constructor() {\r\n // enforce singleton pattern\r\n if (DragDropTouch.instance) {\r\n throw new Error('DragDropTouch instance already created.');\r\n }\r\n // detect passive event support\r\n // https://github.com/Modernizr/Modernizr/issues/1894\r\n let supportsPassive = false;\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('test', () => {}, {\r\n get passive() {\r\n supportsPassive = true;\r\n return true;\r\n },\r\n });\r\n // listen to touch events\r\n if (DragDropTouch.isTouchDevice()) {\r\n // 能响应触摸事件\r\n const d = document;\r\n const ts = this.touchstart;\r\n const tmod = this.touchmoveOnDocument;\r\n const teod = this.touchendOnDocument;\r\n const opt = supportsPassive ? { passive: false, capture: false } : false;\r\n const optPassive = supportsPassive ? { passive: true } : false;\r\n d.addEventListener('touchstart', ts, opt);\r\n d.addEventListener('touchmove', tmod, optPassive);\r\n d.addEventListener('touchend', teod);\r\n d.addEventListener('touchcancel', teod);\r\n this.touchmoveListener = this.touchmove;\r\n this.touchendListener = this.touchend;\r\n this.listenerOpt = opt;\r\n }\r\n }\r\n }\r\n /**\r\n * Gets a reference to the @see:DragDropTouch singleton.\r\n */\r\n static getInstance() {\r\n if (!DragDropTouch.instance) {\r\n DragDropTouch.instance = new DragDropTouch();\r\n }\r\n return DragDropTouch.instance;\r\n }\r\n static isTouchDevice() {\r\n if (typeof window === 'undefined' || typeof document === 'undefined') {\r\n return false;\r\n }\r\n const d: Document = document;\r\n const w: Window = window;\r\n let bool;\r\n if (\r\n 'ontouchstart' in d || // normal mobile device\r\n 'ontouchstart' in w ||\r\n navigator.maxTouchPoints > 0 ||\r\n (navigator as any).msMaxTouchPoints > 0 ||\r\n ((window as any).DocumentTouch && document instanceof (window as any).DocumentTouch)\r\n ) {\r\n bool = true;\r\n } else {\r\n const fakeBody = document.createElement('fakebody');\r\n fakeBody.innerHTML += `\r\n <style>\r\n @media (touch-enabled),(-webkit-touch-enabled),(-moz-touch-enabled),(-o-touch-enabled){\r\n #touch_test {\r\n top: 42px;\r\n position: absolute;\r\n }\r\n }\r\n </style>`;\r\n document.documentElement.appendChild(fakeBody);\r\n const touchTestNode = document.createElement('div');\r\n touchTestNode.id = 'touch_test';\r\n fakeBody.appendChild(touchTestNode);\r\n bool = touchTestNode.offsetTop === 42;\r\n fakeBody.parentElement.removeChild(fakeBody);\r\n }\r\n return bool;\r\n }\r\n // ** event listener binding\r\n bindTouchmoveTouchend(e: TouchEvent) {\r\n this.touchTarget = e.target;\r\n e.target.addEventListener('touchmove', this.touchmoveListener, this.listenerOpt);\r\n e.target.addEventListener('touchend', this.touchendListener);\r\n e.target.addEventListener('touchcancel', this.touchendListener);\r\n }\r\n removeTouchmoveTouchend() {\r\n if (this.touchTarget) {\r\n this.touchTarget.removeEventListener('touchmove', this.touchmoveListener);\r\n this.touchTarget.removeEventListener('touchend', this.touchendListener);\r\n this.touchTarget.removeEventListener('touchcancel', this.touchendListener);\r\n this.touchTarget = undefined;\r\n }\r\n }\r\n // ** event handlers\r\n touchstart = (e: TouchEvent) => {\r\n if (this.shouldHandle(e)) {\r\n // raise double-click and prevent zooming\r\n if (Date.now() - this.lastClick < DragDropTouch.DBLCLICK) {\r\n if (this.dispatchEvent(e, 'dblclick', e.target)) {\r\n e.preventDefault();\r\n this.reset();\r\n return;\r\n }\r\n }\r\n // clear all variables\r\n this.reset();\r\n // get nearest draggable element\r\n const src = this.closestDraggable(e.target);\r\n if (src) {\r\n this.dragSource = src;\r\n this.ptDown = this.getPoint(e);\r\n this.lastTouch = e;\r\n if (DragDropTouch.IS_PRESS_HOLD_MODE) {\r\n this.pressHoldInterval = setTimeout(() => {\r\n this.bindTouchmoveTouchend(e);\r\n this.isDragEnabled = true;\r\n this.touchmove(e);\r\n }, DragDropTouch.PRESS_HOLD_AWAIT);\r\n } else {\r\n e.preventDefault();\r\n this.bindTouchmoveTouchend(e);\r\n }\r\n }\r\n }\r\n };\r\n touchmoveOnDocument = (e) => {\r\n if (this.shouldCancelPressHoldMove(e)) {\r\n this.reset();\r\n return;\r\n }\r\n };\r\n touchmove = (e: TouchEvent) => {\r\n if (this.shouldCancelPressHoldMove(e)) {\r\n this.reset();\r\n return;\r\n }\r\n if (this.shouldHandleMove(e) || this.shouldHandlePressHoldMove(e)) {\r\n const target = this.getTarget(e);\r\n // start dragging\r\n if (this.dragSource && !this.img && this.shouldStartDragging(e)) {\r\n this.dispatchEvent(e, 'dragstart', this.dragSource);\r\n this.createImage(e);\r\n }\r\n // continue dragging\r\n if (this.img) {\r\n this.clearDragoverInterval();\r\n this.lastTouch = e;\r\n e.preventDefault(); // prevent scrolling\r\n if (target !== this.lastTarget) {\r\n // according to drag drop implementation of the browser, dragenterB is supposed to fired before dragleaveA\r\n this.dispatchEvent(e, 'dragenter', target);\r\n this.dispatchEvent(this.lastTouch, 'dragleave', this.lastTarget);\r\n this.lastTarget = target;\r\n }\r\n this.moveImage(e);\r\n this.isDropZone = this.dispatchEvent(e, 'dragover', target);\r\n // should continue dispatch dragover event when touch position stay still\r\n this.setDragoverInterval(e);\r\n }\r\n }\r\n };\r\n touchendOnDocument = (e) => {\r\n if (this.shouldHandle(e)) {\r\n if (!this.img) {\r\n this.dragSource = null;\r\n this.lastClick = Date.now();\r\n }\r\n // finish dragging\r\n this.destroyImage();\r\n if (this.dragSource) {\r\n this.reset();\r\n }\r\n }\r\n };\r\n touchend = (e) => {\r\n if (this.shouldHandle(e)) {\r\n // user clicked the element but didn't drag, so clear the source and simulate a click\r\n if (!this.img) {\r\n this.dragSource = null;\r\n // browser will dispatch click event after trigger touchend, since touchstart didn't preventDefault\r\n this.lastClick = Date.now();\r\n }\r\n // finish dragging\r\n this.destroyImage();\r\n if (this.dragSource) {\r\n if (e.type.indexOf('cancel') < 0 && this.isDropZone) {\r\n this.dispatchEvent(this.lastTouch, 'drop', this.lastTarget);\r\n }\r\n this.dispatchEvent(this.lastTouch, 'dragend', this.dragSource);\r\n this.reset();\r\n }\r\n }\r\n };\r\n // ** utilities\r\n // ignore events that have been handled or that involve more than one touch\r\n shouldHandle(e) {\r\n return e && !e.defaultPrevented && e.touches && e.touches.length < 2;\r\n }\r\n // use regular condition outside of press & hold mode\r\n shouldHandleMove(e) {\r\n return !DragDropTouch.IS_PRESS_HOLD_MODE && this.shouldHandle(e);\r\n }\r\n // allow to handle moves that involve many touches for press & hold\r\n shouldHandlePressHoldMove(e) {\r\n return DragDropTouch.IS_PRESS_HOLD_MODE && this.isDragEnabled && e && e.touches && e.touches.length;\r\n }\r\n // reset data if user drags without pressing & holding\r\n shouldCancelPressHoldMove(e) {\r\n return DragDropTouch.IS_PRESS_HOLD_MODE && !this.isDragEnabled && this.getDelta(e) > DragDropTouch.PRESS_HOLD_MARGIN;\r\n }\r\n // start dragging when mouseover element matches drag handler selector and specified delta is detected\r\n shouldStartDragging(e) {\r\n const dragHandleSelector = this.getDragHandle();\r\n // start dragging when mouseover element matches drag handler selector\r\n if (dragHandleSelector && !this.matchSelector(e.target, dragHandleSelector)) {\r\n return false;\r\n }\r\n // start dragging when specified delta is detected\r\n const delta = this.getDelta(e);\r\n return delta > DragDropTouch.THRESHOLD || (DragDropTouch.IS_PRESS_HOLD_MODE && delta >= DragDropTouch.PRESS_HOLD_THRESHOLD);\r\n }\r\n // find drag handler selector for dragstart only with partial element\r\n getDragHandle() {\r\n if (this.dragSource) {\r\n return this.dragSource.getAttribute(DragDropTouch.DRAG_HANDLE_ATTR) || '';\r\n }\r\n return '';\r\n }\r\n // test if element matches selector\r\n matchSelector(element, selector) {\r\n if (selector) {\r\n const proto: any = Element.prototype;\r\n const func =\r\n proto.matches ||\r\n proto.matchesSelector ||\r\n proto.mozMatchesSelector ||\r\n proto.msMatchesSelector ||\r\n proto.oMatchesSelector ||\r\n proto.webkitMatchesSelector ||\r\n function (s) {\r\n const matches = (this.document || this.ownerDocument).querySelectorAll(s);\r\n let i = matches.length;\r\n while (--i >= 0 && matches.item(i) !== this) {\r\n // do nothing\r\n }\r\n return i > -1;\r\n };\r\n return func.call(element, selector);\r\n }\r\n return true;\r\n }\r\n // clear all members\r\n reset() {\r\n this.removeTouchmoveTouchend();\r\n this.destroyImage();\r\n this.dragSource = null;\r\n this.lastTouch = null;\r\n this.lastTarget = null;\r\n this.ptDown = null;\r\n this.isDragEnabled = false;\r\n this.isDropZone = false;\r\n this.dataTransfer = new DragDropTouch.DataTransfer();\r\n clearInterval(this.pressHoldInterval);\r\n this.clearDragoverInterval();\r\n }\r\n // get point for a touch event\r\n getPoint(e, page?) {\r\n if (e && e.touches) {\r\n e = e.touches[0];\r\n }\r\n return { x: page ? e.pageX : e.clientX, y: page ? e.pageY : e.clientY };\r\n }\r\n // get distance between the current touch event and the first one\r\n getDelta(e) {\r\n if (DragDropTouch.IS_PRESS_HOLD_MODE && !this.ptDown) {\r\n return 0;\r\n }\r\n const p = this.getPoint(e);\r\n return Math.abs(p.x - this.ptDown.x) + Math.abs(p.y - this.ptDown.y);\r\n }\r\n // get the element at a given touch event\r\n getTarget(e: TouchEvent) {\r\n const pt = this.getPoint(e);\r\n let el = document.elementFromPoint(pt.x, pt.y);\r\n while (el && getComputedStyle(el).pointerEvents === 'none') {\r\n el = el.parentElement;\r\n }\r\n return <HTMLElement>el;\r\n }\r\n // create drag image from source element\r\n createImage(e) {\r\n // just in case...\r\n if (this.img) {\r\n this.destroyImage();\r\n }\r\n // create drag image from custom element or drag source\r\n const src = this.imgCustom || this.dragSource;\r\n this.img = src.cloneNode(true);\r\n this.copyStyle(src, this.img);\r\n this.img.style.top = this.img.style.left = '-9999px';\r\n // if creating from drag source, apply offset and opacity\r\n if (!this.imgCustom) {\r\n const rc = src.getBoundingClientRect();\r\n const pt = this.getPoint(e);\r\n this.imgOffset = { x: pt.x - rc.left, y: pt.y - rc.top };\r\n this.img.style.opacity = DragDropTouch.OPACITY.toString();\r\n }\r\n // add image to document\r\n this.moveImage(e);\r\n document.body.appendChild(this.img);\r\n }\r\n // dispose of drag image element\r\n destroyImage() {\r\n if (this.img && this.img.parentElement) {\r\n this.img.parentElement.removeChild(this.img);\r\n }\r\n this.img = null;\r\n this.imgCustom = null;\r\n }\r\n // move the drag image element\r\n moveImage(e) {\r\n requestAnimationFrame(() => {\r\n if (this.img) {\r\n const pt = this.getPoint(e, true);\r\n const s = this.img.style;\r\n s.position = 'absolute';\r\n s.pointerEvents = 'none';\r\n s.zIndex = '999999';\r\n s.left = Math.round(pt.x - this.imgOffset.x) + 'px';\r\n s.top = Math.round(pt.y - this.imgOffset.y) + 'px';\r\n }\r\n });\r\n }\r\n // copy properties from an object to another\r\n copyProps(dst, src, props) {\r\n for (let i = 0; i < props.length; i++) {\r\n const p = props[i];\r\n dst[p] = src[p];\r\n }\r\n }\r\n // copy styles/attributes from drag source to drag image element\r\n copyStyle(src, dst) {\r\n // remove potentially troublesome attributes\r\n DragDropTouch.rmvAttrs.forEach((att) => dst.removeAttribute(att));\r\n // copy canvas content\r\n if (src instanceof HTMLCanvasElement) {\r\n const canSrc = src;\r\n const canDst = dst;\r\n canDst.width = canSrc.width;\r\n canDst.height = canSrc.height;\r\n canDst.getContext('2d').drawImage(canSrc, 0, 0);\r\n }\r\n // copy canvas content for nested canvas element\r\n const srcCanvases = src.querySelectorAll('canvas');\r\n if (srcCanvases.length > 0) {\r\n const dstCanvases = dst.querySelectorAll('canvas');\r\n for (let i = 0; i < dstCanvases.length; i++) {\r\n const cSrc = srcCanvases[i];\r\n const cDst = dstCanvases[i];\r\n cDst.getContext('2d').drawImage(cSrc, 0, 0);\r\n }\r\n }\r\n // copy style (without transitions)\r\n const cs = getComputedStyle(src);\r\n for (let i = 0; i < cs.length; i++) {\r\n const key = cs[i];\r\n if (key.indexOf('transition') < 0) {\r\n dst.style[key] = cs[key];\r\n }\r\n }\r\n dst.style.pointerEvents = 'none';\r\n // and repeat for all children\r\n for (let i = 0; i < src.children.length; i++) {\r\n this.copyStyle(src.children[i], dst.children[i]);\r\n }\r\n }\r\n // synthesize and dispatch an event\r\n // returns true if the event has been handled (e.preventDefault == true)\r\n dispatchEvent(e, type, target) {\r\n if (e && target) {\r\n const evt = document.createEvent('Event');\r\n const t = e.touches ? e.touches[0] : e;\r\n evt.initEvent(type, true, true);\r\n const obj = {\r\n button: 0,\r\n which: 0,\r\n buttons: 1,\r\n dataTransfer: this.dataTransfer,\r\n };\r\n this.copyProps(evt, e, DragDropTouch.kbdProps);\r\n this.copyProps(evt, t, DragDropTouch.ptProps);\r\n this.copyProps(evt, { fromTouch: true }, ['fromTouch']); // mark as from touch event\r\n this.copyProps(evt, obj, Object.keys(obj));\r\n\r\n target.dispatchEvent(evt);\r\n return evt.defaultPrevented;\r\n }\r\n return false;\r\n }\r\n // gets an element's closest draggable ancestor\r\n closestDraggable(e) {\r\n for (; e; e = e.parentElement) {\r\n if (e.hasAttribute('draggable') && e.draggable) {\r\n return e;\r\n }\r\n }\r\n return null;\r\n }\r\n // repeat dispatch dragover event when touch point stay still\r\n setDragoverInterval(e) {\r\n this.dragoverTimer = setInterval(() => {\r\n const target = this.getTarget(e);\r\n if (target !== this.lastTarget) {\r\n this.dispatchEvent(e, 'dragenter', target);\r\n this.dispatchEvent(e, 'dragleave', this.lastTarget);\r\n this.lastTarget = target;\r\n }\r\n this.isDropZone = this.dispatchEvent(e, 'dragover', target);\r\n }, DragDropTouch.DRAG_OVER_TIME);\r\n }\r\n clearDragoverInterval() {\r\n if (this.dragoverTimer) {\r\n clearInterval(this.dragoverTimer);\r\n this.dragoverTimer = undefined;\r\n }\r\n }\r\n}\r\n/* eslint-disable-next-line @typescript-eslint/no-namespace */\r\nexport namespace DragDropTouch {\r\n /**\r\n * Object used to hold the data that is being dragged during drag and drop operations.\r\n *\r\n * It may hold one or more data items of different types. For more information about\r\n * drag and drop operations and data transfer objects, see\r\n * <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer\">HTML Drag and Drop API</a>.\r\n *\r\n * This object is created automatically by the @see:DragDropTouch singleton and is\r\n * accessible through the @see:dataTransfer property of all drag events.\r\n */\r\n export class DataTransfer implements DataTransfer {\r\n files;\r\n items;\r\n private _data;\r\n /**\r\n * Gets or sets the type of drag-and-drop operation currently selected.\r\n * The value must be 'none', 'copy', 'link', or 'move'.\r\n */\r\n private _dropEffect;\r\n get dropEffect() {\r\n return this._dropEffect;\r\n }\r\n set dropEffect(value) {\r\n this._dropEffect = value;\r\n }\r\n /**\r\n * Gets or sets the types of operations that are possible.\r\n * Must be one of 'none', 'copy', 'copyLink', 'copyMove', 'link',\r\n * 'linkMove', 'move', 'all' or 'uninitialized'.\r\n */\r\n private _effectAllowed;\r\n get effectAllowed() {\r\n return this._effectAllowed;\r\n }\r\n set effectAllowed(value) {\r\n this._effectAllowed = value;\r\n }\r\n /**\r\n * Gets an array of strings giving the formats that were set in the @see:dragstart event.\r\n */\r\n private _types;\r\n get types() {\r\n return Object.keys(this._data);\r\n }\r\n\r\n constructor() {\r\n this._dropEffect = 'move';\r\n this._effectAllowed = 'all';\r\n this._data = {};\r\n }\r\n /**\r\n * Removes the data associated with a given type.\r\n *\r\n * The type argument is optional. If the type is empty or not specified, the data\r\n * associated with all types is removed. If data for the specified type does not exist,\r\n * or the data transfer contains no data, this method will have no effect.\r\n *\r\n * @param type Type of data to remove.\r\n */\r\n clearData(type) {\r\n if (type !== null) {\r\n delete this._data[type];\r\n } else {\r\n this._data = null;\r\n }\r\n }\r\n /**\r\n * Retrieves the data for a given type, or an empty string if data for that type does\r\n * not exist or the data transfer contains no data.\r\n *\r\n * @param type Type of data to retrieve.\r\n */\r\n getData(type) {\r\n return this._data[type] || '';\r\n }\r\n\r\n /**\r\n * Set the data for a given type.\r\n *\r\n * For a list of recommended drag types, please see\r\n * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types.\r\n *\r\n * @param type Type of data to add.\r\n * @param value Data to add.\r\n */\r\n setData(type, value) {\r\n this._data[type] = value;\r\n }\r\n /**\r\n * Set the image to be used for dragging if a custom one is desired.\r\n *\r\n * @param img An image element to use as the drag feedback image.\r\n * @param offsetX The horizontal offset within the image.\r\n * @param offsetY The vertical offset within the image.\r\n */\r\n setDragImage(img, offsetX, offsetY) {\r\n const ddt = DragDropTouch.getInstance();\r\n ddt.imgCustom = img;\r\n ddt.imgOffset = { x: offsetX, y: offsetY };\r\n }\r\n }\r\n}\r\n","import { DOCUMENT } from '@angular/common';\r\nimport { Inject, Injectable, NgZone } from '@angular/core';\r\nimport { Subject, Subscription } from 'rxjs';\r\nimport { DragPreviewDirective } from '../directives/drag-preview.directive';\r\nimport { Utils } from '../shared/utils';\r\nimport { DragDropTouch } from '../touch-support/dragdrop-touch';\r\nimport { DraggableDirective } from './../directives/draggable.directive';\r\n\r\n@Injectable()\r\nexport class DragDropService {\r\n dragData: any;\r\n draggedEl: any;\r\n draggedElIdentity: any;\r\n batchDragData: Array<{\r\n identity?: any;\r\n draggable: DraggableDirective;\r\n dragData: any;\r\n }>;\r\n batchDragGroup: string;\r\n batchDragStyle: Array<string>;\r\n batchDragging: boolean;\r\n scope: string | Array<string>;\r\n dropTargets = [];\r\n dropEvent: Subject<any> = new Subject();\r\n dragEndEvent = new Subject<any>();\r\n dragStartEvent = new Subject<any>();\r\n dropOnItem: boolean;\r\n dragFollow: boolean;\r\n dragFollowOptions: {\r\n appendToBody?: boolean;\r\n };\r\n dropOnOrigin: boolean;\r\n draggedElFollowingMouse: boolean;\r\n dragOffset: {\r\n top: number;\r\n left: number;\r\n offsetLeft: number;\r\n offsetTop: number;\r\n width?: number;\r\n height?: number;\r\n };\r\n subscription: Subscription = new Subscription();\r\n dragEmptyImage = new Image();\r\n dragCloneNode: any;\r\n dragOriginPlaceholder: any;\r\n dragItemContainer: any;\r\n dragItemParentName = '';\r\n dragItemChildrenName = '';\r\n intersectionObserver: any = null;\r\n sub;\r\n dragOriginPlaceholderNextSibling: any;\r\n touchInstance;\r\n\r\n /* 协同拖拽需要 */\r\n dragElShowHideEvent = new Subject<boolean>();\r\n dragSyncGroupDirectives;\r\n /* 预览功能 */\r\n dragPreviewDirective: DragPreviewDirective;\r\n document: Document;\r\n\r\n constructor(private ngZone: NgZone, @Inject(DOCUMENT) private doc: any) {\r\n this.touchInstance = DragDropTouch.getInstance();\r\n // service not support OnInit, only support OnDestroy, so write in constructor\r\n // safari的img必须要有src\r\n this.dragEmptyImage.src =\r\n 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==';\r\n this.document = this.doc;\r\n }\r\n newSubscription() {\r\n this.subscription.unsubscribe();\r\n // eslint-disable-next-line no-return-assign\r\n return (this.subscription = new Subscription());\r\n }\r\n\r\n enableDraggedCloneNodeFollowMouse() {\r\n if (!this.dragCloneNode) {\r\n this.dragItemContainer = this.draggedEl.parentElement;\r\n if (this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate) {\r\n this.dragPreviewDirective.createPreview();\r\n this.dragCloneNode = this.dragPreviewDirective.getPreviewElement();\r\n this.dragItemContainer = this.document.body;\r\n } else {\r\n this.dragCloneNode = this.draggedEl.cloneNode(true);\r\n }\r\n\r\n this.dragCloneNode.style.margin = '0';\r\n if (this.dragFollowOptions && this.dragFollowOptions.appendToBody) {\r\n this.dragItemContainer = this.document.body;\r\n this.copyStyle(this.draggedEl, this.dragCloneNode);\r\n }\r\n\r\n if (this.dragItemChildrenName !== '') {\r\n const parentElement = this.dragItemParentName === '' ? this.dragCloneNode : this.document.querySelector(this.dragItemParentName);\r\n const dragItemChildren = parentElement.querySelectorAll(this.dragItemChildrenName);\r\n this.interceptChildNode(parentElement, dragItemChildren);\r\n }\r\n // 拷贝canvas的内容\r\n const originCanvasArr = this.draggedEl.querySelectorAll('canvas');\r\n const targetCanvasArr = this.dragCloneNode.querySelectorAll('canvas');\r\n [].forEach.call(targetCanvasArr, (canvas, index) => {\r\n canvas.getContext('2d').drawImage(originCanvasArr[index], 0, 0);\r\n });\r\n\r\n this.ngZone.runOutsideAngular(() => {\r\n this.document.addEventListener('dragover', this.followMouse4CloneNode, { capture: true, passive: true });\r\n });\r\n this.dragCloneNode.style.width = this.dragOffset.width + 'px';\r\n this.dragCloneNode.style.height = this.dragOffset.height + 'px';\r\n\r\n if (\r\n !(\r\n this.dragPreviewDirective &&\r\n this.dragPreviewDirective.dragPreviewTemplate &&\r\n this.dragPreviewDirective.dragPreviewOptions &&\r\n this.dragPreviewDirective.dragPreviewOptions.skipBatchPreview\r\n )\r\n ) {\r\n // 批量拖拽样式\r\n if (this.batchDragging && this.batchDragData && this.batchDragData.length > 1) {\r\n // 创建一个节点容器\r\n const node = this.document.createElement('div');\r\n node.appendChild(this.dragCloneNode);\r\n node.classList.add('batch-dragged-node');\r\n\r\n /* 计数样式定位 */\r\n if (this.batchDragStyle && this.batchDragStyle.length && this.batchDragStyle.indexOf('badge') > -1) {\r\n const badge = this.document.createElement('div');\r\n badge.innerText = String(this.batchDragData.length);\r\n badge.classList.add('batch-dragged-node-count');\r\n node.style.position = 'relative';\r\n const style = {\r\n position: 'absolute',\r\n right: '5px',\r\n top: '-12px',\r\n height: '24px',\r\n width: '24px',\r\n borderRadius: '12px',\r\n fontSize: '14px',\r\n lineHeight: '24px',\r\n textAlign: 'center',\r\n color: '#fff',\r\n background: ['#5170ff', 'var(--brand-1, #5170ff)'],\r\n };\r\n Utils.addElStyles(badge, style);\r\n node.appendChild(badge);\r\n }\r\n\r\n /* 层叠感样式定位 */\r\n if (this.batchDragStyle && this.batchDragStyle.length && this.batchDragStyle.indexOf('stack') > -1) {\r\n let stack = 2;\r\n if (this.batchDragData.length === 2) {\r\n stack = 1;\r\n }\r\n for (let i = 0; i < stack; i++) {\r\n const stackNode = this.dragCloneNode.cloneNode(false);\r\n const stackStyle = {\r\n position: 'absolute',\r\n left: -5 * (i + 1) + 'px',\r\n top: -5 * (i + 1) + 'px',\r\n zIndex: String(-(i + 1)),\r\n width: this.dragOffset.width + 'px',\r\n height: this.dragOffset.height + 'px',\r\n background: '#fff',\r\n border: ['1px solid #5170ff', '1px solid var(--brand-1, #5170ff)'],\r\n };\r\n Utils.addElStyles(stackNode, stackStyle);\r\n node.appendChild(stackNode);\r\n }\r\n }\r\n this.dragCloneNode = node;\r\n }\r\n }\r\n\r\n this.dragCloneNode.classList.add('drag-clone-node');\r\n if (!(this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate)) {\r\n this.dragCloneNode.style.width = this.dragOffset.width + 'px';\r\n this.dragCloneNode.style.height = this.dragOffset.height + 'px';\r\n }\r\n this.dragCloneNode.style.position = 'fixed';\r\n this.dragCloneNode.style.zIndex = '1090';\r\n this.dragCloneNode.style.pointerEvents = 'none';\r\n this.dragCloneNode.style.top = this.dragOffset.top + 'px';\r\n this.dragCloneNode.style.left = this.dragOffset.left + 'px';\r\n this.dragCloneNode.style.willChange = 'left, top';\r\n this.dragItemContainer.appendChild(this.dragCloneNode);\r\n this.ngZone.runOutsideAngular(() => {\r\n setTimeout(() => {\r\n if (this.draggedEl) {\r\n this.draggedEl.style.display = 'none';\r\n this.dragElShowHideEvent.next(false);\r\n if (this.dragOriginPlaceholder) {\r\n this.dragOriginPlaceholder.style.display = 'block';\r\n }\r\n }\r\n });\r\n });\r\n }\r\n }\r\n\r\n disableDraggedCloneNodeFollowMouse() {\r\n if (this.dragCloneNode) {\r\n this.document.removeEventListener('dragover', this.followMouse4CloneNode, { capture: true });\r\n this.dragItemContainer.removeChild(this.dragCloneNode);\r\n this.draggedEl.style.display = '';\r\n this.dragElShowHideEvent.next(true);\r\n }\r\n if (this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate) {\r\n this.dragPreviewDirective.destroyPreview();\r\n }\r\n this.dragCloneNode = undefined;\r\n this.dragItemContainer = undefined;\r\n\r\n if (this.intersectionObserver) {\r\n this.intersectionObserver.disconnect();\r\n }\r\n }\r\n\r\n interceptChildNode(parentNode, childNodeList) {\r\n const interceptOptions = {\r\n root: parentNode,\r\n };\r\n this.intersectionObserver = new IntersectionObserver(this.setChildNodeHide, interceptOptions);\r\n [].forEach.call(childNodeList, (childNode) => {\r\n this.intersectionObserver.observe(childNode);\r\n });\r\n }\r\n\r\n setChildNodeHide(entries) {\r\n entries.forEach((element) => {\r\n const { isIntersecting, target: childNode } = element;\r\n if (isIntersecting) {\r\n childNode.style.display = 'block';\r\n } else {\r\n childNode.style.display = 'none';\r\n }\r\n });\r\n }\r\n\r\n followMouse4CloneNode = (event) => {\r\n const { offsetLeft, offsetTop } = this.dragOffset;\r\n const { clientX, clientY } = event;\r\n requestAnimationFrame(() => {\r\n if (!this.dragCloneNode) {\r\n return;\r\n }\r\n this.dragCloneNode.style.left = clientX - offsetLeft + 'px';\r\n this.dragCloneNode.style.top = clientY - offsetTop + 'px';\r\n });\r\n };\r\n\r\n getBatchDragData(identity?, order: ((a: any, b: any) => number) | 'select' | 'draggedElFirst' = 'draggedElFirst') {\r\n const result = this.batchDragData.map((dragData) => dragData.dragData);\r\n if (typeof order === 'function') {\r\n result.sort(<(a: any, b: any) => number>order);\r\n } else if (order === 'draggedElFirst') {\r\n let dragData = this.dragData;\r\n if (identity) {\r\n const realDragData = this.batchDragData.filter((dd) => dd.identity === identity).pop().dragData;\r\n dragData = realDragData;\r\n }\r\n result.splice(result.indexOf(dragData), 1);\r\n result.splice(0, 0, dragData);\r\n }\r\n return result;\r\n }\r\n\r\n /** usage:\r\n * constructor(..., private dragDropService: DragDropService) {}\r\n * cleanBatchDragData() { this.dragDropService.cleanBatchDragData(); }\r\n */\r\n public cleanBatchDragData() {\r\n const batchDragData = this.batchDragData;\r\n if (this.batchDragData) {\r\n this.batchDragData\r\n .filter((dragData) => dragData.draggable)\r\n .map((dragData) => dragData.draggable)\r\n .forEach((draggable) => {\r\n draggable.batchDraggable.dragData = undefined;\r\n });\r\n this.batchDragData = undefined;\r\n this.batchDragGroup = undefined;\r\n }\r\n return batchDragData;\r\n }\r\n\r\n public copyStyle(source, target) {\r\n ['id', 'class', 'style', 'draggable'].forEach((att) => {\r\n target.removeAttribute(att);\r\n });\r\n\r\n // copy style (without transitions)\r\n const computedStyle = getComputedStyle(source);\r\n for (let i = 0; i < computedStyle.length; i++) {\r\n const key = computedStyle[i];\r\n if (key.indexOf('transition') < 0) {\r\n target.style[key] = computedStyle[key];\r\n }\r\n }\r\n target.style.pointerEvents = 'none';\r\n // and repeat for all children\r\n for (let i = 0; i < source.children.length; i++) {\r\n this.copyStyle(source.children[i], target.children[i]);\r\n }\r\n }\r\n}\r\n","import { EventEmitter } from '@angular/core';\n\nexport class PreserveNextEventEmitter<T> extends EventEmitter<T> {\n /** 保留注册的 generatorOrNext构成的函数*/\n private _schedulerFns: Set<any>;\n private _isAsync: boolean;\n get schedulerFns() { return this._schedulerFns; }\n\n constructor(isAsync = false) {\n super(isAsync);\n this._isAsync = isAsync;\n }\n\n forceCallback(value: T, once = false) {\n if (this.schedulerFns && this.schedulerFns.size) {\n this.schedulerFns.forEach(fn => {\n fn (value);\n });\n if (once) {\n this.cleanCallbackFn();\n }\n }\n }\n cleanCallbackFn() {\n this._schedulerFns = undefined;\n }\n\n emit(value?: T) { super.emit(value); }\n\n subscribe(generatorOrNext?: any, error?: any, complete?: any): any {\n let schedulerFn: (t: any) => any;\n\n if (generatorOrNext && typeof generatorOrNext === 'object') {\n schedulerFn = this._isAsync ? (value: any) => {\n setTimeout(() => generatorOrNext.next(value));\n } : (value: any) => { generatorOrNext.next(value); };\n } else {\n schedulerFn = this._isAsync ? (value: any) => { setTimeout(() => generatorOrNext(value)); } :\n (value: any) => { generatorOrNext(value); };\n }\n if (!this._schedulerFns) {\n this._schedulerFns = new Set<any>();\n }\n this._schedulerFns.add(schedulerFn);\n\n return super.subscribe(generatorOrNext, error, complete);\n }\n\n}\n","import { ChangeDetectorRef, Component, ElementRef, TemplateRef } from '@angular/core';\n\n@Component({\n selector: 'd-drag-preview',\n templateUrl: './drag-preview.component.html',\n preserveWhitespaces: false,\n})\n\nexport class DragPreviewComponent {\n element;\n data;\n draggedEl;\n dragData;\n batchDragData;\n dragSyncDOMElements;\n templateRef: TemplateRef<any>;\n constructor(private el: ElementRef, private cdr: ChangeDetectorRef) {\n this.element = el.nativeElement;\n }\n public updateTemplate() {\n this.cdr.detectChanges();\n }\n\n}\n","<ng-template\n [ngTemplateOutlet]=\"templateRef\"\n [ngTemplateOutletContext]=\"{\n data: data,\n draggedEl: draggedEl,\n dragData: dragData,\n batchDragData: batchDragData,\n dragSyncDOMElements: dragSyncDOMElements\n }\"\n>\n</ng-template>\n","import { ComponentFactoryResolver, Directive, Input, TemplateRef } from '@angular/core';\nimport { OverlayContainerRef } from 'ng-devui/overlay-container';\nimport { DragDropService } from '../services/drag-drop.service';\nimport { DragPreviewComponent } from './drag-preview.component';\n\n@Directive({\n selector: '[dDraggable][dDragPreview]',\n exportAs: 'dDragPreview'\n})\n\nexport class DragPreviewDirective {\n @Input('dDragPreview') dragPreviewTemplate: TemplateRef<any>;\n @Input() dragPreviewData;\n @Input() dragPreviewOptions = {\n skipBatchPreview: false\n };\n public previewRef;\n constructor(private componentFactoryResolver: ComponentFactoryResolver,\n private overlayContainerRef: OverlayContainerRef, private dragDropService: DragDropService) {\n }\n\n public createPreview() {\n const finalComponentFactoryResolver = this.componentFactoryResolver;\n\n const previewRef = this.overlayContainerRef.createComponent(\n finalComponentFactoryResolver.resolveComponentFactory(DragPreviewComponent)\n );\n this.previewRef = previewRef;\n this.updateData();\n return this.previewRef;\n }\n\n public updateData() {\n Object.assign(this.previewRef.instance, {\n templateRef: this.dragPreviewTemplate,\n data: this.dragPreviewData,\n draggedEl: this.dragDropService.draggedEl,\n dragData: this.dragDropService.dragData,\n batchDragData: this.dragDropService.batchDragData && this.dragDropService.getBatchDragData(),\n dragSyncDOMElements: this.dragDropService.dragSyncGroupDirectives && this.getDragSyncDOMElements()\n });\n this.previewRef.instance.updateTemplate();\n }\n\n public destroyPreview() {\n if (this.previewRef) {\n this.previewRef.hostView.destroy();\n }\n }\n\n public getPreviewElement() {\n return this.previewRef && this.previewRef.instance.element;\n }\n private getDragSyncDOMElements() {\n return this.dragDropService.dragSyncGroupDirectives.map(dir => dir.el.nativeElement);\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n AfterViewInit, Directive, ElementRef, EventEmitter,\n HostBinding, Inject, Input, NgZone, OnDestroy, OnInit, Optional, Output, Renderer2, Self\n} from '@angular/core';\nimport { fromEvent, Subject, Subscription } from 'rxjs';\nimport { DragDropService } from '../services/drag-drop.service';\nimport { Utils } from '../shared/utils';\nimport { PreserveNextEventEmitter } from './../shared/preserve-next-event-emitter';\nimport { DragPreviewDirective } from './drag-preview.directive';\n\n@Directive({\n selector: '[dDraggable]'\n})\n/**\n * Makes an element draggable by adding the draggable html attribute\n */\nexport class DraggableDirective implements OnInit, AfterViewInit, OnDestroy {\n @HostBinding('draggable') draggable = true;\n /**\n * The data that will be available to the droppable directive on its `dropEvent()` event.\n */\n @Input() dragData;\n\n /**\n * The selector that defines the drag Handle. If defined drag will only be allowed if dragged from the selector element.\n */\n @HostBinding('attr.data-drag-handle-selector') // host-binding attribute for communicate with touch support js\n @Input() dragHandle: string;\n\n /**\n * Currently not used\n */\n @Input() dragEffect = 'move';\n\n /**\n * Defines compatible drag drop pairs. Values must match both in draggable and droppable.dropScope.\n */\n @Input() dragScope: string | Array<string> = 'default';\n\n @Input() dragHandleClass = 'drag-handle';\n /**\n * CSS class applied on the draggable that is applied when the item is being dragged.\n */\n @Input() dragOverClass: string;\n\n /**\n * Event fired when Drag is started\n */\n @Output() dragStartEvent: EventEmitter<any> = new EventEmitter<any>();\n\n /**\n * @deprecated\n * Event fired while the element is being dragged\n * 为了性能优化,该函数废弃,请用(drag)自行监听, 如果不需要angular脏检测则最好用runOutsideAngular的addEventListener监听以获得好的性能\n */\n @Output() dragEvent: PreserveNextEventEmitter<any> = new PreserveNextEventEmitter<any>();\n\n /**\n * Event fired when dragged ends\n */\n @Output() dragEndEvent: EventEmitter<any> = new EventEmitter<any>();\n\n /**\n * Keeps track of mouse over element that is used to determine drag handles\n */\n private mouseOverElement: any;\n @Output() dropEndEvent: PreserveNextEventEmitter<any> = new PreserveNextEventEmitter<any>();\n @Input()\n public get disabled(): boolean {\n return this._disabled;\n }\n\n public set disabled(value: boolean) {\n this._disabled = value;\n this.draggable = !this._disabled;\n }\n private _disabled: boolean;\n\n @Input() enableDragFollow = false; // 默认false使用浏览器H5API拖拽, 否则使用原dom定位偏移\n @Input() dragFollowOptions: {\n appendToBody?: boolean;\n };\n @Input() originPlaceholder: {\n show?: boolean;\n tag?: string;\n style?: {[cssProperties: string]: string};\n text?: string;\n removeDelay?: number; // 单位: ms\n };\n @Input() dragIdentity: any; // 用于虚拟滚动的恢复\n\n @Input() dragItemParentName = ''; // 当前拖拽元素的类名或元素名称(类名需要加.),主要用于子节点的截取操作\n @Input() dragItemChildrenName = ''; // 当前拖拽元素的子节点类名或元素名称(类名需要加.)\n\n dragsSub: Subscription = new Subscription();\n destroyDragEndSub: Subscription = new Subscription();\n isDestroyed: boolean;\n private delayRemoveOriginPlaceholderTimer;\n public batchDraggable;\n private dragOriginPlaceholder;\n private dragOriginPlaceholderNextSibling;\n public dragElShowHideEvent = new Subject<boolean>();\n public beforeDragStartEvent = new Subject<boolean>();\n document: Document;\n\n constructor(public el: ElementRef, private renderer: Renderer2, private dragDropService: DragDropService, private ngZone: NgZone,\n @Optional() @Self() public dragPreviewDirective: DragPreviewDirective, @Inject(DOCUMENT) private doc: any\n ) {\n this.document = this.doc;\n }\n\n ngOnInit() {\n this.ngZone.runOutsideAngular(() => {\n this.dragsSub.add(fromEvent(this.el.nativeElement, 'mouseover').subscribe(event => this.mouseover(event)));\n this.dragsSub.add(fromEvent(this.el.nativeElement, 'dragstart').subscribe(event => this.dragStart(event)));\n this.dragsSub.add(fromEvent(this.el.nativeElement, 'dragend').subscribe(event => this.dragEnd(event)));\n });\n }\n\n dropSubscription() {\n const dragDropSub = this.dragDropService.newSubscription();\n dragDropSub.add(\n this.dragDropService.dropEvent.subscribe((event) => {\n this.mouseOverElement = undefined;\n this.renderer.removeClass(this.el.nativeElement, this.dragOverClass);\n this.dropEndEvent.emit(event);\n // 兼容虚拟滚动后被销毁\n if (this.isDestroyed) {\n if (this.dropEndEvent.schedulerFns && this.dropEndEvent.schedulerFns.size > 0) {\n this.dropEndEvent.forceCallback(event, true);\n }\n }\n if (this.dragDropService.dragOriginPlaceholder) {\n if (this.originPlaceholder && this.originPlaceholder.removeDelay > 0\n && !this.dragDropService.dropOnOrigin) { // 非drop到自己的情况\n this.delayRemoveOriginPlaceholder();\n } else {\n this.removeOriginPlaceholder();\n }\n this.dragDropService.draggedElIdentity = undefined;\n }\n this.dragDropService.subscription.unsubscribe();\n }));\n dragDropSub.add(\n this.dragDropService.dragElShowHideEvent.subscribe(this.dragElShowHideEvent)\n );\n }\n\n ngAfterViewInit() {\n this.applyDragHandleClass();\n if (this.dragIdentity) {\n if (this.dragDropService.draggedEl && this.dragIdentity === this.dragDropService.draggedElIdentity) {\n if (this.originPlaceholder && this.originPlaceholder.show !== false) {\n this.insertOriginPlaceholder();\n }\n this.dragDropService.draggedEl = this.el.nativeElement;\n this.el.nativeElement.style.display = 'none'; // recovery don't need to emit event\n }\n }\n }\n\n ngOnDestroy() {\n // 兼容虚拟滚动后被销毁\n this.isDestroyed = true;\n if (this.dragDropService.draggedEl === this.el.nativeElement) {\n this.destroyDragEndSub = new Subscription();\n this.destroyDragEndSub.add(fromEvent(this.el.nativeElement, 'dragend').subscribe(event => {\n this.dragEnd(event);\n if (this.dropEndEvent.schedulerFns && this.dropEndEvent.schedulerFns.size > 0) {\n this.dropEndEvent.forceCallback(event, true);\n }\n this.destroyDragEndSub.unsubscribe();\n this.destroyDragEndSub = undefined;\n }));\n i