| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.nipplejs = f()}})(function(){var define,module,exports;'use strict';// Constantsvar isTouch = !!('ontouchstart' in window);var isPointer = window.PointerEvent ? true : false;var isMSPointer = window.MSPointerEvent ? true : false;var events = {    touch: {        start: 'touchstart',        move: 'touchmove',        end: 'touchend'    },    mouse: {        start: 'mousedown',        move: 'mousemove',        end: 'mouseup'    },    pointer: {        start: 'pointerdown',        move: 'pointermove',        end: 'pointerup'    },    MSPointer: {        start: 'MSPointerDown',        move: 'MSPointerMove',        end: 'MSPointerUp'    }};var toBind;var secondBind = {};if (isPointer) {    toBind = events.pointer;} else if (isMSPointer) {    toBind = events.MSPointer;} else if (isTouch) {    toBind = events.touch;    secondBind = events.mouse;} else {    toBind = events.mouse;}//////////////////////////      UTILS      //////////////////////////var u = {};u.distance = function (p1, p2) {    var dx = p2.x - p1.x;    var dy = p2.y - p1.y;    return Math.sqrt((dx * dx) + (dy * dy));};u.angle = function(p1, p2) {    var dx = p2.x - p1.x;    var dy = p2.y - p1.y;    return u.degrees(Math.atan2(dy, dx));};u.findCoord = function(p, d, a) {    var b = {x: 0, y: 0};    a = u.radians(a);    b.x = p.x - d * Math.cos(a);    b.y = p.y - d * Math.sin(a);    return b;};u.radians = function(a) {    return a * (Math.PI / 180);};u.degrees = function(a) {    return a * (180 / Math.PI);};u.bindEvt = function (el, type, handler) {    if (el.addEventListener) {        el.addEventListener(type, handler, false);    } else if (el.attachEvent) {        el.attachEvent(type, handler);    }};u.unbindEvt = function (el, type, handler) {    if (el.removeEventListener) {        el.removeEventListener(type, handler);    } else if (el.detachEvent) {        el.detachEvent(type, handler);    }};u.trigger = function (el, type, data) {    var evt = new CustomEvent(type, data);    el.dispatchEvent(evt);};u.prepareEvent = function (evt) {    evt.preventDefault();    return evt.type.match(/^touch/) ? evt.changedTouches : evt;};u.getScroll = function () {    var x = (window.pageXOffset !== undefined) ?        window.pageXOffset :        (document.documentElement || document.body.parentNode || document.body)            .scrollLeft;    var y = (window.pageYOffset !== undefined) ?        window.pageYOffset :        (document.documentElement || document.body.parentNode || document.body)            .scrollTop;    return {        x: x,        y: y    };};u.applyPosition = function (el, pos) {    if (pos.x && pos.y) {        el.style.left = pos.x + 'px';        el.style.top = pos.y + 'px';    } else if (pos.top || pos.right || pos.bottom || pos.left) {        el.style.top = pos.top;        el.style.right = pos.right;        el.style.bottom = pos.bottom;        el.style.left = pos.left;    }};u.getTransitionStyle = function (property, values, time) {    var obj = u.configStylePropertyObject(property);    for (var i in obj) {        if (obj.hasOwnProperty(i)) {            if (typeof values === 'string') {                obj[i] = values + ' ' + time;            } else {                var st = '';                for (var j = 0, max = values.length; j < max; j += 1) {                    st += values[j] + ' ' + time + ', ';                }                obj[i] = st.slice(0, -2);            }        }    }    return obj;};u.getVendorStyle = function (property, value) {    var obj = u.configStylePropertyObject(property);    for (var i in obj) {        if (obj.hasOwnProperty(i)) {            obj[i] = value;        }    }    return obj;};u.configStylePropertyObject = function (prop) {    var obj = {};    obj[prop] = '';    var vendors = ['webkit', 'Moz', 'o'];    vendors.forEach(function (vendor) {        obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';    });    return obj;};u.extend = function (objA, objB) {    for (var i in objB) {        if (objB.hasOwnProperty(i)) {            objA[i] = objB[i];        }    }    return objA;};// Overwrite only what's already presentu.safeExtend = function (objA, objB) {    var obj = {};    for (var i in objA) {        if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {            obj[i] = objB[i];        } else if (objA.hasOwnProperty(i)) {            obj[i] = objA[i];        }    }    return obj;};// Map for array or unique item.u.map = function (ar, fn) {    if (ar.length) {        for (var i = 0, max = ar.length; i < max; i += 1) {            fn(ar[i]);        }    } else {        fn(ar);    }};//////////////////////////   SUPER CLASS   //////////////////////////function Super () {};// Basic event system.Super.prototype.on = function (arg, cb) {    var self = this;    var types = arg.split(/[ ,]+/g);    var type;    self._handlers_ = self._handlers_ || {};    for (var i = 0; i < types.length; i += 1) {        type = types[i];        self._handlers_[type] = self._handlers_[type] || [];        self._handlers_[type].push(cb);    }    return self;};Super.prototype.off = function (type, cb) {    var self = this;    self._handlers_ = self._handlers_ || {};    if (type === undefined) {        self._handlers_ = {};    } else if (cb === undefined) {        self._handlers_[type] = null;    } else if (self._handlers_[type] &&            self._handlers_[type].indexOf(cb) >= 0) {        self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);    }    return self;};Super.prototype.trigger = function (arg, data) {    var self = this;    var types = arg.split(/[ ,]+/g);    var type;    self._handlers_ = self._handlers_ || {};    for (var i = 0; i < types.length; i += 1) {        type = types[i];        if (self._handlers_[type] && self._handlers_[type].length) {            self._handlers_[type].forEach(function (handler) {                handler.call(self, {                    type: type,                    target: self                }, data);            });        }    }};// ConfigurationSuper.prototype.config = function (options) {    var self = this;    self.options = self.defaults || {};    if (options) {        self.options = u.safeExtend(self.options, options);    }};// Bind internal events.Super.prototype.bindEvt = function (el, type) {    var self = this;    self._domHandlers_ = self._domHandlers_ || {};    self._domHandlers_[type] = function () {        if (typeof self['on' + type] === 'function') {            self['on' + type].apply(self, arguments);        } else {            console.warn('[WARNING] : Missing "on' + type + '" handler.');        }    };    u.bindEvt(el, toBind[type], self._domHandlers_[type]);    if (secondBind[type]) {        // Support for both touch and mouse at the same time.        u.bindEvt(el, secondBind[type], self._domHandlers_[type]);    }    return self;};// Unbind dom events.Super.prototype.unbindEvt = function (el, type) {    var self = this;    self._domHandlers_ = self._domHandlers_ || {};    u.unbindEvt(el, toBind[type], self._domHandlers_[type]);    if (secondBind[type]) {        // Support for both touch and mouse at the same time.        u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);    }    delete self._domHandlers_[type];    return this;};//////////////////////////   THE NIPPLE    //////////////////////////function Nipple (collection, options) {    this.identifier = options.identifier;    this.position = options.position;    this.frontPosition = options.frontPosition;    this.collection = collection;    // Defaults    this.defaults = {        size: 100,        threshold: 0.1,        color: 'white',        fadeTime: 250,        dataOnly: false,        restOpacity: 0.5,        mode: 'dynamic',        zone: document.body    };    this.config(options);    // Overwrites    if (this.options.mode === 'dynamic') {        this.options.restOpacity = 0;    }    this.id = Nipple.id;    Nipple.id += 1;    this.buildEl()        .stylize();    // Nipple's API.    this.instance = {        el: this.ui.el,        on: this.on.bind(this),        off: this.off.bind(this),        show: this.show.bind(this),        hide: this.hide.bind(this),        add: this.addToDom.bind(this),        remove: this.removeFromDom.bind(this),        destroy: this.destroy.bind(this),        resetDirection: this.resetDirection.bind(this),        computeDirection: this.computeDirection.bind(this),        trigger: this.trigger.bind(this),        position: this.position,        frontPosition: this.frontPosition,        ui: this.ui,        identifier: this.identifier,        id: this.id,        options: this.options    };    return this.instance;};Nipple.prototype = new Super();Nipple.constructor = Nipple;Nipple.id = 0;// Build the dom element of the Nipple instance.Nipple.prototype.buildEl = function (options) {    this.ui = {};    if (this.options.dataOnly) {        return this;    }    this.ui.el = document.createElement('div');    this.ui.back = document.createElement('div');    this.ui.front = document.createElement('div');    this.ui.el.className = 'nipple collection_' + this.collection.id;    this.ui.back.className = 'back';    this.ui.front.className = 'front';    this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +        '_' + this.id);    this.ui.el.appendChild(this.ui.back);    this.ui.el.appendChild(this.ui.front);    return this;};// Apply CSS to the Nipple instance.Nipple.prototype.stylize = function () {    if (this.options.dataOnly) {        return this;    }    var animTime = this.options.fadeTime + 'ms';    var borderStyle = u.getVendorStyle('borderRadius', '50%');    var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);    var styles = {};    styles.el = {        position: 'absolute',        opacity: this.options.restOpacity,        display: 'block',        'zIndex': 999    };    styles.back = {        position: 'absolute',        display: 'block',        width: this.options.size + 'px',        height: this.options.size + 'px',        marginLeft: -this.options.size / 2 + 'px',        marginTop: -this.options.size / 2 + 'px',        background: this.options.color,        'opacity': '.5'    };    styles.front = {        width: this.options.size / 2 + 'px',        height: this.options.size / 2 + 'px',        position: 'absolute',        display: 'block',        marginLeft: -this.options.size / 4 + 'px',        marginTop: -this.options.size / 4 + 'px',        background: this.options.color,        'opacity': '.5'    };    u.extend(styles.el, transitStyle);    u.extend(styles.back, borderStyle);    u.extend(styles.front, borderStyle);    this.applyStyles(styles);    return this;};Nipple.prototype.applyStyles = function (styles) {    // Apply styles    for (var i in this.ui) {        if (this.ui.hasOwnProperty(i)) {            for (var j in styles[i]) {                this.ui[i].style[j] = styles[i][j];            }        }    }    return this;};// Inject the Nipple instance into DOM.Nipple.prototype.addToDom = function () {    // We're not adding it if we're dataOnly or already in dom.    if (this.options.dataOnly || document.body.contains(this.ui.el)) {        return this;    }    this.options.zone.appendChild(this.ui.el);    return this;};// Remove the Nipple instance from DOM.Nipple.prototype.removeFromDom = function () {    if (this.options.dataOnly || !document.body.contains(this.ui.el)) {        return this;    }    this.options.zone.removeChild(this.ui.el);    return this;};// Entirely destroy this nippleNipple.prototype.destroy = function () {    clearTimeout(this.removeTimeout);    clearTimeout(this.showTimeout);    clearTimeout(this.restTimeout);    this.trigger('destroyed', this.instance);    this.removeFromDom();    this.off();};// Fade in the Nipple instance.Nipple.prototype.show = function (cb) {    var self = this;    if (self.options.dataOnly) {        return self;    }    clearTimeout(self.removeTimeout);    clearTimeout(self.showTimeout);    clearTimeout(self.restTimeout);    self.addToDom();    self.restCallback();    setTimeout(function () {        self.ui.el.style.opacity = 1;    }, 0);    self.showTimeout = setTimeout(function () {        self.trigger('shown', self.instance);        if (typeof cb === 'function') {            cb.call(this);        }    }, self.options.fadeTime);    return self;};// Fade out the Nipple instance.Nipple.prototype.hide = function (cb) {    var self = this;    if (self.options.dataOnly) {        return self;    }    self.ui.el.style.opacity = self.options.restOpacity;    clearTimeout(self.removeTimeout);    clearTimeout(self.showTimeout);    clearTimeout(self.restTimeout);    self.removeTimeout = setTimeout(        function () {            var display = self.options.mode === 'dynamic' ? 'none' : 'block';            self.ui.el.style.display = display;            if (typeof cb === 'function') {                cb.call(self);            }            self.trigger('hidden', self.instance);        },        self.options.fadeTime    );    self.restPosition();    return self;};Nipple.prototype.restPosition = function (cb) {    var self = this;    self.frontPosition = {        x: 0,        y: 0    };    var animTime = self.options.fadeTime + 'ms';    var transitStyle = {};    transitStyle.front = u.getTransitionStyle('transition',        ['top', 'left'], animTime);    var styles = {front: {}};    styles.front = {        left: self.frontPosition.x + 'px',        top: self.frontPosition.y + 'px'    };    self.applyStyles(transitStyle);    self.applyStyles(styles);    self.restTimeout = setTimeout(        function () {            if (typeof cb === 'function') {                cb.call(self);            }            self.restCallback();        },        self.options.fadeTime    );};Nipple.prototype.restCallback = function () {    var self = this;    var transitStyle = {};    transitStyle.front = u.getTransitionStyle('transition', 'none', '');    self.applyStyles(transitStyle);    self.trigger('rested', self.instance);};Nipple.prototype.resetDirection = function () {    // Fully rebuild the object to let the iteration possible.    this.direction = {        x: false,        y: false,        angle: false    };};Nipple.prototype.computeDirection = function (obj) {    var rAngle = obj.angle.radian;    var angle45 = Math.PI / 4;    var angle90 = Math.PI / 2;    var direction, directionX, directionY;    // Angular direction    //     \  UP /    //      \   /    // LEFT       RIGHT    //      /   \    //     /DOWN \    //    if (rAngle > angle45 && rAngle < (angle45 * 3)) {        direction = 'up';    } else if (rAngle > -angle45 && rAngle <= angle45) {        direction = 'left';    } else if (rAngle > (-angle45 * 3) && rAngle <= -angle45) {        direction = 'down';    } else {        direction = 'right';    }    // Plain direction    //    UP                 |    // _______               | RIGHT    //                  LEFT |    //   DOWN                |    if (rAngle > -angle90 && rAngle < angle90) {        directionX = 'left';    } else {        directionX = 'right';    }    if (rAngle > 0) {        directionY = 'up';    } else {        directionY = 'down';    }    if (obj.force > this.options.threshold) {        var oldDirection = {};        for (var i in this.direction) {            if (this.direction.hasOwnProperty(i)) {                oldDirection[i] = this.direction[i];            }        }        var same = {};        this.direction = {            x: directionX,            y: directionY,            angle: direction        };        obj.direction = this.direction;        for (var i in oldDirection) {            if (oldDirection[i] === this.direction[i]) {                same[i] = true;            }        }        // If all 3 directions are the same, we don't trigger anything.        if (same.x && same.y && same.angle) {            return obj;        }        if (!same.x || !same.y) {            this.trigger('plain', obj);        }        if (!same.x) {            this.trigger('plain:' + directionX, obj);        }        if (!same.y) {            this.trigger('plain:' + directionY, obj);        }        if (!same.angle) {            this.trigger('dir dir:' + direction, obj);        }    }    return obj;};/* global Nipple, Super *///////////////////////////////   THE COLLECTION    //////////////////////////////function Collection (manager, options) {    var self = this;    self.nipples = [];    self.idles = [];    self.actives = [];    self.ids = [];    self.pressureIntervals = {};    self.manager = manager;    self.id = Collection.id;    Collection.id += 1;    // Defaults    self.defaults = {        zone: document.body,        multitouch: false,        maxNumberOfNipples: 10,        mode: 'dynamic',        position: {top: 0, left: 0},        catchDistance: 200,        size: 100,        threshold: 0.1,        color: 'white',        fadeTime: 250,        dataOnly: false,        restOpacity: 0.5    };    self.config(options);    // Overwrites    if (self.options.mode === 'static' || self.options.mode === 'semi') {        self.options.multitouch = false;    }    if (!self.options.multitouch) {        self.options.maxNumberOfNipples = 1;    }    self.updateBox();    self.prepareNipples();    self.bindings();    self.begin();    return self.nipples;}Collection.prototype = new Super();Collection.constructor = Collection;Collection.id = 0;Collection.prototype.prepareNipples = function () {    var self = this;    var nips = self.nipples;    // Public API Preparation.    nips.on = self.on.bind(self);    nips.off = self.off.bind(self);    nips.options = self.options;    nips.destroy = self.destroy.bind(self);    nips.ids = self.ids;    nips.id = self.id;    nips.processOnMove = self.processOnMove.bind(self);    nips.processOnEnd = self.processOnEnd.bind(self);    nips.get = function (id) {        if (id === undefined) {            return nips[0];        }        for (var i = 0, max = nips.length; i < max; i += 1) {            if (nips[i].identifier === id) {                return nips[i];            }        }        return false;    };};Collection.prototype.bindings = function () {    var self = this;    // Touch start event.    self.bindEvt(self.options.zone, 'start');    // Avoid native touch actions (scroll, zoom etc...) on the zone.    self.options.zone.style.touchAction = 'none';    self.options.zone.style.msTouchAction = 'none';};Collection.prototype.begin = function () {    var self = this;    var opts = self.options;    // We place our static nipple    // if needed.    if (opts.mode === 'static') {        var nipple = self.createNipple(            opts.position,            self.manager.getIdentifier()        );        // Add it to the dom.        nipple.add();        // Store it in idles.        self.idles.push(nipple);    }};// Nipple FactoryCollection.prototype.createNipple = function (position, identifier) {    var self = this;    var scroll = u.getScroll();    var toPutOn = {};    var opts = self.options;    if (position.x && position.y) {        toPutOn = {            x: position.x -                (scroll.x + self.box.left),            y: position.y -                (scroll.y + self.box.top)        };    } else if (            position.top ||            position.right ||            position.bottom ||            position.left        ) {        // We need to compute the position X / Y of the joystick.        var dumb = document.createElement('DIV');        dumb.style.display = 'hidden';        dumb.style.top = position.top;        dumb.style.right = position.right;        dumb.style.bottom = position.bottom;        dumb.style.left = position.left;        dumb.style.position = 'absolute';        opts.zone.appendChild(dumb);        var dumbBox = dumb.getBoundingClientRect();        opts.zone.removeChild(dumb);        toPutOn = position;        position = {            x: dumbBox.left + scroll.x,            y: dumbBox.top + scroll.y        };    }    var nipple = new Nipple(self, {        color: opts.color,        size: opts.size,        threshold: opts.threshold,        fadeTime: opts.fadeTime,        dataOnly: opts.dataOnly,        restOpacity: opts.restOpacity,        mode: opts.mode,        identifier: identifier,        position: position,        zone: opts.zone,        frontPosition: {            x: 0,            y: 0        }    });    if (!opts.dataOnly) {        u.applyPosition(nipple.ui.el, toPutOn);        u.applyPosition(nipple.ui.front, nipple.frontPosition);    }    self.nipples.push(nipple);    self.trigger('added ' + nipple.identifier + ':added', nipple);    self.manager.trigger('added ' + nipple.identifier + ':added', nipple);    self.bindNipple(nipple);    return nipple;};Collection.prototype.updateBox = function () {    var self = this;    self.box = self.options.zone.getBoundingClientRect();};Collection.prototype.bindNipple = function (nipple) {    var self = this;    var type;    // Bubble up identified events.    var handler = function (evt, data) {        // Identify the event type with the nipple's id.        type = evt.type + ' ' + data.id + ':' + evt.type;        self.trigger(type, data);    };    // When it gets destroyed.    nipple.on('destroyed', self.onDestroyed.bind(self));    // Other events that will get bubbled up.    nipple.on('shown hidden rested dir plain', handler);    nipple.on('dir:up dir:right dir:down dir:left', handler);    nipple.on('plain:up plain:right plain:down plain:left', handler);};Collection.prototype.pressureFn = function (touch, nipple, identifier) {    var self = this;    var previousPressure = 0;    clearInterval(self.pressureIntervals[identifier]);    // Create an interval that will read the pressure every 100ms    self.pressureIntervals[identifier] = setInterval(function () {        var pressure = touch.force || touch.pressure ||            touch.webkitForce || 0;        if (pressure !== previousPressure) {            nipple.trigger('pressure', pressure);            self.trigger('pressure ' +                nipple.identifier + ':pressure', pressure);            previousPressure = pressure;        }    }.bind(self), 100);};Collection.prototype.onstart = function (evt) {    var self = this;    var opts = self.options;    evt = u.prepareEvent(evt);    // Update the box position    self.updateBox();    var process = function (touch) {        // If we can create new nipples        // meaning we don't have more active nipples than we should.        if (self.actives.length < opts.maxNumberOfNipples) {            self.processOnStart(touch);        }    };    u.map(evt, process);    // We ask upstream to bind the document    // on 'move' and 'end'    self.manager.bindDocument();    return false;};Collection.prototype.processOnStart = function (evt) {    var self = this;    var opts = self.options;    var indexInIdles;    var identifier = self.manager.getIdentifier(evt);    var pressure = evt.force || evt.pressure || evt.webkitForce || 0;    var position = {        x: evt.pageX,        y: evt.pageY    };    var nipple = self.getOrCreate(identifier, position);    // Update its touch identifier    nipple.identifier = identifier;    var process = function (nip) {        // Trigger the start.        nip.trigger('start', nip);        self.trigger('start ' + nip.id + ':start', nip);        nip.show();        if (pressure > 0) {            self.pressureFn(evt, nip, nip.identifier);        }        // Trigger the first move event.        self.processOnMove(evt);    };    // Transfer it from idles to actives.    if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {        self.idles.splice(indexInIdles, 1);    }    // Store the nipple in the actives array    self.actives.push(nipple);    self.ids.push(nipple.identifier);    if (opts.mode !== 'semi') {        process(nipple);    } else {        // In semi we check the distance of the touch        // to decide if we have to reset the nipple        var distance = u.distance(position, nipple.position);        if (distance <= opts.catchDistance) {            process(nipple);        } else {            nipple.destroy();            self.processOnStart(evt);            return;        }    }    return nipple;};Collection.prototype.getOrCreate = function (identifier, position) {    var self = this;    var opts = self.options;    var nipple;    // If we're in static or semi, we might already have an active.    if (/(semi|static)/.test(opts.mode)) {        // Get the active one.        // TODO: Multi-touche for semi and static will start here.        // Return the nearest one.        nipple = self.idles[0];        if (nipple) {            self.idles.splice(0, 1);            return nipple;        }        if (opts.mode === 'semi') {            // If we're in semi mode, we need to create one.            return self.createNipple(position, identifier);        }        console.warn('Coudln\'t find the needed nipple.');        return false;    }    // In dynamic, we create a new one.    nipple = self.createNipple(position, identifier);    return nipple;};Collection.prototype.processOnMove = function (evt) {    var self = this;    var opts = self.options;    var identifier = self.manager.getIdentifier(evt);    var nipple = self.nipples.get(identifier);    if (!nipple) {        // This is here just for safety.        // It shouldn't happen.        console.error('Found zombie joystick with ID ' + identifier);        self.manager.removeIdentifier(identifier);        return;    }    nipple.identifier = identifier;    var size = nipple.options.size / 2;    var pos = {        x: evt.pageX,        y: evt.pageY    };    var dist = u.distance(pos, nipple.position);    var angle = u.angle(pos, nipple.position);    var rAngle = u.radians(angle);    var force = dist / size;    // If distance is bigger than nipple's size    // we clamp the position.    if (dist > size) {        dist = size;        pos = u.findCoord(nipple.position, dist, angle);    }    nipple.frontPosition = {        x: pos.x - nipple.position.x,        y: pos.y - nipple.position.y    };    if (!opts.dataOnly) {        u.applyPosition(nipple.ui.front, nipple.frontPosition);    }    // Prepare event's datas.    var toSend = {        identifier: nipple.identifier,        position: pos,        force: force,        pressure: evt.force || evt.pressure || evt.webkitForce || 0,        distance: dist,        angle: {            radian: rAngle,            degree: angle        },        instance: nipple    };    // Compute the direction's datas.    toSend = nipple.computeDirection(toSend);    // Offset angles to follow units circle.    toSend.angle = {        radian: u.radians(180 - angle),        degree: 180 - angle    };    // Send everything to everyone.    nipple.trigger('move', toSend);    self.trigger('move ' + nipple.id + ':move', toSend);};Collection.prototype.processOnEnd = function (evt) {    var self = this;    var opts = self.options;    var identifier = self.manager.getIdentifier(evt);    var nipple = self.nipples.get(identifier);    var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);    if (!nipple) {        return;    }    if (!opts.dataOnly) {        nipple.hide(function () {            if (opts.mode === 'dynamic') {                nipple.trigger('removed', nipple);                self.trigger('removed ' + nipple.id + ':removed', nipple);                self.manager                    .trigger('removed ' + nipple.id + ':removed', nipple);                nipple.destroy();            }        });    }    // Clear the pressure interval reader    clearInterval(self.pressureIntervals[nipple.identifier]);    // Reset the direciton of the nipple, to be able to trigger a new direction    // on start.    nipple.resetDirection();    nipple.trigger('end', nipple);    self.trigger('end ' + nipple.id + ':end', nipple);    // Remove identifier from our bank.    if (self.ids.indexOf(nipple.identifier) >= 0) {        self.ids.splice(self.ids.indexOf(nipple.identifier), 1);    }    // Clean our actives array.    if (self.actives.indexOf(nipple) >= 0) {        self.actives.splice(self.actives.indexOf(nipple), 1);    }    if (/(semi|static)/.test(opts.mode)) {        // Transfer nipple from actives to idles        // if we're in semi or static mode.        self.idles.push(nipple);    } else if (self.nipples.indexOf(nipple) >= 0) {        // Only if we're not in semi or static mode        // we can remove the instance.        self.nipples.splice(self.nipples.indexOf(nipple), 1);    }    // We unbind move and end.    self.manager.unbindDocument();    // We add back the identifier of the idle nipple;    if (/(semi|static)/.test(opts.mode)) {        self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;    }};// Remove destroyed nipple from the listsCollection.prototype.onDestroyed = function(evt, nipple) {    var self = this;    if (self.nipples.indexOf(nipple) >= 0) {        self.nipples.splice(self.nipples.indexOf(nipple), 1);    }    if (self.actives.indexOf(nipple) >= 0) {        self.actives.splice(self.actives.indexOf(nipple), 1);    }    if (self.idles.indexOf(nipple) >= 0) {        self.idles.splice(self.idles.indexOf(nipple), 1);    }    if (self.ids.indexOf(nipple.identifier) >= 0) {        self.ids.splice(self.ids.indexOf(nipple.identifier), 1);    }    // Remove the identifier from our bank    self.manager.removeIdentifier(nipple.identifier);    // We unbind move and end.    self.manager.unbindDocument();};// Cleanly destroy the managerCollection.prototype.destroy = function () {    var self = this;    self.unbindEvt(self.options.zone, 'start');    // Destroy nipples.    self.nipples.forEach(function(nipple) {        nipple.destroy();    });    // Clean 3DTouch intervals.    for (var i in self.pressureIntervals) {        if (self.pressureIntervals.hasOwnProperty(i)) {            clearInterval(self.pressureIntervals[i]);        }    }    // Notify the manager passing the instance    self.trigger('destroyed', self.nipples);    // We unbind move and end.    self.manager.unbindDocument();    // Unbind everything.    self.off();};/* global u, Super, Collection *///////////////////////////     MANAGER     //////////////////////////function Manager (options) {    var self = this;    self.ids = {};    self.index = 0;    self.collections = [];    self.config(options);    self.prepareCollections();    // Listen for resize, to reposition every joysticks    var resizeTimer;    u.bindEvt(window, 'resize', function (evt) {        clearTimeout(resizeTimer);        resizeTimer = setTimeout(function () {            var pos;            var scroll = u.getScroll();            self.collections.forEach(function (collection) {                collection.forEach(function (nipple) {                    pos = nipple.el.getBoundingClientRect();                    nipple.position = {                        x: scroll.x + pos.left,                        y: scroll.y + pos.top                    };                });            });        }, 100);    });    return self.collections;};Manager.prototype = new Super();Manager.constructor = Manager;Manager.prototype.prepareCollections = function () {    var self = this;    // Public API Preparation.    self.collections.create = self.create.bind(self);    // Listen to anything    self.collections.on = self.on.bind(self);    // Unbind general events    self.collections.off = self.off.bind(self);    // Destroy everything    self.collections.destroy = self.destroy.bind(self);    // Get any nipple    self.collections.get = function (id) {        var nipple;        self.collections.every(function (collection) {            if (nipple = collection.get(id)) {                return false;            }            return true;        });        return nipple;    };};Manager.prototype.create = function (options) {    return this.createCollection(options);};// Collection FactoryManager.prototype.createCollection = function (options) {    var self = this;    var collection = new Collection(self, options);    self.bindCollection(collection);    self.collections.push(collection);    return collection;};Manager.prototype.bindCollection = function (collection) {    var self = this;    var type;    // Bubble up identified events.    var handler = function (evt, data) {        // Identify the event type with the nipple's identifier.        type = evt.type + ' ' + data.id + ':' + evt.type;        self.trigger(type, data);    };    // When it gets destroyed we clean.    collection.on('destroyed', self.onDestroyed.bind(self));    // Other events that will get bubbled up.    collection.on('shown hidden rested dir plain', handler);    collection.on('dir:up dir:right dir:down dir:left', handler);    collection.on('plain:up plain:right plain:down plain:left', handler);};Manager.prototype.bindDocument = function () {    var self = this;    // Bind only if not already binded    if (!self.binded) {        self.bindEvt(document, 'move')            .bindEvt(document, 'end');        self.binded = true;    }};Manager.prototype.unbindDocument = function (force) {    var self = this;    // If there are no touch left    // unbind the document.    if (!Object.keys(self.ids).length || force === true) {        self.unbindEvt(document, 'move')            .unbindEvt(document, 'end');        self.binded = false;    }};Manager.prototype.getIdentifier = function (evt) {    var id;    // If no event, simple increment    if (!evt) {        id = this.index;    } else {        // Extract identifier from event object.        // Unavailable in mouse events so replaced by latest increment.        id = evt.identifier === undefined ? evt.pointerId : evt.identifier;        if (id === undefined) {            id = this.latest || 0;        }    }    if (this.ids[id] === undefined) {        this.ids[id] = this.index;        this.index += 1;    }    // Keep the latest id used in case we're using an unidentified mouseEvent    this.latest = id;    return this.ids[id];};Manager.prototype.removeIdentifier = function (identifier) {    var removed = {};    for (var id in this.ids) {        if (this.ids[id] === identifier) {            removed.id = id;            removed.identifier = this.ids[id];            delete this.ids[id];            break;        }    }    return removed;};Manager.prototype.onmove = function (evt) {    var self = this;    self.onAny('move', evt);    return false;};Manager.prototype.onend = function (evt) {    var self = this;    self.onAny('end', evt);    return false;};Manager.prototype.onAny = function (which, evt) {    var self = this;    var id;    var processFn = 'processOn' + which.charAt(0).toUpperCase() +        which.slice(1);    evt = u.prepareEvent(evt);    var processColl = function (e, id, coll) {        if (coll.ids.indexOf(id) >= 0) {            coll[processFn](e);            // Mark the event to avoid cleaning it later.            e._found_ = true;        }    };    var processEvt = function (e) {        id = self.getIdentifier(e);        u.map(self.collections, processColl.bind(null, e, id));        // If the event isn't handled by any collection,        // we need to clean its identifier.        if (!e._found_) {            self.removeIdentifier(id);        }    };    u.map(evt, processEvt);    return false;};// Cleanly destroy the managerManager.prototype.destroy = function () {    var self = this;    self.unbindDocument(true);    self.ids = {};    self.index = 0;    self.collections.forEach(function(collection) {        collection.destroy();    });    self.off();};// When a collection gets destroyed// we clean behind.Manager.prototype.onDestroyed = function (evt, coll) {    var self = this;    if (self.collections.indexOf(coll) < 0) {        return false;    }    self.collections.splice(self.collections.indexOf(coll), 1);};var factory = new Manager();return {    create: function (options) {        return factory.create(options);    },    factory: factory};});
 |