mirror of
https://github.com/Shopify/draggable.git
synced 2026-01-18 15:54:06 +00:00
171 lines
4.2 KiB
JavaScript
171 lines
4.2 KiB
JavaScript
import AbstractPlugin from 'shared/AbstractPlugin';
|
|
|
|
const onSortableSorted = Symbol('onSortableSorted');
|
|
const onSortableSort = Symbol('onSortableSort');
|
|
|
|
/**
|
|
* SortAnimation default options
|
|
* @property {Object} defaultOptions
|
|
* @property {Number} defaultOptions.duration
|
|
* @property {String} defaultOptions.easingFunction
|
|
* @type {Object}
|
|
*/
|
|
export const defaultOptions = {
|
|
duration: 150,
|
|
easingFunction: 'ease-in-out',
|
|
};
|
|
|
|
/**
|
|
* SortAnimation plugin adds sort animation for sortable
|
|
* @class SortAnimation
|
|
* @module SortAnimation
|
|
* @extends AbstractPlugin
|
|
*/
|
|
export default class SortAnimation extends AbstractPlugin {
|
|
/**
|
|
* SortAnimation constructor.
|
|
* @constructs SortAnimation
|
|
* @param {Draggable} draggable - Draggable instance
|
|
*/
|
|
constructor(draggable) {
|
|
super(draggable);
|
|
|
|
/**
|
|
* SortAnimation options
|
|
* @property {Object} options
|
|
* @property {Number} defaultOptions.duration
|
|
* @property {String} defaultOptions.easingFunction
|
|
* @type {Object}
|
|
*/
|
|
this.options = {
|
|
...defaultOptions,
|
|
...this.getOptions(),
|
|
};
|
|
|
|
/**
|
|
* Last animation frame
|
|
* @property {Number} lastAnimationFrame
|
|
* @type {Number}
|
|
*/
|
|
this.lastAnimationFrame = null;
|
|
this.lastElements = [];
|
|
|
|
this[onSortableSorted] = this[onSortableSorted].bind(this);
|
|
this[onSortableSort] = this[onSortableSort].bind(this);
|
|
}
|
|
|
|
/**
|
|
* Attaches plugins event listeners
|
|
*/
|
|
attach() {
|
|
this.draggable.on('sortable:sort', this[onSortableSort]);
|
|
this.draggable.on('sortable:sorted', this[onSortableSorted]);
|
|
}
|
|
|
|
/**
|
|
* Detaches plugins event listeners
|
|
*/
|
|
detach() {
|
|
this.draggable.off('sortable:sort', this[onSortableSort]);
|
|
this.draggable.off('sortable:sorted', this[onSortableSorted]);
|
|
}
|
|
|
|
/**
|
|
* Returns options passed through draggable
|
|
* @return {Object}
|
|
*/
|
|
getOptions() {
|
|
return this.draggable.options.sortAnimation || {};
|
|
}
|
|
|
|
/**
|
|
* Sortable sort handler
|
|
* @param {SortableSortEvent} sortableEvent
|
|
* @private
|
|
*/
|
|
[onSortableSort]({dragEvent}) {
|
|
const {sourceContainer} = dragEvent;
|
|
const elements = this.draggable.getDraggableElementsForContainer(sourceContainer);
|
|
this.lastElements = Array.from(elements).map((el) => {
|
|
return {
|
|
domEl: el,
|
|
offsetTop: el.offsetTop,
|
|
offsetLeft: el.offsetLeft,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sortable sorted handler
|
|
* @param {SortableSortedEvent} sortableEvent
|
|
* @private
|
|
*/
|
|
[onSortableSorted]({oldIndex, newIndex}) {
|
|
if (oldIndex === newIndex) {
|
|
return;
|
|
}
|
|
|
|
const effectedElements = [];
|
|
let start;
|
|
let end;
|
|
let num;
|
|
if (oldIndex > newIndex) {
|
|
start = newIndex;
|
|
end = oldIndex - 1;
|
|
num = 1;
|
|
} else {
|
|
start = oldIndex + 1;
|
|
end = newIndex;
|
|
num = -1;
|
|
}
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
const from = this.lastElements[i];
|
|
const to = this.lastElements[i + num];
|
|
effectedElements.push({from, to});
|
|
}
|
|
cancelAnimationFrame(this.lastAnimationFrame);
|
|
|
|
// Can be done in a separate frame
|
|
this.lastAnimationFrame = requestAnimationFrame(() => {
|
|
effectedElements.forEach((element) => animate(element, this.options));
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Animates two elements
|
|
* @param {Object} element
|
|
* @param {Object} element.from
|
|
* @param {Object} element.to
|
|
* @param {Object} options
|
|
* @param {Number} options.duration
|
|
* @param {String} options.easingFunction
|
|
* @private
|
|
*/
|
|
function animate({from, to}, {duration, easingFunction}) {
|
|
const domEl = from.domEl;
|
|
const x = from.offsetLeft - to.offsetLeft;
|
|
const y = from.offsetTop - to.offsetTop;
|
|
|
|
domEl.style.pointerEvents = 'none';
|
|
domEl.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
|
|
requestAnimationFrame(() => {
|
|
domEl.addEventListener('transitionend', resetElementOnTransitionEnd);
|
|
domEl.style.transition = `transform ${duration}ms ${easingFunction}`;
|
|
domEl.style.transform = '';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resets animation style properties after animation has completed
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
function resetElementOnTransitionEnd(event) {
|
|
event.target.style.transition = '';
|
|
event.target.style.pointerEvents = '';
|
|
event.target.removeEventListener('transitionend', resetElementOnTransitionEnd);
|
|
}
|