--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/media/js/lib/yui/yui3-3.15.0/build/gesture-simulate/gesture-simulate-debug.js Mon Mar 10 15:19:48 2014 +0100
@@ -0,0 +1,1321 @@
+YUI.add('gesture-simulate', function (Y, NAME) {
+
+/**
+ * Simulate high-level user gestures by generating a set of native DOM events.
+ *
+ * @module gesture-simulate
+ * @requires event-simulate, async-queue, node-screen
+ */
+
+var NAME = "gesture-simulate",
+
+ // phantomjs check may be temporary, until we determine if it really support touch all the way through, like it claims to (http://code.google.com/p/phantomjs/issues/detail?id=375)
+ SUPPORTS_TOUCH = ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.phantomjs) && !(Y.UA.chrome && Y.UA.chrome < 6)),
+
+ gestureNames = {
+ tap: 1,
+ doubletap: 1,
+ press: 1,
+ move: 1,
+ flick: 1,
+ pinch: 1,
+ rotate: 1
+ },
+
+ touchEvents = {
+ touchstart: 1,
+ touchmove: 1,
+ touchend: 1,
+ touchcancel: 1
+ },
+
+ document = Y.config.doc,
+ emptyTouchList,
+
+ EVENT_INTERVAL = 20, // 20ms
+ START_PAGEX, // will be adjusted to the node element center
+ START_PAGEY, // will be adjusted to the node element center
+
+ // defaults that user can override.
+ DEFAULTS = {
+ // tap gestures
+ HOLD_TAP: 10, // 10ms
+ DELAY_TAP: 10, // 10ms
+
+ // press gesture
+ HOLD_PRESS: 3000, // 3sec
+ MIN_HOLD_PRESS: 1000, // 1sec
+ MAX_HOLD_PRESS: 60000, // 1min
+
+ // move gesture
+ DISTANCE_MOVE: 200, // 200 pixels
+ DURATION_MOVE: 1000, // 1sec
+ MAX_DURATION_MOVE: 5000,// 5sec
+
+ // flick gesture
+ MIN_VELOCITY_FLICK: 1.3,
+ DISTANCE_FLICK: 200, // 200 pixels
+ DURATION_FLICK: 1000, // 1sec
+ MAX_DURATION_FLICK: 5000,// 5sec
+
+ // pinch/rotation
+ DURATION_PINCH: 1000 // 1sec
+ },
+
+ TOUCH_START = 'touchstart',
+ TOUCH_MOVE = 'touchmove',
+ TOUCH_END = 'touchend',
+
+ GESTURE_START = 'gesturestart',
+ GESTURE_CHANGE = 'gesturechange',
+ GESTURE_END = 'gestureend',
+
+ MOUSE_UP = 'mouseup',
+ MOUSE_MOVE = 'mousemove',
+ MOUSE_DOWN = 'mousedown',
+ MOUSE_CLICK = 'click',
+ MOUSE_DBLCLICK = 'dblclick',
+
+ X_AXIS = 'x',
+ Y_AXIS = 'y';
+
+
+function Simulations(node) {
+ if(!node) {
+ Y.error(NAME+': invalid target node');
+ }
+ this.node = node;
+ this.target = Y.Node.getDOMNode(node);
+
+ var startXY = this.node.getXY(),
+ dims = this._getDims();
+
+ START_PAGEX = startXY[0] + (dims[0])/2;
+ START_PAGEY = startXY[1] + (dims[1])/2;
+}
+
+Simulations.prototype = {
+
+ /**
+ * Helper method to convert a degree to a radian.
+ *
+ * @method _toRadian
+ * @private
+ * @param {Number} deg A degree to be converted to a radian.
+ * @return {Number} The degree in radian.
+ */
+ _toRadian: function(deg) {
+ return deg * (Math.PI/180);
+ },
+
+ /**
+ * Helper method to get height/width while accounting for
+ * rotation/scale transforms where possible by using the
+ * bounding client rectangle height/width instead of the
+ * offsetWidth/Height which region uses.
+ * @method _getDims
+ * @private
+ * @return {Array} Array with [height, width]
+ */
+ _getDims : function() {
+ var region,
+ width,
+ height;
+
+ // Ideally, this should be in DOM somewhere.
+ if (this.target.getBoundingClientRect) {
+ region = this.target.getBoundingClientRect();
+
+ if ("height" in region) {
+ height = region.height;
+ } else {
+ // IE7,8 has getBCR, but no height.
+ height = Math.abs(region.bottom - region.top);
+ }
+
+ if ("width" in region) {
+ width = region.width;
+ } else {
+ // IE7,8 has getBCR, but no width.
+ width = Math.abs(region.right - region.left);
+ }
+ } else {
+ region = this.node.get("region");
+ width = region.width;
+ height = region.height;
+ }
+
+ return [width, height];
+ },
+
+ /**
+ * Helper method to convert a point relative to the node element into
+ * the point in the page coordination.
+ *
+ * @method _calculateDefaultPoint
+ * @private
+ * @param {Array} point A point relative to the node element.
+ * @return {Array} The same point in the page coordination.
+ */
+ _calculateDefaultPoint: function(point) {
+
+ var height;
+
+ if(!Y.Lang.isArray(point) || point.length === 0) {
+ point = [START_PAGEX, START_PAGEY];
+ } else {
+ if(point.length == 1) {
+ height = this._getDims[1];
+ point[1] = height/2;
+ }
+ // convert to page(viewport) coordination
+ point[0] = this.node.getX() + point[0];
+ point[1] = this.node.getY() + point[1];
+ }
+
+ return point;
+ },
+
+ /**
+ * The "rotate" and "pinch" methods are essencially same with the exact same
+ * arguments. Only difference is the required parameters. The rotate method
+ * requires "rotation" parameter while the pinch method requires "startRadius"
+ * and "endRadius" parameters.
+ *
+ * @method rotate
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Array} center A center point where the pinch gesture of two fingers
+ * should happen. It is relative to the top left corner of the target
+ * node element.
+ * @param {Number} startRadius A radius of start circle where 2 fingers are
+ * on when the gesture starts. This is optional. The default is a fourth of
+ * either target node width or height whichever is smaller.
+ * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
+ * the pinch or spread gestures are completed. This is optional.
+ * The default is a fourth of either target node width or height whichever is less.
+ * @param {Number} duration A duration of the gesture in millisecond.
+ * @param {Number} start A start angle(0 degree at 12 o'clock) where the
+ * gesture should start. Default is 0.
+ * @param {Number} rotation A rotation in degree. It is required.
+ */
+ rotate: function(cb, center, startRadius, endRadius, duration, start, rotation) {
+ var radius,
+ r1 = startRadius, // optional
+ r2 = endRadius; // optional
+
+ if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
+ radius = (this.target.offsetWidth < this.target.offsetHeight)?
+ this.target.offsetWidth/4 : this.target.offsetHeight/4;
+ r1 = radius;
+ r2 = radius;
+ }
+
+ // required
+ if(!Y.Lang.isNumber(rotation)) {
+ Y.error(NAME+'Invalid rotation detected.');
+ }
+
+ this.pinch(cb, center, r1, r2, duration, start, rotation);
+ },
+
+ /**
+ * The "rotate" and "pinch" methods are essencially same with the exact same
+ * arguments. Only difference is the required parameters. The rotate method
+ * requires "rotation" parameter while the pinch method requires "startRadius"
+ * and "endRadius" parameters.
+ *
+ * The "pinch" gesture can simulate various 2 finger gestures such as pinch,
+ * spread and/or rotation. The "startRadius" and "endRadius" are required.
+ * If endRadius is larger than startRadius, it becomes a spread gesture
+ * otherwise a pinch gesture.
+ *
+ * @method pinch
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Array} center A center point where the pinch gesture of two fingers
+ * should happen. It is relative to the top left corner of the target
+ * node element.
+ * @param {Number} startRadius A radius of start circle where 2 fingers are
+ * on when the gesture starts. This paramenter is required.
+ * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
+ * the pinch or spread gestures are completed. This parameter is required.
+ * @param {Number} duration A duration of the gesture in millisecond.
+ * @param {Number} start A start angle(0 degree at 12 o'clock) where the
+ * gesture should start. Default is 0.
+ * @param {Number} rotation If rotation is desired during the pinch or
+ * spread gestures, this parameter can be used. Default is 0 degree.
+ */
+ pinch: function(cb, center, startRadius, endRadius, duration, start, rotation) {
+ var eventQueue,
+ i,
+ interval = EVENT_INTERVAL,
+ touches,
+ id = 0,
+ r1 = startRadius, // required
+ r2 = endRadius, // required
+ radiusPerStep,
+ centerX, centerY,
+ startScale, endScale, scalePerStep,
+ startRot, endRot, rotPerStep,
+ path1 = {start: [], end: []}, // paths for 1st and 2nd fingers.
+ path2 = {start: [], end: []},
+ steps,
+ touchMove;
+
+ center = this._calculateDefaultPoint(center);
+
+ if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
+ Y.error(NAME+'Invalid startRadius and endRadius detected.');
+ }
+
+ if(!Y.Lang.isNumber(duration) || duration <= 0) {
+ duration = DEFAULTS.DURATION_PINCH;
+ }
+
+ if(!Y.Lang.isNumber(start)) {
+ start = 0.0;
+ } else {
+ start = start%360;
+ while(start < 0) {
+ start += 360;
+ }
+ }
+
+ if(!Y.Lang.isNumber(rotation)) {
+ rotation = 0.0;
+ }
+
+ Y.AsyncQueue.defaults.timeout = interval;
+ eventQueue = new Y.AsyncQueue();
+
+ // range determination
+ centerX = center[0];
+ centerY = center[1];
+
+ startRot = start;
+ endRot = start + rotation;
+
+ // 1st finger path
+ path1.start = [
+ centerX + r1*Math.sin(this._toRadian(startRot)),
+ centerY - r1*Math.cos(this._toRadian(startRot))
+ ];
+ path1.end = [
+ centerX + r2*Math.sin(this._toRadian(endRot)),
+ centerY - r2*Math.cos(this._toRadian(endRot))
+ ];
+
+ // 2nd finger path
+ path2.start = [
+ centerX - r1*Math.sin(this._toRadian(startRot)),
+ centerY + r1*Math.cos(this._toRadian(startRot))
+ ];
+ path2.end = [
+ centerX - r2*Math.sin(this._toRadian(endRot)),
+ centerY + r2*Math.cos(this._toRadian(endRot))
+ ];
+
+ startScale = 1.0;
+ endScale = endRadius/startRadius;
+
+ // touch/gesture start
+ eventQueue.add({
+ fn: function() {
+ var coord1, coord2, coord, touches;
+
+ // coordinate for each touch object.
+ coord1 = {
+ pageX: path1.start[0],
+ pageY: path1.start[1],
+ clientX: path1.start[0],
+ clientY: path1.start[1]
+ };
+ coord2 = {
+ pageX: path2.start[0],
+ pageY: path2.start[1],
+ clientX: path2.start[0],
+ clientY: path2.start[1]
+ };
+ touches = this._createTouchList([Y.merge({
+ identifier: (id++)
+ }, coord1), Y.merge({
+ identifier: (id++)
+ }, coord2)]);
+
+ // coordinate for top level event
+ coord = {
+ pageX: (path1.start[0] + path2.start[0])/2,
+ pageY: (path1.start[0] + path2.start[1])/2,
+ clientX: (path1.start[0] + path2.start[0])/2,
+ clientY: (path1.start[0] + path2.start[1])/2
+ };
+
+ this._simulateEvent(this.target, TOUCH_START, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches,
+ scale: startScale,
+ rotation: startRot
+ }, coord));
+
+ if(Y.UA.ios >= 2.0) {
+ /* gesture starts when the 2nd finger touch starts.
+ * The implementation will fire 1 touch start event for both fingers,
+ * simulating 2 fingers touched on the screen at the same time.
+ */
+ this._simulateEvent(this.target, GESTURE_START, Y.merge({
+ scale: startScale,
+ rotation: startRot
+ }, coord));
+ }
+ },
+ timeout: 0,
+ context: this
+ });
+
+ // gesture change
+ steps = Math.floor(duration/interval);
+ radiusPerStep = (r2 - r1)/steps;
+ scalePerStep = (endScale - startScale)/steps;
+ rotPerStep = (endRot - startRot)/steps;
+
+ touchMove = function(step) {
+ var radius = r1 + (radiusPerStep)*step,
+ px1 = centerX + radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
+ py1 = centerY - radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
+ px2 = centerX - radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
+ py2 = centerY + radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
+ px = (px1+px2)/2,
+ py = (py1+py2)/2,
+ coord1, coord2, coord, touches;
+
+ // coordinate for each touch object.
+ coord1 = {
+ pageX: px1,
+ pageY: py1,
+ clientX: px1,
+ clientY: py1
+ };
+ coord2 = {
+ pageX: px2,
+ pageY: py2,
+ clientX: px2,
+ clientY: py2
+ };
+ touches = this._createTouchList([Y.merge({
+ identifier: (id++)
+ }, coord1), Y.merge({
+ identifier: (id++)
+ }, coord2)]);
+
+ // coordinate for top level event
+ coord = {
+ pageX: px,
+ pageY: py,
+ clientX: px,
+ clientY: py
+ };
+
+ this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches,
+ scale: startScale + scalePerStep*step,
+ rotation: startRot + rotPerStep*step
+ }, coord));
+
+ if(Y.UA.ios >= 2.0) {
+ this._simulateEvent(this.target, GESTURE_CHANGE, Y.merge({
+ scale: startScale + scalePerStep*step,
+ rotation: startRot + rotPerStep*step
+ }, coord));
+ }
+ };
+
+ for (i=0; i < steps; i++) {
+ eventQueue.add({
+ fn: touchMove,
+ args: [i],
+ context: this
+ });
+ }
+
+ // gesture end
+ eventQueue.add({
+ fn: function() {
+ var emptyTouchList = this._getEmptyTouchList(),
+ coord1, coord2, coord, touches;
+
+ // coordinate for each touch object.
+ coord1 = {
+ pageX: path1.end[0],
+ pageY: path1.end[1],
+ clientX: path1.end[0],
+ clientY: path1.end[1]
+ };
+ coord2 = {
+ pageX: path2.end[0],
+ pageY: path2.end[1],
+ clientX: path2.end[0],
+ clientY: path2.end[1]
+ };
+ touches = this._createTouchList([Y.merge({
+ identifier: (id++)
+ }, coord1), Y.merge({
+ identifier: (id++)
+ }, coord2)]);
+
+ // coordinate for top level event
+ coord = {
+ pageX: (path1.end[0] + path2.end[0])/2,
+ pageY: (path1.end[0] + path2.end[1])/2,
+ clientX: (path1.end[0] + path2.end[0])/2,
+ clientY: (path1.end[0] + path2.end[1])/2
+ };
+
+ if(Y.UA.ios >= 2.0) {
+ this._simulateEvent(this.target, GESTURE_END, Y.merge({
+ scale: endScale,
+ rotation: endRot
+ }, coord));
+ }
+
+ this._simulateEvent(this.target, TOUCH_END, Y.merge({
+ touches: emptyTouchList,
+ targetTouches: emptyTouchList,
+ changedTouches: touches,
+ scale: endScale,
+ rotation: endRot
+ }, coord));
+ },
+ context: this
+ });
+
+ if(cb && Y.Lang.isFunction(cb)) {
+ eventQueue.add({
+ fn: cb,
+
+ // by default, the callback runs the node context where
+ // simulateGesture method is called.
+ context: this.node
+
+ //TODO: Use args to pass error object as 1st param if there is an error.
+ //args:
+ });
+ }
+
+ eventQueue.run();
+ },
+
+ /**
+ * The "tap" gesture can be used for various single touch point gestures
+ * such as single tap, N number of taps, long press. The default is a single
+ * tap.
+ *
+ * @method tap
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Array} point A point(relative to the top left corner of the
+ * target node element) where the tap gesture should start. The default
+ * is the center of the taget node.
+ * @param {Number} times The number of taps. Default is 1.
+ * @param {Number} hold The hold time in milliseconds between "touchstart" and
+ * "touchend" event generation. Default is 10ms.
+ * @param {Number} delay The time gap in millisecond between taps if this
+ * gesture has more than 1 tap. Default is 10ms.
+ */
+ tap: function(cb, point, times, hold, delay) {
+ var eventQueue = new Y.AsyncQueue(),
+ emptyTouchList = this._getEmptyTouchList(),
+ touches,
+ coord,
+ i,
+ touchStart,
+ touchEnd;
+
+ point = this._calculateDefaultPoint(point);
+
+ if(!Y.Lang.isNumber(times) || times < 1) {
+ times = 1;
+ }
+
+ if(!Y.Lang.isNumber(hold)) {
+ hold = DEFAULTS.HOLD_TAP;
+ }
+
+ if(!Y.Lang.isNumber(delay)) {
+ delay = DEFAULTS.DELAY_TAP;
+ }
+
+ coord = {
+ pageX: point[0],
+ pageY: point[1],
+ clientX: point[0],
+ clientY: point[1]
+ };
+
+ touches = this._createTouchList([Y.merge({identifier: 0}, coord)]);
+
+ touchStart = function() {
+ this._simulateEvent(this.target, TOUCH_START, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches
+ }, coord));
+ };
+
+ touchEnd = function() {
+ this._simulateEvent(this.target, TOUCH_END, Y.merge({
+ touches: emptyTouchList,
+ targetTouches: emptyTouchList,
+ changedTouches: touches
+ }, coord));
+ };
+
+ for (i=0; i < times; i++) {
+ eventQueue.add({
+ fn: touchStart,
+ context: this,
+ timeout: (i === 0)? 0 : delay
+ });
+
+ eventQueue.add({
+ fn: touchEnd,
+ context: this,
+ timeout: hold
+ });
+ }
+
+ if(times > 1 && !SUPPORTS_TOUCH) {
+ eventQueue.add({
+ fn: function() {
+ this._simulateEvent(this.target, MOUSE_DBLCLICK, coord);
+ },
+ context: this
+ });
+ }
+
+ if(cb && Y.Lang.isFunction(cb)) {
+ eventQueue.add({
+ fn: cb,
+
+ // by default, the callback runs the node context where
+ // simulateGesture method is called.
+ context: this.node
+
+ //TODO: Use args to pass error object as 1st param if there is an error.
+ //args:
+ });
+ }
+
+ eventQueue.run();
+ },
+
+ /**
+ * The "flick" gesture is a specialized "move" that has some velocity
+ * and the movement always runs either x or y axis. The velocity is calculated
+ * with "distance" and "duration" arguments. If the calculated velocity is
+ * below than the minimum velocity, the given duration will be ignored and
+ * new duration will be created to make a valid flick gesture.
+ *
+ * @method flick
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Array} point A point(relative to the top left corner of the
+ * target node element) where the flick gesture should start. The default
+ * is the center of the taget node.
+ * @param {String} axis Either "x" or "y".
+ * @param {Number} distance A distance in pixels to flick.
+ * @param {Number} duration A duration of the gesture in millisecond.
+ *
+ */
+ flick: function(cb, point, axis, distance, duration) {
+ var path;
+
+ point = this._calculateDefaultPoint(point);
+
+ if(!Y.Lang.isString(axis)) {
+ axis = X_AXIS;
+ } else {
+ axis = axis.toLowerCase();
+ if(axis !== X_AXIS && axis !== Y_AXIS) {
+ Y.error(NAME+'(flick): Only x or y axis allowed');
+ }
+ }
+
+ if(!Y.Lang.isNumber(distance)) {
+ distance = DEFAULTS.DISTANCE_FLICK;
+ }
+
+ if(!Y.Lang.isNumber(duration)){
+ duration = DEFAULTS.DURATION_FLICK; // ms
+ } else {
+ if(duration > DEFAULTS.MAX_DURATION_FLICK) {
+ duration = DEFAULTS.MAX_DURATION_FLICK;
+ }
+ }
+
+ /*
+ * Check if too slow for a flick.
+ * Adjust duration if the calculated velocity is less than
+ * the minimum velcocity to be claimed as a flick.
+ */
+ if(Math.abs(distance)/duration < DEFAULTS.MIN_VELOCITY_FLICK) {
+ duration = Math.abs(distance)/DEFAULTS.MIN_VELOCITY_FLICK;
+ }
+
+ path = {
+ start: Y.clone(point),
+ end: [
+ (axis === X_AXIS) ? point[0]+distance : point[0],
+ (axis === Y_AXIS) ? point[1]+distance : point[1]
+ ]
+ };
+
+ this._move(cb, path, duration);
+ },
+
+ /**
+ * The "move" gesture simulate the movement of any direction between
+ * the straight line of start and end point for the given duration.
+ * The path argument is an object with "point", "xdist" and "ydist" properties.
+ * The "point" property is an array with x and y coordinations(relative to the
+ * top left corner of the target node element) while "xdist" and "ydist"
+ * properties are used for the distance along the x and y axis. A negative
+ * distance number can be used to drag either left or up direction.
+ *
+ * If no arguments are given, it will simulate the default move, which
+ * is moving 200 pixels from the center of the element to the positive X-axis
+ * direction for 1 sec.
+ *
+ * @method move
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Object} path An object with "point", "xdist" and "ydist".
+ * @param {Number} duration A duration of the gesture in millisecond.
+ */
+ move: function(cb, path, duration) {
+ var convertedPath;
+
+ if(!Y.Lang.isObject(path)) {
+ path = {
+ point: this._calculateDefaultPoint([]),
+ xdist: DEFAULTS.DISTANCE_MOVE,
+ ydist: 0
+ };
+ } else {
+ // convert to the page coordination
+ if(!Y.Lang.isArray(path.point)) {
+ path.point = this._calculateDefaultPoint([]);
+ } else {
+ path.point = this._calculateDefaultPoint(path.point);
+ }
+
+ if(!Y.Lang.isNumber(path.xdist)) {
+ path.xdist = DEFAULTS.DISTANCE_MOVE;
+ }
+
+ if(!Y.Lang.isNumber(path.ydist)) {
+ path.ydist = 0;
+ }
+ }
+
+ if(!Y.Lang.isNumber(duration)){
+ duration = DEFAULTS.DURATION_MOVE; // ms
+ } else {
+ if(duration > DEFAULTS.MAX_DURATION_MOVE) {
+ duration = DEFAULTS.MAX_DURATION_MOVE;
+ }
+ }
+
+ convertedPath = {
+ start: Y.clone(path.point),
+ end: [path.point[0]+path.xdist, path.point[1]+path.ydist]
+ };
+
+ this._move(cb, convertedPath, duration);
+ },
+
+ /**
+ * A base method on top of "move" and "flick" methods. The method takes
+ * the path with start/end properties and duration to generate a set of
+ * touch events for the movement gesture.
+ *
+ * @method _move
+ * @private
+ * @param {Function} cb The callback to execute when the gesture simulation
+ * is completed.
+ * @param {Object} path An object with "start" and "end" properties. Each
+ * property should be an array with x and y coordination (e.g. start: [100, 50])
+ * @param {Number} duration A duration of the gesture in millisecond.
+ */
+ _move: function(cb, path, duration) {
+ var eventQueue,
+ i,
+ interval = EVENT_INTERVAL,
+ steps, stepX, stepY,
+ id = 0,
+ touchMove;
+
+ if(!Y.Lang.isNumber(duration)){
+ duration = DEFAULTS.DURATION_MOVE; // ms
+ } else {
+ if(duration > DEFAULTS.MAX_DURATION_MOVE) {
+ duration = DEFAULTS.MAX_DURATION_MOVE;
+ }
+ }
+
+ if(!Y.Lang.isObject(path)) {
+ path = {
+ start: [
+ START_PAGEX,
+ START_PAGEY
+ ],
+ end: [
+ START_PAGEX + DEFAULTS.DISTANCE_MOVE,
+ START_PAGEY
+ ]
+ };
+ } else {
+ if(!Y.Lang.isArray(path.start)) {
+ path.start = [
+ START_PAGEX,
+ START_PAGEY
+ ];
+ }
+ if(!Y.Lang.isArray(path.end)) {
+ path.end = [
+ START_PAGEX + DEFAULTS.DISTANCE_MOVE,
+ START_PAGEY
+ ];
+ }
+ }
+
+ Y.AsyncQueue.defaults.timeout = interval;
+ eventQueue = new Y.AsyncQueue();
+
+ // start
+ eventQueue.add({
+ fn: function() {
+ var coord = {
+ pageX: path.start[0],
+ pageY: path.start[1],
+ clientX: path.start[0],
+ clientY: path.start[1]
+ },
+ touches = this._createTouchList([
+ Y.merge({identifier: (id++)}, coord)
+ ]);
+
+ this._simulateEvent(this.target, TOUCH_START, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches
+ }, coord));
+ },
+ timeout: 0,
+ context: this
+ });
+
+ // move
+ steps = Math.floor(duration/interval);
+ stepX = (path.end[0] - path.start[0])/steps;
+ stepY = (path.end[1] - path.start[1])/steps;
+
+ touchMove = function(step) {
+ var px = path.start[0]+(stepX * step),
+ py = path.start[1]+(stepY * step),
+ coord = {
+ pageX: px,
+ pageY: py,
+ clientX: px,
+ clientY: py
+ },
+ touches = this._createTouchList([
+ Y.merge({identifier: (id++)}, coord)
+ ]);
+
+ this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches
+ }, coord));
+ };
+
+ for (i=0; i < steps; i++) {
+ eventQueue.add({
+ fn: touchMove,
+ args: [i],
+ context: this
+ });
+ }
+
+ // last move
+ eventQueue.add({
+ fn: function() {
+ var coord = {
+ pageX: path.end[0],
+ pageY: path.end[1],
+ clientX: path.end[0],
+ clientY: path.end[1]
+ },
+ touches = this._createTouchList([
+ Y.merge({identifier: id}, coord)
+ ]);
+
+ this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
+ touches: touches,
+ targetTouches: touches,
+ changedTouches: touches
+ }, coord));
+ },
+ timeout: 0,
+ context: this
+ });
+
+ // end
+ eventQueue.add({
+ fn: function() {
+ var coord = {
+ pageX: path.end[0],
+ pageY: path.end[1],
+ clientX: path.end[0],
+ clientY: path.end[1]
+ },
+ emptyTouchList = this._getEmptyTouchList(),
+ touches = this._createTouchList([
+ Y.merge({identifier: id}, coord)
+ ]);
+
+ this._simulateEvent(this.target, TOUCH_END, Y.merge({
+ touches: emptyTouchList,
+ targetTouches: emptyTouchList,
+ changedTouches: touches
+ }, coord));
+ },
+ context: this
+ });
+
+ if(cb && Y.Lang.isFunction(cb)) {
+ eventQueue.add({
+ fn: cb,
+
+ // by default, the callback runs the node context where
+ // simulateGesture method is called.
+ context: this.node
+
+ //TODO: Use args to pass error object as 1st param if there is an error.
+ //args:
+ });
+ }
+
+ eventQueue.run();
+ },
+
+ /**
+ * Helper method to return a singleton instance of empty touch list.
+ *
+ * @method _getEmptyTouchList
+ * @private
+ * @return {TouchList | Array} An empty touch list object.
+ */
+ _getEmptyTouchList: function() {
+ if(!emptyTouchList) {
+ emptyTouchList = this._createTouchList([]);
+ }
+
+ return emptyTouchList;
+ },
+
+ /**
+ * Helper method to convert an array with touch points to TouchList object as
+ * defined in http://www.w3.org/TR/touch-events/
+ *
+ * @method _createTouchList
+ * @private
+ * @param {Array} touchPoints
+ * @return {TouchList | Array} If underlaying platform support creating touch list
+ * a TouchList object will be returned otherwise a fake Array object
+ * will be returned.
+ */
+ _createTouchList: function(touchPoints) {
+ /*
+ * Android 4.0.3 emulator:
+ * Native touch api supported starting in version 4.0 (Ice Cream Sandwich).
+ * However the support seems limited. In Android 4.0.3 emulator, I got
+ * "TouchList is not defined".
+ */
+ var touches = [],
+ touchList,
+ self = this;
+
+ if(!!touchPoints && Y.Lang.isArray(touchPoints)) {
+ if(Y.UA.android && Y.UA.android >= 4.0 || Y.UA.ios && Y.UA.ios >= 2.0) {
+ Y.each(touchPoints, function(point) {
+ if(!point.identifier) {point.identifier = 0;}
+ if(!point.pageX) {point.pageX = 0;}
+ if(!point.pageY) {point.pageY = 0;}
+ if(!point.screenX) {point.screenX = 0;}
+ if(!point.screenY) {point.screenY = 0;}
+
+ touches.push(document.createTouch(Y.config.win,
+ self.target,
+ point.identifier,
+ point.pageX, point.pageY,
+ point.screenX, point.screenY));
+ });
+
+ touchList = document.createTouchList.apply(document, touches);
+ } else if(Y.UA.ios && Y.UA.ios < 2.0) {
+ Y.error(NAME+': No touch event simulation framework present.');
+ } else {
+ // this will inclide android(Y.UA.android && Y.UA.android < 4.0)
+ // and desktops among all others.
+
+ /*
+ * Touch APIs are broken in androids older than 4.0. We will use
+ * simulated touch apis for these versions.
+ */
+ touchList = [];
+ Y.each(touchPoints, function(point) {
+ if(!point.identifier) {point.identifier = 0;}
+ if(!point.clientX) {point.clientX = 0;}
+ if(!point.clientY) {point.clientY = 0;}
+ if(!point.pageX) {point.pageX = 0;}
+ if(!point.pageY) {point.pageY = 0;}
+ if(!point.screenX) {point.screenX = 0;}
+ if(!point.screenY) {point.screenY = 0;}
+
+ touchList.push({
+ target: self.target,
+ identifier: point.identifier,
+ clientX: point.clientX,
+ clientY: point.clientY,
+ pageX: point.pageX,
+ pageY: point.pageY,
+ screenX: point.screenX,
+ screenY: point.screenY
+ });
+ });
+
+ touchList.item = function(i) {
+ return touchList[i];
+ };
+ }
+ } else {
+ Y.error(NAME+': Invalid touchPoints passed');
+ }
+
+ return touchList;
+ },
+
+ /**
+ * @method _simulateEvent
+ * @private
+ * @param {HTMLElement} target The DOM element that's the target of the event.
+ * @param {String} type The type of event or name of the supported gesture to simulate
+ * (i.e., "click", "doubletap", "flick").
+ * @param {Object} options (Optional) Extra options to copy onto the event object.
+ * For gestures, options are used to refine the gesture behavior.
+ */
+ _simulateEvent: function(target, type, options) {
+ var touches;
+
+ if (touchEvents[type]) {
+ if(SUPPORTS_TOUCH) {
+ Y.Event.simulate(target, type, options);
+ } else {
+ // simulate using mouse events if touch is not applicable on this platform.
+ // but only single touch event can be simulated.
+ if(this._isSingleTouch(options.touches, options.targetTouches, options.changedTouches)) {
+ type = {
+ touchstart: MOUSE_DOWN,
+ touchmove: MOUSE_MOVE,
+ touchend: MOUSE_UP
+ }[type];
+
+ options.button = 0;
+ options.relatedTarget = null; // since we are not using mouseover event.
+
+ // touchend has none in options.touches.
+ touches = (type === MOUSE_UP)? options.changedTouches : options.touches;
+
+ options = Y.mix(options, {
+ screenX: touches.item(0).screenX,
+ screenY: touches.item(0).screenY,
+ clientX: touches.item(0).clientX,
+ clientY: touches.item(0).clientY
+ }, true);
+
+ Y.Event.simulate(target, type, options);
+
+ if(type == MOUSE_UP) {
+ Y.Event.simulate(target, MOUSE_CLICK, options);
+ }
+ } else {
+ Y.error("_simulateEvent(): Event '" + type + "' has multi touch objects that can't be simulated in your platform.");
+ }
+ }
+ } else {
+ // pass thru for all non touch events
+ Y.Event.simulate(target, type, options);
+ }
+ },
+
+ /**
+ * Helper method to check the single touch.
+ * @method _isSingleTouch
+ * @private
+ * @param {TouchList} touches
+ * @param {TouchList} targetTouches
+ * @param {TouchList} changedTouches
+ */
+ _isSingleTouch: function(touches, targetTouches, changedTouches) {
+ return (touches && (touches.length <= 1)) &&
+ (targetTouches && (targetTouches.length <= 1)) &&
+ (changedTouches && (changedTouches.length <= 1));
+ }
+};
+
+/*
+ * A gesture simulation class.
+ */
+Y.GestureSimulation = Simulations;
+
+/*
+ * Various simulation default behavior properties. If user override
+ * Y.GestureSimulation.defaults, overriden values will be used and this
+ * should be done before the gesture simulation.
+ */
+Y.GestureSimulation.defaults = DEFAULTS;
+
+/*
+ * The high level gesture names that YUI knows how to simulate.
+ */
+Y.GestureSimulation.GESTURES = gestureNames;
+
+/**
+ * Simulates the higher user level gesture of the given name on a target.
+ * This method generates a set of low level touch events(Apple specific gesture
+ * events as well for the iOS platforms) asynchronously. Note that gesture
+ * simulation is relying on `Y.Event.simulate()` method to generate
+ * the touch events under the hood. The `Y.Event.simulate()` method
+ * itself is a synchronous method.
+ *
+ * Users are suggested to use `Node.simulateGesture()` method which
+ * basically calls this method internally. Supported gestures are `tap`,
+ * `doubletap`, `press`, `move`, `flick`, `pinch` and `rotate`.
+ *
+ * The `pinch` gesture is used to simulate the pinching and spreading of two
+ * fingers. During a pinch simulation, rotation is also possible. Essentially
+ * `pinch` and `rotate` simulations share the same base implementation to allow
+ * both pinching and rotation at the same time. The only difference is `pinch`
+ * requires `start` and `end` option properties while `rotate` requires `rotation`
+ * option property.
+ *
+ * The `pinch` and `rotate` gestures can be described as placing 2 fingers along a
+ * circle. Pinching and spreading can be described by start and end circles while
+ * rotation occurs on a single circle. If the radius of the start circle is greater
+ * than the end circle, the gesture becomes a pinch, otherwise it is a spread spread.
+ *
+ * @example
+ *
+ * var node = Y.one("#target");
+ *
+ * // double tap example
+ * node.simulateGesture("doubletap", function() {
+ * // my callback function
+ * });
+ *
+ * // flick example from the center of the node, move 50 pixels down for 50ms)
+ * node.simulateGesture("flick", {
+ * axis: y,
+ * distance: -100
+ * duration: 50
+ * }, function() {
+ * // my callback function
+ * });
+ *
+ * // simulate rotating a node 75 degrees counter-clockwise
+ * node.simulateGesture("rotate", {
+ * rotation: -75
+ * });
+ *
+ * // simulate a pinch and a rotation at the same time.
+ * // fingers start on a circle of radius 100 px, placed at top/bottom
+ * // fingers end on a circle of radius 50px, placed at right/left
+ * node.simulateGesture("pinch", {
+ * r1: 100,
+ * r2: 50,
+ * start: 0
+ * rotation: 90
+ * });
+ *
+ * @method simulateGesture
+ * @param {HTMLElement|Node} node The YUI node or HTML element that's the target
+ * of the event.
+ * @param {String} name The name of the supported gesture to simulate. The
+ * supported gesture name is one of "tap", "doubletap", "press", "move",
+ * "flick", "pinch" and "rotate".
+ * @param {Object} [options] Extra options used to define the gesture behavior:
+ *
+ * Valid options properties for the `tap` gesture:
+ *
+ * @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
+ * where the tap should be simulated. Default is the center of the node
+ * element.
+ * @param {Number} [options.hold=10] (Optional) The hold time in milliseconds.
+ * This is the time between `touchstart` and `touchend` event generation.
+ * @param {Number} [options.times=1] (Optional) Indicates the number of taps.
+ * @param {Number} [options.delay=10] (Optional) The number of milliseconds
+ * before the next tap simulation happens. This is valid only when `times`
+ * is more than 1.
+ *
+ * Valid options properties for the `doubletap` gesture:
+ *
+ * @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
+ * where the doubletap should be simulated. Default is the center of the
+ * node element.
+ *
+ * Valid options properties for the `press` gesture:
+ *
+ * @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
+ * where the press should be simulated. Default is the center of the node
+ * element.
+ * @param {Number} [options.hold=3000] (Optional) The hold time in milliseconds.
+ * This is the time between `touchstart` and `touchend` event generation.
+ * Default is 3000ms (3 seconds).
+ *
+ * Valid options properties for the `move` gesture:
+ *
+ * @param {Object} [options.path] (Optional) Indicates the path of the finger
+ * movement. It's an object with three optional properties: `point`,
+ * `xdist` and `ydist`.
+ * @param {Array} [options.path.point] A starting point of the gesture.
+ * Default is the center of the node element.
+ * @param {Number} [options.path.xdist=200] A distance to move in pixels
+ * along the X axis. A negative distance value indicates moving left.
+ * @param {Number} [options.path.ydist=0] A distance to move in pixels
+ * along the Y axis. A negative distance value indicates moving up.
+ * @param {Number} [options.duration=1000] (Optional) The duration of the
+ * gesture in milliseconds.
+ *
+ * Valid options properties for the `flick` gesture:
+ *
+ * @param {Array} [options.point] (Optional) Indicates the [x, y] coordinates
+ * where the flick should be simulated. Default is the center of the
+ * node element.
+ * @param {String} [options.axis='x'] (Optional) Valid values are either
+ * "x" or "y". Indicates axis to move along. The flick can move to one of
+ * 4 directions(left, right, up and down).
+ * @param {Number} [options.distance=200] (Optional) Distance to move in pixels
+ * @param {Number} [options.duration=1000] (Optional) The duration of the
+ * gesture in milliseconds. User given value could be automatically
+ * adjusted by the framework if it is below the minimum velocity to be
+ * a flick gesture.
+ *
+ * Valid options properties for the `pinch` gesture:
+ *
+ * @param {Array} [options.center] (Optional) The center of the circle where
+ * two fingers are placed. Default is the center of the node element.
+ * @param {Number} [options.r1] (Required) Pixel radius of the start circle
+ * where 2 fingers will be on when the gesture starts. The circles are
+ * centered at the center of the element.
+ * @param {Number} [options.r2] (Required) Pixel radius of the end circle
+ * when this gesture ends.
+ * @param {Number} [options.duration=1000] (Optional) The duration of the
+ * gesture in milliseconds.
+ * @param {Number} [options.start=0] (Optional) Starting degree of the first
+ * finger. The value is relative to the path of the north. Default is 0
+ * (i.e., 12:00 on a clock).
+ * @param {Number} [options.rotation=0] (Optional) Degrees to rotate from
+ * the starting degree. A negative value means rotation to the
+ * counter-clockwise direction.
+ *
+ * Valid options properties for the `rotate` gesture:
+ *
+ * @param {Array} [options.center] (Optional) The center of the circle where
+ * two fingers are placed. Default is the center of the node element.
+ * @param {Number} [options.r1] (Optional) Pixel radius of the start circle
+ * where 2 fingers will be on when the gesture starts. The circles are
+ * centered at the center of the element. Default is a fourth of the node
+ * element width or height, whichever is smaller.
+ * @param {Number} [options.r2] (Optional) Pixel radius of the end circle
+ * when this gesture ends. Default is a fourth of the node element width or
+ * height, whichever is smaller.
+ * @param {Number} [options.duration=1000] (Optional) The duration of the
+ * gesture in milliseconds.
+ * @param {Number} [options.start=0] (Optional) Starting degree of the first
+ * finger. The value is relative to the path of the north. Default is 0
+ * (i.e., 12:00 on a clock).
+ * @param {Number} [options.rotation] (Required) Degrees to rotate from
+ * the starting degree. A negative value means rotation to the
+ * counter-clockwise direction.
+ *
+ * @param {Function} [cb] The callback to execute when the asynchronouse gesture
+ * simulation is completed.
+ * @param {Error} cb.err An error object if the simulation is failed.
+ * @for Event
+ * @static
+ */
+Y.Event.simulateGesture = function(node, name, options, cb) {
+
+ node = Y.one(node);
+
+ var sim = new Y.GestureSimulation(node);
+ name = name.toLowerCase();
+
+ if(!cb && Y.Lang.isFunction(options)) {
+ cb = options;
+ options = {};
+ }
+
+ options = options || {};
+
+ if (gestureNames[name]) {
+ switch(name) {
+ // single-touch: point gestures
+ case 'tap':
+ sim.tap(cb, options.point, options.times, options.hold, options.delay);
+ break;
+ case 'doubletap':
+ sim.tap(cb, options.point, 2);
+ break;
+ case 'press':
+ if(!Y.Lang.isNumber(options.hold)) {
+ options.hold = DEFAULTS.HOLD_PRESS;
+ } else if(options.hold < DEFAULTS.MIN_HOLD_PRESS) {
+ options.hold = DEFAULTS.MIN_HOLD_PRESS;
+ } else if(options.hold > DEFAULTS.MAX_HOLD_PRESS) {
+ options.hold = DEFAULTS.MAX_HOLD_PRESS;
+ }
+ sim.tap(cb, options.point, 1, options.hold);
+ break;
+
+ // single-touch: move gestures
+ case 'move':
+ sim.move(cb, options.path, options.duration);
+ break;
+ case 'flick':
+ sim.flick(cb, options.point, options.axis, options.distance,
+ options.duration);
+ break;
+
+ // multi-touch: pinch/rotation gestures
+ case 'pinch':
+ sim.pinch(cb, options.center, options.r1, options.r2,
+ options.duration, options.start, options.rotation);
+ break;
+ case 'rotate':
+ sim.rotate(cb, options.center, options.r1, options.r2,
+ options.duration, options.start, options.rotation);
+ break;
+ }
+ } else {
+ Y.error(NAME+': Not a supported gesture simulation: '+name);
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["async-queue", "event-simulate", "node-screen"]});