mirror of
https://github.com/Shopify/draggable.git
synced 2025-12-08 20:15:56 +00:00
175 lines
4.5 KiB
JavaScript
175 lines
4.5 KiB
JavaScript
import AbstractPlugin from 'shared/AbstractPlugin';
|
|
import {closest} from 'shared/utils';
|
|
|
|
import {CollidableInEvent, CollidableOutEvent} from './CollidableEvent';
|
|
|
|
const onDragMove = Symbol('onDragMove');
|
|
const onDragStop = Symbol('onDragStop');
|
|
const onRequestAnimationFrame = Symbol('onRequestAnimationFrame');
|
|
|
|
/**
|
|
* Collidable plugin which detects colliding elements while dragging
|
|
* @class Collidable
|
|
* @module Collidable
|
|
* @extends AbstractPlugin
|
|
*/
|
|
export default class Collidable extends AbstractPlugin {
|
|
/**
|
|
* Collidable constructor.
|
|
* @constructs Collidable
|
|
* @param {Draggable} draggable - Draggable instance
|
|
*/
|
|
constructor(draggable) {
|
|
super(draggable);
|
|
|
|
/**
|
|
* Keeps track of currently colliding elements
|
|
* @property {HTMLElement|null} currentlyCollidingElement
|
|
* @type {HTMLElement|null}
|
|
*/
|
|
this.currentlyCollidingElement = null;
|
|
|
|
/**
|
|
* Keeps track of currently colliding elements
|
|
* @property {HTMLElement|null} lastCollidingElement
|
|
* @type {HTMLElement|null}
|
|
*/
|
|
this.lastCollidingElement = null;
|
|
|
|
/**
|
|
* Animation frame for finding colliding elements
|
|
* @property {Number|null} currentAnimationFrame
|
|
* @type {Number|null}
|
|
*/
|
|
this.currentAnimationFrame = null;
|
|
|
|
this[onDragMove] = this[onDragMove].bind(this);
|
|
this[onDragStop] = this[onDragStop].bind(this);
|
|
this[onRequestAnimationFrame] = this[onRequestAnimationFrame].bind(this);
|
|
}
|
|
|
|
/**
|
|
* Attaches plugins event listeners
|
|
*/
|
|
attach() {
|
|
this.draggable
|
|
.on('drag:move', this[onDragMove])
|
|
.on('drag:stop', this[onDragStop]);
|
|
}
|
|
|
|
/**
|
|
* Detaches plugins event listeners
|
|
*/
|
|
detach() {
|
|
this.draggable
|
|
.off('drag:move', this[onDragMove])
|
|
.off('drag:stop', this[onDragStop]);
|
|
}
|
|
|
|
/**
|
|
* Returns current collidables based on `collidables` option
|
|
* @return {HTMLElement[]}
|
|
*/
|
|
getCollidables() {
|
|
const collidables = this.draggable.options.collidables;
|
|
|
|
if (typeof collidables === 'string') {
|
|
return Array.prototype.slice.call(document.querySelectorAll(collidables));
|
|
} else if (
|
|
collidables instanceof NodeList ||
|
|
collidables instanceof Array
|
|
) {
|
|
return Array.prototype.slice.call(collidables);
|
|
} else if (collidables instanceof HTMLElement) {
|
|
return [collidables];
|
|
} else if (typeof collidables === 'function') {
|
|
return collidables();
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Drag move handler
|
|
* @private
|
|
* @param {DragMoveEvent} event - Drag move event
|
|
*/
|
|
[onDragMove](event) {
|
|
const target = event.sensorEvent.target;
|
|
|
|
this.currentAnimationFrame = requestAnimationFrame(
|
|
this[onRequestAnimationFrame](target),
|
|
);
|
|
|
|
if (this.currentlyCollidingElement) {
|
|
event.cancel();
|
|
}
|
|
|
|
const collidableInEvent = new CollidableInEvent({
|
|
dragEvent: event,
|
|
collidingElement: this.currentlyCollidingElement,
|
|
});
|
|
|
|
const collidableOutEvent = new CollidableOutEvent({
|
|
dragEvent: event,
|
|
collidingElement: this.lastCollidingElement,
|
|
});
|
|
|
|
const enteringCollidable = Boolean(
|
|
this.currentlyCollidingElement &&
|
|
this.lastCollidingElement !== this.currentlyCollidingElement,
|
|
);
|
|
const leavingCollidable = Boolean(
|
|
!this.currentlyCollidingElement && this.lastCollidingElement,
|
|
);
|
|
|
|
if (enteringCollidable) {
|
|
if (this.lastCollidingElement) {
|
|
this.draggable.trigger(collidableOutEvent);
|
|
}
|
|
|
|
this.draggable.trigger(collidableInEvent);
|
|
} else if (leavingCollidable) {
|
|
this.draggable.trigger(collidableOutEvent);
|
|
}
|
|
|
|
this.lastCollidingElement = this.currentlyCollidingElement;
|
|
}
|
|
|
|
/**
|
|
* Drag stop handler
|
|
* @private
|
|
* @param {DragStopEvent} event - Drag stop event
|
|
*/
|
|
[onDragStop](event) {
|
|
const lastCollidingElement =
|
|
this.currentlyCollidingElement || this.lastCollidingElement;
|
|
const collidableOutEvent = new CollidableOutEvent({
|
|
dragEvent: event,
|
|
collidingElement: lastCollidingElement,
|
|
});
|
|
|
|
if (lastCollidingElement) {
|
|
this.draggable.trigger(collidableOutEvent);
|
|
}
|
|
|
|
this.lastCollidingElement = null;
|
|
this.currentlyCollidingElement = null;
|
|
}
|
|
|
|
/**
|
|
* Animation frame function
|
|
* @private
|
|
* @param {HTMLElement} target - Current move target
|
|
* @return {Function}
|
|
*/
|
|
[onRequestAnimationFrame](target) {
|
|
return () => {
|
|
const collidables = this.getCollidables();
|
|
this.currentlyCollidingElement = closest(target, (element) =>
|
|
collidables.includes(element),
|
|
);
|
|
};
|
|
}
|
|
}
|