|
@@ -0,0 +1,785 @@
|
|
|
+/*! Snap.js v2.0.0-rc1 */
|
|
|
+(function(win, doc) {
|
|
|
+
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ // Our export
|
|
|
+ var Namespace = 'Snap';
|
|
|
+
|
|
|
+ // Our main toolbelt
|
|
|
+ var utils = {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Deeply extends two objects
|
|
|
+ * @param {Object} destination The destination object
|
|
|
+ * @param {Object} source The custom options to extend destination by
|
|
|
+ * @return {Object} The desination object
|
|
|
+ */
|
|
|
+ extend: function(destination, source) {
|
|
|
+ var property;
|
|
|
+ for (property in source) {
|
|
|
+ if (source[property] && source[property].constructor && source[property].constructor === Object) {
|
|
|
+ destination[property] = destination[property] || {};
|
|
|
+ utils.extend(destination[property], source[property]);
|
|
|
+ } else {
|
|
|
+ destination[property] = source[property];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return destination;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Our Snap global that initializes our instance
|
|
|
+ * @param {Object} opts The custom Snap.js options
|
|
|
+ */
|
|
|
+ var Core = function( opts ) {
|
|
|
+
|
|
|
+ var self = this;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Our default settings for a Snap instance
|
|
|
+ * @type {Object}
|
|
|
+ */
|
|
|
+ var settings = self.settings = {
|
|
|
+ element: null,
|
|
|
+ dragger: null,
|
|
|
+ disable: 'none',
|
|
|
+ addBodyClasses: true,
|
|
|
+ hyperextensible: true,
|
|
|
+ resistance: 0.5,
|
|
|
+ flickThreshold: 50,
|
|
|
+ transitionSpeed: 0.3,
|
|
|
+ easing: 'ease',
|
|
|
+ maxPosition: 266,
|
|
|
+ minPosition: -266,
|
|
|
+ tapToClose: true,
|
|
|
+ touchToDrag: true,
|
|
|
+ clickToDrag: true,
|
|
|
+ slideIntent: 40, // degrees
|
|
|
+ minDragDistance: 5
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Stores internally global data
|
|
|
+ * @type {Object}
|
|
|
+ */
|
|
|
+ var cache = self.cache = {
|
|
|
+ isDragging: false,
|
|
|
+ simpleStates: {
|
|
|
+ opening: null,
|
|
|
+ towards: null,
|
|
|
+ hyperExtending: null,
|
|
|
+ halfway: null,
|
|
|
+ flick: null,
|
|
|
+ translation: {
|
|
|
+ absolute: 0,
|
|
|
+ relative: 0,
|
|
|
+ sinceDirectionChange: 0,
|
|
|
+ percentage: 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ var eventList = self.eventList = {};
|
|
|
+
|
|
|
+ utils.extend(utils, {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines if we are interacting with a touch device
|
|
|
+ * @type {Boolean}
|
|
|
+ */
|
|
|
+ hasTouch: ('ontouchstart' in doc.documentElement || win.navigator.msPointerEnabled),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the appropriate event type based on whether we are a touch device or not
|
|
|
+ * @param {String} action The "action" event you're looking for: up, down, move, out
|
|
|
+ * @return {String} The browsers supported event name
|
|
|
+ */
|
|
|
+ eventType: function(action) {
|
|
|
+ var eventTypes = {
|
|
|
+ down: (utils.hasTouch ? 'touchstart' : settings.clickToDrag ? 'mousedown' : ''),
|
|
|
+ move: (utils.hasTouch ? 'touchmove' : settings.clickToDrag ? 'mousemove' : ''),
|
|
|
+ up: (utils.hasTouch ? 'touchend' : settings.clickToDrag ? 'mouseup': ''),
|
|
|
+ out: (utils.hasTouch ? 'touchcancel' : settings.clickToDrag ? 'mouseout' : '')
|
|
|
+ };
|
|
|
+ return eventTypes[action];
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the correct "cursor" position on both browser and mobile
|
|
|
+ * @param {String} t The coordinate to retrieve, either "X" or "Y"
|
|
|
+ * @param {Object} e The event object being triggered
|
|
|
+ * @return {Number} The desired coordiante for the events interaction
|
|
|
+ */
|
|
|
+ page: function(t, e){
|
|
|
+ return (utils.hasTouch && e.touches.length && e.touches[0]) ? e.touches[0]['page'+t] : e['page'+t];
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ klass: {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if an element has a class name
|
|
|
+ * @param {Object} el The element to check
|
|
|
+ * @param {String} name The class name to search for
|
|
|
+ * @return {Boolean} Returns true if the class exists
|
|
|
+ */
|
|
|
+ has: function(el, name){
|
|
|
+ return (el.className).indexOf(name) !== -1;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds a class name to an element
|
|
|
+ * @param {Object} el The element to add to
|
|
|
+ * @param {String} name The class name to add
|
|
|
+ */
|
|
|
+ add: function(el, name){
|
|
|
+ if(!utils.klass.has(el, name) && settings.addBodyClasses){
|
|
|
+ el.className += " "+name;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes a class name
|
|
|
+ * @param {Object} el The element to remove from
|
|
|
+ * @param {String} name The class name to remove
|
|
|
+ */
|
|
|
+ remove: function(el, name){
|
|
|
+ if(utils.klass.has(el, name) && settings.addBodyClasses){
|
|
|
+ el.className = (el.className).replace(name, "").replace(/^\s+|\s+$/g, '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Dispatch a custom Snap.js event
|
|
|
+ * @param {String} type The event name
|
|
|
+ */
|
|
|
+ dispatchEvent: function(type) {
|
|
|
+ if( typeof eventList[type] === 'function') {
|
|
|
+ return eventList[type].apply();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines the browsers vendor prefix for CSS3
|
|
|
+ * @return {String} The browsers vendor prefix
|
|
|
+ */
|
|
|
+ vendor: function(){
|
|
|
+ var tmp = doc.createElement("div"),
|
|
|
+ prefixes = 'webkit Moz O ms'.split(' '),
|
|
|
+ i;
|
|
|
+ for (i in prefixes) {
|
|
|
+ if (typeof tmp.style[prefixes[i] + 'Transition'] !== 'undefined') {
|
|
|
+ return prefixes[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines the browsers vendor prefix for transition callback events
|
|
|
+ * @return {String} The event name
|
|
|
+ */
|
|
|
+ transitionCallback: function(){
|
|
|
+ return (cache.vendor==='Moz' || cache.vendor==='ms') ? 'transitionend' : cache.vendor+'TransitionEnd';
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines if the users browser supports CSS3 transformations
|
|
|
+ * @return {[type]} [description]
|
|
|
+ */
|
|
|
+ canTransform: function(){
|
|
|
+ return typeof settings.element.style[cache.vendor+'Transform'] !== 'undefined';
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines an angle between two points
|
|
|
+ * @param {Number} x The X coordinate
|
|
|
+ * @param {Number} y The Y coordinate
|
|
|
+ * @return {Number} The number of degrees between the two points
|
|
|
+ */
|
|
|
+ angleOfDrag: function(x, y) {
|
|
|
+ var degrees, theta;
|
|
|
+ // Calc Theta
|
|
|
+ theta = Math.atan2(-(cache.startDragY - y), (cache.startDragX - x));
|
|
|
+ if (theta < 0) {
|
|
|
+ theta += 2 * Math.PI;
|
|
|
+ }
|
|
|
+ // Calc Degrees
|
|
|
+ degrees = Math.floor(theta * (180 / Math.PI) - 180);
|
|
|
+ if (degrees < 0 && degrees > -180) {
|
|
|
+ degrees = 360 - Math.abs(degrees);
|
|
|
+ }
|
|
|
+ return Math.abs(degrees);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ events: {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds an event to an element
|
|
|
+ * @param {Object} element Element to add event to
|
|
|
+ * @param {String} eventName The event name
|
|
|
+ * @param {Function} func Callback function
|
|
|
+ */
|
|
|
+ addEvent: function addEvent(element, eventName, func) {
|
|
|
+ if (element.addEventListener) {
|
|
|
+ return element.addEventListener(eventName, func, false);
|
|
|
+ } else if (element.attachEvent) {
|
|
|
+ return element.attachEvent("on" + eventName, func);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes an event to an element
|
|
|
+ * @param {Object} element Element to remove event from
|
|
|
+ * @param {String} eventName The event name
|
|
|
+ * @param {Function} func Callback function
|
|
|
+ */
|
|
|
+ removeEvent: function addEvent(element, eventName, func) {
|
|
|
+ if (element.addEventListener) {
|
|
|
+ return element.removeEventListener(eventName, func, false);
|
|
|
+ } else if (element.attachEvent) {
|
|
|
+ return element.detachEvent("on" + eventName, func);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Prevents the default event
|
|
|
+ * @param {Object} e The event object
|
|
|
+ */
|
|
|
+ prevent: function(e) {
|
|
|
+ if (e.preventDefault) {
|
|
|
+ e.preventDefault();
|
|
|
+ } else {
|
|
|
+ e.returnValue = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Searches the parent element until a specified attribute has been matched
|
|
|
+ * @param {Object} el The element to search from
|
|
|
+ * @param {String} attr The attribute to search for
|
|
|
+ * @return {Object|null} Returns a matched element if it exists, else, null
|
|
|
+ */
|
|
|
+ parentUntil: function(el, attr) {
|
|
|
+ var isStr = typeof attr === 'string';
|
|
|
+ while (el.parentNode) {
|
|
|
+ if (isStr && el.getAttribute && el.getAttribute(attr)){
|
|
|
+ return el;
|
|
|
+ } else if(!isStr && el === attr){
|
|
|
+ return el;
|
|
|
+ }
|
|
|
+ el = el.parentNode;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ var action = self.action = {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles translating the elements position
|
|
|
+ * @type {Object}
|
|
|
+ */
|
|
|
+ translate: {
|
|
|
+ get: {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the amount an element is translated
|
|
|
+ * @param {Number} index The index desired from the CSS3 values of translate3d
|
|
|
+ * @return {Number} The amount of pixels an element is translated
|
|
|
+ */
|
|
|
+ matrix: function(index) {
|
|
|
+
|
|
|
+ if( !cache.canTransform ){
|
|
|
+ return parseInt(settings.element.style.left, 10);
|
|
|
+ } else {
|
|
|
+ var matrix = win.getComputedStyle(settings.element)[cache.vendor+'Transform'].match(/\((.*)\)/),
|
|
|
+ ieOffset = 8;
|
|
|
+ if (matrix) {
|
|
|
+ matrix = matrix[1].split(',');
|
|
|
+
|
|
|
+ // Internet Explorer likes to give us 16 fucking values
|
|
|
+ if(matrix.length===16){
|
|
|
+ index+=ieOffset;
|
|
|
+ }
|
|
|
+ return parseInt(matrix[index], 10);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Called when the element has finished transitioning
|
|
|
+ */
|
|
|
+ easeCallback: function(fn){
|
|
|
+ settings.element.style[cache.vendor+'Transition'] = '';
|
|
|
+ cache.translation = action.translate.get.matrix(4);
|
|
|
+ cache.easing = false;
|
|
|
+
|
|
|
+ if(cache.easingTo===0){
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-right');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-left');
|
|
|
+ }
|
|
|
+
|
|
|
+ if( cache.once ){
|
|
|
+ cache.once.call(self, self.state());
|
|
|
+ delete cache.once;
|
|
|
+ }
|
|
|
+
|
|
|
+ utils.dispatchEvent('animated');
|
|
|
+ utils.events.removeEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback);
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Animates the pane by the specified amount of pixels
|
|
|
+ * @param {Number} n The amount of pixels to move the pane
|
|
|
+ */
|
|
|
+ easeTo: function(n, cb) {
|
|
|
+
|
|
|
+ if( !cache.canTransform ){
|
|
|
+ cache.translation = n;
|
|
|
+ action.translate.x(n);
|
|
|
+ } else {
|
|
|
+ cache.easing = true;
|
|
|
+ cache.easingTo = n;
|
|
|
+
|
|
|
+ settings.element.style[cache.vendor+'Transition'] = 'all ' + settings.transitionSpeed + 's ' + settings.easing;
|
|
|
+
|
|
|
+ cache.once = cb;
|
|
|
+
|
|
|
+ utils.events.addEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback);
|
|
|
+ action.translate.x(n);
|
|
|
+ }
|
|
|
+ if(n===0){
|
|
|
+ settings.element.style[cache.vendor+'Transform'] = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Immediately translates the element on its X axis
|
|
|
+ * @param {Number} n Amount of pixels to translate
|
|
|
+ */
|
|
|
+ x: function(n) {
|
|
|
+ if( (settings.disable==='left' && n>0) ||
|
|
|
+ (settings.disable==='right' && n<0)
|
|
|
+ ){ return; }
|
|
|
+
|
|
|
+ if( !settings.hyperextensible ){
|
|
|
+ if( n===settings.maxPosition || n>settings.maxPosition ){
|
|
|
+ n=settings.maxPosition;
|
|
|
+ } else if( n===settings.minPosition || n<settings.minPosition ){
|
|
|
+ n=settings.minPosition;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ n = parseInt(n, 10);
|
|
|
+ if(isNaN(n)){
|
|
|
+ n = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( cache.canTransform ){
|
|
|
+ var theTranslate = 'translate3d(' + n + 'px, 0,0)';
|
|
|
+ settings.element.style[cache.vendor+'Transform'] = theTranslate;
|
|
|
+ } else {
|
|
|
+ settings.element.style.width = (win.innerWidth || doc.documentElement.clientWidth)+'px';
|
|
|
+
|
|
|
+ settings.element.style.left = n+'px';
|
|
|
+ settings.element.style.right = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles all the events that interface with dragging
|
|
|
+ * @type {Object}
|
|
|
+ */
|
|
|
+ drag: {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Begins listening for drag events on our element
|
|
|
+ */
|
|
|
+ listen: function() {
|
|
|
+ cache.translation = 0;
|
|
|
+ cache.easing = false;
|
|
|
+ utils.events.addEvent(self.settings.element, utils.eventType('down'), action.drag.startDrag);
|
|
|
+ utils.events.addEvent(self.settings.element, utils.eventType('move'), action.drag.dragging);
|
|
|
+ utils.events.addEvent(self.settings.element, utils.eventType('up'), action.drag.endDrag);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Stops listening for drag events on our element
|
|
|
+ */
|
|
|
+ stopListening: function() {
|
|
|
+ utils.events.removeEvent(settings.element, utils.eventType('down'), action.drag.startDrag);
|
|
|
+ utils.events.removeEvent(settings.element, utils.eventType('move'), action.drag.dragging);
|
|
|
+ utils.events.removeEvent(settings.element, utils.eventType('up'), action.drag.endDrag);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fired immediately when the user begins to drag the content pane
|
|
|
+ * @param {Object} e Event object
|
|
|
+ */
|
|
|
+ startDrag: function(e) {
|
|
|
+ // No drag on ignored elements
|
|
|
+ var target = e.target ? e.target : e.srcElement,
|
|
|
+ ignoreParent = utils.parentUntil(target, 'data-snap-ignore');
|
|
|
+
|
|
|
+ if (ignoreParent) {
|
|
|
+ utils.dispatchEvent('ignore');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(settings.dragger){
|
|
|
+ var dragParent = utils.parentUntil(target, settings.dragger);
|
|
|
+
|
|
|
+ // Only use dragger if we're in a closed state
|
|
|
+ if( !dragParent &&
|
|
|
+ (cache.translation !== settings.minPosition &&
|
|
|
+ cache.translation !== settings.maxPosition
|
|
|
+ )){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ utils.dispatchEvent('start');
|
|
|
+ settings.element.style[cache.vendor+'Transition'] = '';
|
|
|
+ cache.isDragging = true;
|
|
|
+
|
|
|
+ cache.intentChecked = false;
|
|
|
+ cache.startDragX = utils.page('X', e);
|
|
|
+ cache.startDragY = utils.page('Y', e);
|
|
|
+ cache.dragWatchers = {
|
|
|
+ current: 0,
|
|
|
+ last: 0,
|
|
|
+ hold: 0,
|
|
|
+ state: ''
|
|
|
+ };
|
|
|
+ cache.simpleStates = {
|
|
|
+ opening: null,
|
|
|
+ towards: null,
|
|
|
+ hyperExtending: null,
|
|
|
+ halfway: null,
|
|
|
+ flick: null,
|
|
|
+ translation: {
|
|
|
+ absolute: 0,
|
|
|
+ relative: 0,
|
|
|
+ sinceDirectionChange: 0,
|
|
|
+ percentage: 0
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fired while the user is moving the content pane
|
|
|
+ * @param {Object} e Event object
|
|
|
+ */
|
|
|
+ dragging: function(e) {
|
|
|
+
|
|
|
+ if (cache.isDragging && settings.touchToDrag) {
|
|
|
+
|
|
|
+ var thePageX = utils.page('X', e),
|
|
|
+ thePageY = utils.page('Y', e),
|
|
|
+ translated = cache.translation,
|
|
|
+ absoluteTranslation = action.translate.get.matrix(4),
|
|
|
+ whileDragX = thePageX - cache.startDragX,
|
|
|
+ openingLeft = absoluteTranslation > 0,
|
|
|
+ translateTo = whileDragX,
|
|
|
+ diff;
|
|
|
+
|
|
|
+ // Shown no intent already
|
|
|
+ if((cache.intentChecked && !cache.hasIntent)){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(settings.addBodyClasses){
|
|
|
+ if((absoluteTranslation)>0){
|
|
|
+ utils.klass.add(doc.body, 'snapjs-left');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-right');
|
|
|
+ } else if((absoluteTranslation)<0){
|
|
|
+ utils.klass.add(doc.body, 'snapjs-right');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-left');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cache.hasIntent === false || cache.hasIntent === null) {
|
|
|
+
|
|
|
+ var deg = utils.angleOfDrag(thePageX, thePageY),
|
|
|
+ inRightRange = (deg >= 0 && deg <= settings.slideIntent) || (deg <= 360 && deg > (360 - settings.slideIntent)),
|
|
|
+ inLeftRange = (deg >= 180 && deg <= (180 + settings.slideIntent)) || (deg <= 180 && deg >= (180 - settings.slideIntent));
|
|
|
+ if (!inLeftRange && !inRightRange) {
|
|
|
+ cache.hasIntent = false;
|
|
|
+ } else {
|
|
|
+ cache.hasIntent = true;
|
|
|
+ }
|
|
|
+ cache.intentChecked = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ (settings.minDragDistance>=Math.abs(thePageX-cache.startDragX)) || // Has user met minimum drag distance?
|
|
|
+ (cache.hasIntent === false)
|
|
|
+ ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ utils.events.prevent(e);
|
|
|
+ utils.dispatchEvent('drag');
|
|
|
+
|
|
|
+ cache.dragWatchers.current = thePageX;
|
|
|
+
|
|
|
+ // Determine which direction we are going
|
|
|
+ if (cache.dragWatchers.last > thePageX) {
|
|
|
+ if (cache.dragWatchers.state !== 'left') {
|
|
|
+ cache.dragWatchers.state = 'left';
|
|
|
+ cache.dragWatchers.hold = thePageX;
|
|
|
+ }
|
|
|
+ cache.dragWatchers.last = thePageX;
|
|
|
+ } else if (cache.dragWatchers.last < thePageX) {
|
|
|
+ if (cache.dragWatchers.state !== 'right') {
|
|
|
+ cache.dragWatchers.state = 'right';
|
|
|
+ cache.dragWatchers.hold = thePageX;
|
|
|
+ }
|
|
|
+ cache.dragWatchers.last = thePageX;
|
|
|
+ }
|
|
|
+ if (openingLeft) {
|
|
|
+ // Pulling too far to the right
|
|
|
+ if (settings.maxPosition < absoluteTranslation) {
|
|
|
+ diff = (absoluteTranslation - settings.maxPosition) * settings.resistance;
|
|
|
+ translateTo = whileDragX - diff;
|
|
|
+ }
|
|
|
+ cache.simpleStates = {
|
|
|
+ opening: 'left',
|
|
|
+ towards: cache.dragWatchers.state,
|
|
|
+ hyperExtending: settings.maxPosition < absoluteTranslation,
|
|
|
+ halfway: absoluteTranslation > (settings.maxPosition / 2),
|
|
|
+ flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold,
|
|
|
+ translation: {
|
|
|
+ absolute: absoluteTranslation,
|
|
|
+ relative: whileDragX,
|
|
|
+ sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold),
|
|
|
+ percentage: (absoluteTranslation/settings.maxPosition)*100
|
|
|
+ }
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // Pulling too far to the left
|
|
|
+ if (settings.minPosition > absoluteTranslation) {
|
|
|
+ diff = (absoluteTranslation - settings.minPosition) * settings.resistance;
|
|
|
+ translateTo = whileDragX - diff;
|
|
|
+ }
|
|
|
+ cache.simpleStates = {
|
|
|
+ opening: 'right',
|
|
|
+ towards: cache.dragWatchers.state,
|
|
|
+ hyperExtending: settings.minPosition > absoluteTranslation,
|
|
|
+ halfway: absoluteTranslation < (settings.minPosition / 2),
|
|
|
+ flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold,
|
|
|
+ translation: {
|
|
|
+ absolute: absoluteTranslation,
|
|
|
+ relative: whileDragX,
|
|
|
+ sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold),
|
|
|
+ percentage: (absoluteTranslation/settings.minPosition)*100
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ action.translate.x(translateTo + translated);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fired when the user releases the content pane
|
|
|
+ * @param {Object} e Event object
|
|
|
+ */
|
|
|
+ endDrag: function(e) {
|
|
|
+ if (cache.isDragging) {
|
|
|
+ utils.dispatchEvent('end');
|
|
|
+ var translated = action.translate.get.matrix(4);
|
|
|
+
|
|
|
+ // Tap Close
|
|
|
+ if (cache.dragWatchers.current === 0 && translated !== 0 && settings.tapToClose) {
|
|
|
+ utils.dispatchEvent('close');
|
|
|
+ utils.events.prevent(e);
|
|
|
+ action.translate.easeTo(0);
|
|
|
+ cache.isDragging = false;
|
|
|
+ cache.startDragX = 0;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Revealing Left
|
|
|
+ if (cache.simpleStates.opening === 'left') {
|
|
|
+ // Halfway, Flicking, or Too Far Out
|
|
|
+ if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) {
|
|
|
+ if (cache.simpleStates.flick && cache.simpleStates.towards === 'left') { // Flicking Closed
|
|
|
+ action.translate.easeTo(0);
|
|
|
+ } else if (
|
|
|
+ (cache.simpleStates.flick && cache.simpleStates.towards === 'right') || // Flicking Open OR
|
|
|
+ (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending
|
|
|
+ ) {
|
|
|
+ action.translate.easeTo(settings.maxPosition); // Open Left
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ action.translate.easeTo(0); // Close Left
|
|
|
+ }
|
|
|
+ // Revealing Right
|
|
|
+ } else if (cache.simpleStates.opening === 'right') {
|
|
|
+ // Halfway, Flicking, or Too Far Out
|
|
|
+ if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) {
|
|
|
+ if (cache.simpleStates.flick && cache.simpleStates.towards === 'right') { // Flicking Closed
|
|
|
+ action.translate.easeTo(0);
|
|
|
+ } else if (
|
|
|
+ (cache.simpleStates.flick && cache.simpleStates.towards === 'left') || // Flicking Open OR
|
|
|
+ (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending
|
|
|
+ ) {
|
|
|
+ action.translate.easeTo(settings.minPosition); // Open Right
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ action.translate.easeTo(0); // Close Right
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cache.isDragging = false;
|
|
|
+ cache.startDragX = utils.page('X', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ // Initialize
|
|
|
+ if (opts.element) {
|
|
|
+ utils.extend(settings, opts);
|
|
|
+ cache.vendor = utils.vendor();
|
|
|
+ cache.canTransform = utils.canTransform();
|
|
|
+ action.drag.listen();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ utils.extend(Core.prototype, {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Opens the specified side menu
|
|
|
+ * @param {String} side Must be "left" or "right"
|
|
|
+ */
|
|
|
+ open: function(side, cb) {
|
|
|
+ utils.dispatchEvent('open');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-expand-left');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-expand-right');
|
|
|
+
|
|
|
+ if (side === 'left') {
|
|
|
+ this.cache.simpleStates.opening = 'left';
|
|
|
+ this.cache.simpleStates.towards = 'right';
|
|
|
+ utils.klass.add(doc.body, 'snapjs-left');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-right');
|
|
|
+ this.action.translate.easeTo(this.settings.maxPosition, cb);
|
|
|
+ } else if (side === 'right') {
|
|
|
+ this.cache.simpleStates.opening = 'right';
|
|
|
+ this.cache.simpleStates.towards = 'left';
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-left');
|
|
|
+ utils.klass.add(doc.body, 'snapjs-right');
|
|
|
+ this.action.translate.easeTo(this.settings.minPosition, cb);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Closes the pane
|
|
|
+ */
|
|
|
+ close: function(cb) {
|
|
|
+ utils.dispatchEvent('close');
|
|
|
+ this.action.translate.easeTo(0, cb);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Hides the content pane completely allowing for full menu visibility
|
|
|
+ * @param {String} side Must be "left" or "right"
|
|
|
+ */
|
|
|
+ expand: function(side){
|
|
|
+ var to = win.innerWidth || doc.documentElement.clientWidth;
|
|
|
+
|
|
|
+ if(side==='left'){
|
|
|
+ utils.dispatchEvent('expandLeft');
|
|
|
+ utils.klass.add(doc.body, 'snapjs-expand-left');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-expand-right');
|
|
|
+ } else {
|
|
|
+ utils.dispatchEvent('expandRight');
|
|
|
+ utils.klass.add(doc.body, 'snapjs-expand-right');
|
|
|
+ utils.klass.remove(doc.body, 'snapjs-expand-left');
|
|
|
+ to *= -1;
|
|
|
+ }
|
|
|
+ this.action.translate.easeTo(to);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Listen in to custom Snap events
|
|
|
+ * @param {String} evt The snap event name
|
|
|
+ * @param {Function} fn Callback function
|
|
|
+ * @return {Object} Snap instance
|
|
|
+ */
|
|
|
+ on: function(evt, fn) {
|
|
|
+ this.eventList[evt] = fn;
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Stops listening to custom Snap events
|
|
|
+ * @param {String} evt The snap event name
|
|
|
+ */
|
|
|
+ off: function(evt) {
|
|
|
+ if (this.eventList[evt]) {
|
|
|
+ this.eventList[evt] = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Enables Snap.js events
|
|
|
+ */
|
|
|
+ enable: function() {
|
|
|
+ utils.dispatchEvent('enable');
|
|
|
+ this.action.drag.listen();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Disables Snap.js events
|
|
|
+ */
|
|
|
+ disable: function() {
|
|
|
+ utils.dispatchEvent('disable');
|
|
|
+ this.action.drag.stopListening();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates the instances settings
|
|
|
+ * @param {Object} opts The Snap options to set
|
|
|
+ */
|
|
|
+ settings: function(opts){
|
|
|
+ utils.extend(this.settings, opts);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns information about the state of the content pane
|
|
|
+ * @return {Object} Information regarding the state of the pane
|
|
|
+ */
|
|
|
+ state: function() {
|
|
|
+ var state,
|
|
|
+ fromLeft = this.action.translate.get.matrix(4);
|
|
|
+ if (fromLeft === this.settings.maxPosition) {
|
|
|
+ state = 'left';
|
|
|
+ } else if (fromLeft === this.settings.minPosition) {
|
|
|
+ state = 'right';
|
|
|
+ } else {
|
|
|
+ state = 'closed';
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ state: state,
|
|
|
+ info: this.cache.simpleStates
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Assign to the global namespace
|
|
|
+ this[Namespace] = Core;
|
|
|
+
|
|
|
+}).call(this, window, document);
|