AutorÃa | Ultima modificación | Ver Log |
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, // 20msSTART_PAGEX, // will be adjusted to the node element centerSTART_PAGEY, // will be adjusted to the node element center// defaults that user can override.DEFAULTS = {// tap gesturesHOLD_TAP: 10, // 10msDELAY_TAP: 10, // 10ms// press gestureHOLD_PRESS: 3000, // 3secMIN_HOLD_PRESS: 1000, // 1secMAX_HOLD_PRESS: 60000, // 1min// move gestureDISTANCE_MOVE: 200, // 200 pixelsDURATION_MOVE: 1000, // 1secMAX_DURATION_MOVE: 5000,// 5sec// flick gestureMIN_VELOCITY_FLICK: 1.3,DISTANCE_FLICK: 200, // 200 pixelsDURATION_FLICK: 1000, // 1secMAX_DURATION_FLICK: 5000,// 5sec// pinch/rotationDURATION_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) coordinationpoint[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, // optionalr2 = endRadius; // optionalif(!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;}// requiredif(!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, // requiredr2 = endRadius, // requiredradiusPerStep,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 determinationcenterX = center[0];centerY = center[1];startRot = start;endRot = start + rotation;// 1st finger pathpath1.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 pathpath2.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 starteventQueue.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 eventcoord = {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 changesteps = 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 eventcoord = {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 endeventQueue.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 eventcoord = {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 coordinationif(!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();// starteventQueue.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});// movesteps = 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 moveeventQueue.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});// endeventQueue.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 eventsY.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 gesturescase '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 gesturescase '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 gesturescase '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);}};}, '3.18.1', {"requires": ["async-queue", "event-simulate", "node-screen"]});