| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 | import {	toggleClass,	getRect,	index,	closest,	on,	off,	clone,	css,	setRect,	unsetRect,	matrix,	expando} from '../../src/utils.js';import dispatchEvent from '../../src/EventDispatcher.js';let multiDragElements = [],	multiDragClones = [],	lastMultiDragSelect, // for selection with modifier key down (SHIFT)	multiDragSortable,	initialFolding = false, // Initial multi-drag fold when drag started	folding = false, // Folding any other time	dragStarted = false,	dragEl,	clonesFromRect,	clonesHidden;function MultiDragPlugin() {	function MultiDrag(sortable) {		// Bind all private methods		for (let fn in this) {			if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {				this[fn] = this[fn].bind(this);			}		}		if (sortable.options.supportPointer) {			on(document, 'pointerup', this._deselectMultiDrag);		} else {			on(document, 'mouseup', this._deselectMultiDrag);			on(document, 'touchend', this._deselectMultiDrag);		}		on(document, 'keydown', this._checkKeyDown);		on(document, 'keyup', this._checkKeyUp);		this.defaults = {			selectedClass: 'sortable-selected',			multiDragKey: null,			setData(dataTransfer, dragEl) {				let data = '';				if (multiDragElements.length && multiDragSortable === sortable) {					multiDragElements.forEach((multiDragElement, i) => {						data += (!i ? '' : ', ') + multiDragElement.textContent;					});				} else {					data = dragEl.textContent;				}				dataTransfer.setData('Text', data);			}		};	}	MultiDrag.prototype = {		multiDragKeyDown: false,		isMultiDrag: false,		delayStartGlobal({ dragEl: dragged }) {			dragEl = dragged;		},		delayEnded() {			this.isMultiDrag = ~multiDragElements.indexOf(dragEl);		},		setupClone({ sortable, cancel }) {			if (!this.isMultiDrag) return;			for (let i = 0; i < multiDragElements.length; i++) {				multiDragClones.push(clone(multiDragElements[i]));				multiDragClones[i].sortableIndex = multiDragElements[i].sortableIndex;				multiDragClones[i].draggable = false;				multiDragClones[i].style['will-change'] = '';				toggleClass(multiDragClones[i], this.options.selectedClass, false);				multiDragElements[i] === dragEl && toggleClass(multiDragClones[i], this.options.chosenClass, false);			}			sortable._hideClone();			cancel();		},		clone({ sortable, rootEl, dispatchSortableEvent, cancel }) {			if (!this.isMultiDrag) return;			if (!this.options.removeCloneOnHide) {				if (multiDragElements.length && multiDragSortable === sortable) {					insertMultiDragClones(true, rootEl);					dispatchSortableEvent('clone');					cancel();				}			}		},		showClone({ cloneNowShown, rootEl, cancel }) {			if (!this.isMultiDrag) return;			insertMultiDragClones(false, rootEl);			multiDragClones.forEach(clone => {				css(clone, 'display', '');			});			cloneNowShown();			clonesHidden = false;			cancel();		},		hideClone({ sortable, cloneNowHidden, cancel }) {			if (!this.isMultiDrag) return;			multiDragClones.forEach(clone => {				css(clone, 'display', 'none');				if (this.options.removeCloneOnHide && clone.parentNode) {					clone.parentNode.removeChild(clone);				}			});			cloneNowHidden();			clonesHidden = true;			cancel();		},		dragStartGlobal({ sortable }) {			if (!this.isMultiDrag && multiDragSortable) {				multiDragSortable.multiDrag._deselectMultiDrag();			}			multiDragElements.forEach(multiDragElement => {				multiDragElement.sortableIndex = index(multiDragElement);			});			// Sort multi-drag elements			multiDragElements = multiDragElements.sort(function(a, b) {				return a.sortableIndex - b.sortableIndex;			});			dragStarted = true;		},		dragStarted({ sortable }) {			if (!this.isMultiDrag) return;			if (this.options.sort) {				// Capture rects,				// hide multi drag elements (by positioning them absolute),				// set multi drag elements rects to dragRect,				// show multi drag elements,				// animate to rects,				// unset rects & remove from DOM				sortable.captureAnimationState();				if (this.options.animation) {					multiDragElements.forEach(multiDragElement => {						if (multiDragElement === dragEl) return;						css(multiDragElement, 'position', 'absolute');					});					let dragRect = getRect(dragEl, false, true, true);					multiDragElements.forEach(multiDragElement => {						if (multiDragElement === dragEl) return;						setRect(multiDragElement, dragRect);					});					folding = true;					initialFolding = true;				}			}			sortable.animateAll(() => {				folding = false;				initialFolding = false;				if (this.options.animation) {					multiDragElements.forEach(multiDragElement => {						unsetRect(multiDragElement);					});				}				// Remove all auxiliary multidrag items from el, if sorting enabled				if (this.options.sort) {					removeMultiDragElements();				}			});		},		dragOver({ target, completed, cancel }) {			if (folding && ~multiDragElements.indexOf(target)) {				completed(false);				cancel();			}		},		revert({ fromSortable, rootEl, sortable, dragRect }) {			if (multiDragElements.length > 1) {				// Setup unfold animation				multiDragElements.forEach(multiDragElement => {					sortable.addAnimationState({						target: multiDragElement,						rect: folding ? getRect(multiDragElement) : dragRect					});					unsetRect(multiDragElement);					multiDragElement.fromRect = dragRect;					fromSortable.removeAnimationState(multiDragElement);				});				folding = false;				insertMultiDragElements(!this.options.removeCloneOnHide, rootEl);			}		},		dragOverCompleted({ sortable, isOwner, insertion, activeSortable, parentEl, putSortable }) {			let options = this.options;			if (insertion) {				// Clones must be hidden before folding animation to capture dragRectAbsolute properly				if (isOwner) {					activeSortable._hideClone();				}				initialFolding = false;				// If leaving sort:false root, or already folding - Fold to new location				if (options.animation && multiDragElements.length > 1 && (folding || !isOwner && !activeSortable.options.sort && !putSortable)) {					// Fold: Set all multi drag elements's rects to dragEl's rect when multi-drag elements are invisible					let dragRectAbsolute = getRect(dragEl, false, true, true);					multiDragElements.forEach(multiDragElement => {						if (multiDragElement === dragEl) return;						setRect(multiDragElement, dragRectAbsolute);						// Move element(s) to end of parentEl so that it does not interfere with multi-drag clones insertion if they are inserted						// while folding, and so that we can capture them again because old sortable will no longer be fromSortable						parentEl.appendChild(multiDragElement);					});					folding = true;				}				// Clones must be shown (and check to remove multi drags) after folding when interfering multiDragElements are moved out				if (!isOwner) {					// Only remove if not folding (folding will remove them anyways)					if (!folding) {						removeMultiDragElements();					}					if (multiDragElements.length > 1) {						let clonesHiddenBefore = clonesHidden;						activeSortable._showClone(sortable);						// Unfold animation for clones if showing from hidden						if (activeSortable.options.animation && !clonesHidden && clonesHiddenBefore) {							multiDragClones.forEach(clone => {								activeSortable.addAnimationState({									target: clone,									rect: clonesFromRect								});								clone.fromRect = clonesFromRect;								clone.thisAnimationDuration = null;							});						}					} else {						activeSortable._showClone(sortable);					}				}			}		},		dragOverAnimationCapture({ dragRect, isOwner, activeSortable }) {			multiDragElements.forEach(multiDragElement => {				multiDragElement.thisAnimationDuration = null;			});			if (activeSortable.options.animation && !isOwner && activeSortable.multiDrag.isMultiDrag) {				clonesFromRect = Object.assign({}, dragRect);				let dragMatrix = matrix(dragEl, true);				clonesFromRect.top -= dragMatrix.f;				clonesFromRect.left -= dragMatrix.e;			}		},		dragOverAnimationComplete() {			if (folding) {				folding = false;				removeMultiDragElements();			}		},		drop({ originalEvent: evt, rootEl, parentEl, sortable, dispatchSortableEvent, oldIndex, putSortable }) {			let toSortable = (putSortable || this.sortable);			if (!evt) return;			let options = this.options,				children = parentEl.children;			// Multi-drag selection			if (!dragStarted) {				if (options.multiDragKey && !this.multiDragKeyDown) {					this._deselectMultiDrag();				}				toggleClass(dragEl, options.selectedClass, !~multiDragElements.indexOf(dragEl));				if (!~multiDragElements.indexOf(dragEl)) {					multiDragElements.push(dragEl);					dispatchEvent({						sortable,						rootEl,						name: 'select',						targetEl: dragEl,						originalEvt: evt					});					// Modifier activated, select from last to dragEl					if (evt.shiftKey && lastMultiDragSelect && sortable.el.contains(lastMultiDragSelect)) {						let lastIndex = index(lastMultiDragSelect),							currentIndex = index(dragEl);						if (~lastIndex && ~currentIndex && lastIndex !== currentIndex) {							// Must include lastMultiDragSelect (select it), in case modified selection from no selection							// (but previous selection existed)							let n, i;							if (currentIndex > lastIndex) {								i = lastIndex;								n = currentIndex;							} else {								i = currentIndex;								n = lastIndex + 1;							}							for (; i < n; i++) {								if (~multiDragElements.indexOf(children[i])) continue;								toggleClass(children[i], options.selectedClass, true);								multiDragElements.push(children[i]);								dispatchEvent({									sortable,									rootEl,									name: 'select',									targetEl: children[i],									originalEvt: evt								});							}						}					} else {						lastMultiDragSelect = dragEl;					}					multiDragSortable = toSortable;				} else {					multiDragElements.splice(multiDragElements.indexOf(dragEl), 1);					lastMultiDragSelect = null;					dispatchEvent({						sortable,						rootEl,						name: 'deselect',						targetEl: dragEl,						originalEvt: evt					});				}			}			// Multi-drag drop			if (dragStarted && this.isMultiDrag) {				// Do not "unfold" after around dragEl if reverted				if ((parentEl[expando].options.sort || parentEl !== rootEl) && multiDragElements.length > 1) {					let dragRect = getRect(dragEl),						multiDragIndex = index(dragEl, ':not(.' + this.options.selectedClass + ')');					if (!initialFolding && options.animation) dragEl.thisAnimationDuration = null;					toSortable.captureAnimationState();					if (!initialFolding) {						if (options.animation) {							dragEl.fromRect = dragRect;							multiDragElements.forEach(multiDragElement => {								multiDragElement.thisAnimationDuration = null;								if (multiDragElement !== dragEl) {									let rect = folding ? getRect(multiDragElement) : dragRect;									multiDragElement.fromRect = rect;									// Prepare unfold animation									toSortable.addAnimationState({										target: multiDragElement,										rect: rect									});								}							});						}						// Multi drag elements are not necessarily removed from the DOM on drop, so to reinsert						// properly they must all be removed						removeMultiDragElements();						multiDragElements.forEach(multiDragElement => {							if (children[multiDragIndex]) {								parentEl.insertBefore(multiDragElement, children[multiDragIndex]);							} else {								parentEl.appendChild(multiDragElement);							}							multiDragIndex++;						});						// If initial folding is done, the elements may have changed position because they are now						// unfolding around dragEl, even though dragEl may not have his index changed, so update event						// must be fired here as Sortable will not.						if (oldIndex === index(dragEl)) {							let update = false;							multiDragElements.forEach(multiDragElement => {								if (multiDragElement.sortableIndex !== index(multiDragElement)) {									update = true;									return;								}							});							if (update) {								dispatchSortableEvent('update');							}						}					}					// Must be done after capturing individual rects (scroll bar)					multiDragElements.forEach(multiDragElement => {						unsetRect(multiDragElement);					});					toSortable.animateAll();				}				multiDragSortable = toSortable;			}			// Remove clones if necessary			if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {				multiDragClones.forEach(clone => {					clone.parentNode && clone.parentNode.removeChild(clone);				});			}		},		nullingGlobal() {			this.isMultiDrag =			dragStarted = false;			multiDragClones.length = 0;		},		destroyGlobal() {			this._deselectMultiDrag();			off(document, 'pointerup', this._deselectMultiDrag);			off(document, 'mouseup', this._deselectMultiDrag);			off(document, 'touchend', this._deselectMultiDrag);			off(document, 'keydown', this._checkKeyDown);			off(document, 'keyup', this._checkKeyUp);		},		_deselectMultiDrag(evt) {			if (typeof dragStarted !== "undefined" && dragStarted) return;			// Only deselect if selection is in this sortable			if (multiDragSortable !== this.sortable) return;			// Only deselect if target is not item in this sortable			if (evt && closest(evt.target, this.options.draggable, this.sortable.el, false)) return;			// Only deselect if left click			if (evt && evt.button !== 0) return;			while (multiDragElements.length) {				let el = multiDragElements[0];				toggleClass(el, this.options.selectedClass, false);				multiDragElements.shift();				dispatchEvent({					sortable: this.sortable,					rootEl: this.sortable.el,					name: 'deselect',					targetEl: el,					originalEvt: evt				});			}		},		_checkKeyDown(evt) {			if (evt.key === this.options.multiDragKey) {				this.multiDragKeyDown = true;			}		},		_checkKeyUp(evt) {			if (evt.key === this.options.multiDragKey) {				this.multiDragKeyDown = false;			}		}	};	return Object.assign(MultiDrag, {		// Static methods & properties		pluginName: 'multiDrag',		utils: {			/**			 * Selects the provided multi-drag item			 * @param  {HTMLElement} el    The element to be selected			 */			select(el) {				let sortable = el.parentNode[expando];				if (!sortable || !sortable.options.multiDrag || ~multiDragElements.indexOf(el)) return;				if (multiDragSortable && multiDragSortable !== sortable) {					multiDragSortable.multiDrag._deselectMultiDrag();					multiDragSortable = sortable;				}				toggleClass(el, sortable.options.selectedClass, true);				multiDragElements.push(el);			},			/**			 * Deselects the provided multi-drag item			 * @param  {HTMLElement} el    The element to be deselected			 */			deselect(el) {				let sortable = el.parentNode[expando],					index = multiDragElements.indexOf(el);				if (!sortable || !sortable.options.multiDrag || !~index) return;				toggleClass(el, sortable.options.selectedClass, false);				multiDragElements.splice(index, 1);			}		},		eventProperties() {			const oldIndicies = [],				newIndicies = [];			multiDragElements.forEach(multiDragElement => {				oldIndicies.push({					multiDragElement,					index: multiDragElement.sortableIndex				});				// multiDragElements will already be sorted if folding				let newIndex;				if (folding && multiDragElement !== dragEl) {					newIndex = -1;				} else if (folding) {					newIndex = index(multiDragElement, ':not(.' + this.options.selectedClass + ')');				} else {					newIndex = index(multiDragElement);				}				newIndicies.push({					multiDragElement,					index: newIndex				});			});			return {				items: [...multiDragElements],				clones: [...multiDragClones],				oldIndicies,				newIndicies			};		},		optionListeners: {			multiDragKey(key) {				key = key.toLowerCase();				if (key === 'ctrl') {					key = 'Control';				} else if (key.length > 1) {					key = key.charAt(0).toUpperCase() + key.substr(1);				}				return key;			}		}	});}function insertMultiDragElements(clonesInserted, rootEl) {	multiDragElements.forEach((multiDragElement, i) => {		let target = rootEl.children[multiDragElement.sortableIndex + (clonesInserted ? Number(i) : 0)];		if (target) {			rootEl.insertBefore(multiDragElement, target);		} else {			rootEl.appendChild(multiDragElement);		}	});}/** * Insert multi-drag clones * @param  {[Boolean]} elementsInserted  Whether the multi-drag elements are inserted * @param  {HTMLElement} rootEl */function insertMultiDragClones(elementsInserted, rootEl) {	multiDragClones.forEach((clone, i) => {		let target = rootEl.children[clone.sortableIndex + (elementsInserted ? Number(i) : 0)];		if (target) {			rootEl.insertBefore(clone, target);		} else {			rootEl.appendChild(clone);		}	});}function removeMultiDragElements() {	multiDragElements.forEach(multiDragElement => {		if (multiDragElement === dragEl) return;		multiDragElement.parentNode && multiDragElement.parentNode.removeChild(multiDragElement);	});}export default MultiDragPlugin;
 |