/* ractive-events-tap ================== Version 0.1.2. On mobile devices, using `on-click` isn't good enough. Tapping the touchscreen will fire a simulated click event, but only after a 300 millisecond delay, which makes your app feel sluggish. It also causes the tapped area to highlight, which in most cases looks a bit messy. Instead, use `on-tap`. When you tap an area, the simulated click event will be prevented, and the user's action is responded to instantly. The `on-tap` event also differs from `on-click` in that the click event will (frankly rather bizarrely) fire even if you hold the mouse down over a single element for several seconds and waggle it about. Pointer events are also supported, as is pressing the spacebar when the relevant element is focused (which triggers a click event, and is good for accessibility). ========================== Troubleshooting: If you're using a module system in your app (AMD or something more nodey) then you may need to change the paths below, where it says `require( 'ractive' )` or `define([ 'ractive' ]...)`. ========================== Usage: Include this file on your page below Ractive, e.g: Or, if you're using a module loader, require this module: // requiring the plugin will 'activate' it - no need to use // the return value require( 'ractive-events-tap' ); Add a tap event in the normal fashion:
tap me!
Then add a handler: ractive.on( 'foo', function ( event ) { alert( 'tapped' ); }); */ (function ( global, factory ) { 'use strict'; // Common JS (i.e. browserify) environment if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) { factory( require( 'ractive' ) ); } // AMD? else if ( typeof define === 'function' && define.amd ) { define([ 'ractive' ], factory ); } // browser global else if ( global.Ractive ) { factory( global.Ractive ); } else { throw new Error( 'Could not find Ractive! It must be loaded before the ractive-events-tap plugin' ); } }( typeof window !== 'undefined' ? window : this, function ( Ractive ) { 'use strict'; var tap = function ( node, fire ) { var mousedown, touchstart, focusHandler, distanceThreshold, timeThreshold, preventMousedownEvents, preventMousedownTimeout; distanceThreshold = 5; // maximum pixels pointer can move before cancel timeThreshold = 400; // maximum milliseconds between down and up before cancel mousedown = function ( event ) { var currentTarget, x, y, pointerId, up, move, cancel; if ( preventMousedownEvents ) { return; } if ( event.which !== undefined && event.which !== 1 ) { return; } x = event.clientX; y = event.clientY; currentTarget = this; // This will be null for mouse events. pointerId = event.pointerId; up = function ( event ) { if ( event.pointerId != pointerId ) { return; } fire({ node: currentTarget, original: event }); cancel(); }; move = function ( event ) { if ( event.pointerId != pointerId ) { return; } if ( ( Math.abs( event.clientX - x ) >= distanceThreshold ) || ( Math.abs( event.clientY - y ) >= distanceThreshold ) ) { cancel(); } }; cancel = function () { node.removeEventListener( 'MSPointerUp', up, false ); document.removeEventListener( 'MSPointerMove', move, false ); document.removeEventListener( 'MSPointerCancel', cancel, false ); node.removeEventListener( 'pointerup', up, false ); document.removeEventListener( 'pointermove', move, false ); document.removeEventListener( 'pointercancel', cancel, false ); node.removeEventListener( 'click', up, false ); document.removeEventListener( 'mousemove', move, false ); }; if ( window.navigator.pointerEnabled ) { node.addEventListener( 'pointerup', up, false ); document.addEventListener( 'pointermove', move, false ); document.addEventListener( 'pointercancel', cancel, false ); } else if ( window.navigator.msPointerEnabled ) { node.addEventListener( 'MSPointerUp', up, false ); document.addEventListener( 'MSPointerMove', move, false ); document.addEventListener( 'MSPointerCancel', cancel, false ); } else { node.addEventListener( 'click', up, false ); document.addEventListener( 'mousemove', move, false ); } setTimeout( cancel, timeThreshold ); }; if ( window.navigator.pointerEnabled ) { node.addEventListener( 'pointerdown', mousedown, false ); } else if ( window.navigator.msPointerEnabled ) { node.addEventListener( 'MSPointerDown', mousedown, false ); } else { node.addEventListener( 'mousedown', mousedown, false ); } touchstart = function ( event ) { var currentTarget, x, y, touch, finger, move, up, cancel; if ( event.touches.length !== 1 ) { return; } touch = event.touches[0]; x = touch.clientX; y = touch.clientY; currentTarget = this; finger = touch.identifier; up = function ( event ) { var touch; touch = event.changedTouches[0]; if ( touch.identifier !== finger ) { cancel(); } event.preventDefault(); // prevent compatibility mouse event // for the benefit of mobile Firefox and old Android browsers, we need this absurd hack. preventMousedownEvents = true; clearTimeout( preventMousedownTimeout ); preventMousedownTimeout = setTimeout( function () { preventMousedownEvents = false; }, 400 ); fire({ node: currentTarget, original: event }); cancel(); }; move = function ( event ) { var touch; if ( event.touches.length !== 1 || event.touches[0].identifier !== finger ) { cancel(); } touch = event.touches[0]; if ( ( Math.abs( touch.clientX - x ) >= distanceThreshold ) || ( Math.abs( touch.clientY - y ) >= distanceThreshold ) ) { cancel(); } }; cancel = function () { node.removeEventListener( 'touchend', up, false ); window.removeEventListener( 'touchmove', move, false ); window.removeEventListener( 'touchcancel', cancel, false ); }; node.addEventListener( 'touchend', up, false ); window.addEventListener( 'touchmove', move, false ); window.addEventListener( 'touchcancel', cancel, false ); setTimeout( cancel, timeThreshold ); }; node.addEventListener( 'touchstart', touchstart, false ); // native buttons, and elements, should fire a tap event // when the space key is pressed if ( node.tagName === 'BUTTON' || node.type === 'button' ) { focusHandler = function () { var blurHandler, keydownHandler; keydownHandler = function ( event ) { if ( event.which === 32 ) { // space key fire({ node: node, original: event }); } }; blurHandler = function () { node.removeEventListener( 'keydown', keydownHandler, false ); node.removeEventListener( 'blur', blurHandler, false ); }; node.addEventListener( 'keydown', keydownHandler, false ); node.addEventListener( 'blur', blurHandler, false ); }; node.addEventListener( 'focus', focusHandler, false ); } return { teardown: function () { node.removeEventListener( 'pointerdown', mousedown, false ); node.removeEventListener( 'MSPointerDown', mousedown, false ); node.removeEventListener( 'mousedown', mousedown, false ); node.removeEventListener( 'touchstart', touchstart, false ); node.removeEventListener( 'focus', focusHandler, false ); } }; }; Ractive.events.tap = tap; }));