1842 lines
54 KiB
JavaScript
1842 lines
54 KiB
JavaScript
|
import { reactive, openBlock, createBlock, resolveDynamicComponent, normalizeClass, createSlots, withCtx, renderSlot, normalizeProps, guardReactiveProps, createElementBlock, createCommentVNode, renderList, TransitionGroup, h, nextTick } from 'vue';
|
||
|
|
||
|
function mitt(n){return {all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e]);},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]));},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e);}),(i=n.get("*"))&&i.slice().map(function(n){n(t,e);});}}}
|
||
|
|
||
|
/**
|
||
|
* This is the class of the global object that holds the state of the drag and drop during its progress. It emits events
|
||
|
* reporting its state evolution during the progress of the drag and drop. Its data is reactive and listeners can be
|
||
|
* attached to it using the method on.
|
||
|
*/
|
||
|
class DnD {
|
||
|
|
||
|
inProgress = false;
|
||
|
type = null;
|
||
|
data = null;
|
||
|
source = null;
|
||
|
top = null;
|
||
|
position = null;
|
||
|
eventBus = mitt();
|
||
|
success = null;
|
||
|
|
||
|
startDrag (source, event, x, y, type, data) {
|
||
|
this.type = type;
|
||
|
this.data = data;
|
||
|
this.source = source;
|
||
|
this.position = { x, y };
|
||
|
this.top = null;
|
||
|
this.inProgress = true;
|
||
|
this.emit(event, 'dragstart');
|
||
|
this.emit(event, 'dragtopchanged', { previousTop: null });
|
||
|
}
|
||
|
|
||
|
resetVariables () {
|
||
|
this.inProgress = false;
|
||
|
this.data = null;
|
||
|
this.source = null;
|
||
|
this.position = null;
|
||
|
this.success = null;
|
||
|
}
|
||
|
|
||
|
stopDrag (event) {
|
||
|
this.success = this.top !== null && this.top['compatibleMode'] && this.top['dropAllowed'];
|
||
|
if (this.top !== null) {
|
||
|
this.emit(event, 'drop');
|
||
|
}
|
||
|
this.emit(event, 'dragend');
|
||
|
this.resetVariables();
|
||
|
}
|
||
|
|
||
|
cancelDrag (event) {
|
||
|
this.success = false;
|
||
|
this.emit(event, 'dragend');
|
||
|
this.resetVariables();
|
||
|
}
|
||
|
|
||
|
mouseMove (event, comp) {
|
||
|
if (this.inProgress) {
|
||
|
let prevent = false;
|
||
|
const previousTop = this.top;
|
||
|
if (comp === null) {
|
||
|
// The mouse move event reached the top of the document without hitting a drop component.
|
||
|
this.top = null;
|
||
|
prevent = true;
|
||
|
}
|
||
|
else if (comp['isDropMask']) {
|
||
|
// The mouse move event bubbled until it reached a drop mask.
|
||
|
this.top = null;
|
||
|
prevent = true;
|
||
|
}
|
||
|
else if (comp['candidate'](this.type, this.data, this.source)) {
|
||
|
// The mouse move event bubbled until it reached a drop component that participates in the current drag operation.
|
||
|
this.top = comp;
|
||
|
prevent = true;
|
||
|
}
|
||
|
|
||
|
if (prevent) {
|
||
|
// We prevent the mouse move event from bubbling further up the tree because it reached the foremost drop component and that component is all that matters.
|
||
|
event.stopPropagation();
|
||
|
}
|
||
|
if (this.top !== previousTop) {
|
||
|
this.emit(event.detail.native, 'dragtopchanged', { previousTop: previousTop });
|
||
|
}
|
||
|
this.position = {
|
||
|
x: event.detail.x,
|
||
|
y: event.detail.y
|
||
|
};
|
||
|
this.emit(event.detail.native, 'dragpositionchanged');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
emit (native, event, data = {}) {
|
||
|
this.eventBus.emit(event, {
|
||
|
type: this.type,
|
||
|
data: this.data,
|
||
|
top: this.top,
|
||
|
source: this.source,
|
||
|
position: this.position,
|
||
|
success: this.success,
|
||
|
native,
|
||
|
...data
|
||
|
});
|
||
|
}
|
||
|
|
||
|
on (event, callback) {
|
||
|
this.eventBus.on(event, callback);
|
||
|
}
|
||
|
|
||
|
off (event, callback) {
|
||
|
this.eventBus.off(event, callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const dnd = reactive(new DnD());
|
||
|
|
||
|
var DragAwareMixin = {
|
||
|
data () {
|
||
|
return {
|
||
|
isDropMask: false
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
dragInProgress () {
|
||
|
return dnd.inProgress;
|
||
|
},
|
||
|
dragData () {
|
||
|
return dnd.data;
|
||
|
},
|
||
|
dragType () {
|
||
|
return dnd.type;
|
||
|
},
|
||
|
dragPosition () {
|
||
|
return dnd.position;
|
||
|
},
|
||
|
dragSource () {
|
||
|
return dnd.source;
|
||
|
},
|
||
|
dragTop () {
|
||
|
return dnd.top;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This files contains the primitives required to create drag images from HTML elements that serve as models. A snapshot
|
||
|
* of the computed styles of the model elements is taken when creating the drag image, so that it will look the same as
|
||
|
* the model, no matter where the drag images is grafted into the DOM.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Creates a drag image using the given element as model.
|
||
|
*/
|
||
|
function createDragImage (el) {
|
||
|
const clone = deepClone(el);
|
||
|
clone.style.position = 'fixed';
|
||
|
clone.style.margin = '0';
|
||
|
clone.style['z-index'] = '1000';
|
||
|
clone.style.transition = 'opacity 0.2s';
|
||
|
return clone;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clones the given element and all its descendants.
|
||
|
*/
|
||
|
function deepClone (el) {
|
||
|
const clone = el.cloneNode(true);
|
||
|
copyStyle(el, clone);
|
||
|
const vSrcElements = el.getElementsByTagName('*');
|
||
|
const vDstElements = clone.getElementsByTagName('*');
|
||
|
for (let i = vSrcElements.length; i--;) {
|
||
|
const vSrcElement = vSrcElements[i];
|
||
|
const vDstElement = vDstElements[i];
|
||
|
copyStyle(vSrcElement, vDstElement);
|
||
|
}
|
||
|
return clone;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copy the computed styles from src to destination.
|
||
|
*/
|
||
|
function copyStyle (src, destination) {
|
||
|
const computedStyle = window.getComputedStyle(src);
|
||
|
for (const key of computedStyle) {
|
||
|
if (key === 'width') {
|
||
|
// IE11
|
||
|
const width = computedStyle.getPropertyValue('box-sizing') === 'border-box' ?
|
||
|
src.clientWidth :
|
||
|
src.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
|
||
|
destination.style.setProperty('width', width + 'px');
|
||
|
}
|
||
|
else if (key === 'height') {
|
||
|
// IE11
|
||
|
const height = computedStyle.getPropertyValue('box-sizing') === 'border-box' ?
|
||
|
src.clientHeight :
|
||
|
src.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
|
||
|
destination.style.setProperty('height', height + 'px');
|
||
|
}
|
||
|
else {
|
||
|
destination.style.setProperty(key, computedStyle.getPropertyValue(key), computedStyle.getPropertyPriority(key));
|
||
|
}
|
||
|
}
|
||
|
destination.style.pointerEvents = 'none';
|
||
|
}
|
||
|
|
||
|
// Forked from https://gist.github.com/gre/296291b8ce0d8fe6e1c3ea4f1d1c5c3b
|
||
|
const regex = /(auto|scroll)/;
|
||
|
|
||
|
const style = (node, prop) =>
|
||
|
getComputedStyle(node, null).getPropertyValue(prop);
|
||
|
|
||
|
const scroll = (node) =>
|
||
|
regex.test(
|
||
|
style(node, 'overflow') +
|
||
|
style(node, 'overflow-y') +
|
||
|
style(node, 'overflow-x'));
|
||
|
|
||
|
const scrollparent = (node) =>
|
||
|
!node || node===document.body
|
||
|
? document.body
|
||
|
: scroll(node)
|
||
|
? node
|
||
|
: scrollparent(node.parentNode);
|
||
|
|
||
|
// Forked from https://github.com/bennadel/JavaScript-Demos/blob/master/demos/window-edge-scrolling/index.htm
|
||
|
// Code was altered to work with scrollable containers
|
||
|
|
||
|
var timer = null;
|
||
|
|
||
|
function cancelScrollAction () {
|
||
|
clearTimeout(timer);
|
||
|
}
|
||
|
|
||
|
function performEdgeScroll (event, container, clientX, clientY, edgeSize) {
|
||
|
if (!container || !edgeSize) {
|
||
|
cancelScrollAction();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// NOTE: Much of the information here, with regard to document dimensions,
|
||
|
// viewport dimensions, and window scrolling is derived from JavaScript.info.
|
||
|
// I am consuming it here primarily as NOTE TO SELF.
|
||
|
// --
|
||
|
// Read More: https://javascript.info/size-and-scroll-window
|
||
|
// --
|
||
|
// CAUTION: The viewport and document dimensions can all be CACHED and then
|
||
|
// recalculated on window-resize events (for the most part). I am keeping it
|
||
|
// all here in the mousemove event handler to remove as many of the moving
|
||
|
// parts as possible and keep the demo as simple as possible.
|
||
|
|
||
|
// Get the viewport-relative coordinates of the mousemove event.
|
||
|
var rect = container.getBoundingClientRect();
|
||
|
var isBody = container === document.body;
|
||
|
|
||
|
var viewportX = clientX - rect.left;
|
||
|
var viewportY = clientY - rect.top;
|
||
|
if (isBody) {
|
||
|
viewportX = clientX;
|
||
|
viewportY = clientY;
|
||
|
}
|
||
|
|
||
|
// Get the viewport dimensions.
|
||
|
var viewportWidth = rect.width;
|
||
|
var viewportHeight = rect.height;
|
||
|
if (isBody) {
|
||
|
viewportWidth = document.documentElement.clientWidth;
|
||
|
viewportHeight = document.documentElement.clientHeight;
|
||
|
}
|
||
|
|
||
|
// Next, we need to determine if the mouse is within the "edge" of the
|
||
|
// viewport, which may require scrolling the window. To do this, we need to
|
||
|
// calculate the boundaries of the edge in the viewport (these coordinates
|
||
|
// are relative to the viewport grid system).
|
||
|
var edgeTop = edgeSize;
|
||
|
var edgeLeft = edgeSize;
|
||
|
var edgeBottom = ( viewportHeight - edgeSize );
|
||
|
var edgeRight = ( viewportWidth - edgeSize );
|
||
|
|
||
|
var isInLeftEdge = ( viewportX < edgeLeft );
|
||
|
var isInRightEdge = ( viewportX > edgeRight );
|
||
|
var isInTopEdge = ( viewportY < edgeTop );
|
||
|
var isInBottomEdge = ( viewportY > edgeBottom );
|
||
|
|
||
|
// If the mouse is not in the viewport edge, there's no need to calculate
|
||
|
// anything else.
|
||
|
if (!(isInLeftEdge || isInRightEdge || isInTopEdge || isInBottomEdge)) {
|
||
|
cancelScrollAction();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If we made it this far, the user's mouse is located within the edge of the
|
||
|
// viewport. As such, we need to check to see if scrolling needs to be done.
|
||
|
|
||
|
// Get the document dimensions.
|
||
|
var documentWidth = Math.max(
|
||
|
container.scrollWidth,
|
||
|
container.offsetWidth,
|
||
|
container.clientWidth
|
||
|
);
|
||
|
var documentHeight = Math.max(
|
||
|
container.scrollHeight,
|
||
|
container.offsetHeight,
|
||
|
container.clientHeight
|
||
|
);
|
||
|
|
||
|
// Calculate the maximum scroll offset in each direction. Since you can only
|
||
|
// scroll the overflow portion of the document, the maximum represents the
|
||
|
// length of the document that is NOT in the viewport.
|
||
|
var maxScrollX = (documentWidth - viewportWidth);
|
||
|
var maxScrollY = (documentHeight - viewportHeight);
|
||
|
|
||
|
// As we examine the mousemove event, we want to adjust the window scroll in
|
||
|
// immediate response to the event; but, we also want to continue adjusting
|
||
|
// the window scroll if the user rests their mouse in the edge boundary. To
|
||
|
// do this, we'll invoke the adjustment logic immediately. Then, we'll setup
|
||
|
// a timer that continues to invoke the adjustment logic while the window can
|
||
|
// still be scrolled in a particular direction.
|
||
|
(function checkForWindowScroll () {
|
||
|
cancelScrollAction();
|
||
|
|
||
|
if (adjustWindowScroll()) {
|
||
|
timer = setTimeout( checkForWindowScroll, 30 );
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
// Adjust the window scroll based on the user's mouse position. Returns True
|
||
|
// or False depending on whether or not the window scroll was changed.
|
||
|
function adjustWindowScroll () {
|
||
|
// Get the current scroll position of the document.
|
||
|
var currentScrollX = container.scrollLeft;
|
||
|
var currentScrollY = container.scrollTop;
|
||
|
if (isBody) {
|
||
|
currentScrollX = window.pageXOffset;
|
||
|
currentScrollY = window.pageYOffset;
|
||
|
}
|
||
|
|
||
|
// Determine if the window can be scrolled in any particular direction.
|
||
|
var canScrollUp = (currentScrollY > 0);
|
||
|
var canScrollDown = (currentScrollY < maxScrollY);
|
||
|
var canScrollLeft = (currentScrollX > 0);
|
||
|
var canScrollRight = (currentScrollX < maxScrollX);
|
||
|
|
||
|
// Since we can potentially scroll in two directions at the same time,
|
||
|
// let's keep track of the next scroll, starting with the current scroll.
|
||
|
// Each of these values can then be adjusted independently in the logic
|
||
|
// below.
|
||
|
var nextScrollX = currentScrollX;
|
||
|
var nextScrollY = currentScrollY;
|
||
|
|
||
|
// As we examine the mouse position within the edge, we want to make the
|
||
|
// incremental scroll changes more "intense" the closer that the user
|
||
|
// gets the viewport edge. As such, we'll calculate the percentage that
|
||
|
// the user has made it "through the edge" when calculating the delta.
|
||
|
// Then, that use that percentage to back-off from the "max" step value.
|
||
|
var maxStep = 50;
|
||
|
|
||
|
// Should we scroll left?
|
||
|
if (isInLeftEdge && canScrollLeft) {
|
||
|
const intensity = ((edgeLeft - viewportX) / edgeSize);
|
||
|
nextScrollX = (nextScrollX - (maxStep * intensity));
|
||
|
}
|
||
|
// Should we scroll right?
|
||
|
else if (isInRightEdge && canScrollRight) {
|
||
|
const intensity = ((viewportX - edgeRight) / edgeSize);
|
||
|
nextScrollX = (nextScrollX + (maxStep * intensity));
|
||
|
}
|
||
|
|
||
|
// Should we scroll up?
|
||
|
if (isInTopEdge && canScrollUp) {
|
||
|
const intensity = ((edgeTop - viewportY) / edgeSize);
|
||
|
nextScrollY = (nextScrollY - (maxStep * intensity));
|
||
|
}
|
||
|
// Should we scroll down?
|
||
|
else if (isInBottomEdge && canScrollDown) {
|
||
|
const intensity = ((viewportY - edgeBottom) / edgeSize);
|
||
|
nextScrollY = (nextScrollY + (maxStep * intensity));
|
||
|
}
|
||
|
|
||
|
// Sanitize invalid maximums. An invalid scroll offset won't break the
|
||
|
// subsequent .scrollTo() call; however, it will make it harder to
|
||
|
// determine if the .scrollTo() method should have been called in the
|
||
|
// first place.
|
||
|
nextScrollX = Math.max(0, Math.min(maxScrollX, nextScrollX));
|
||
|
nextScrollY = Math.max(0, Math.min(maxScrollY, nextScrollY));
|
||
|
|
||
|
if (
|
||
|
(nextScrollX !== currentScrollX) ||
|
||
|
(nextScrollY !== currentScrollY)
|
||
|
) {
|
||
|
(isBody ? window : container).scrollTo(nextScrollX, nextScrollY);
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
var DragMixin = {
|
||
|
mixins: [DragAwareMixin],
|
||
|
props: {
|
||
|
type: {
|
||
|
type: String,
|
||
|
default: null
|
||
|
},
|
||
|
data: {
|
||
|
default: null
|
||
|
},
|
||
|
dragImageOpacity: {
|
||
|
type: Number,
|
||
|
default: 0.7
|
||
|
},
|
||
|
disabled: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
goBack: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
handle: {
|
||
|
type: String,
|
||
|
default: null
|
||
|
},
|
||
|
delta: {
|
||
|
type: Number,
|
||
|
default: 0
|
||
|
},
|
||
|
delay: {
|
||
|
type: Number,
|
||
|
default: 0
|
||
|
},
|
||
|
dragClass: {
|
||
|
type: String,
|
||
|
default: null
|
||
|
},
|
||
|
vibration: {
|
||
|
type: Number,
|
||
|
default: 0
|
||
|
},
|
||
|
scrollingEdgeSize: {
|
||
|
type: Number,
|
||
|
default: 100
|
||
|
}
|
||
|
},
|
||
|
emits: ['dragstart', 'dragend', 'cut', 'copy'],
|
||
|
data () {
|
||
|
return {
|
||
|
dragInitialised: false,
|
||
|
dragStarted: false,
|
||
|
ignoreNextClick: false,
|
||
|
initialUserSelect: null,
|
||
|
downEvent: null,
|
||
|
startPosition: null,
|
||
|
delayTimer: null,
|
||
|
scrollContainer: null
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
cssClasses () {
|
||
|
const clazz = {
|
||
|
'dnd-drag': true
|
||
|
};
|
||
|
if (!this.disabled) {
|
||
|
return {
|
||
|
...clazz,
|
||
|
'drag-source': this.dragInProgress && this.dragSource === this,
|
||
|
'drag-mode-copy': this.currentDropMode === 'copy',
|
||
|
'drag-mode-cut': this.currentDropMode === 'cut',
|
||
|
'drag-mode-reordering': this.currentDropMode === 'reordering',
|
||
|
'drag-no-handle': !this.handle
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
return clazz;
|
||
|
}
|
||
|
},
|
||
|
currentDropMode () {
|
||
|
if (this.dragInProgress && this.dragSource === this) {
|
||
|
if (this.dragTop && this.dragTop['dropAllowed']) {
|
||
|
if (this.dragTop['reordering']) {
|
||
|
return 'reordering';
|
||
|
}
|
||
|
else {
|
||
|
return this.dragTop['mode'];
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
methods: {
|
||
|
onSelectStart (e) {
|
||
|
e.stopPropagation();
|
||
|
e.preventDefault();
|
||
|
},
|
||
|
performVibration () {
|
||
|
// If browser can perform vibration and user has defined a vibration, perform it
|
||
|
if (this.vibration > 0 && window.navigator && window.navigator.vibrate) {
|
||
|
window.navigator.vibrate(this.vibration);
|
||
|
}
|
||
|
},
|
||
|
onMouseDown (e) {
|
||
|
let target = null;
|
||
|
let goodButton = false;
|
||
|
if (e.type === 'mousedown') {
|
||
|
const mouse = e;
|
||
|
target = e.target;
|
||
|
goodButton = mouse.buttons === 1;
|
||
|
}
|
||
|
else {
|
||
|
const touch = e;
|
||
|
target = touch.touches[0].target;
|
||
|
goodButton = true;
|
||
|
}
|
||
|
|
||
|
if (this.disabled || this.downEvent !== null || !goodButton) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check that the target element is eligible for starting a drag
|
||
|
// Includes checking against the handle selector
|
||
|
// or whether the element contains 'dnd-no-drag' class (which should disable dragging from that
|
||
|
// sub-element of a draggable parent)
|
||
|
const goodTarget = !target.matches('.dnd-no-drag, .dnd-no-drag *') &&
|
||
|
(
|
||
|
!this.handle ||
|
||
|
target.matches(this.handle + ', ' + this.handle + ' *')
|
||
|
);
|
||
|
|
||
|
if (!goodTarget) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.scrollContainer = scrollparent(target);
|
||
|
this.initialUserSelect = document.body.style.userSelect;
|
||
|
document.documentElement.style.userSelect = 'none'; // Permet au drag de se poursuivre normalement même
|
||
|
// quand on quitte un élémént avec overflow: hidden.
|
||
|
this.dragStarted = false;
|
||
|
this.downEvent = e;
|
||
|
if (this.downEvent.type === 'mousedown') {
|
||
|
const mouse = e;
|
||
|
this.startPosition = {
|
||
|
x: mouse.clientX,
|
||
|
y: mouse.clientY
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
const touch = e;
|
||
|
this.startPosition = {
|
||
|
x: touch.touches[0].clientX,
|
||
|
y: touch.touches[0].clientY
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (this.delay) {
|
||
|
this.dragInitialised = false;
|
||
|
clearTimeout(this.delayTimer);
|
||
|
this.delayTimer = setTimeout(() => {
|
||
|
this.dragInitialised = true;
|
||
|
this.performVibration();
|
||
|
}, this.delay);
|
||
|
}
|
||
|
else {
|
||
|
this.dragInitialised = true;
|
||
|
this.performVibration();
|
||
|
}
|
||
|
|
||
|
document.addEventListener('click', this.onMouseClick, true);
|
||
|
document.addEventListener('mouseup', this.onMouseUp);
|
||
|
document.addEventListener('touchend', this.onMouseUp);
|
||
|
document.addEventListener('selectstart', this.onSelectStart);
|
||
|
document.addEventListener('keyup', this.onKeyUp);
|
||
|
|
||
|
setTimeout(() => {
|
||
|
document.addEventListener('mousemove', this.onMouseMove);
|
||
|
document.addEventListener('touchmove', this.onMouseMove, { passive: false });
|
||
|
document.addEventListener('easy-dnd-move', this.onEasyDnDMove);
|
||
|
}, 0);
|
||
|
|
||
|
// Prevents event from bubbling to ancestor drag components and initiate several drags at the same time
|
||
|
e.stopPropagation();
|
||
|
},
|
||
|
// Prevent the user from accidentally causing a click event
|
||
|
// if they have just attempted a drag event
|
||
|
onMouseClick (e) {
|
||
|
if (this.ignoreNextClick) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation && e.stopPropagation();
|
||
|
e.stopImmediatePropagation && e.stopImmediatePropagation();
|
||
|
this.ignoreNextClick = false;
|
||
|
return false;
|
||
|
}
|
||
|
},
|
||
|
onMouseMove (e) {
|
||
|
// We ignore the mousemove event that follows touchend :
|
||
|
if (this.downEvent === null) return;
|
||
|
|
||
|
// On touch devices, we ignore fake mouse events and deal with touch events only.
|
||
|
if (this.downEvent.type === 'touchstart' && e.type === 'mousemove') return;
|
||
|
|
||
|
// Find out event target and pointer position :
|
||
|
let target = null;
|
||
|
let x = null;
|
||
|
let y = null;
|
||
|
if (e.type === 'touchmove') {
|
||
|
const touch = e;
|
||
|
x = touch.touches[0].clientX;
|
||
|
y = touch.touches[0].clientY;
|
||
|
target = document.elementFromPoint(x, y);
|
||
|
if (!target) {
|
||
|
// Mouse going off screen. Ignore event.
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const mouse = e;
|
||
|
x = mouse.clientX;
|
||
|
y = mouse.clientY;
|
||
|
target = mouse.target;
|
||
|
}
|
||
|
|
||
|
// Distance between current event and start position :
|
||
|
const dist = Math.sqrt(Math.pow(this.startPosition.x - x, 2) + Math.pow(this.startPosition.y - y, 2));
|
||
|
|
||
|
// If the drag has not begun yet and distance from initial point is greater than delta, we start the drag :
|
||
|
if (!this.dragStarted && dist > this.delta) {
|
||
|
// If they have dragged greater than the delta before the delay period has ended,
|
||
|
// It means that they attempted to perform another action (such as scrolling) on the page
|
||
|
if (!this.dragInitialised) {
|
||
|
clearTimeout(this.delayTimer);
|
||
|
}
|
||
|
else {
|
||
|
this.ignoreNextClick = true;
|
||
|
this.dragStarted = true;
|
||
|
dnd.startDrag(this, this.downEvent, this.startPosition.x, this.startPosition.y, this.type, this.data);
|
||
|
document.documentElement.classList.add('drag-in-progress');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dispatch custom easy-dnd-move event :
|
||
|
if (this.dragStarted) {
|
||
|
// If cursor/touch is at edge of container, perform scroll if available
|
||
|
// If this.dragTop is defined, it means they are dragging on top of another DropList/EasyDnd component
|
||
|
// if dropTop is a DropList, use the scrollingEdgeSize of that container if it exists, otherwise use the scrollingEdgeSize of the Drag component
|
||
|
const currEdgeSize = this.dragTop && this.dragTop.$props.scrollingEdgeSize !== undefined ?
|
||
|
this.dragTop.$props.scrollingEdgeSize :
|
||
|
this.scrollingEdgeSize;
|
||
|
|
||
|
if (currEdgeSize) {
|
||
|
const currScrollContainer = this.dragTop ? scrollparent(this.dragTop.$el) : this.scrollContainer;
|
||
|
performEdgeScroll(e, currScrollContainer, x, y, currEdgeSize);
|
||
|
}
|
||
|
else {
|
||
|
cancelScrollAction();
|
||
|
}
|
||
|
|
||
|
const custom = new CustomEvent('easy-dnd-move', {
|
||
|
bubbles: true,
|
||
|
cancelable: true,
|
||
|
detail: {
|
||
|
x,
|
||
|
y,
|
||
|
native: e
|
||
|
}
|
||
|
});
|
||
|
target.dispatchEvent(custom);
|
||
|
}
|
||
|
|
||
|
// Prevent scroll on touch devices if they were performing a drag
|
||
|
if (this.dragInitialised && e.cancelable) {
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
},
|
||
|
onEasyDnDMove (e) {
|
||
|
dnd.mouseMove(e, null);
|
||
|
},
|
||
|
onMouseUp (e) {
|
||
|
// On touch devices, we ignore fake mouse events and deal with touch events only.
|
||
|
if (this.downEvent.type === 'touchstart' && e.type === 'mouseup') return;
|
||
|
|
||
|
// This delay makes sure that when the click event that results from the mouseup is produced, the drag is
|
||
|
// still in progress. So by checking the flag dnd.inProgress, one can tell apart true clicks from drag and
|
||
|
// drop artefacts.
|
||
|
setTimeout(() => {
|
||
|
this.cancelDragActions();
|
||
|
|
||
|
if (this.dragStarted) {
|
||
|
dnd.stopDrag(e);
|
||
|
}
|
||
|
this.finishDrag();
|
||
|
}, 0);
|
||
|
},
|
||
|
onKeyUp (e) {
|
||
|
// If ESC is pressed, cancel the drag
|
||
|
if (e.key === 'Escape') {
|
||
|
this.cancelDragActions();
|
||
|
|
||
|
setTimeout(() => {
|
||
|
dnd.cancelDrag(e);
|
||
|
this.finishDrag();
|
||
|
}, 0);
|
||
|
}
|
||
|
},
|
||
|
cancelDragActions () {
|
||
|
this.dragInitialised = false;
|
||
|
clearTimeout(this.delayTimer);
|
||
|
cancelScrollAction();
|
||
|
},
|
||
|
finishDrag () {
|
||
|
this.downEvent = null;
|
||
|
this.scrollContainer = null;
|
||
|
|
||
|
if (this.dragStarted) {
|
||
|
document.documentElement.classList.remove('drag-in-progress');
|
||
|
}
|
||
|
document.removeEventListener('click', this.onMouseClick, true);
|
||
|
document.removeEventListener('mousemove', this.onMouseMove);
|
||
|
document.removeEventListener('touchmove', this.onMouseMove);
|
||
|
document.removeEventListener('easy-dnd-move', this.onEasyDnDMove);
|
||
|
document.removeEventListener('mouseup', this.onMouseUp);
|
||
|
document.removeEventListener('touchend', this.onMouseUp);
|
||
|
document.removeEventListener('selectstart', this.onSelectStart);
|
||
|
document.removeEventListener('keyup', this.onKeyUp);
|
||
|
document.documentElement.style.userSelect = this.initialUserSelect;
|
||
|
},
|
||
|
dndDragStart (ev) {
|
||
|
if (ev.source === this) {
|
||
|
this.$emit('dragstart', ev);
|
||
|
}
|
||
|
},
|
||
|
dndDragEnd (ev) {
|
||
|
if (ev.source === this) {
|
||
|
this.$emit('dragend', ev);
|
||
|
}
|
||
|
},
|
||
|
createDragImage (selfTransform) {
|
||
|
let image;
|
||
|
if (this.$slots['drag-image']) {
|
||
|
const el = this.$refs['drag-image'] || document.createElement('div');
|
||
|
if (el.childElementCount !== 1) {
|
||
|
image = createDragImage(el);
|
||
|
}
|
||
|
else {
|
||
|
image = createDragImage(el.children.item(0));
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
image = createDragImage(this.$el);
|
||
|
image.style.transform = selfTransform;
|
||
|
}
|
||
|
|
||
|
if (this.dragClass) {
|
||
|
image.classList.add(this.dragClass);
|
||
|
}
|
||
|
image.classList.add('dnd-ghost');
|
||
|
image['__opacity'] = this.dragImageOpacity;
|
||
|
return image;
|
||
|
}
|
||
|
},
|
||
|
created () {
|
||
|
dnd.on('dragstart', this.dndDragStart);
|
||
|
dnd.on('dragend', this.dndDragEnd);
|
||
|
},
|
||
|
mounted () {
|
||
|
this.$el.addEventListener('mousedown', this.onMouseDown);
|
||
|
this.$el.addEventListener('touchstart', this.onMouseDown);
|
||
|
},
|
||
|
beforeUnmount () {
|
||
|
dnd.off('dragstart', this.dndDragStart);
|
||
|
dnd.off('dragend', this.dndDragEnd);
|
||
|
|
||
|
this.$el.removeEventListener('mousedown', this.onMouseDown);
|
||
|
this.$el.removeEventListener('touchstart', this.onMouseDown);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var script$4 = {
|
||
|
name: 'Drag',
|
||
|
mixins: [DragMixin],
|
||
|
props: {
|
||
|
/**
|
||
|
* Tag to be used as root of this component. Defaults to div.
|
||
|
*/
|
||
|
tag: {
|
||
|
type: [String, Object, Function],
|
||
|
default: 'div'
|
||
|
}
|
||
|
},
|
||
|
computed: {
|
||
|
dynamicSlots () {
|
||
|
return Object.entries(this.$slots).filter(([key]) => key !== 'drag-image' && key !== 'default');
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const _hoisted_1$2 = {
|
||
|
key: 0,
|
||
|
ref: "drag-image",
|
||
|
class: "__drag-image"
|
||
|
};
|
||
|
|
||
|
function render$3(_ctx, _cache, $props, $setup, $data, $options) {
|
||
|
return (openBlock(), createBlock(resolveDynamicComponent($props.tag), {
|
||
|
class: normalizeClass(_ctx.cssClasses)
|
||
|
}, createSlots({
|
||
|
default: withCtx(() => [
|
||
|
renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(_ctx.$slots['default'] || {}))),
|
||
|
(_ctx.dragInitialised)
|
||
|
? (openBlock(), createElementBlock("div", _hoisted_1$2, [
|
||
|
renderSlot(_ctx.$slots, "drag-image")
|
||
|
], 512 /* NEED_PATCH */))
|
||
|
: createCommentVNode("v-if", true)
|
||
|
]),
|
||
|
_: 2 /* DYNAMIC */
|
||
|
}, [
|
||
|
renderList($options.dynamicSlots, ([slot, args]) => {
|
||
|
return {
|
||
|
name: slot,
|
||
|
fn: withCtx(() => [
|
||
|
renderSlot(_ctx.$slots, slot, normalizeProps(guardReactiveProps(args)))
|
||
|
])
|
||
|
}
|
||
|
})
|
||
|
]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["class"]))
|
||
|
}
|
||
|
|
||
|
script$4.render = render$3;
|
||
|
script$4.__scopeId = "data-v-f87407ce";
|
||
|
|
||
|
function dropAllowed (inst) {
|
||
|
if (inst.dragInProgress && inst.typeAllowed) {
|
||
|
return inst.compatibleMode && inst.effectiveAcceptsData(inst.dragData, inst.dragType);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function doDrop (inst, event) {
|
||
|
inst.$emit('drop', event);
|
||
|
event.source.$emit(inst.mode, event);
|
||
|
}
|
||
|
|
||
|
function candidate (inst, type) {
|
||
|
return inst.effectiveAcceptsType(type);
|
||
|
}
|
||
|
|
||
|
var DropMixin = {
|
||
|
mixins: [DragAwareMixin],
|
||
|
props: {
|
||
|
acceptsType: {
|
||
|
type: [String, Array, Function],
|
||
|
default: null
|
||
|
},
|
||
|
acceptsData: {
|
||
|
type: Function,
|
||
|
default: () => {
|
||
|
return true;
|
||
|
}
|
||
|
},
|
||
|
mode: {
|
||
|
type: String,
|
||
|
default: 'copy'
|
||
|
},
|
||
|
dragImageOpacity: {
|
||
|
type: Number,
|
||
|
default: 0.7
|
||
|
}
|
||
|
},
|
||
|
emits: ['dragover', 'dragenter', 'dragleave', 'dragend', 'drop'],
|
||
|
data () {
|
||
|
return {
|
||
|
isDrop: true
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
compatibleMode () {
|
||
|
return this.dragInProgress ? true : null;
|
||
|
},
|
||
|
dropIn () {
|
||
|
if (this.dragInProgress) {
|
||
|
return this.dragTop === this;
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
typeAllowed () {
|
||
|
if (this.dragInProgress) {
|
||
|
return this.effectiveAcceptsType(this.dragType);
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
dropAllowed () {
|
||
|
return dropAllowed(this);
|
||
|
},
|
||
|
cssClasses () {
|
||
|
const clazz = {
|
||
|
'dnd-drop': true
|
||
|
};
|
||
|
if (this.dropIn !== null) {
|
||
|
clazz['drop-in'] = this.dropIn;
|
||
|
clazz['drop-out'] = !this.dropIn;
|
||
|
}
|
||
|
if (this.typeAllowed !== null) {
|
||
|
clazz['type-allowed'] = this.typeAllowed;
|
||
|
clazz['type-forbidden'] = !this.typeAllowed;
|
||
|
}
|
||
|
if (this.dropAllowed !== null) {
|
||
|
clazz['drop-allowed'] = this.dropAllowed;
|
||
|
clazz['drop-forbidden'] = !this.dropAllowed;
|
||
|
}
|
||
|
return clazz;
|
||
|
}
|
||
|
},
|
||
|
methods: {
|
||
|
effectiveAcceptsType (type) {
|
||
|
if (this.acceptsType === null) {
|
||
|
return true;
|
||
|
}
|
||
|
else if (typeof (this.acceptsType) === 'string' || typeof(this.acceptsType) === 'number') {
|
||
|
return this.acceptsType === type;
|
||
|
}
|
||
|
else if (typeof (this.acceptsType) === 'object' && Array.isArray(this.acceptsType)) {
|
||
|
return this.acceptsType.includes(type);
|
||
|
}
|
||
|
else {
|
||
|
return this.acceptsType(type);
|
||
|
}
|
||
|
},
|
||
|
effectiveAcceptsData (data, type) {
|
||
|
return this.acceptsData(data, type);
|
||
|
},
|
||
|
onDragPositionChanged (event) {
|
||
|
if (this === event.top) {
|
||
|
this.$emit('dragover', event);
|
||
|
}
|
||
|
},
|
||
|
onDragTopChanged (event) {
|
||
|
if (this === event.top) {
|
||
|
this.$emit('dragenter', event);
|
||
|
}
|
||
|
if (this === event.previousTop) {
|
||
|
this.$emit('dragleave', event);
|
||
|
}
|
||
|
},
|
||
|
onDragEnd (event) {
|
||
|
if (this === event.top) {
|
||
|
this.$emit('dragend', event);
|
||
|
}
|
||
|
},
|
||
|
onDrop (event) {
|
||
|
if (this.dropIn && this.compatibleMode && this.dropAllowed) {
|
||
|
this.doDrop(event);
|
||
|
}
|
||
|
},
|
||
|
doDrop (event) {
|
||
|
doDrop(this, event);
|
||
|
},
|
||
|
/**
|
||
|
* Returns true if the current drop area participates in the current drag operation.
|
||
|
*/
|
||
|
candidate (type) {
|
||
|
return candidate(this, type);
|
||
|
},
|
||
|
createDragImage () {
|
||
|
let image = 'source';
|
||
|
if (this.$refs['drag-image']) {
|
||
|
const el = this.$refs['drag-image'];
|
||
|
if (el.childElementCount !== 1) {
|
||
|
image = createDragImage(el);
|
||
|
}
|
||
|
else {
|
||
|
image = createDragImage(el.children.item(0));
|
||
|
}
|
||
|
image['__opacity'] = this.dragImageOpacity;
|
||
|
image.classList.add('dnd-ghost');
|
||
|
}
|
||
|
return image;
|
||
|
},
|
||
|
onDnDMove (e) {
|
||
|
dnd.mouseMove(e, this);
|
||
|
}
|
||
|
},
|
||
|
created () {
|
||
|
dnd.on('dragpositionchanged', this.onDragPositionChanged);
|
||
|
dnd.on('dragtopchanged', this.onDragTopChanged);
|
||
|
dnd.on('drop', this.onDrop);
|
||
|
dnd.on('dragend', this.onDragEnd);
|
||
|
},
|
||
|
mounted () {
|
||
|
this.$el.addEventListener('easy-dnd-move', this.onDnDMove);
|
||
|
},
|
||
|
beforeUnmount () {
|
||
|
this.$el.removeEventListener('easy-dnd-move', this.onDnDMove);
|
||
|
|
||
|
dnd.off('dragpositionchanged', this.onDragPositionChanged);
|
||
|
dnd.off('dragtopchanged', this.onDragTopChanged);
|
||
|
dnd.off('drop', this.onDrop);
|
||
|
dnd.off('dragend', this.onDragEnd);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var script$3 = {
|
||
|
name: 'Drop',
|
||
|
mixins: [DropMixin],
|
||
|
props: {
|
||
|
tag: {
|
||
|
type: [String, Object, Function],
|
||
|
default: 'div'
|
||
|
}
|
||
|
},
|
||
|
computed: {
|
||
|
dynamicSlots () {
|
||
|
return Object.entries(this.$slots).filter(([key]) => key !== 'drag-image' && key !== 'default');
|
||
|
},
|
||
|
showDragImage () {
|
||
|
return this.dragInProgress && this.typeAllowed && !!this.$slots['drag-image'];
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const _hoisted_1$1 = {
|
||
|
key: 0,
|
||
|
ref: "drag-image",
|
||
|
class: "__drag-image"
|
||
|
};
|
||
|
|
||
|
function render$2(_ctx, _cache, $props, $setup, $data, $options) {
|
||
|
return (openBlock(), createBlock(resolveDynamicComponent($props.tag), {
|
||
|
class: normalizeClass(_ctx.cssClasses)
|
||
|
}, createSlots({
|
||
|
default: withCtx(() => [
|
||
|
renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(_ctx.$slots['default'] || {}))),
|
||
|
($options.showDragImage)
|
||
|
? (openBlock(), createElementBlock("div", _hoisted_1$1, [
|
||
|
renderSlot(_ctx.$slots, "drag-image", {
|
||
|
type: _ctx.dragType,
|
||
|
data: _ctx.dragData
|
||
|
})
|
||
|
], 512 /* NEED_PATCH */))
|
||
|
: createCommentVNode("v-if", true)
|
||
|
]),
|
||
|
_: 2 /* DYNAMIC */
|
||
|
}, [
|
||
|
renderList($options.dynamicSlots, ([slot, args]) => {
|
||
|
return {
|
||
|
name: slot,
|
||
|
fn: withCtx(() => [
|
||
|
renderSlot(_ctx.$slots, slot, normalizeProps(guardReactiveProps(args)))
|
||
|
])
|
||
|
}
|
||
|
})
|
||
|
]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["class"]))
|
||
|
}
|
||
|
|
||
|
script$3.render = render$2;
|
||
|
script$3.__scopeId = "data-v-12a39e52";
|
||
|
|
||
|
var script$2 = {
|
||
|
name: 'DropMask',
|
||
|
mixins: [DragAwareMixin],
|
||
|
props: {
|
||
|
tag: {
|
||
|
type: [String, Object, Function],
|
||
|
default: 'div'
|
||
|
}
|
||
|
},
|
||
|
data () {
|
||
|
return {
|
||
|
isDropMask: true
|
||
|
};
|
||
|
},
|
||
|
mounted () {
|
||
|
this.$el.addEventListener('easy-dnd-move', this.onDndMove);
|
||
|
},
|
||
|
beforeUnmount () {
|
||
|
this.$el.removeEventListener('easy-dnd-move', this.onDndMove);
|
||
|
},
|
||
|
methods: {
|
||
|
createDragImage () {
|
||
|
return 'source';
|
||
|
},
|
||
|
onDndMove (e) {
|
||
|
dnd.mouseMove(e, this);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
||
|
return (openBlock(), createBlock(resolveDynamicComponent($props.tag), null, createSlots({ _: 2 /* DYNAMIC */ }, [
|
||
|
renderList(_ctx.$slots, (args, slot) => {
|
||
|
return {
|
||
|
name: slot,
|
||
|
fn: withCtx(() => [
|
||
|
renderSlot(_ctx.$slots, slot, normalizeProps(guardReactiveProps(args)))
|
||
|
])
|
||
|
}
|
||
|
})
|
||
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||
|
}
|
||
|
|
||
|
script$2.render = render$1;
|
||
|
|
||
|
var script$1 = {
|
||
|
name: 'DragFeedback'
|
||
|
};
|
||
|
|
||
|
const _hoisted_1 = { class: "DragFeedback" };
|
||
|
|
||
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||
|
return (openBlock(), createElementBlock("div", _hoisted_1, [
|
||
|
renderSlot(_ctx.$slots, "default")
|
||
|
]))
|
||
|
}
|
||
|
|
||
|
script$1.render = render;
|
||
|
|
||
|
class Grid {
|
||
|
reference;
|
||
|
referenceOriginalPosition;
|
||
|
magnets = [];
|
||
|
|
||
|
constructor (collection, upToIndex, direction, fromIndex) {
|
||
|
this.reference = collection.item(0).parentNode;
|
||
|
this.referenceOriginalPosition = {
|
||
|
x: this.reference.getBoundingClientRect().left - this.reference.scrollLeft,
|
||
|
y: this.reference.getBoundingClientRect().top - this.reference.scrollTop,
|
||
|
};
|
||
|
let index = 0;
|
||
|
for (const child of collection) {
|
||
|
if (index > upToIndex) break;
|
||
|
const rect = child.getBoundingClientRect();
|
||
|
const hasNestedDrop = child.classList.contains('dnd-drop') || child.getElementsByClassName('dnd-drop').length > 0;
|
||
|
let horizontal = false;
|
||
|
if (hasNestedDrop) {
|
||
|
if (direction === 'auto') {
|
||
|
// Auto mode not supported for now. Row or column must be defined explicitly if there are nested drop lists.
|
||
|
throw 'Easy-DnD error : a drop list is missing one of these attributes : \'row\' or \'column\'.';
|
||
|
}
|
||
|
else {
|
||
|
horizontal = direction === 'row';
|
||
|
}
|
||
|
}
|
||
|
if (fromIndex === null) {
|
||
|
// Inserting mode.
|
||
|
this.magnets.push(hasNestedDrop ? this.before(rect, horizontal) : this.center(rect));
|
||
|
}
|
||
|
else {
|
||
|
// Reordering mode.
|
||
|
this.magnets.push(hasNestedDrop ? (
|
||
|
fromIndex < index ? this.after : this.before
|
||
|
)(rect, horizontal) : this.center(rect));
|
||
|
}
|
||
|
// Debug : show magnets :
|
||
|
//document.body.insertAdjacentHTML("beforeend", "<div style='background-color: red; position: fixed; width: 1px; height: 1px; top:" + this.magnets[index].y + "px; left:" + this.magnets[index].x + "px;' ></div>")
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the center of the rectangle.
|
||
|
*/
|
||
|
center (rect) {
|
||
|
return {
|
||
|
x: rect.left + rect.width / 2,
|
||
|
y: rect.top + rect.height / 2
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* When horizontal is true / false, returns middle of the left / top side of the rectangle.
|
||
|
*/
|
||
|
before (rect, horizontal) {
|
||
|
return horizontal ? {
|
||
|
x: rect.left,
|
||
|
y: rect.top + rect.height / 2
|
||
|
} : {
|
||
|
x: rect.left + rect.width / 2,
|
||
|
y: rect.top
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* When horizontal is true / false, returns middle of the right / bottom side of the rectangle.
|
||
|
*/
|
||
|
after (rect, horizontal) {
|
||
|
return horizontal ? {
|
||
|
x: rect.left + rect.width,
|
||
|
y: rect.top + rect.height / 2
|
||
|
} : {
|
||
|
x: rect.left + rect.width / 2,
|
||
|
y: rect.top + rect.height
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* In case the user scrolls during the drag, the position of the magnets are not what they used to be when the drag
|
||
|
* started. A correction must be applied that takes into account the amount of scroll. This correction is the
|
||
|
* difference between the current position of the parent element and its position when the drag started.
|
||
|
*/
|
||
|
correction () {
|
||
|
return {
|
||
|
x: this.reference.getBoundingClientRect().left - this.reference.scrollLeft - this.referenceOriginalPosition.x,
|
||
|
y: this.reference.getBoundingClientRect().top - this.reference.scrollTop - this.referenceOriginalPosition.y,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
closestIndex (position) {
|
||
|
const x = position.x - this.correction().x;
|
||
|
const y = position.y - this.correction().y;
|
||
|
let minDist = 999999;
|
||
|
let index = -1;
|
||
|
for (let i = 0; i < this.magnets.length; i++) {
|
||
|
const magnet = this.magnets[i];
|
||
|
const dist = Math.sqrt(Math.pow(magnet.x - x, 2) + Math.pow(magnet.y - y, 2));
|
||
|
if (dist < minDist) {
|
||
|
minDist = dist;
|
||
|
index = i;
|
||
|
}
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class DnDEvent {
|
||
|
type;
|
||
|
data;
|
||
|
top;
|
||
|
previousTop;
|
||
|
source;
|
||
|
position;
|
||
|
success;
|
||
|
native;
|
||
|
}
|
||
|
|
||
|
class ReorderEvent {
|
||
|
from;
|
||
|
to;
|
||
|
|
||
|
constructor (from, to) {
|
||
|
this.from = from;
|
||
|
this.to = to;
|
||
|
}
|
||
|
|
||
|
apply (array) {
|
||
|
const temp = array[this.from];
|
||
|
array.splice(this.from, 1);
|
||
|
array.splice(this.to, 0, temp);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
class InsertEvent {
|
||
|
type;
|
||
|
data;
|
||
|
index;
|
||
|
|
||
|
constructor (type, data, index) {
|
||
|
this.type = type;
|
||
|
this.data = data;
|
||
|
this.index = index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var script = {
|
||
|
name: 'DropList',
|
||
|
mixins: [DropMixin],
|
||
|
props: {
|
||
|
tag: {
|
||
|
type: [String, Object, Function],
|
||
|
default: 'div'
|
||
|
},
|
||
|
items: {
|
||
|
type: Array,
|
||
|
required: true
|
||
|
},
|
||
|
row: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
column: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
noAnimations: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
scrollingEdgeSize: {
|
||
|
type: Number,
|
||
|
default: undefined
|
||
|
}
|
||
|
},
|
||
|
emits: ['reorder', 'insert'],
|
||
|
data () {
|
||
|
return {
|
||
|
grid: null,
|
||
|
forbiddenKeys: [],
|
||
|
feedbackKey: null,
|
||
|
fromIndex: null
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
rootTag () {
|
||
|
if (this.noAnimations) {
|
||
|
return this.tag;
|
||
|
}
|
||
|
return TransitionGroup;
|
||
|
},
|
||
|
rootProps () {
|
||
|
if (this.noAnimations) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
tag: this.tag,
|
||
|
css: false
|
||
|
};
|
||
|
},
|
||
|
direction () {
|
||
|
// todo - rewrite this logic
|
||
|
if (this.row) return 'row';
|
||
|
if (this.column) return 'column';
|
||
|
return 'auto';
|
||
|
},
|
||
|
reordering () {
|
||
|
if (dnd.inProgress) {
|
||
|
return dnd.source.$el.parentElement === this.$el;
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
closestIndex () {
|
||
|
if (this.grid) {
|
||
|
return this.grid.closestIndex(dnd.position);
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
dropAllowed () {
|
||
|
if (this.dragInProgress) {
|
||
|
if (this.reordering) {
|
||
|
return this.items.length > 1;
|
||
|
}
|
||
|
else {
|
||
|
// todo - eventually refactor so that this isn't necessary
|
||
|
if (!dropAllowed(this)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (this.forbiddenKeys !== null && this.feedbackKey !== null) {
|
||
|
return !this.forbiddenKeys.includes(this.feedbackKey);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
},
|
||
|
itemsBeforeFeedback () {
|
||
|
if (this.closestIndex === 0) {
|
||
|
return [];
|
||
|
}
|
||
|
return this.items.slice(0, this.closestIndex);
|
||
|
},
|
||
|
itemsAfterFeedback () {
|
||
|
if (this.closestIndex === this.items.length) {
|
||
|
return [];
|
||
|
}
|
||
|
return this.items.slice(this.closestIndex);
|
||
|
},
|
||
|
itemsBeforeReorderingFeedback () {
|
||
|
if (this.closestIndex <= this.fromIndex) {
|
||
|
return this.items.slice(0, this.closestIndex);
|
||
|
}
|
||
|
return this.items.slice(0, this.closestIndex + 1);
|
||
|
},
|
||
|
itemsAfterReorderingFeedback () {
|
||
|
if (this.closestIndex <= this.fromIndex) {
|
||
|
return this.items.slice(this.closestIndex);
|
||
|
}
|
||
|
return this.items.slice(this.closestIndex + 1);
|
||
|
},
|
||
|
reorderedItems () {
|
||
|
const toIndex = this.closestIndex;
|
||
|
const reordered = [...this.items];
|
||
|
const temp = reordered[this.fromIndex];
|
||
|
|
||
|
reordered.splice(this.fromIndex, 1);
|
||
|
reordered.splice(toIndex, 0, temp);
|
||
|
return reordered;
|
||
|
},
|
||
|
clazz () {
|
||
|
return {
|
||
|
'drop-list': true,
|
||
|
'reordering': this.reordering === true,
|
||
|
'inserting': this.reordering === false,
|
||
|
...(this.reordering === false ? this.cssClasses : { 'dnd-drop': true })
|
||
|
};
|
||
|
},
|
||
|
showDragFeedback () {
|
||
|
return this.dragInProgress && this.typeAllowed && !this.reordering;
|
||
|
},
|
||
|
showInsertingDragImage () {
|
||
|
return this.dragInProgress && this.typeAllowed && !this.reordering && !!this.$slots['drag-image'];
|
||
|
},
|
||
|
showReorderingDragImage () {
|
||
|
return this.dragInProgress && this.reordering && !!this.$slots['reordering-drag-image'];
|
||
|
},
|
||
|
hasReorderingFeedback () {
|
||
|
return !!this.$slots['reordering-feedback'];
|
||
|
},
|
||
|
hasEmptySlot () {
|
||
|
return !!this.$slots['empty'];
|
||
|
}
|
||
|
},
|
||
|
created () {
|
||
|
dnd.on('dragstart', this.onDragStart);
|
||
|
dnd.on('dragend', this.onDragEnd);
|
||
|
},
|
||
|
beforeUnmount () {
|
||
|
dnd.off('dragstart', this.onDragStart);
|
||
|
dnd.off('dragend', this.onDragEnd);
|
||
|
},
|
||
|
methods: {
|
||
|
// Presence of feedback node in the DOM and of keys in the virtual DOM required => delayed until what
|
||
|
// depends on drag data has been processed.
|
||
|
refresh () {
|
||
|
this.$nextTick(() => {
|
||
|
this.grid = this.computeInsertingGrid();
|
||
|
this.feedbackKey = this.computeFeedbackKey();
|
||
|
this.forbiddenKeys = this.computeForbiddenKeys();
|
||
|
});
|
||
|
},
|
||
|
onDragStart (event) {
|
||
|
if (this.candidate(dnd.type)) {
|
||
|
if (this.reordering) {
|
||
|
this.fromIndex = Array.prototype.indexOf.call(event.source.$el.parentElement.children, event.source.$el);
|
||
|
this.grid = this.computeReorderingGrid();
|
||
|
}
|
||
|
else {
|
||
|
this.refresh();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
onDragEnd () {
|
||
|
this.fromIndex = null;
|
||
|
this.feedbackKey = null;
|
||
|
this.forbiddenKeys = null;
|
||
|
this.grid = null;
|
||
|
},
|
||
|
doDrop (event) {
|
||
|
if (this.reordering) {
|
||
|
if (this.fromIndex !== this.closestIndex) {
|
||
|
this.$emit('reorder', new ReorderEvent(
|
||
|
this.fromIndex,
|
||
|
this.closestIndex
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// todo - eventually remove the need for this
|
||
|
doDrop(this, event);
|
||
|
this.$emit('insert', new InsertEvent(
|
||
|
event.type,
|
||
|
event.data,
|
||
|
this.closestIndex
|
||
|
));
|
||
|
}
|
||
|
},
|
||
|
candidate (type) {
|
||
|
return candidate(this, type) || this.reordering;
|
||
|
},
|
||
|
computeForbiddenKeys () {
|
||
|
return (this.noAnimations ? [] : this.$refs.component.$slots['default']())
|
||
|
.map(vn => vn.key)
|
||
|
.filter(k => !!k && k !== 'drag-image' && k !== 'drag-feedback');
|
||
|
},
|
||
|
computeFeedbackKey () {
|
||
|
return this.$refs['feedback']['$slots']['default']()[0]['key'];
|
||
|
},
|
||
|
computeInsertingGrid () {
|
||
|
if (this.$refs.feedback.$el.children.length < 1) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const feedback = this.$refs.feedback.$el.children[0];
|
||
|
const clone = feedback.cloneNode(true);
|
||
|
const tg = this.$el;
|
||
|
if (tg.children.length > this.items.length) {
|
||
|
tg.insertBefore(clone, tg.children[this.items.length]);
|
||
|
}
|
||
|
else {
|
||
|
tg.appendChild(clone);
|
||
|
}
|
||
|
const grid = new Grid(tg.children, this.items.length, this.direction, null);
|
||
|
tg.removeChild(clone);
|
||
|
return grid;
|
||
|
},
|
||
|
computeReorderingGrid () {
|
||
|
return new Grid(this.$el.children, this.items.length - 1, this.direction, this.fromIndex);
|
||
|
},
|
||
|
createDragImage () {
|
||
|
let image;
|
||
|
if (this.$refs['drag-image']) {
|
||
|
const el = this.$refs['drag-image'];
|
||
|
let model;
|
||
|
if (el.childElementCount !== 1) {
|
||
|
model = el;
|
||
|
}
|
||
|
else {
|
||
|
model = el.children.item(0);
|
||
|
}
|
||
|
const clone = model.cloneNode(true);
|
||
|
const tg = this.$el;
|
||
|
tg.appendChild(clone);
|
||
|
image = createDragImage(clone);
|
||
|
tg.removeChild(clone);
|
||
|
image['__opacity'] = this.dragImageOpacity;
|
||
|
image.classList.add('dnd-ghost');
|
||
|
}
|
||
|
else {
|
||
|
image = 'source';
|
||
|
}
|
||
|
return image;
|
||
|
}
|
||
|
},
|
||
|
render () {
|
||
|
if (!this.$slots['item']) {
|
||
|
throw 'The "Item" slot must be defined to use DropList';
|
||
|
}
|
||
|
|
||
|
if (!this.$slots['feedback']) {
|
||
|
throw 'The "Feedback" slot must be defined to use DropList';
|
||
|
}
|
||
|
|
||
|
let defaultArr = [];
|
||
|
if (this.dropIn && this.dropAllowed) {
|
||
|
if (this.reordering) {
|
||
|
if (this.hasReorderingFeedback) {
|
||
|
const itemsReorderingBefore = this.itemsBeforeReorderingFeedback.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: index,
|
||
|
reorder: false
|
||
|
})[0];
|
||
|
});
|
||
|
if (itemsReorderingBefore.length > 0) {
|
||
|
defaultArr = defaultArr.concat(itemsReorderingBefore);
|
||
|
}
|
||
|
|
||
|
defaultArr.push(this.$slots['reordering-feedback']({
|
||
|
key: 'reordering-feedback',
|
||
|
item: this.items[this.fromIndex]
|
||
|
})[0]);
|
||
|
|
||
|
const itemsReorderingAfter = this.itemsAfterReorderingFeedback.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: this.itemsBeforeReorderingFeedback.length + index,
|
||
|
reorder: false
|
||
|
})[0];
|
||
|
});
|
||
|
if (itemsReorderingAfter.length > 0) {
|
||
|
defaultArr = defaultArr.concat(itemsReorderingAfter);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const reorderedItems = this.reorderedItems.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: index,
|
||
|
reorder: index === this.closestIndex
|
||
|
})[0];
|
||
|
});
|
||
|
if (reorderedItems.length > 0) {
|
||
|
defaultArr = defaultArr.concat(reorderedItems);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const itemsBefore = this.itemsBeforeFeedback.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: index,
|
||
|
reorder: false
|
||
|
})[0];
|
||
|
});
|
||
|
if (itemsBefore.length > 0) {
|
||
|
defaultArr = defaultArr.concat(itemsBefore);
|
||
|
}
|
||
|
|
||
|
defaultArr.push(this.$slots['feedback']({
|
||
|
key: 'drag-feedback',
|
||
|
data: this.dragData,
|
||
|
type: this.dragType
|
||
|
})[0]);
|
||
|
|
||
|
const itemsAfter = this.itemsAfterFeedback.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: this.itemsBeforeFeedback.length + index,
|
||
|
reorder: false
|
||
|
})[0];
|
||
|
});
|
||
|
if (itemsAfter.length > 0) {
|
||
|
defaultArr = defaultArr.concat(itemsAfter);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const defaultItems = this.items.map((item, index) => {
|
||
|
return this.$slots['item']({
|
||
|
item: item,
|
||
|
index: index,
|
||
|
reorder: false
|
||
|
})[0];
|
||
|
});
|
||
|
|
||
|
if (defaultItems.length > 0) {
|
||
|
defaultArr = defaultArr.concat(defaultItems);
|
||
|
}
|
||
|
else if (this.hasEmptySlot) {
|
||
|
defaultArr.push(this.$slots['empty']()[0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.showDragFeedback) {
|
||
|
defaultArr.push(h(
|
||
|
script$1,
|
||
|
{
|
||
|
class: '__feedback',
|
||
|
ref: 'feedback',
|
||
|
key: 'drag-feedback'
|
||
|
},
|
||
|
{
|
||
|
default: () => this.$slots['feedback']({
|
||
|
type: this.dragType,
|
||
|
data: this.dragData
|
||
|
})[0]
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
if (this.showReorderingDragImage) {
|
||
|
defaultArr.push(h(
|
||
|
'div',
|
||
|
{
|
||
|
class: '__drag-image',
|
||
|
ref: 'drag-image',
|
||
|
key: 'reordering-drag-image'
|
||
|
},
|
||
|
{
|
||
|
default: () => this.$slots['reordering-drag-image']({
|
||
|
item: this.items[this.fromIndex]
|
||
|
})[0]
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
if (this.showInsertingDragImage) {
|
||
|
defaultArr.push(h(
|
||
|
'div',
|
||
|
{
|
||
|
class: '__drag-image',
|
||
|
ref: 'drag-image',
|
||
|
key: 'inserting-drag-image'
|
||
|
},
|
||
|
{
|
||
|
default: () => this.$slots['drag-image']({
|
||
|
type: this.dragType,
|
||
|
data: this.dragData
|
||
|
})[0]
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
return h(
|
||
|
this.rootTag,
|
||
|
{
|
||
|
ref: 'component',
|
||
|
class: this.clazz,
|
||
|
...this.rootProps
|
||
|
},
|
||
|
{
|
||
|
default: () => defaultArr
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
script.__scopeId = "data-v-230f65e3";
|
||
|
|
||
|
/**
|
||
|
* This class reacts to drag events emitted by the dnd object to manage a sequence of drag images and fade from one to the
|
||
|
* other as the drag progresses.
|
||
|
*/
|
||
|
class DragImagesManager {
|
||
|
|
||
|
selfTransform = null;
|
||
|
clones = null;
|
||
|
source = null;
|
||
|
sourcePos = null;
|
||
|
sourceClone = null;
|
||
|
|
||
|
constructor () {
|
||
|
dnd.on('dragstart', this.onDragStart.bind(this));
|
||
|
dnd.on('dragtopchanged', this.onDragTopChanged.bind(this));
|
||
|
dnd.on('dragpositionchanged', this.onDragPositionChanged.bind(this));
|
||
|
dnd.on('dragend', this.onDragEnd.bind(this));
|
||
|
}
|
||
|
|
||
|
onDragStart (event) {
|
||
|
// If go-back=true and it is still animating while they attempt another drag,
|
||
|
// it will bug out. Best to clean up any existing elements on the page before
|
||
|
// attempting to start the next animation
|
||
|
this.cleanUp();
|
||
|
|
||
|
this.sourcePos = {
|
||
|
x: event.source.$el.getBoundingClientRect().left,
|
||
|
y: event.source.$el.getBoundingClientRect().top
|
||
|
};
|
||
|
this.selfTransform = 'translate(-' + (event.position.x - this.sourcePos.x) + 'px, -' + (event.position.y - this.sourcePos.y) + 'px)';
|
||
|
this.clones = new Map();
|
||
|
this.source = event.source;
|
||
|
}
|
||
|
|
||
|
onDragEnd (event) {
|
||
|
nextTick()
|
||
|
.then(() => {
|
||
|
if (!event.success && this.source && this.source['goBack']) {
|
||
|
// Restore the drag image that is active when hovering outside any drop zone :
|
||
|
const img = this.switch(null);
|
||
|
|
||
|
// Move it back to its original place :
|
||
|
window.requestAnimationFrame(() => {
|
||
|
img.style.transition = 'all 0.5s';
|
||
|
window.requestAnimationFrame(() => {
|
||
|
img.style.left = this.sourcePos.x + 'px';
|
||
|
img.style.top = this.sourcePos.y + 'px';
|
||
|
img.style.transform = 'translate(0,0)';
|
||
|
const handler = () => {
|
||
|
this.cleanUp();
|
||
|
img.removeEventListener('transitionend', handler);
|
||
|
};
|
||
|
img.addEventListener('transitionend', handler);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
this.cleanUp();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
cleanUp () {
|
||
|
if (this.clones) {
|
||
|
this.clones.forEach((clone) => {
|
||
|
if (clone.parentNode === document.body) {
|
||
|
document.body.removeChild(clone);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (this.sourceClone !== null) {
|
||
|
if (this.sourceClone.parentNode === document.body) {
|
||
|
document.body.removeChild(this.sourceClone);
|
||
|
}
|
||
|
}
|
||
|
this.selfTransform = null;
|
||
|
this.clones = null;
|
||
|
this.source = null;
|
||
|
this.sourceClone = null;
|
||
|
this.sourcePos = null;
|
||
|
}
|
||
|
|
||
|
onDragTopChanged (event) {
|
||
|
this.switch(event.top);
|
||
|
}
|
||
|
|
||
|
switch (top) {
|
||
|
this.clones.forEach(clone => {
|
||
|
clone.style.opacity = '0';
|
||
|
});
|
||
|
if (this.sourceClone) {
|
||
|
this.sourceClone.style.opacity = '0';
|
||
|
}
|
||
|
|
||
|
let activeClone;
|
||
|
if (top === null) {
|
||
|
activeClone = this.getSourceClone();
|
||
|
}
|
||
|
else {
|
||
|
if (!this.clones.has(top)) {
|
||
|
let clone = top['createDragImage'](this.selfTransform);
|
||
|
if (clone === 'source') {
|
||
|
clone = this.getSourceClone();
|
||
|
}
|
||
|
else if (clone !== null) {
|
||
|
clone.style.opacity = '0';
|
||
|
document.body.appendChild(clone);
|
||
|
}
|
||
|
this.clones.set(top, clone);
|
||
|
}
|
||
|
activeClone = this.clones.get(top);
|
||
|
}
|
||
|
|
||
|
if (activeClone !== null) {
|
||
|
activeClone.offsetWidth; // Forces browser reflow
|
||
|
activeClone.style.opacity = activeClone['__opacity'];
|
||
|
activeClone.style.visibility = 'visible';
|
||
|
}
|
||
|
|
||
|
return activeClone;
|
||
|
}
|
||
|
|
||
|
getSourceClone () {
|
||
|
if (this.sourceClone === null) {
|
||
|
this.sourceClone = this.source['createDragImage'](this.selfTransform);
|
||
|
this.sourceClone.style.opacity = '0';
|
||
|
document.body.appendChild(this.sourceClone);
|
||
|
}
|
||
|
return this.sourceClone;
|
||
|
}
|
||
|
|
||
|
onDragPositionChanged () {
|
||
|
this.clones.forEach((clone) => {
|
||
|
clone.style.left = dnd.position.x + 'px';
|
||
|
clone.style.top = dnd.position.y + 'px';
|
||
|
});
|
||
|
if (this.sourceClone) {
|
||
|
this.sourceClone.style.left = dnd.position.x + 'px';
|
||
|
this.sourceClone.style.top = dnd.position.y + 'px';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
new DragImagesManager();
|
||
|
|
||
|
export { DnDEvent, script$4 as Drag, DragAwareMixin, script$1 as DragFeedback, DragImagesManager, DragMixin, script$3 as Drop, script as DropList, script$2 as DropMask, DropMixin, InsertEvent, ReorderEvent, createDragImage, dnd };
|