diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2014-07-04 15:38:35 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2014-07-04 15:38:35 +0200 |
commit | f1901cab0a19704b3f53718834f492a07d00682e (patch) | |
tree | 5c4818ab74f6a72548352ee4f10f1a2a599fb931 /core/js | |
parent | 2327d41b11e11ca0553c829b5d7adeb0f000a474 (diff) | |
download | nextcloud-server-f1901cab0a19704b3f53718834f492a07d00682e.tar.gz nextcloud-server-f1901cab0a19704b3f53718834f492a07d00682e.zip |
update snap.js to v2.0.0-rc1
Diffstat (limited to 'core/js')
-rwxr-xr-x | core/js/snap.js | 1349 |
1 files changed, 783 insertions, 566 deletions
diff --git a/core/js/snap.js b/core/js/snap.js index 0bbefe44203..19942e866a9 100755 --- a/core/js/snap.js +++ b/core/js/snap.js @@ -1,568 +1,785 @@ -/* - * Snap.js - * - * Copyright 2013, Jacob Kelley - http://jakiestfu.com/ - * Released under the MIT Licence - * http://opensource.org/licenses/MIT - * - * Github: http://github.com/jakiestfu/Snap.js/ - * Version: 1.9.3 - */ -/*jslint browser: true*/ -/*global define, module, ender*/ +/*! Snap.js v2.0.0-rc1 */ (function(win, doc) { - 'use strict'; - var Snap = Snap || function(userOpts) { - var 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, - slideIntent: 40, // degrees - minDragDistance: 5 - }, - cache = { - simpleStates: { - opening: null, - towards: null, - hyperExtending: null, - halfway: null, - flick: null, - translation: { - absolute: 0, - relative: 0, - sinceDirectionChange: 0, - percentage: 0 - } - } - }, - eventList = {}, - utils = { - hasTouch: ('ontouchstart' in doc.documentElement || win.navigator.msPointerEnabled), - eventType: function(action) { - var eventTypes = { - down: (utils.hasTouch ? 'touchstart' : 'mousedown'), - move: (utils.hasTouch ? 'touchmove' : 'mousemove'), - up: (utils.hasTouch ? 'touchend' : 'mouseup'), - out: (utils.hasTouch ? 'touchcancel' : 'mouseout') - }; - return eventTypes[action]; - }, - page: function(t, e){ - return (utils.hasTouch && e.touches.length && e.touches[0]) ? e.touches[0]['page'+t] : e['page'+t]; - }, - klass: { - has: function(el, name){ - return (el.className).indexOf(name) !== -1; - }, - add: function(el, name){ - if(!utils.klass.has(el, name) && settings.addBodyClasses){ - el.className += " "+name; - } - }, - remove: function(el, name){ - if(settings.addBodyClasses){ - el.className = (el.className).replace(name, "").replace(/^\s+|\s+$/g, ''); - } - } - }, - dispatchEvent: function(type) { - if (typeof eventList[type] === 'function') { - return eventList[type].call(); - } - }, - 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]; - } - } - }, - transitionCallback: function(){ - return (cache.vendor==='Moz' || cache.vendor==='ms') ? 'transitionend' : cache.vendor+'TransitionEnd'; - }, - canTransform: function(){ - return typeof settings.element.style[cache.vendor+'Transform'] !== 'undefined'; - }, - deepExtend: function(destination, source) { - var property; - for (property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - utils.deepExtend(destination[property], source[property]); - } else { - destination[property] = source[property]; - } - } - return destination; - }, - 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: { - 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); - } - }, - 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); - } - }, - prevent: function(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - } - }, - 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; - } - }, - action = { - translate: { - get: { - matrix: function(index) { - - if( !utils.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(','); - if(matrix.length===16){ - index+=ieOffset; - } - return parseInt(matrix[index], 10); - } - return 0; - } - } - }, - easeCallback: function(){ - settings.element.style[cache.vendor+'Transition'] = ''; - cache.translation = action.translate.get.matrix(4); - cache.easing = false; - clearInterval(cache.animatingInterval); - - if(cache.easingTo===0){ - utils.klass.remove(doc.body, 'snapjs-right'); - utils.klass.remove(doc.body, 'snapjs-left'); - } - - utils.dispatchEvent('animated'); - utils.events.removeEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); - }, - easeTo: function(n) { - - if( !utils.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.animatingInterval = setInterval(function() { - utils.dispatchEvent('animating'); - }, 1); - - utils.events.addEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); - action.translate.x(n); - } - if(n===0){ - settings.element.style[cache.vendor+'Transform'] = ''; - } - }, - 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( utils.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 = ''; - } - } - }, - drag: { - listen: function() { - cache.translation = 0; - cache.easing = false; - utils.events.addEvent(settings.element, utils.eventType('down'), action.drag.startDrag); - utils.events.addEvent(settings.element, utils.eventType('move'), action.drag.dragging); - utils.events.addEvent(settings.element, utils.eventType('up'), action.drag.endDrag); - }, - 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); - }, - 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.hasIntent = null; - 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 - } - }; - }, - 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); - } - }, - 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); - } - } - } - }, - init = function(opts) { - if (opts.element) { - utils.deepExtend(settings, opts); - cache.vendor = utils.vendor(); - action.drag.listen(); - } - }; - /* - * Public - */ - this.open = function(side) { - utils.dispatchEvent('open'); - utils.klass.remove(doc.body, 'snapjs-expand-left'); - utils.klass.remove(doc.body, 'snapjs-expand-right'); - - if (side === 'left') { - cache.simpleStates.opening = 'left'; - cache.simpleStates.towards = 'right'; - utils.klass.add(doc.body, 'snapjs-left'); - utils.klass.remove(doc.body, 'snapjs-right'); - action.translate.easeTo(settings.maxPosition); - } else if (side === 'right') { - cache.simpleStates.opening = 'right'; - cache.simpleStates.towards = 'left'; - utils.klass.remove(doc.body, 'snapjs-left'); - utils.klass.add(doc.body, 'snapjs-right'); - action.translate.easeTo(settings.minPosition); - } - }; - this.close = function() { - utils.dispatchEvent('close'); - action.translate.easeTo(0); - }; - this.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; - } - action.translate.easeTo(to); - }; - - this.on = function(evt, fn) { - eventList[evt] = fn; - return this; - }; - this.off = function(evt) { - if (eventList[evt]) { - eventList[evt] = false; - } - }; - - this.enable = function() { - utils.dispatchEvent('enable'); - action.drag.listen(); - }; - this.disable = function() { - utils.dispatchEvent('disable'); - action.drag.stopListening(); - }; - - this.settings = function(opts){ - utils.deepExtend(settings, opts); - }; - - this.state = function() { - var state, - fromLeft = action.translate.get.matrix(4); - if (fromLeft === settings.maxPosition) { - state = 'left'; - } else if (fromLeft === settings.minPosition) { - state = 'right'; - } else { - state = 'closed'; - } - return { - state: state, - info: cache.simpleStates - }; - }; - init(userOpts); - }; - if ((typeof module !== 'undefined') && module.exports) { - module.exports = Snap; - } - if (typeof ender === 'undefined') { - this.Snap = Snap; - } - if ((typeof define === "function") && define.amd) { - define("snap", [], function() { - return Snap; - }); - } + + '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); |