UNPKG

ng-devui

Version:

DevUI components based on Angular

1,148 lines (1,141 loc) 153 kB
import * as i1 from '@angular/common'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { ElementRef, Injectable, Inject, EventEmitter, Component, Directive, Input, Optional, Self, HostBinding, Output, QueryList, HostListener, NgModule } from '@angular/core'; import { Subject, Subscription, fromEvent, BehaviorSubject, merge } from 'rxjs'; import * as i1$1 from 'ng-devui/overlay-container'; import { debounceTime, tap, throttleTime, filter, distinctUntilChanged } from 'rxjs/operators'; class Utils { /** * Polyfill for element.matches. * See: https://developer.mozilla.org/en/docs/Web/API/Element/matches#Polyfill * element */ static matches(element, selectorName) { const proto = Element.prototype; const func = proto.matches || proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector || function (s) { const matches = (this.document || this.ownerDocument).querySelectorAll(s); let i = matches.length; while (--i >= 0 && matches.item(i) !== this) { // do nothing } return i > -1; }; return func.call(element, selectorName); } /** * Applies the specified css class on nativeElement * elementRef * className */ static addClass(elementRef, className) { if (className === undefined) { return; } const e = this.getElementWithValidClassList(elementRef); if (e) { e.classList.add(className); } } /** * Removes the specified class from nativeElement * elementRef * className */ static removeClass(elementRef, className) { if (className === undefined) { return; } const e = this.getElementWithValidClassList(elementRef); if (e) { e.classList.remove(className); } } /** * Gets element with valid classList * * elementRef * @returns ElementRef | null */ static getElementWithValidClassList(elementRef) { const e = elementRef instanceof ElementRef ? elementRef.nativeElement : elementRef; if (e.classList !== undefined && e.classList !== null) { return e; } return null; } static slice(args, slice, sliceEnd) { const ret = []; let len = args.length; if (len === 0) { return ret; } const start = slice < 0 ? Math.max(0, slice + len) : slice || 0; if (sliceEnd !== undefined) { len = sliceEnd < 0 ? sliceEnd + len : sliceEnd; } while (len-- > start) { ret[len - start] = args[len]; } return ret; } // 动态添加styles static addElStyles(el, styles) { if (styles instanceof Object) { for (const s in styles) { if (Object.prototype.hasOwnProperty.call(styles, s)) { if (Array.isArray(styles[s])) { // 用于支持兼容渐退 styles[s].forEach((val) => { el.style[s] = val; }); } else { el.style[s] = styles[s]; } } } } } static dispatchEventToUnderElement(event, target, eventType) { const up = target || event.target; up.style.display = 'none'; const { x, y } = { x: event.clientX, y: event.clientY }; const under = document.elementFromPoint(x, y); up.style.display = ''; if (!under) { return event; } const ev = document.createEvent('DragEvent'); ev.initMouseEvent(eventType || event.type, true, true, window, 0, event.screenX, event.screenY, event.clientX, event.clientY, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, event.button, event.relatedTarget); if (ev.dataTransfer !== null) { ev.dataTransfer.setData('text', ''); ev.dataTransfer.effectAllowed = event.dataTransfer.effectAllowed; } setTimeout(() => { under.dispatchEvent(ev); }, 0); return event; } } /** * 2020.03.23-Modified from https://github.com/Bernardo-Castilho/dragdroptouch, license: MIT,reason:Converting .js file to .ts file */ class DragDropTouch { static { this.THRESHOLD = 5; } // pixels to move before drag starts static { this.OPACITY = 0.5; } // drag image opacity static { this.DBLCLICK = 500; } // max ms between clicks in a double click static { this.DRAG_OVER_TIME = 300; } // interval ms when drag over static { this.CTX_MENU = 900; } // ms to hold before raising 'contextmenu' event static { this.IS_PRESS_HOLD_MODE = true; } // decides of press & hold mode presence static { this.PRESS_HOLD_AWAIT = 400; } // ms to wait before press & hold is detected static { this.PRESS_HOLD_MARGIN = 25; } // pixels that finger might shiver while pressing static { this.PRESS_HOLD_THRESHOLD = 0; } // pixels to move before drag starts static { this.DRAG_HANDLE_ATTR = 'data-drag-handle-selector'; } static { this.rmvAttrs = 'id,class,style,draggable'.split(','); } static { this.kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(','); } static { this.ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(','); } static { this.instance = null; } constructor() { this.lastClick = 0; // ** event handlers this.touchstart = (e) => { if (this.shouldHandle(e)) { // raise double-click and prevent zooming if (Date.now() - this.lastClick < DragDropTouch.DBLCLICK) { if (this.dispatchEvent(e, 'dblclick', e.target)) { e.preventDefault(); this.reset(); return; } } // clear all variables this.reset(); // get nearest draggable element const src = this.closestDraggable(e.target); if (src) { this.dragSource = src; this.ptDown = this.getPoint(e); this.lastTouch = e; if (DragDropTouch.IS_PRESS_HOLD_MODE) { this.pressHoldInterval = setTimeout(() => { this.bindTouchmoveTouchend(e); this.isDragEnabled = true; this.touchmove(e); }, DragDropTouch.PRESS_HOLD_AWAIT); } else { e.preventDefault(); this.bindTouchmoveTouchend(e); } } } }; this.touchmoveOnDocument = (e) => { if (this.shouldCancelPressHoldMove(e)) { this.reset(); return; } }; this.touchmove = (e) => { if (this.shouldCancelPressHoldMove(e)) { this.reset(); return; } if (this.shouldHandleMove(e) || this.shouldHandlePressHoldMove(e)) { const target = this.getTarget(e); // start dragging if (this.dragSource && !this.img && this.shouldStartDragging(e)) { this.dispatchEvent(e, 'dragstart', this.dragSource); this.createImage(e); } // continue dragging if (this.img) { this.clearDragoverInterval(); this.lastTouch = e; e.preventDefault(); // prevent scrolling if (target !== this.lastTarget) { // according to drag drop implementation of the browser, dragenterB is supposed to fired before dragleaveA this.dispatchEvent(e, 'dragenter', target); this.dispatchEvent(this.lastTouch, 'dragleave', this.lastTarget); this.lastTarget = target; } this.moveImage(e); this.isDropZone = this.dispatchEvent(e, 'dragover', target); // should continue dispatch dragover event when touch position stay still this.setDragoverInterval(e); } } }; this.touchendOnDocument = (e) => { if (this.shouldHandle(e)) { if (!this.img) { this.dragSource = null; this.lastClick = Date.now(); } // finish dragging this.destroyImage(); if (this.dragSource) { this.reset(); } } }; this.touchend = (e) => { if (this.shouldHandle(e)) { // user clicked the element but didn't drag, so clear the source and simulate a click if (!this.img) { this.dragSource = null; // browser will dispatch click event after trigger touchend, since touchstart didn't preventDefault this.lastClick = Date.now(); } // finish dragging this.destroyImage(); if (this.dragSource) { if (e.type.indexOf('cancel') < 0 && this.isDropZone) { this.dispatchEvent(this.lastTouch, 'drop', this.lastTarget); } this.dispatchEvent(this.lastTouch, 'dragend', this.dragSource); this.reset(); } } }; // enforce singleton pattern if (DragDropTouch.instance) { throw new Error('DragDropTouch instance already created.'); } // detect passive event support // https://github.com/Modernizr/Modernizr/issues/1894 let supportsPassive = false; if (typeof document !== 'undefined') { document.addEventListener('test', () => { }, { get passive() { supportsPassive = true; return true; }, }); // listen to touch events if (DragDropTouch.isTouchDevice()) { // 能响应触摸事件 const d = document; const ts = this.touchstart; const tmod = this.touchmoveOnDocument; const teod = this.touchendOnDocument; const opt = supportsPassive ? { passive: false, capture: false } : false; const optPassive = supportsPassive ? { passive: true } : false; d.addEventListener('touchstart', ts, opt); d.addEventListener('touchmove', tmod, optPassive); d.addEventListener('touchend', teod); d.addEventListener('touchcancel', teod); this.touchmoveListener = this.touchmove; this.touchendListener = this.touchend; this.listenerOpt = opt; } } } /** * Gets a reference to the @see:DragDropTouch singleton. */ static getInstance() { if (!DragDropTouch.instance) { DragDropTouch.instance = new DragDropTouch(); } return DragDropTouch.instance; } static isTouchDevice() { if (typeof window === 'undefined' || typeof document === 'undefined') { return false; } const d = document; const w = window; let bool; if ('ontouchstart' in d || // normal mobile device 'ontouchstart' in w || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 || (window.DocumentTouch && document instanceof window.DocumentTouch)) { bool = true; } else { const fakeBody = document.createElement('fakebody'); fakeBody.innerHTML += ` <style> @media (touch-enabled),(-webkit-touch-enabled),(-moz-touch-enabled),(-o-touch-enabled){ #touch_test { top: 42px; position: absolute; } } </style>`; document.documentElement.appendChild(fakeBody); const touchTestNode = document.createElement('div'); touchTestNode.id = 'touch_test'; fakeBody.appendChild(touchTestNode); bool = touchTestNode.offsetTop === 42; fakeBody.parentElement.removeChild(fakeBody); } return bool; } // ** event listener binding bindTouchmoveTouchend(e) { this.touchTarget = e.target; e.target.addEventListener('touchmove', this.touchmoveListener, this.listenerOpt); e.target.addEventListener('touchend', this.touchendListener); e.target.addEventListener('touchcancel', this.touchendListener); } removeTouchmoveTouchend() { if (this.touchTarget) { this.touchTarget.removeEventListener('touchmove', this.touchmoveListener); this.touchTarget.removeEventListener('touchend', this.touchendListener); this.touchTarget.removeEventListener('touchcancel', this.touchendListener); this.touchTarget = undefined; } } // ** utilities // ignore events that have been handled or that involve more than one touch shouldHandle(e) { return e && !e.defaultPrevented && e.touches && e.touches.length < 2; } // use regular condition outside of press & hold mode shouldHandleMove(e) { return !DragDropTouch.IS_PRESS_HOLD_MODE && this.shouldHandle(e); } // allow to handle moves that involve many touches for press & hold shouldHandlePressHoldMove(e) { return DragDropTouch.IS_PRESS_HOLD_MODE && this.isDragEnabled && e && e.touches && e.touches.length; } // reset data if user drags without pressing & holding shouldCancelPressHoldMove(e) { return DragDropTouch.IS_PRESS_HOLD_MODE && !this.isDragEnabled && this.getDelta(e) > DragDropTouch.PRESS_HOLD_MARGIN; } // start dragging when mouseover element matches drag handler selector and specified delta is detected shouldStartDragging(e) { const dragHandleSelector = this.getDragHandle(); // start dragging when mouseover element matches drag handler selector if (dragHandleSelector && !this.matchSelector(e.target, dragHandleSelector)) { return false; } // start dragging when specified delta is detected const delta = this.getDelta(e); return delta > DragDropTouch.THRESHOLD || (DragDropTouch.IS_PRESS_HOLD_MODE && delta >= DragDropTouch.PRESS_HOLD_THRESHOLD); } // find drag handler selector for dragstart only with partial element getDragHandle() { if (this.dragSource) { return this.dragSource.getAttribute(DragDropTouch.DRAG_HANDLE_ATTR) || ''; } return ''; } // test if element matches selector matchSelector(element, selector) { if (selector) { const proto = Element.prototype; const func = proto.matches || proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector || function (s) { const matches = (this.document || this.ownerDocument).querySelectorAll(s); let i = matches.length; while (--i >= 0 && matches.item(i) !== this) { // do nothing } return i > -1; }; return func.call(element, selector); } return true; } // clear all members reset() { this.removeTouchmoveTouchend(); this.destroyImage(); this.dragSource = null; this.lastTouch = null; this.lastTarget = null; this.ptDown = null; this.isDragEnabled = false; this.isDropZone = false; this.dataTransfer = new DragDropTouch.DataTransfer(); clearInterval(this.pressHoldInterval); this.clearDragoverInterval(); } // get point for a touch event getPoint(e, page) { if (e && e.touches) { e = e.touches[0]; } return { x: page ? e.pageX : e.clientX, y: page ? e.pageY : e.clientY }; } // get distance between the current touch event and the first one getDelta(e) { if (DragDropTouch.IS_PRESS_HOLD_MODE && !this.ptDown) { return 0; } const p = this.getPoint(e); return Math.abs(p.x - this.ptDown.x) + Math.abs(p.y - this.ptDown.y); } // get the element at a given touch event getTarget(e) { const pt = this.getPoint(e); let el = document.elementFromPoint(pt.x, pt.y); while (el && getComputedStyle(el).pointerEvents === 'none') { el = el.parentElement; } return el; } // create drag image from source element createImage(e) { // just in case... if (this.img) { this.destroyImage(); } // create drag image from custom element or drag source const src = this.imgCustom || this.dragSource; this.img = src.cloneNode(true); this.copyStyle(src, this.img); this.img.style.top = this.img.style.left = '-9999px'; // if creating from drag source, apply offset and opacity if (!this.imgCustom) { const rc = src.getBoundingClientRect(); const pt = this.getPoint(e); this.imgOffset = { x: pt.x - rc.left, y: pt.y - rc.top }; this.img.style.opacity = DragDropTouch.OPACITY.toString(); } // add image to document this.moveImage(e); document.body.appendChild(this.img); } // dispose of drag image element destroyImage() { if (this.img && this.img.parentElement) { this.img.parentElement.removeChild(this.img); } this.img = null; this.imgCustom = null; } // move the drag image element moveImage(e) { requestAnimationFrame(() => { if (this.img) { const pt = this.getPoint(e, true); const s = this.img.style; s.position = 'absolute'; s.pointerEvents = 'none'; s.zIndex = '999999'; s.left = Math.round(pt.x - this.imgOffset.x) + 'px'; s.top = Math.round(pt.y - this.imgOffset.y) + 'px'; } }); } // copy properties from an object to another copyProps(dst, src, props) { for (let i = 0; i < props.length; i++) { const p = props[i]; dst[p] = src[p]; } } // copy styles/attributes from drag source to drag image element copyStyle(src, dst) { // remove potentially troublesome attributes DragDropTouch.rmvAttrs.forEach((att) => dst.removeAttribute(att)); // copy canvas content if (src instanceof HTMLCanvasElement) { const canSrc = src; const canDst = dst; canDst.width = canSrc.width; canDst.height = canSrc.height; canDst.getContext('2d').drawImage(canSrc, 0, 0); } // copy canvas content for nested canvas element const srcCanvases = src.querySelectorAll('canvas'); if (srcCanvases.length > 0) { const dstCanvases = dst.querySelectorAll('canvas'); for (let i = 0; i < dstCanvases.length; i++) { const cSrc = srcCanvases[i]; const cDst = dstCanvases[i]; cDst.getContext('2d').drawImage(cSrc, 0, 0); } } // copy style (without transitions) const cs = getComputedStyle(src); for (let i = 0; i < cs.length; i++) { const key = cs[i]; if (key.indexOf('transition') < 0) { dst.style[key] = cs[key]; } } dst.style.pointerEvents = 'none'; // and repeat for all children for (let i = 0; i < src.children.length; i++) { this.copyStyle(src.children[i], dst.children[i]); } } // synthesize and dispatch an event // returns true if the event has been handled (e.preventDefault == true) dispatchEvent(e, type, target) { if (e && target) { const evt = document.createEvent('Event'); const t = e.touches ? e.touches[0] : e; evt.initEvent(type, true, true); const obj = { button: 0, which: 0, buttons: 1, dataTransfer: this.dataTransfer, }; this.copyProps(evt, e, DragDropTouch.kbdProps); this.copyProps(evt, t, DragDropTouch.ptProps); this.copyProps(evt, { fromTouch: true }, ['fromTouch']); // mark as from touch event this.copyProps(evt, obj, Object.keys(obj)); target.dispatchEvent(evt); return evt.defaultPrevented; } return false; } // gets an element's closest draggable ancestor closestDraggable(e) { for (; e; e = e.parentElement) { if (e.hasAttribute('draggable') && e.draggable) { return e; } } return null; } // repeat dispatch dragover event when touch point stay still setDragoverInterval(e) { this.dragoverTimer = setInterval(() => { const target = this.getTarget(e); if (target !== this.lastTarget) { this.dispatchEvent(e, 'dragenter', target); this.dispatchEvent(e, 'dragleave', this.lastTarget); this.lastTarget = target; } this.isDropZone = this.dispatchEvent(e, 'dragover', target); }, DragDropTouch.DRAG_OVER_TIME); } clearDragoverInterval() { if (this.dragoverTimer) { clearInterval(this.dragoverTimer); this.dragoverTimer = undefined; } } } /* eslint-disable-next-line @typescript-eslint/no-namespace */ (function (DragDropTouch) { /** * Object used to hold the data that is being dragged during drag and drop operations. * * It may hold one or more data items of different types. For more information about * drag and drop operations and data transfer objects, see * <a href="https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer">HTML Drag and Drop API</a>. * * This object is created automatically by the @see:DragDropTouch singleton and is * accessible through the @see:dataTransfer property of all drag events. */ class DataTransfer { get dropEffect() { return this._dropEffect; } set dropEffect(value) { this._dropEffect = value; } get effectAllowed() { return this._effectAllowed; } set effectAllowed(value) { this._effectAllowed = value; } get types() { return Object.keys(this._data); } constructor() { this._dropEffect = 'move'; this._effectAllowed = 'all'; this._data = {}; } /** * Removes the data associated with a given type. * * The type argument is optional. If the type is empty or not specified, the data * associated with all types is removed. If data for the specified type does not exist, * or the data transfer contains no data, this method will have no effect. * * @param type Type of data to remove. */ clearData(type) { if (type !== null) { delete this._data[type]; } else { this._data = null; } } /** * Retrieves the data for a given type, or an empty string if data for that type does * not exist or the data transfer contains no data. * * @param type Type of data to retrieve. */ getData(type) { return this._data[type] || ''; } /** * Set the data for a given type. * * For a list of recommended drag types, please see * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types. * * @param type Type of data to add. * @param value Data to add. */ setData(type, value) { this._data[type] = value; } /** * Set the image to be used for dragging if a custom one is desired. * * @param img An image element to use as the drag feedback image. * @param offsetX The horizontal offset within the image. * @param offsetY The vertical offset within the image. */ setDragImage(img, offsetX, offsetY) { const ddt = DragDropTouch.getInstance(); ddt.imgCustom = img; ddt.imgOffset = { x: offsetX, y: offsetY }; } } DragDropTouch.DataTransfer = DataTransfer; })(DragDropTouch || (DragDropTouch = {})); class DragDropService { constructor(ngZone, doc) { this.ngZone = ngZone; this.doc = doc; this.dropTargets = []; this.dropEvent = new Subject(); this.dragEndEvent = new Subject(); this.dragStartEvent = new Subject(); this.subscription = new Subscription(); this.dragEmptyImage = new Image(); this.dragItemParentName = ''; this.dragItemChildrenName = ''; this.intersectionObserver = null; /* 协同拖拽需要 */ this.dragElShowHideEvent = new Subject(); this.followMouse4CloneNode = (event) => { const { offsetLeft, offsetTop } = this.dragOffset; const { clientX, clientY } = event; requestAnimationFrame(() => { if (!this.dragCloneNode) { return; } this.dragCloneNode.style.left = clientX - offsetLeft + 'px'; this.dragCloneNode.style.top = clientY - offsetTop + 'px'; }); }; this.touchInstance = DragDropTouch.getInstance(); // service not support OnInit, only support OnDestroy, so write in constructor // safari的img必须要有src this.dragEmptyImage.src = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=='; this.document = this.doc; } newSubscription() { this.subscription.unsubscribe(); // eslint-disable-next-line no-return-assign return (this.subscription = new Subscription()); } enableDraggedCloneNodeFollowMouse() { if (!this.dragCloneNode) { this.dragItemContainer = this.draggedEl.parentElement; if (this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate) { this.dragPreviewDirective.createPreview(); this.dragCloneNode = this.dragPreviewDirective.getPreviewElement(); this.dragItemContainer = this.document.body; } else { this.dragCloneNode = this.draggedEl.cloneNode(true); } this.dragCloneNode.style.margin = '0'; if (this.dragFollowOptions && this.dragFollowOptions.appendToBody) { this.dragItemContainer = this.document.body; this.copyStyle(this.draggedEl, this.dragCloneNode); } if (this.dragItemChildrenName !== '') { const parentElement = this.dragItemParentName === '' ? this.dragCloneNode : this.document.querySelector(this.dragItemParentName); const dragItemChildren = parentElement.querySelectorAll(this.dragItemChildrenName); this.interceptChildNode(parentElement, dragItemChildren); } // 拷贝canvas的内容 const originCanvasArr = this.draggedEl.querySelectorAll('canvas'); const targetCanvasArr = this.dragCloneNode.querySelectorAll('canvas'); [].forEach.call(targetCanvasArr, (canvas, index) => { canvas.getContext('2d').drawImage(originCanvasArr[index], 0, 0); }); this.ngZone.runOutsideAngular(() => { this.document.addEventListener('dragover', this.followMouse4CloneNode, { capture: true, passive: true }); }); this.dragCloneNode.style.width = this.dragOffset.width + 'px'; this.dragCloneNode.style.height = this.dragOffset.height + 'px'; if (!(this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate && this.dragPreviewDirective.dragPreviewOptions && this.dragPreviewDirective.dragPreviewOptions.skipBatchPreview)) { // 批量拖拽样式 if (this.batchDragging && this.batchDragData && this.batchDragData.length > 1) { // 创建一个节点容器 const node = this.document.createElement('div'); node.appendChild(this.dragCloneNode); node.classList.add('batch-dragged-node'); /* 计数样式定位 */ if (this.batchDragStyle && this.batchDragStyle.length && this.batchDragStyle.indexOf('badge') > -1) { const badge = this.document.createElement('div'); badge.innerText = String(this.batchDragData.length); badge.classList.add('batch-dragged-node-count'); node.style.position = 'relative'; const style = { position: 'absolute', right: '5px', top: '-12px', height: '24px', width: '24px', borderRadius: '12px', fontSize: '14px', lineHeight: '24px', textAlign: 'center', color: '#fff', background: ['#5170ff', 'var(--brand-1, #5170ff)'], }; Utils.addElStyles(badge, style); node.appendChild(badge); } /* 层叠感样式定位 */ if (this.batchDragStyle && this.batchDragStyle.length && this.batchDragStyle.indexOf('stack') > -1) { let stack = 2; if (this.batchDragData.length === 2) { stack = 1; } for (let i = 0; i < stack; i++) { const stackNode = this.dragCloneNode.cloneNode(false); const stackStyle = { position: 'absolute', left: -5 * (i + 1) + 'px', top: -5 * (i + 1) + 'px', zIndex: String(-(i + 1)), width: this.dragOffset.width + 'px', height: this.dragOffset.height + 'px', background: '#fff', border: ['1px solid #5170ff', '1px solid var(--brand-1, #5170ff)'], }; Utils.addElStyles(stackNode, stackStyle); node.appendChild(stackNode); } } this.dragCloneNode = node; } } this.dragCloneNode.classList.add('drag-clone-node'); if (!(this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate)) { this.dragCloneNode.style.width = this.dragOffset.width + 'px'; this.dragCloneNode.style.height = this.dragOffset.height + 'px'; } this.dragCloneNode.style.position = 'fixed'; this.dragCloneNode.style.zIndex = '1090'; this.dragCloneNode.style.pointerEvents = 'none'; this.dragCloneNode.style.top = this.dragOffset.top + 'px'; this.dragCloneNode.style.left = this.dragOffset.left + 'px'; this.dragCloneNode.style.willChange = 'left, top'; this.dragItemContainer.appendChild(this.dragCloneNode); this.ngZone.runOutsideAngular(() => { setTimeout(() => { if (this.draggedEl) { this.draggedEl.style.display = 'none'; this.dragElShowHideEvent.next(false); if (this.dragOriginPlaceholder) { this.dragOriginPlaceholder.style.display = 'block'; } } }); }); } } disableDraggedCloneNodeFollowMouse() { if (this.dragCloneNode) { this.document.removeEventListener('dragover', this.followMouse4CloneNode, { capture: true }); this.dragItemContainer.removeChild(this.dragCloneNode); this.draggedEl.style.display = ''; this.dragElShowHideEvent.next(true); } if (this.dragPreviewDirective && this.dragPreviewDirective.dragPreviewTemplate) { this.dragPreviewDirective.destroyPreview(); } this.dragCloneNode = undefined; this.dragItemContainer = undefined; if (this.intersectionObserver) { this.intersectionObserver.disconnect(); } } interceptChildNode(parentNode, childNodeList) { const interceptOptions = { root: parentNode, }; this.intersectionObserver = new IntersectionObserver(this.setChildNodeHide, interceptOptions); [].forEach.call(childNodeList, (childNode) => { this.intersectionObserver.observe(childNode); }); } setChildNodeHide(entries) { entries.forEach((element) => { const { isIntersecting, target: childNode } = element; if (isIntersecting) { childNode.style.display = 'block'; } else { childNode.style.display = 'none'; } }); } getBatchDragData(identity, order = 'draggedElFirst') { const result = this.batchDragData.map((dragData) => dragData.dragData); if (typeof order === 'function') { result.sort(order); } else if (order === 'draggedElFirst') { let dragData = this.dragData; if (identity) { const realDragData = this.batchDragData.filter((dd) => dd.identity === identity).pop().dragData; dragData = realDragData; } result.splice(result.indexOf(dragData), 1); result.splice(0, 0, dragData); } return result; } /** usage: * constructor(..., private dragDropService: DragDropService) {} * cleanBatchDragData() { this.dragDropService.cleanBatchDragData(); } */ cleanBatchDragData() { const batchDragData = this.batchDragData; if (this.batchDragData) { this.batchDragData .filter((dragData) => dragData.draggable) .map((dragData) => dragData.draggable) .forEach((draggable) => { draggable.batchDraggable.dragData = undefined; }); this.batchDragData = undefined; this.batchDragGroup = undefined; } return batchDragData; } copyStyle(source, target) { ['id', 'class', 'style', 'draggable'].forEach((att) => { target.removeAttribute(att); }); // copy style (without transitions) const computedStyle = getComputedStyle(source); for (let i = 0; i < computedStyle.length; i++) { const key = computedStyle[i]; if (key.indexOf('transition') < 0) { target.style[key] = computedStyle[key]; } } target.style.pointerEvents = 'none'; // and repeat for all children for (let i = 0; i < source.children.length; i++) { this.copyStyle(source.children[i], target.children[i]); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragDropService, deps: [{ token: i0.NgZone }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragDropService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragDropService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i0.NgZone }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] }] }); class PreserveNextEventEmitter extends EventEmitter { get schedulerFns() { return this._schedulerFns; } constructor(isAsync = false) { super(isAsync); this._isAsync = isAsync; } forceCallback(value, once = false) { if (this.schedulerFns && this.schedulerFns.size) { this.schedulerFns.forEach(fn => { fn(value); }); if (once) { this.cleanCallbackFn(); } } } cleanCallbackFn() { this._schedulerFns = undefined; } emit(value) { super.emit(value); } subscribe(generatorOrNext, error, complete) { let schedulerFn; if (generatorOrNext && typeof generatorOrNext === 'object') { schedulerFn = this._isAsync ? (value) => { setTimeout(() => generatorOrNext.next(value)); } : (value) => { generatorOrNext.next(value); }; } else { schedulerFn = this._isAsync ? (value) => { setTimeout(() => generatorOrNext(value)); } : (value) => { generatorOrNext(value); }; } if (!this._schedulerFns) { this._schedulerFns = new Set(); } this._schedulerFns.add(schedulerFn); return super.subscribe(generatorOrNext, error, complete); } } class DragPreviewComponent { constructor(el, cdr) { this.el = el; this.cdr = cdr; this.element = el.nativeElement; } updateTemplate() { this.cdr.detectChanges(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragPreviewComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: DragPreviewComponent, selector: "d-drag-preview", ngImport: i0, template: "<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", dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragPreviewComponent, decorators: [{ type: Component, args: [{ selector: 'd-drag-preview', preserveWhitespaces: false, template: "<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" }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }] }); class DragPreviewDirective { constructor(componentFactoryResolver, overlayContainerRef, dragDropService) { this.componentFactoryResolver = componentFactoryResolver; this.overlayContainerRef = overlayContainerRef; this.dragDropService = dragDropService; this.dragPreviewOptions = { skipBatchPreview: false }; } createPreview() { const finalComponentFactoryResolver = this.componentFactoryResolver; const previewRef = this.overlayContainerRef.createComponent(finalComponentFactoryResolver.resolveComponentFactory(DragPreviewComponent)); this.previewRef = previewRef; this.updateData(); return this.previewRef; } updateData() { Object.assign(this.previewRef.instance, { templateRef: this.dragPreviewTemplate, data: this.dragPreviewData, draggedEl: this.dragDropService.draggedEl, dragData: this.dragDropService.dragData, batchDragData: this.dragDropService.batchDragData && this.dragDropService.getBatchDragData(), dragSyncDOMElements: this.dragDropService.dragSyncGroupDirectives && this.getDragSyncDOMElements() }); this.previewRef.instance.updateTemplate(); } destroyPreview() { if (this.previewRef) { this.previewRef.hostView.destroy(); } } getPreviewElement() { return this.previewRef && this.previewRef.instance.element; } getDragSyncDOMElements() { return this.dragDropService.dragSyncGroupDirectives.map(dir => dir.el.nativeElement); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragPreviewDirective, deps: [{ token: i0.ComponentFactoryResolver }, { token: i1$1.OverlayContainerRef }, { token: DragDropService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: DragPreviewDirective, selector: "[dDraggable][dDragPreview]", inputs: { dragPreviewTemplate: ["dDragPreview", "dragPreviewTemplate"], dragPreviewData: "dragPreviewData", dragPreviewOptions: "dragPreviewOptions" }, exportAs: ["dDragPreview"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DragPreviewDirective, decorators: [{ type: Directive, args: [{ selector: '[dDraggable][dDragPreview]', exportAs: 'dDragPreview' }] }], ctorParameters: () => [{ type: i0.ComponentFactoryResolver }, { type: i1$1.OverlayContainerRef }, { type: DragDropService }], propDecorators: { dragPreviewTemplate: [{ type: Input, args: ['dDragPreview'] }], dragPreviewData: [{ type: Input }], dragPreviewOptions: [{ type: Input }] } }); /** * Makes an element draggable by adding the draggable html attribute */ class DraggableDirective { get disabled() { return this._disabled; } set disabled(value) { this._disabled = value; this.draggable = !this._disabled; } constructor(el, renderer, dragDropService, ngZone, dragPreviewDirective, doc) { this.el = el; this.renderer = renderer; this.dragDropService = dragDropService; this.ngZone = ngZone; this.dragPreviewDirective = dragPreviewDirective; this.doc = doc; this.draggable = true; /** * Currently not used */ this.dragEffect = 'move'; /** * Defines compatible drag drop pairs. Values must match both in draggable and droppable.dropScope. */ this.dragScope = 'default'; this.dragHandleClass = 'drag-handle'; /** * Event fired when Drag is started */ this.dragStartEvent = new EventEmitter(); /** * @deprecated * Event fired while the element is being dragged * 为了性能优化,该函数废弃,请用(drag)自行监听, 如果不需要angular脏检测则最好用runOutsideAngular的addEventListener监听以获得好的性能 */ this.dragEvent = new PreserveNextEventEmitter(); /** * Event fired when dragged ends */ this.dragEndEvent = new EventEmitter(); this.dropEndEvent = new PreserveNextEventEmitter(); this.enableDragFollow = false; // 默认false使用浏览器H5API拖拽, 否则使用原dom定位偏移 this.dragItemParentName = ''; // 当前拖拽元素的类名或元素名称(类名需要加.),主要用于子节点的截取操作 this.dragItemChildrenName = ''; // 当前拖拽元素的子节点类名或元素名称(类名需要加.) this.dragsSub = new Subscription(); this.destroyDragEndSub = new Subscription(); this.dragElShowHideEvent = new Subject(); this.beforeDragStartEvent = new Subject(); this.insertOriginPlaceholder = (directShow = true, updateService = true) => { if (this.delayRemoveOriginPlaceholderTimer) { clearTimeout(this.delayRemoveOriginPlaceholderTimer); this.delayRemoveOriginPlaceholderTimer = undefined; } const node = this.document.createElement(this.originPlaceholder.tag || 'div'); const rect = this.el.nativeElement.getBoundingClientRect(); if (directShow) { node.style.display = 'block'; } else { node.style.display = 'none'; } node.style.width = rect.width + 'px'; node.style.height = rect.height + 'px'; node.classList.add('drag-origin-placeholder'); if (this.originPlaceholder.text) { node.innerText = this.originPlaceholder.text; } if (this.originPlaceholder.style) { Utils.addElStyles(node, this.originPlaceholder.style); } if (updateService) { this.dragDropService.dragOriginPlaceholder = node; this.dragDropService.dragOriginPlaceholderNextSibling = this.el.nativeElement.nextSibling; } else { node.classList.add('side-drag-origin-placeholder'); const originCloneNode = this.el.nativeElement.cloneNode(true); originCloneNode.style.margin = 0; originCloneNode.style.pointerEvents = 'none'; originCloneNode.style.opacity = '0.3'; node.appendChild(originCloneNode); } this.dragOriginPlaceholder = node; this.dragOriginPlaceholderNextSibling = this.el.nativeElement.nextSibling; this.el.nativeElement.parentElement.insertBefore(node, this.el.nativeElement.nextSibling); }; this.removeOriginPlaceholder = (updateService = true) => { if (this.dragOriginPlaceholder) { this.dragOriginPlaceholder.parentElement.removeChild(this.dragOriginPlaceholder); } if (updateService) { this.dragDropService.dragOriginPlaceholder = undefined; this.dragDropService.dragOriginPlaceholderNextSibling = undefined; } this.dragOriginPlaceholder = undefined; this.dragOriginPlaceholderNextSibling = undefined; }; this.delayRemoveOriginPlaceholder = (updateService = true) => { const timeout = this.originPlaceholder.removeDelay; const delayOriginPlaceholder = this.dragOriginPlaceholder; const dragOriginPlaceholderNextSibling = this.findNextSibling(this.dragOriginPlaceholderNextSibling); // 需要临时移动位置,保证被ngFor刷新之后位置是正确的 // ngFor刷新的原理是有变化的部分都刷新,夹在变化部分中间的内容将被刷到变化部分之后的位置,所以需要恢复位置 // setTimeout是等ngFor的View刷新, 后续需要订阅sortContainer的view的更新才需要重新恢复位置 if (delayOriginPlaceholder.parentElement.contains(dragOriginPlaceholderNextSibling)) { delayOriginPlaceholder.parentElement.insertBefore(delayOriginPlaceholder, dragOriginPlaceholderNextSibling); } setTimeout(() => { if (delayOriginPlaceholder.parentElement.contains(dragOriginPlaceholderNextSibling)) { delayOriginPlaceholder.parentElement.insertBefore(delayOriginPlaceholder, dragOriginPlaceholderNextSibling); } delayOriginPlaceholder.classList.add('delay-deletion'); this.delayRemoveOriginPlaceholderTimer = setTimeout(() => { delayOriginPlaceholder.parentElement.removeChild(delayOriginPlaceholder); if (this.document.body.contains(this.el.nativeElement)) { this.el.nativeElement.style.display = ''; this.dragDropService.dragElShowHideEvent.next(false); } }, timeout); if (updateService) { this.dragDropService.dragOriginPlaceholder = undefined; this.dragDropService.dragOriginPlaceholderNextSibling = undefined; } this.dragOriginPlaceholder = undefined; this.dragOrigi