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;
|