Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / scrollview-base / scrollview-base-debug.js
blob7b05d791e8b555787430329a6d9868288645c507
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('scrollview-base', function(Y) {
9 /**
10  * The scrollview-base module provides a basic ScrollView Widget, without scrollbar indicators
11  *
12  * @module scrollview-base
13  */
15 var getClassName = Y.ClassNameManager.getClassName,
16     SCROLLVIEW = 'scrollview',
17     CLASS_NAMES = {
18         vertical: getClassName(SCROLLVIEW, 'vert'),
19         horizontal: getClassName(SCROLLVIEW, 'horiz')
20     },
21     EV_SCROLL_END = 'scrollEnd',
22     EV_SCROLL_FLICK = 'flick',
24     FLICK = EV_SCROLL_FLICK,
25     DRAG = "drag",
26     
27     MOUSEWHEEL_ENABLED = true,
29     UI = 'ui',
30     
31     LEFT = "left",
32     TOP = "top",
33     
34     PX = "px",
36     SCROLL_Y = "scrollY",
37     SCROLL_X = "scrollX",
38     BOUNCE = "bounce",
39     DISABLED = "disabled",
41     DIM_X = "x",
42     DIM_Y = "y",
44     BOUNDING_BOX = "boundingBox",
45     CONTENT_BOX = "contentBox",
47     EMPTY = "",
48     ZERO = "0s",
50     IE = Y.UA.ie,
51     
52     Transition = Y.Transition,
54     NATIVE_TRANSITIONS = Transition.useNative,
56     _constrain = function (val, min, max) { 
57         return Math.min(Math.max(val, min), max);
58     };
60 /**
61  * ScrollView provides a scrollable widget, supporting flick gestures, across both touch and mouse based devices. 
62  *
63  * @class ScrollView
64  * @param config {Object} Object literal with initial attribute values
65  * @extends Widget
66  * @constructor
67  */
68 function ScrollView() {
69     ScrollView.superclass.constructor.apply(this, arguments);
72 Y.ScrollView = Y.extend(ScrollView, Y.Widget, {
73     
74     // Y.ScrollView prototype
75     
76     /**
77      * Designated initializer
78      *
79      * @method initializer
80      */
81     initializer: function() {
82         /**
83          * Notification event fired at the end of a scroll transition
84          * 
85          * @event scrollEnd
86          * @param e {EventFacade} The default event facade.
87          */
89         /**
90          * Notification event fired at the end of a flick gesture (the flick animation may still be in progress)
91          * 
92          * @event flick
93          * @param e {EventFacade} The default event facade.
94          */
95         var sv = this;
96         
97         // Cache - they're write once, and not going to change
98         sv._cb = sv.get(CONTENT_BOX);
99         sv._bb = sv.get(BOUNDING_BOX);
100     },
102     /** 
103      * Override the contentBox sizing method, since the contentBox height
104      * should not be that of the boundingBox.
105      *
106      * @method _uiSizeCB
107      * @protected
108      */
109     _uiSizeCB: function() {},
111     /**
112      * Content box transition callback
113      *
114      * @method _onTransEnd
115      * @param {Event.Facade} e The event facade
116      * @private
117      */
118     _onTransEnd: function(e) {
119         this.fire(EV_SCROLL_END);
120     },
122     /**
123      * bindUI implementation
124      *
125      * Hooks up events for the widget
126      * @method bindUI
127      */
128     bindUI: function() {
129         var sv = this;
131         sv._bindDrag(sv.get(DRAG));
132         sv._bindFlick(sv.get(FLICK));
133         // Note: You can find _bindMousewheel() inside syncUI(), becuase it depends on UI details
135         sv._bindAttrs();
137         // IE SELECT HACK. See if we can do this non-natively and in the gesture for a future release.
138         if (IE) {
139             sv._fixIESelect(sv._bb, sv._cb);
140         }
141     },
143     /**
144      * @method _bindAttrs
145      * @private 
146      */
147     _bindAttrs : function() {
149         var sv = this,
150             scrollChangeHandler = sv._afterScrollChange,
151             dimChangeHandler = sv._afterDimChange;
153         this.after({
154             'disabledChange': sv._afterDisabledChange,
155             'flickChange'   : sv._afterFlickChange,
156             'dragChange'    : sv._afterDragChange,
157             'scrollYChange' : scrollChangeHandler,
158             'scrollXChange' : scrollChangeHandler,
159             'heightChange'  : dimChangeHandler,
160             'widthChange'   : dimChangeHandler
161         });
163         // Helps avoid potential CSS race where in the styles from
164         // scrollview-list-skin.css are applied after syncUI() fires.
165         // Without a _uiDimensionChange() call, the scrollview only 
166         // scrolls partially due to the fact that styles added in the CSS
167         // altered the height/width of the bounding box.
168         // TODO: Remove?
169         if (!IE) {
170             this.after('renderedChange', function(e) {
171                 //this._uiDimensionsChange();
172             });
173         }
174     },
176     /**
177      * Bind (or unbind) gesture move listeners required for drag support
178      * 
179      * @method _bindDrag
180      * @param drag {boolean} If true, the method binds listener to enable drag (gesturemovestart). If false, the method unbinds gesturemove listeners for drag support.
181      * @private 
182      */
183     _bindDrag : function(drag) {
184         var bb = this._bb;
186         if (drag) {
187             bb.on('drag|gesturemovestart', Y.bind(this._onGestureMoveStart, this));
188         } else {
189             bb.detach('drag|*');
190         }
191     },
193     /**
194      * Bind (or unbind) flick listeners.
195      * 
196      * @method _bindFlick
197      * @param flick {Object|boolean} If truthy, the method binds listeners for flick support. If false, the method unbinds flick listeners.  
198      * @private
199      */
200     _bindFlick : function(flick) {
201         var cb = this._cb;
203         if (flick) {
204             cb.on("flick|flick", Y.bind(this._flick, this), flick);
205         } else {
206             cb.detach('flick|*');
207         }
208     },
209     
210     /**
211      * Bind (or unbind) mousewheel listeners.
212      * 
213      * @method _bindMousewheel
214      * @param mousewheel {Object|boolean} If truthy, the method binds listeners for mousewheel support. If false, the method unbinds mousewheel listeners.  
215      * @private
216      */
217     _bindMousewheel : function(mousewheel) {
218         var cb = this._cb;
219         
220         // Only enable for vertical scrollviews
221         if (this._scrollsVertical) {
222             if (mousewheel) {
223                 cb.on("mousewheel", Y.bind(this._mousewheel, this), mousewheel);
224             } else {
225                 cb.detach('mousewheel|*');
226             }
227         }
228     },
230     /**
231      * syncUI implementation.
232      *
233      * Update the scroll position, based on the current value of scrollX/scrollY.
234      *
235      * @method syncUI
236      */
237     syncUI: function() {
238         this._cDisabled = this.get(DISABLED);
239         this._uiDimensionsChange();
240         this._bindMousewheel(MOUSEWHEEL_ENABLED);
241         this.scrollTo(this.get(SCROLL_X), this.get(SCROLL_Y));
242     },
244     /**
245      * Scroll the element to a given xy coordinate
246      *
247      * @method scrollTo
248      * @param x {Number} The x-position to scroll to
249      * @param y {Number} The y-position to scroll to
250      * @param duration {Number} Duration, in ms, of the scroll animation (default is 0)
251      * @param easing {String} An easing equation if duration is set
252      */
253     scrollTo: function(x, y, duration, easing) {
254         
255         // TODO: Figure out a better way to detect mousewheel events
256         if (easing === undefined) {
257             if ( y < this._minScrollY) {
258                 y = this._minScrollY;
259             }
260             else if ( y > this._maxScrollY) {
261                 y = this._maxScrollY;
262             }
263         }
264         
265         if (!this._cDisabled) {
266             var cb = this._cb,
267                 xSet = (x !== null),
268                 ySet = (y !== null),
269                 xMove = (xSet) ? x * -1 : 0,
270                 yMove = (ySet) ? y * -1 : 0,
271                 transition,
272                 TRANS = ScrollView._TRANSITION,
273                 callback = this._transEndCB;
274     
275             duration = duration || 0;
276             easing = easing || ScrollView.EASING;
277     
278             if (xSet) {
279                 this.set(SCROLL_X, x, { src: UI });
280             }
281     
282             if (ySet) {
283                 this.set(SCROLL_Y, y, { src: UI });
284             }
285     
286             if (NATIVE_TRANSITIONS) {
287                 // ANDROID WORKAROUND - try and stop existing transition, before kicking off new one.
288                 cb.setStyle(TRANS.DURATION, ZERO).setStyle(TRANS.PROPERTY, EMPTY);
289             }
290     
291             if (duration !== 0) {
292     
293                 transition = {
294                     easing : easing,
295                     duration : duration/1000
296                 };
297     
298                 if (NATIVE_TRANSITIONS) {
299                     transition.transform = this._transform(xMove, yMove);
300                 } else {
301                     if (xSet) { transition.left = xMove + PX; }
302                     if (ySet) { transition.top = yMove + PX; }
303                 }
304     
305                 Y.log("Transition: duration, easing:" + [transition.duration, transition.easing], "scrollview");
306     
307                 if (!callback) {
308                     callback = this._transEndCB = Y.bind(this._onTransEnd, this);
309                 }
310     
311                 cb.transition(transition, callback);
312     
313             } else {
314                 if (NATIVE_TRANSITIONS) {
315                     cb.setStyle('transform', this._transform(xMove, yMove));
316                 } else {
317                     if (xSet) { cb.setStyle(LEFT, xMove + PX); }
318                     if (ySet) { cb.setStyle(TOP, yMove + PX); }
319                 }
320             }
321         }
322     },
324     /**
325      * Utility method, to create the translate transform string with the
326      * x, y translation amounts provided.
327      *
328      * @method _transform
329      * @param {Number} x Number of pixels to translate along the x axis
330      * @param {Number} y Number of pixels to translate along the y axis
331      * @private
332      */
333     _transform : function(x, y) {
334         // TODO: Would we be better off using a Matrix for this?
335         return (this._forceHWTransforms) ? 'translate('+ x +'px,'+ y +'px) translateZ(0px)' : 'translate('+ x +'px,'+ y +'px)';
336     },
338     /**
339      * Utility method, to move the given element to the given xy position
340      *
341      * @method _moveTo
342      * @param node {Node} The node to move
343      * @param x {Number} The x-position to move to
344      * @param y {Number} The y-position to move to
345      * @private
346      */
347     _moveTo : function(node, x, y) {
348         if (NATIVE_TRANSITIONS) {
349             node.setStyle('transform', this._transform(x, y));
350         } else {
351             node.setStyle(LEFT, x + PX);
352             node.setStyle(TOP, y + PX);
353         }
354     },
356     /**
357      * Flag driving whether or not we should try and force H/W acceleration when transforming. Currently enabled by default for Webkit.
358      * Used by the _transform method.
359      *
360      * @property _forceHWTransforms
361      * @type boolean
362      * @protected
363      */
364     _forceHWTransforms: Y.UA.webkit ? true : false,
366     /**
367      * <p>Used to control whether or not ScrollView's internal
368      * gesturemovestart, gesturemove and gesturemoveend
369      * event listeners should preventDefault. The value is an
370      * object, with "start", "move" and "end" properties used to 
371      * specify which events should preventDefault and which shouldn't:</p>
372      *
373      * <pre>
374      * {
375      *    start : false,
376      *    move : true,
377      *    end : false
378      * }
379      * </pre>
380      *
381      * <p>The default values are set up in order to prevent panning,
382      * on touch devices, while allowing click listeners on elements inside 
383      * the ScrollView to be notified as expected.</p> 
384      *
385      * @property _prevent
386      * @type Object
387      * @protected
388      */
389     _prevent : {
390         start : false,
391         move : true,
392         end : false
393     },
395     /**
396      * gesturemovestart event handler
397      *
398      * @method _onGestureMoveStart
399      * @param e {Event.Facade} The gesturemovestart event facade
400      * @private
401      */
402     _onGestureMoveStart: function(e) {
403         
404         var sv = this,
405             bb = sv._bb;
407         if (!sv._cDisabled) {
409             if (sv._prevent.start) {
410                 e.preventDefault();
411             }
412     
413             sv._killTimer();
414     
415             sv._hm = bb.on('drag|gesturemove', Y.bind(sv._onGestureMove, sv));
416             sv._hme = bb.on('drag|gesturemoveend', Y.bind(sv._onGestureMoveEnd, sv));
417     
418             sv._startY = e.clientY + sv.get(SCROLL_Y);
419             sv._startX = e.clientX + sv.get(SCROLL_X);
420             sv._startClientY = sv._endClientY = e.clientY;
421             sv._startClientX = sv._endClientX = e.clientX;
422     
423             /**
424              * Internal state, defines whether or not the scrollview is currently being dragged
425              * 
426              * @property _isDragging
427              * @type boolean
428              * @protected
429              */
430             sv._isDragging = false;
431     
432             /**
433              * Internal state, defines whether or not the scrollview is currently animating a flick
434              * 
435              * @property _flicking
436              * @type boolean
437              * @protected
438              */
439             sv._flicking = false;
440     
441             /**
442              * Internal state, defines whether or not the scrollview needs to snap to a boundary edge
443              * 
444              * @property _snapToEdge
445              * @type boolean
446              * @protected
447              */
448             sv._snapToEdge = false;
449         }
450     },    
451     
452     /**
453      * gesturemove event handler
454      *
455      * @method _onGestureMove
456      * @param e {Event.Facade} The gesturemove event facade
457      * @private
458      */
459     _onGestureMove: function(e) {
461         var sv = this;
463         if (sv._prevent.move) {
464             e.preventDefault();
465         }
467         sv._isDragging = true;
468         sv._endClientY = e.clientY;
469         sv._endClientX = e.clientX;
471         if (sv._scrollsVertical) {
472             sv.set(SCROLL_Y, -(e.clientY - sv._startY));
473         }
475         if(sv._scrollsHorizontal) {
476             sv.set(SCROLL_X, -(e.clientX - sv._startX));
477         }
478     },
480     /**
481      * gestureend event handler
482      *
483      * @method _onGestureMoveEnd
484      * @param e {Event.Facade} The gesturemoveend event facade
485      * @private
486      */
487     _onGestureMoveEnd: function(e) {
489         if (this._prevent.end) {
490             e.preventDefault();
491         }
493         var sv = this, // kweight
494             minY = sv._minScrollY,
495             maxY = sv._maxScrollY,
496             minX = sv._minScrollX,
497             maxX = sv._maxScrollX,
498             vert = sv._scrollsVertical,
499             horiz = sv._scrollsHorizontal,
500             startPoint =  vert ? sv._startClientY : sv._startClientX,
501             endPoint = vert ? sv._endClientY : sv._endClientX,
502             distance = startPoint - endPoint,
503             absDistance = Math.abs(distance),
504             bb = sv._bb,
505             x, y, xOrig, yOrig;
507         sv._hm.detach();
508         sv._hme.detach();
510         /**
511          * Internal state, defines whether or not the scrollview has been scrolled half it's width/height
512          * 
513          * @property _scrolledHalfway
514          * @type boolean
515          * @protected
516          */
517         sv._scrolledHalfway = sv._snapToEdge = sv._isDragging = false;
519         /**
520          * Contains the distance (postive or negative) in pixels by which the scrollview was last scrolled. This is useful when
521          * setting up click listeners on the scrollview content, which on mouse based devices are always fired, even after a
522          * drag/flick. 
523          * 
524          * <p>Touch based devices don't currently fire a click event, if the finger has been moved (beyond a threshold) so this check isn't required,
525          * if working in a purely touch based environment</p>
526          * 
527          * @property lastScrolledAmt
528          * @type Number
529          * @public
530          */
531         sv.lastScrolledAmt = distance;
533         // Check for halfway
534         if((horiz && absDistance > bb.get('offsetWidth')/2) || (vert && absDistance > bb.get('offsetHeight')/2)) {
535             sv._scrolledHalfway = true;
537             /**
538              * Internal state, defines whether or not the scrollview has been scrolled in the forward (distance > 0), or backward (distance < 0) direction
539              * 
540              * @property _scrolledForward
541              * @type boolean
542              * @protected
543              */
544             sv._scrolledForward = distance > 0;
545         }
547         // Check for min/max
548         if (vert) {
549             yOrig = sv.get(SCROLL_Y);
550             y = _constrain(yOrig, minY, maxY);
551         }
553         if (horiz) {
554             xOrig = sv.get(SCROLL_X);
555             x = _constrain(xOrig, minX, maxX);
556         }
558         if (x !== xOrig || y !== yOrig) {
559             this._snapToEdge = true;
560             if (vert) {
561                 sv.set(SCROLL_Y, y);
562             }
563             if (horiz) {
564                 sv.set(SCROLL_X, x);
565             }
566         }
568         Y.log("half:" + sv._scrolledHalfway + ", fwd:"  + sv._scrolledForward, "scrollview");
570         if(sv._snapToEdge) {
571             return;
572         }
574         sv.fire(EV_SCROLL_END, {
575             onGestureMoveEnd: true
576         });
578         return;
579     },
581     /**
582      * After listener for changes to the scrollX or scrollY attribute
583      *
584      * @method _afterScrollChange
585      * @param e {Event.Facade} The event facade
586      * @protected
587      */
588     _afterScrollChange : function(e) {
589         var duration = e.duration,
590             easing = e.easing,
591             val = e.newVal;
592         if(e.src !== UI) {
593             if (e.attrName == SCROLL_X) {
594                 this._uiScrollTo(val, null, duration, easing);
595             } else {
596                 this._uiScrollTo(null, val, duration, easing);
597             }
598         }
599     },
601     /**
602      * After listener for changes to the flick attribute
603      *
604      * @method _afterFlickChange
605      * @param e {Event.Facade} The event facade
606      * @protected
607      */
608     _afterFlickChange : function(e) {
609         this._bindFlick(e.newVal);
610     },
611     
612     /**
613      * After listener for changes to the disabled attribute
614      *
615      * @method _afterDisabledChange
616      * @param e {Event.Facade} The event facade
617      * @protected
618      */
619     _afterDisabledChange : function(e) {
620         // Cache for performance - we check during move
621         this._cDisabled = e.newVal;
622     },
624     /**
625      * After listener for changes to the drag attribute
626      *
627      * @method _afterDragChange
628      * @param e {Event.Facade} The event facade
629      * @protected
630      */
631     _afterDragChange : function(e) {
632         this._bindDrag(e.newVal);
633     },
635     /**
636      * Used to move the ScrollView content
637      *
638      * @method _uiScrollTo
639      * @param x {Number}
640      * @param y {Number}
641      * @param duration {Number}
642      * @param easing {String}
643      * @protected
644      * 
645      */
646     _uiScrollTo : function(x, y, duration, easing) {
647         // TODO: This doesn't seem right. This is not UI logic. 
648         duration = duration || this._snapToEdge ? 400 : 0;
649         easing = easing || this._snapToEdge ? ScrollView.SNAP_EASING : null;
651         this.scrollTo(x, y, duration, easing);
652     },
654     /**
655      * After listener for the height or width attribute
656      *
657      * @method _afterDimChange
658      * @param e {Event.Facade} The event facade
659      * @protected
660      */
661     _afterDimChange: function() {
662         this._uiDimensionsChange();
663     },
665     /**
666     * Utility method to obtain scrollWidth, scrollHeight,
667     * accounting for the impact of translate on scrollWidth, scrollHeight
668     * @method _getScrollDims
669     * @returns {Array} The offsetWidth, offsetHeight, scrollWidth and scrollHeight as an array: [offsetWidth, offsetHeight, scrollWidth, scrollHeight]
670     * @private
671     */
672     _getScrollDims: function() {
673         var dims,
675             // Ideally using CSSMatrix - don't think we have it normalized yet though.
676             // origX = (new WebKitCSSMatrix(cb.getComputedStyle("transform"))).e;
677             // origY = (new WebKitCSSMatrix(cb.getComputedStyle("transform"))).f;
679             origX = this.get(SCROLL_X),
680             origY = this.get(SCROLL_Y),
682             cb = this.get(CONTENT_BOX),
683             bb = this.get(BOUNDING_BOX),
685             HWTransform,
687             TRANS = ScrollView._TRANSITION;
689         // TODO: Is this OK? Just in case it's called 'during' a transition.
690         if (NATIVE_TRANSITIONS) {
691             cb.setStyle(TRANS.DURATION, ZERO);
692             cb.setStyle(TRANS.PROPERTY, EMPTY);
693         }
695         HWTransform = this._forceHWTransforms;
696         this._forceHWTransforms = false;  // the z translation was causing issues with picking up accurate scrollWidths in Chrome/Mac.
698         this._moveTo(cb, 0, 0);
699         dims = [bb.get("offsetWidth"), bb.get("offsetHeight"), bb.get('scrollWidth'), bb.get('scrollHeight')];
700         this._moveTo(cb, -1*origX, -1*origY);
702         this._forceHWTransforms = HWTransform;
704         return dims;
705     },
707     /**
708      * This method gets invoked whenever the height or width attributes change,
709      * allowing us to determine which scrolling axes need to be enabled.
710      *
711      * @method _uiDimensionsChange
712      * @protected
713      */
714     _uiDimensionsChange: function() {
715         var sv = this,
716             bb = sv._bb,
717             CLASS_NAMES = ScrollView.CLASS_NAMES,
719             scrollDims = this._getScrollDims(),
721             width = scrollDims[0],
722             height = scrollDims[1],
723             scrollWidth = scrollDims[2],
724             scrollHeight = scrollDims[3];
726         if (height && scrollHeight > height) {
727             sv._scrollsVertical = true;
728             sv._maxScrollY = scrollHeight - height;
729             sv._minScrollY = 0;
730             sv._scrollHeight = scrollHeight;
731             sv._height = height;
732             bb.addClass(CLASS_NAMES.vertical);
733         } else {
734             sv._scrollsVertical = false;
735             delete sv._maxScrollY;
736             delete sv._minScrollY;
737             delete sv._scrollHeight;
738             delete sv._height;
739             bb.removeClass(CLASS_NAMES.vertical);
740         }
742         if (width && scrollWidth > width) {
743             sv._scrollsHorizontal = true;
744             sv._maxScrollX = scrollWidth - width;
745             sv._minScrollX = 0;
746             sv._scrollWidth = scrollWidth;
747             sv._width = width;            
748             bb.addClass(CLASS_NAMES.horizontal);
749         } else {
750             sv._scrollsHorizontal = false;
751             delete sv._maxScrollX;
752             delete sv._minScrollX;
753             delete sv._scrollWidth;
754             delete sv._width;
755             bb.removeClass(CLASS_NAMES.horizontal);
756         }
758         /**
759          * Internal state, defines whether or not the scrollview can scroll vertically 
760          * 
761          * @property _scrollsVertical
762          * @type boolean
763          * @protected
764          */
765         
766         /**
767          * Internal state, defines the maximum amount that the scrollview can be scrolled along the Y axis 
768          * 
769          * @property _maxScrollY
770          * @type number
771          * @protected
772          */
774         /**
775          * Internal state, defines the minimum amount that the scrollview can be scrolled along the Y axis 
776          * 
777          * @property _minScrollY
778          * @type number
779          * @protected
780          */
782         /**
783          * Internal state, cached scrollHeight, for performance 
784          * 
785          * @property _scrollHeight
786          * @type number
787          * @protected
788          */
790         /**
791          * Internal state, defines whether or not the scrollview can scroll horizontally 
792          * 
793          * @property _scrollsHorizontal
794          * @type boolean
795          * @protected
796          */
797         
798         /**
799          * Internal state, defines the maximum amount that the scrollview can be scrolled along the X axis 
800          * 
801          * @property _maxScrollX
802          * @type number
803          * @protected
804          */
806         /**
807          * Internal state, defines the minimum amount that the scrollview can be scrolled along the X axis 
808          * 
809          * @property _minScrollX
810          * @type number
811          * @protected
812          */
814         /**
815          * Internal state, cached scrollWidth, for performance 
816          * 
817          * @property _scrollWidth
818          * @type number
819          * @protected
820          */
821     },
823     /**
824      * Execute a flick at the end of a scroll action
825      *
826      * @method _flick
827      * @param distance {Number} The distance (in px) the user scrolled before the flick
828      * @param time {Number} The number of ms the scroll event lasted before the flick
829      * @protected
830      */
831     _flick: function(e) {
832         
833         var flick = e.flick,
834             sv = this;
835         
836         if (!sv._cDisabled) {
837     
838             /**
839              * Internal state, currently calculated velocity from the flick 
840              * 
841              * @property _currentVelocity
842              * @type number
843              * @protected
844              */
845             sv._currentVelocity = flick.velocity;
846             sv._flicking = true;
847     
848             sv._cDecel = sv.get('deceleration');
849             sv._cBounce = sv.get('bounce');
850     
851             sv._pastYEdge = false;
852             sv._pastXEdge = false;
853     
854             sv._flickFrame();
855     
856             sv.fire(EV_SCROLL_FLICK);
857         }
858     },
860     _mousewheel: function(e) {
861         var scrollY = this.get('scrollY'),
862             contentBox = this._cb,
863             scrollOffset = 10, // 10px
864             scrollToY = scrollY - (e.wheelDelta * scrollOffset);
866         this.scrollTo(0, scrollToY);
867         
868         // if we have scrollbars plugin, update & set the flash timer on the scrollbar
869         if (this.scrollbars) {
870             // TODO: The scrollbars should handle this themselves
871             this.scrollbars._update();
872             this.scrollbars.flash();
873             // or just this
874             // this.scrollbars._hostDimensionsChange();
875         }
877         // prevent browser default behavior on mouse scroll
878         e.preventDefault();
879     },
881     /**
882      * Execute a single frame in the flick animation
883      *
884      * @method _flickFrame
885      * @protected
886      */
887     _flickFrame: function() {
888         var sv = this,
889             newY,
890             maxY,
891             minY,
892             newX,
893             maxX,
894             minX,
895             scrollsVertical  = sv._scrollsVertical,
896             scrollsHorizontal = sv._scrollsHorizontal,
897             deceleration = sv._cDecel,
898             bounce = sv._cBounce,
899             vel = sv._currentVelocity,
900             step = ScrollView.FRAME_STEP;
902         if (scrollsVertical) {
903             maxY = sv._maxScrollY;
904             minY = sv._minScrollY;
905             newY = sv.get(SCROLL_Y) - (vel * step);
906         }
908         if (scrollsHorizontal) {
909             maxX = sv._maxScrollX;
910             minX = sv._minScrollX;
911             newX = sv.get(SCROLL_X) - (vel * step);
912         }
913         
914         vel = sv._currentVelocity = (vel * deceleration);
916         if(Math.abs(vel).toFixed(4) <= 0.015) {
917             sv._flicking = false;
918             sv._killTimer(!(sv._pastYEdge || sv._pastXEdge));
920             if(scrollsVertical) {
921                 if(newY < minY) {
922                     sv._snapToEdge = true;
923                     sv.set(SCROLL_Y, minY);
924                 } else if(newY > maxY) {
925                     sv._snapToEdge = true;
926                     sv.set(SCROLL_Y, maxY);
927                 }
928             }
930             if(scrollsHorizontal) {
931                 if(newX < minX) {
932                     sv._snapToEdge = true;
933                     sv.set(SCROLL_X, minX);
934                 } else if(newX > maxX) {
935                     sv._snapToEdge = true;
936                     sv.set(SCROLL_X, maxX);
937                 }
938             }
940             return;
941         }
943         if (scrollsVertical) {
944             if (newY < minY || newY > maxY) {
945                 sv._pastYEdge = true;
946                 sv._currentVelocity *= bounce;
947             }
949             sv.set(SCROLL_Y, newY);
950         }
952         if (scrollsHorizontal) {
953             if (newX < minX || newX > maxX) {
954                 sv._pastXEdge = true;
955                 sv._currentVelocity *= bounce;
956             }
958             sv.set(SCROLL_X, newX);
959         }
961         if (!sv._flickTimer) {
962             sv._flickTimer = Y.later(step, sv, '_flickFrame', null, true);
963         }
964     },
966     /**
967      * Stop the animation timer
968      *
969      * @method _killTimer
970      * @param fireEvent {Boolean} If true, fire the scrollEnd event
971      * @protected
972      */
973     _killTimer: function(fireEvent) {
974         var sv = this;
975         if(sv._flickTimer) {
976             sv._flickTimer.cancel();
977             sv._flickTimer = null;
978         }
980         if(fireEvent) {
981             sv.fire(EV_SCROLL_END);
982         }
983     },
985     /**
986      * The scrollX, scrollY setter implementation
987      *
988      * @method _setScroll
989      * @private
990      * @param {Number} val
991      * @param {String} dim
992      *
993      * @return {Number} The constrained value, if it exceeds min/max range
994      */
995     _setScroll : function(val, dim) {
996         if (this._cDisabled) {
997             val = Y.Attribute.INVALID_VALUE;
998         } else {
999     
1000             var bouncing = this._cachedBounce || this.get(BOUNCE),
1001                 range = ScrollView.BOUNCE_RANGE,
1002     
1003                 maxScroll = (dim == DIM_X) ? this._maxScrollX : this._maxScrollY,
1004     
1005                 min = bouncing ? -range : 0,
1006                 max = bouncing ? maxScroll + range : maxScroll;
1007     
1008             if(!bouncing || !this._isDragging) {
1009                 if(val < min) {
1010                     val = min;
1011                 } else if(val > max) {
1012                     val = max;
1013                 }            
1014             }
1015         }
1017         return val;
1018     },
1020     /**
1021      * Setter for the scrollX attribute
1022      *
1023      * @method _setScrollX
1024      * @param val {Number} The new scrollX value
1025      * @return {Number} The normalized value
1026      * @protected
1027      */    
1028     _setScrollX: function(val) {
1029         return this._setScroll(val, DIM_X);
1030     },
1032     /**
1033      * Setter for the scrollY ATTR
1034      *
1035      * @method _setScrollY
1036      * @param val {Number} The new scrollY value
1037      * @return {Number} The normalized value 
1038      * @protected
1039      */
1040     _setScrollY: function(val) {
1041         return this._setScroll(val, DIM_Y);
1042     }
1043     
1044 }, {
1045    
1046    // Y.ScrollView static properties
1048    /**
1049     * The identity of the widget.
1050     *
1051     * @property NAME
1052     * @type String
1053     * @default 'scrollview'
1054     * @readOnly
1055     * @protected
1056     * @static
1057     */
1058    NAME: 'scrollview',
1060    /**
1061     * Static property used to define the default attribute configuration of
1062     * the Widget.
1063     *
1064     * @property ATTRS
1065     * @type {Object}
1066     * @protected
1067     * @static
1068     */
1069     ATTRS: {
1071         /**
1072          * The scroll position in the y-axis
1073          *
1074          * @attribute scrollY
1075          * @type Number
1076          * @default 0
1077          */
1078         scrollY: {
1079             value: 0,
1080             setter: '_setScrollY'
1081         },
1083         /**
1084          * The scroll position in the x-axis
1085          *
1086          * @attribute scrollX
1087          * @type Number
1088          * @default 0
1089          */
1090         scrollX: {
1091             value: 0,
1092             setter: '_setScrollX'
1093         },
1095         /**
1096          * Drag coefficent for inertial scrolling. The closer to 1 this
1097          * value is, the less friction during scrolling.
1098          *
1099          * @attribute deceleration
1100          * @default 0.93
1101          */
1102         deceleration: {
1103             value: 0.93
1104         },
1106         /**
1107          * Drag coefficient for intertial scrolling at the upper
1108          * and lower boundaries of the scrollview. Set to 0 to 
1109          * disable "rubber-banding".
1110          *
1111          * @attribute bounce
1112          * @type Number
1113          * @default 0.1
1114          */
1115         bounce: {
1116             value: 0.1
1117         },
1119         /**
1120          * The minimum distance and/or velocity which define a flick. Can be set to false,
1121          * to disable flick support (note: drag support is enabled/disabled separately)
1122          *
1123          * @attribute flick
1124          * @type Object
1125          * @default Object with properties minDistance = 10, minVelocity = 0.3.
1126          */
1127         flick: {
1128             value: {
1129                 minDistance: 10,
1130                 minVelocity: 0.3
1131             }
1132         },
1134         /**
1135          * Enable/Disable dragging the ScrollView content (note: flick support is enabled/disabled separately)
1136          * @attribute drag
1137          * @type boolean
1138          * @default true
1139          */
1140         drag: {
1141             value: true
1142         }
1143     },
1145     /**
1146      * List of class names used in the scrollview's DOM
1147      *
1148      * @property CLASS_NAMES
1149      * @type Object
1150      * @static
1151      */
1152     CLASS_NAMES: CLASS_NAMES,
1154     /**
1155      * Flag used to source property changes initiated from the DOM
1156      *
1157      * @property UI_SRC
1158      * @type String
1159      * @static
1160      * @default "ui"
1161      */
1162     UI_SRC: UI,
1164     /**
1165      * The default bounce distance in pixels
1166      *
1167      * @property BOUNCE_RANGE
1168      * @type Number
1169      * @static
1170      * @default 150
1171      */
1172     BOUNCE_RANGE : 150,
1174     /**
1175      * The interval used when animating the flick
1176      *
1177      * @property FRAME_STEP
1178      * @type Number
1179      * @static
1180      * @default 30
1181      */
1182     FRAME_STEP : 30,
1184     /**
1185      * The default easing used when animating the flick
1186      *
1187      * @property EASING
1188      * @type String
1189      * @static
1190      * @default 'cubic-bezier(0, 0.1, 0, 1.0)'
1191      */
1192     EASING : 'cubic-bezier(0, 0.1, 0, 1.0)',
1194     /**
1195      * The default easing to use when animating the bounce snap back.
1196      *
1197      * @property SNAP_EASING
1198      * @type String
1199      * @static
1200      * @default 'ease-out'
1201      */
1202     SNAP_EASING : 'ease-out',
1204     /**
1205      * Object map of style property names used to set transition properties.
1206      * Defaults to the vendor prefix established by the Transition module.  
1207      * The configured property names are `_TRANSITION.DURATION` (e.g. "WebkitTransitionDuration") and
1208      * `_TRANSITION.PROPERTY (e.g. "WebkitTransitionProperty").
1209      *
1210      * @property _TRANSITION
1211      * @private
1212      */
1213     _TRANSITION : {
1214         DURATION : Transition._VENDOR_PREFIX + "TransitionDuration",
1215         PROPERTY : Transition._VENDOR_PREFIX + "TransitionProperty"
1216     }
1220 }, '3.5.0' ,{requires:['widget', 'event-gestures', 'event-mousewheel', 'transition'], skinnable:true});