Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
229 lines
No EOL
5.6 KiB
JavaScript
Executable file
229 lines
No EOL
5.6 KiB
JavaScript
Executable file
(function(define){'use strict';define(function(require,exports,module){
|
|
/*globals define*//*jshint node:true*/
|
|
|
|
/**
|
|
* Dependencies
|
|
*/
|
|
|
|
var events = require('evt');
|
|
|
|
/**
|
|
* Exports
|
|
*/
|
|
|
|
module.exports = Drag;
|
|
|
|
/**
|
|
* Mixin Emitter
|
|
*/
|
|
|
|
events(Drag.prototype);
|
|
|
|
/**
|
|
* Pointer event abstraction to make
|
|
* it work for touch and mouse.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var pointer = [
|
|
{ down: 'touchstart', up: 'touchend', move: 'touchmove' },
|
|
{ down: 'mousedown', up: 'mouseup', move: 'mousemove' }
|
|
]['ontouchstart' in window ? 0 : 1];
|
|
|
|
/**
|
|
* Drag creates a draggable 'handle' element,
|
|
* constrained within a 'container' element.
|
|
*
|
|
* Drag instances emit useful events and provides
|
|
* methods to support common draggable UI use-cases,
|
|
* like `snapToClosestEdge`
|
|
*
|
|
* In Gaia we use `Drag` for our switch components.
|
|
*
|
|
* Usage:
|
|
*
|
|
* // Create a new `Drag`
|
|
* var drag = new Drag({
|
|
* container: myContainer,
|
|
* handle: myHandle
|
|
* });
|
|
*
|
|
* // Once elements are in the DOM we
|
|
* // need to take some measurements
|
|
* drag.updateDimensions();
|
|
*
|
|
* Options:
|
|
*
|
|
* - {Element} `container`
|
|
* - {Element} `handle`
|
|
*
|
|
* @param {Object} options
|
|
*/
|
|
function Drag(options) {
|
|
this.container = { el: options.container };
|
|
this.handle = { el: options.handle };
|
|
this.onTouchStart = this.onTouchStart.bind(this);
|
|
this.onTouchMove = this.onTouchMove.bind(this);
|
|
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
this.slideDuration = options.slideDuration || 140;
|
|
this.tapTime = options.tapTime || 180;
|
|
this.bindEvents();
|
|
}
|
|
|
|
Drag.prototype.bindEvents = function() {
|
|
this.container.el.addEventListener(pointer.down, this.onTouchStart);
|
|
};
|
|
|
|
Drag.prototype.onTouchStart = function(e) {
|
|
this.updateDimensions();
|
|
this.touch = ~e.type.indexOf('mouse') ? e : e.touches[0];
|
|
this.firstTouch = this.touch;
|
|
this.startTime = e.timeStamp;
|
|
|
|
addEventListener(pointer.move, this.onTouchMove);
|
|
addEventListener(pointer.up, this.onTouchEnd);
|
|
};
|
|
|
|
Drag.prototype.onTouchMove = function(e) {
|
|
e.preventDefault();
|
|
e = ~e.type.indexOf('mouse') ? e : e.touches[0];
|
|
|
|
var delta = {
|
|
x: e.clientX - this.touch.clientX,
|
|
y: e.clientY - this.touch.clientY
|
|
};
|
|
|
|
this.dragging = true;
|
|
this.move(delta);
|
|
this.touch = e;
|
|
};
|
|
|
|
Drag.prototype.onTouchEnd = function(e) {
|
|
var tapped = (e.timeStamp - this.startTime) < this.tapTime;
|
|
this.dragging = false;
|
|
|
|
removeEventListener(pointer.move, this.onTouchMove);
|
|
removeEventListener(pointer.up, this.onTouchEnd);
|
|
|
|
if (tapped) { this.emit('tapped', e); }
|
|
else { this.emit('ended', e); }
|
|
};
|
|
|
|
Drag.prototype.move = function(delta) {
|
|
this.translate({
|
|
x: this.handle.position.x + delta.x,
|
|
y: this.handle.position.y + delta.y
|
|
});
|
|
};
|
|
|
|
Drag.prototype.set = function(pos) {
|
|
if (!this.edges) { this.pendingSet = pos; return; }
|
|
var x = typeof pos.x === 'string' ? this.edges[pos.x] : (pos.x || 0);
|
|
var y = typeof pos.y === 'string' ? this.edges[pos.y] : (pos.y || 0);
|
|
this.translate({ x: x, y: y });
|
|
};
|
|
|
|
Drag.prototype.snapToClosestEdge = function() {
|
|
var edges = this.getClosestEdges();
|
|
|
|
this.translate({
|
|
x: this.edges[edges.x],
|
|
y: this.edges[edges.y]
|
|
});
|
|
|
|
this.emit('snapped', edges);
|
|
};
|
|
|
|
Drag.prototype.translate = function(options) {
|
|
var position = this.clamp(options);
|
|
var translate = 'translate(' + position.x + 'px,' + position.y + 'px)';
|
|
var ratio = {
|
|
x: (position.x / this.max.x) || 0,
|
|
y: (position.y / this.max.y) || 0
|
|
};
|
|
|
|
this.setTransition(position);
|
|
|
|
// Set the transform to move the handle
|
|
this.handle.el.style.transform = translate;
|
|
|
|
// Update the handle position reference
|
|
this.handle.position = position;
|
|
|
|
// Emit event with useful data
|
|
this.emit('translate', {
|
|
position: {
|
|
px: position,
|
|
ratio: ratio
|
|
}
|
|
});
|
|
};
|
|
|
|
Drag.prototype.clamp = function(position) {
|
|
return {
|
|
x: Math.max(this.min.x, Math.min(this.max.x, position.x)),
|
|
y: Math.max(this.min.y, Math.min(this.max.y, position.y)),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* [setTransition description]
|
|
* @param {[type]} position [description]
|
|
*/
|
|
Drag.prototype.setTransition = function(position) {
|
|
var duration = !this.dragging ? this.transitionDuration(position) : 0;
|
|
this.handle.el.style.transitionDuration = duration + 'ms';
|
|
};
|
|
|
|
Drag.prototype.transitionDuration = function(position) {
|
|
var current = this.handle.position;
|
|
var distanceX = Math.abs(current.x - position.x);
|
|
var distanceY = Math.abs(current.y - position.y);
|
|
var distance = Math.max(distanceX, distanceY);
|
|
var axis = distanceY > distanceX ? 'y' : 'x';
|
|
var ratio = distance / this.max[axis];
|
|
return this.slideDuration * ratio;
|
|
};
|
|
|
|
Drag.prototype.getClosestEdges = function() {
|
|
return {
|
|
x: this.handle.position.x <= (this.max.x / 2) ? 'left' : 'right',
|
|
y: this.handle.position.y <= (this.max.y / 2) ? 'top' : 'bottom'
|
|
};
|
|
};
|
|
|
|
Drag.prototype.updateDimensions = function() {
|
|
var container = this.container.el.getBoundingClientRect();
|
|
var handle = this.handle.el.getBoundingClientRect();
|
|
|
|
this.min = { x: 0, y: 0 };
|
|
this.max = {
|
|
x: container.width - handle.width,
|
|
y: container.height - handle.height
|
|
};
|
|
|
|
this.edges = {
|
|
top: this.min.y,
|
|
right: this.max.x,
|
|
bottom: this.max.y,
|
|
left: this.min.x
|
|
};
|
|
|
|
this.handle.position = {
|
|
x: handle.left - container.left,
|
|
y: handle.top - container.top
|
|
};
|
|
|
|
this.clearPendingSet();
|
|
};
|
|
|
|
Drag.prototype.clearPendingSet = function() {
|
|
if (!this.pendingSet) { return; }
|
|
this.set(this.pendingSet);
|
|
delete this.pendingSet;
|
|
};
|
|
|
|
});})((function(n,w){'use strict';return typeof define=='function'&&define.amd?
|
|
define:typeof module=='object'?function(c){c(require,exports,module);}:
|
|
function(c){var m={exports:{}},r=function(n){return w[n];};
|
|
w[n]=c(r,m.exports,m)||m.exports;};})('drag',this)); |