NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / dial / dial.js
blob0c3c8cca630380a72c79b6495b59b7b4efe345dd
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('dial', function (Y, NAME) {
10 /**
11  * Create a circular dial value range input visualized as a draggable handle on a
12  * background element.
13  *
14  * @module dial
15  */
16     var supportsVML = false;
17         //testVMLNode;
19     if (Y.UA.ie && Y.UA.ie < 9){
20         supportsVML = true;
21     }
23     var Lang = Y.Lang,
24         Widget = Y.Widget,
25         Node = Y.Node;
27     /**
28      * Create a dial to represent an input control capable of representing a
29      * series of intermediate states based on the position of the Dial's handle.
30      * These states are typically aligned to a value algorithm whereby the angle of the handle's
31      * position corresponds to a given value.
32      *
33      * @class Dial
34      * @extends Widget
35      * @param config {Object} Configuration object
36      * @constructor
37      */
38     function Dial(config) {
39         Dial.superclass.constructor.apply(this, arguments);
40     }
42     // Y.Dial static properties
44     /**
45      * The identity of the widget.
46      *
47      * @property NAME
48      * @type String
49      * @default 'dial'
50      * @readOnly
51      * @protected
52      * @static
53      */
54     Dial.NAME = "dial";
56     /**
57      * Static property used to define the default attribute configuration of
58      * the Widget.
59      *
60      * @property ATTRS
61      * @type {Object}
62      * @protected
63      * @static
64      */
65     Dial.ATTRS = {
67         /**
68          * minimum value allowed
69          *
70          * @attribute min
71          * @type {Number}
72          * @default -220
73          */
74         min : {
75             value:-220
76         },
78         /**
79          * maximum value allowed
80          *
81          * @attribute max
82          * @type {Number}
83          * @default 220
84          */
85         max : {
86             value:220
87         },
89         /**
90          * diameter of the circular background object.
91          * Other objects scale accordingly.
92          * Set this only before rendering.
93          *
94          * @attribute diameter
95          * @type {Number} the number of px in diameter
96          * @default 100
97          * @writeOnce
98          */
99         diameter : {
100             value:100
101         },
103         /**
104          * diameter of the handle object which users drag to change the value.
105          * Dial sets the pixel dimension of the handle equal to handleDiameter * diameter.
106          * Set this only before rendering.
107          *
108          * @attribute handleDiameter
109          * @type {Number}
110          * @default 0.2
111          * @writeOnce
112          */
113         handleDiameter : {
114             value:0.2
115         },
117         /**
118          * diameter of the marker object which follows the angle of the handle during value changes.
119          * Dial sets the pixel dimension of the marker equal to markerDiameter * diameter.
120          * Set this only before rendering.
121          *
122          * @attribute markerDiameter
123          * @type {Number}
124          * @default 0.1
125          * @writeOnce
126          */
127         markerDiameter : {
128             value:0.1
129         },
131         /**
132          * diameter of the center button object.
133          * Dial sets the pixel dimension of the centerButton equal to centerButtonDiameter * diameter.
134          * Set this only before rendering.
135          *
136          * @attribute centerButtonDiameter
137          * @type {Number}
138          * @default 0.1
139          * @writeOnce
140          */
141         centerButtonDiameter : {
142             value:0.5
143         },
145         /**
146          * initial value of the Dial
147          *
148          * @attribute value
149          * @type {Number}
150          * @default 0
151          */
152         value : {
153             value:0,
154             validator: function(val) {
155                 return this._validateValue(val);
156             }
157         },
159         /**
160          * amount to increment/decrement the dial value
161          * when the arrow up/down/left/right keys are pressed
162          *
163          * @attribute minorStep
164          * @type {Number}
165          * @default 1
166          */
167         minorStep : {
168             value:1
169         },
171         /**
172          * amount to increment/decrement the dial value
173          * when the page up/down keys are pressed
174          *
175          * @attribute majorStep
176          * @type {Number}
177          * @default 10
178          */
179         majorStep : {
180             value:10
181         },
183         /**
184          * number of value increments in one 360 degree revolution
185          * of the handle around the dial
186          *
187          * @attribute stepsPerRevolution
188          * @type {Number}
189          * @default 100
190          */
191         stepsPerRevolution : {
192             value:100
193         },
195         /**
196          * number of decimal places of accuracy in the value
197          *
198          * @attribute decimalPlaces
199          * @type {Number}
200          * @default 0
201          */
202         decimalPlaces : {
203             value:0
204         },
206         /**
207          * visible strings for the dial UI. This attribute is
208          * defined by the base Widget class but has an empty value. The
209          * Dial is simply providing a default value for the attribute.
210          * Gets localized strings in the current language
211          *
212          * @attribute strings
213          * @type {Object} the values are HTML strings
214          * @default {label: 'My label', resetStr: 'Reset', tooltipHandle: 'Drag to set value'}
215          */
216         strings: {
217             valueFn: function () {
218                 return Y.Intl.get('dial');
219             }
220         },
222         /**
223          * distance from the center of the dial to the
224          * center of the marker and handle, when at rest.
225          * The value is a percent of the radius of the dial.
226          *
227          * @attribute handleDistance
228          * @type {number}
229          * @default 0.75
230          */
231         handleDistance:{
232             value:0.75
233         }
235     };
237     /**
238      * returns a properly formed yui class name
239      *
240      * @method
241      * @param {String} string to be appended at the end of class name
242      * @return
243      * @private
244      */
245     function makeClassName(str) {
246         return Y.ClassNameManager.getClassName(Dial.NAME, str);
247     }
249          /** array of static constants used to identify the classname applied to the Dial DOM objects
250          *
251      * @property CSS_CLASSES
252      * @type {Array}
253      * @private
254      * @static
255      */
256     Dial.CSS_CLASSES = {
257         label : makeClassName("label"),
258         labelString : makeClassName("label-string"),
259         valueString : makeClassName("value-string"),
260         northMark : makeClassName("north-mark"),
261         ring : makeClassName('ring'),
262         ringVml : makeClassName('ring-vml'),
263         marker : makeClassName("marker"),
264         markerVml : makeClassName("marker-vml"),
265         markerMaxMin : makeClassName("marker-max-min"),
266         centerButton : makeClassName("center-button"),
267         centerButtonVml : makeClassName('center-button-vml'),
268         resetString : makeClassName("reset-string"),
269         handle : makeClassName("handle"),
270         handleVml : makeClassName("handle-vml"),
271         hidden : makeClassName("hidden"),
272         dragging : Y.ClassNameManager.getClassName("dd-dragging")
273     };
275     /* Static constants used to define the markup templates used to create Dial DOM elements */
278     /**
279      * template that will contain the Dial's label.
280      *
281      * @property LABEL_TEMPLATE
282      * @type {HTML}
283      * @default &lt;div class="[...-label]">&lt;span id="" class="[...-label-string]">{label}&lt;/span>&lt;span class="[...-value-string]">&lt;/span>&lt;/div>
284      * @protected
285      */
287         Dial.LABEL_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.label + '"><span id="" class="' + Dial.CSS_CLASSES.labelString + '">{label}</span><span class="' + Dial.CSS_CLASSES.valueString + '"></span></div>';
289         if(supportsVML === false){
290                 /**
291                  * template that will contain the Dial's background ring.
292                  *
293                  * @property RING_TEMPLATE
294                  * @type {HTML}
295                  * @default &lt;div class="[...-ring]">&lt;div class="[...-northMark]">&lt;/div>&lt;/div>
296                  * @protected
297                  */
298                 Dial.RING_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.ring + '"><div class="' + Dial.CSS_CLASSES.northMark + '"></div></div>';
300                 /**
301                  * template that will contain the Dial's current angle marker.
302                  *
303                  * @property MARKER_TEMPLATE
304                  * @type {HTML}
305                  * @default &lt;div class="[...-marker] [...-marker-hidden]">&lt;div class="[...-markerUser]">&lt;/div>&lt;/div>
306                  * @protected
307                  */
308                 Dial.MARKER_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.marker + ' ' + Dial.CSS_CLASSES.hidden + '"></div>';
310                 /**
311                  * template that will contain the Dial's center button.
312                  *
313                  * @property CENTER_BUTTON_TEMPLATE
314                  * @type {HTML}
315                  * @default &lt;div class="[...-centerButton]">&lt;div class="[...-resetString]">' + Y.Lang.sub('{resetStr}', Dial.ATTRS.strings.value) + '&lt;/div>&lt;/div>
316                  * @protected
317                  */
318                 Dial.CENTER_BUTTON_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.centerButton + '"><div class="' + Dial.CSS_CLASSES.resetString + ' ' + Dial.CSS_CLASSES.hidden + '">{resetStr}</div></div>';
320                 /**
321                  * template that will contain the Dial's handle.
322                  *
323                  * @property HANDLE_TEMPLATE
324                  * @type {HTML}
325                  * @default &lt;div class="[...-handle]">&lt;div class="[...-handleUser]" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider"  tabindex="0">&lt;/div>&lt;/div>';// title="{tooltipHandle}"
326                  * @protected
327                  */
328                 Dial.HANDLE_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.handle + '" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider"  tabindex="0" title="{tooltipHandle}">';
330         }else{ // VML case
331                 Dial.RING_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.ring +  ' ' + Dial.CSS_CLASSES.ringVml + '">'+
332                                                                 '<div class="' + Dial.CSS_CLASSES.northMark + '"></div>'+
333                                                                         '<v:oval strokecolor="#ceccc0" strokeweight="1px"><v:fill type=gradient color="#8B8A7F" color2="#EDEDEB" angle="45"/></v:oval>'+
334                                                                 '</div>'+
335                                                                 '';
336                 Dial.MARKER_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.markerVml + ' ' + Dial.CSS_CLASSES.hidden + '">'+
337                                                                                 '<v:oval stroked="false">'+
338                                                                                         '<v:fill opacity="20%" color="#000"/>'+
339                                                                                 '</v:oval>'+
340                                                                 '</div>'+
341                                                                 '';
342                 Dial.CENTER_BUTTON_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.centerButton + ' ' + Dial.CSS_CLASSES.centerButtonVml + '">'+
343                                                                                         '<v:oval strokecolor="#ceccc0" strokeweight="1px">'+
344                                                                                                 '<v:fill type=gradient color="#C7C5B9" color2="#fefcf6" colors="35% #d9d7cb, 65% #fefcf6" angle="45"/>'+
345                                                                                                 '<v:shadow on="True" color="#000" opacity="10%" offset="2px, 2px"/>'+
346                                                                                         '</v:oval>'+
347                                                                                         '<div class="' + Dial.CSS_CLASSES.resetString + ' ' + Dial.CSS_CLASSES.hidden + '">{resetStr}</div>'+
348                                                                         '</div>'+
349                                                                         '';
350                 Dial.HANDLE_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.handleVml + '" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider"  tabindex="0" title="{tooltipHandle}">'+
351                                                                                 '<v:oval stroked="false">'+
352                                                                                         '<v:fill opacity="20%" color="#6C3A3A"/>'+
353                                                                                 '</v:oval>'+
354                                                                 '</div>'+
355                                                                 '';
356         }
358     /* Dial extends the base Widget class */
359     Y.extend(Dial, Widget, {
361         /**
362          * creates the DOM structure for the Dial.
363          *
364          * @method renderUI
365          * @protected
366          */
367         renderUI : function() {
368             this._renderLabel();
369             this._renderRing();
370             this._renderMarker();
371             this._renderCenterButton();
372             this._renderHandle();
374             // object handles
375             this.contentBox = this.get("contentBox");
377             // constants
378             this._originalValue = this.get('value');
379             this._minValue = this.get('min'); // saves doing a .get many times, but we need to remember to update this if/when we allow changing min or max after instantiation
380             this._maxValue = this.get('max');
381             this._stepsPerRevolution = this.get('stepsPerRevolution');
382             this._minTimesWrapped = (Math.floor(this._minValue / this._stepsPerRevolution - 1));
383             this._maxTimesWrapped = (Math.floor(this._maxValue / this._stepsPerRevolution + 1));
385             // variables
386             this._timesWrapped = 0;
387             this._angle = this._getAngleFromValue(this.get('value'));
388             this._prevAng = this._angle;
390             // init
391             this._setTimesWrappedFromValue(this._originalValue);
392             this._handleNode.set('aria-valuemin', this._minValue);
393             this._handleNode.set('aria-valuemax', this._maxValue);
394         },
396         /**
397          * Sets -webkit-border-radius to 50% of width/height of the ring, handle, marker, and center-button.
398          * This is needed for iOS 3.x.
399          * The objects render square if the radius is > 50% of the width/height
400          * @method _setBorderRadius
401          * @private
402          */
403         _setBorderRadius : function(){
404             this._ringNode.setStyles({'WebkitBorderRadius':this._ringNodeRadius + 'px',
405                                         'MozBorderRadius':this._ringNodeRadius + 'px',
406                                         'borderRadius':this._ringNodeRadius + 'px'
407                                      });
408             this._handleNode.setStyles({'WebkitBorderRadius':this._handleNodeRadius + 'px',
409                                         'MozBorderRadius':this._handleNodeRadius + 'px',
410                                         'borderRadius':this._handleNodeRadius + 'px'
411                                      });
412             this._markerNode.setStyles({'WebkitBorderRadius':this._markerNodeRadius + 'px',
413                                         'MozBorderRadius':this._markerNodeRadius + 'px',
414                                         'borderRadius':this._markerNodeRadius + 'px'
415                                      });
416             this._centerButtonNode.setStyles({'WebkitBorderRadius':this._centerButtonNodeRadius + 'px',
417                                         'MozBorderRadius':this._centerButtonNodeRadius + 'px',
418                                         'borderRadius':this._centerButtonNodeRadius + 'px'
419                                      });
420         },
422         /**
423          * Handles the mouseenter on the centerButton
424          *
425          * @method _handleCenterButtonEnter
426          * @protected
427          */
428         _handleCenterButtonEnter : function(){
429             this._resetString.removeClass(Dial.CSS_CLASSES.hidden);
430         },
432         /**
433          * Handles the mouseleave on the centerButton
434          *
435          * @method _handleCenterButtonLeave
436          * @protected
437          */
438         _handleCenterButtonLeave : function(){
439             this._resetString.addClass(Dial.CSS_CLASSES.hidden);
440         },
442         /**
443          * Creates the Y.DD.Drag instance used for the handle movement and
444          * binds Dial interaction to the configured value model.
445          *
446          * @method bindUI
447          * @protected
448          */
449         bindUI : function() {
451             this.after("valueChange", this._afterValueChange);
453             var boundingBox = this.get("boundingBox"),
454                 // Looking for a key event which will fire continously across browsers while the key is held down.
455                 keyEvent = (!Y.UA.opera) ? "down:" : "press:",
456                 // 38, 40 = arrow up/down, 33, 34 = page up/down,  35 , 36 = end/home
457                 keyEventSpec = keyEvent + "38,40,33,34,35,36",
458                 // 37 , 39 = arrow left/right
459                 keyLeftRightSpec = keyEvent + "37,39",
460                 // 37 , 39 = arrow left/right + meta (command/apple key) for mac
461                 keyLeftRightSpecMeta = keyEvent + "37+meta,39+meta",
462                 Drag = Y.DD.Drag;
464             Y.on("key", Y.bind(this._onDirectionKey, this), boundingBox, keyEventSpec);
465             Y.on("key", Y.bind(this._onLeftRightKey, this), boundingBox, keyLeftRightSpec);
466             boundingBox.on("key", this._onLeftRightKeyMeta, keyLeftRightSpecMeta, this);
468             Y.on('mouseenter', Y.bind(this._handleCenterButtonEnter, this), this._centerButtonNode);
469             Y.on('mouseleave', Y.bind(this._handleCenterButtonLeave, this), this._centerButtonNode);
470             // Needed to replace mousedown/up with gesturemovestart/end to make behavior on touch devices work the same.
471             Y.on('gesturemovestart', Y.bind(this._resetDial, this), this._centerButtonNode);  //[#2530441]
472             Y.on('gesturemoveend', Y.bind(this._handleCenterButtonMouseup, this), this._centerButtonNode);
475             Y.on(Drag.START_EVENT, Y.bind(this._handleHandleMousedown, this), this._handleNode);
476             Y.on(Drag.START_EVENT, Y.bind(this._handleMousedown, this), this._ringNode); // [#2530766]
478             //TODO: Can this be merged this into the drag:end event listener to avoid another registration?
479             Y.on('gesturemoveend', Y.bind(this._handleRingMouseup, this), this._ringNode);
481             this._dd1 = new Drag({ //// [#2530206] changed global this._dd1 from just var dd1 = new Y.DD.drag so
482                 node: this._handleNode,
483                 on : {
484                     'drag:drag' : Y.bind(this._handleDrag, this),
485                     'drag:start' : Y.bind(this._handleDragStart, this),
486                     'drag:end' : Y.bind(this._handleDragEnd, this) //,
487                 }
488             });
489             Y.bind(this._dd1.addHandle(this._ringNode), this); // [#2530206] added the ring as a handle to the dd1 (the dd of the handleNode)
490         },
492         /**
493          * Sets _timesWrapped based on Dial value
494          * to net integer revolutions the user dragged the handle around the Dial
495          *
496          * @method _setTimesWrappedFromValue
497          * @param val {Number} current value of the Dial
498          * @private
499          */
500         _setTimesWrappedFromValue : function(val){
501             if(val % this._stepsPerRevolution === 0){
502                 this._timesWrapped = (val / this._stepsPerRevolution);
503             }else{
504                 this._timesWrapped = Math.floor(val / this._stepsPerRevolution);
505             }
506         },
508         /**
509          * gets the angle of the line from the center of the Dial to the center of the handle
510          *
511          * @method _getAngleFromHandleCenter
512          * @param handleCenterX {number}
513          * @param handleCenterY {number}
514          * @return ang {number} the angle
515          * @protected
516          */
517         _getAngleFromHandleCenter : function(handleCenterX, handleCenterY){
518             var ang = Math.atan( (this._dialCenterY - handleCenterY)  /  (this._dialCenterX - handleCenterX)  ) * (180 / Math.PI);
519             ang = ((this._dialCenterX - handleCenterX) < 0) ? ang + 90 : ang + 90 + 180; // Compensate for neg angles from Math.atan
520             return ang;
521         },
523         /**
524          * calculates the XY of the center of the dial relative to the ring node.
525          * This is needed for calculating the angle of the handle
526          *
527          * @method _calculateDialCenter
528          * @protected
529          */
530         _calculateDialCenter : function(){ // #2531111 value, and marker don't track handle when dial position changes on page (resize when inline)
531             this._dialCenterX = this._ringNode.get('offsetWidth') / 2;
532             this._dialCenterY = this._ringNode.get('offsetHeight') / 2;
533         },
535         /**
536          * Handles the mouseup on the ring
537          *
538          * @method _handleRingMouseup
539          * @protected
540          */
541         _handleRingMouseup : function(){
542             this._handleNode.focus();  // need to re-focus on the handle so keyboard is accessible [#2530206]
543         },
545         /**
546          * Handles the mouseup on the centerButton
547          *
548          * @method _handleCenterButtonMouseup
549          * @protected
550          */
551         _handleCenterButtonMouseup : function(){
552             this._handleNode.focus();  // need to re-focus on the handle so keyboard is accessible [#2530206]
553         },
555         /**
556          * Handles the mousedown on the handle
557          *
558          * @method _handleHandleMousedown
559          * @protected
560          */
561         _handleHandleMousedown : function(){
562             this._handleNode.focus();  // need to re-focus on the handle so keyboard is accessible [#2530206]
563             // this is better done here instead of on _handleDragEnd
564             // because we should make the keyboard accessible after a click of the handle
565         },
567         /**
568          * handles the user dragging the handle around the Dial, gets the angle,
569          * checks for wrapping around top center.
570          * Sets the new value of the Dial
571          *
572          * @method _handleDrag
573          * @param e {DOMEvent} the drag event object
574          * @protected
575          */
576         _handleDrag : function(e){
577             var handleCenterX,
578             handleCenterY,
579             ang,
580             newValue;
582             // The event was emitted from drag:drag of handle.
583             // The center of the handle is top left position of the handle node + radius of handle.
584             // This is different than a mousedown on the ring.
585             handleCenterX = (parseInt(this._handleNode.getStyle('left'),10) + this._handleNodeRadius);
586             handleCenterY = (parseInt(this._handleNode.getStyle('top'),10) + this._handleNodeRadius);
587             ang = this._getAngleFromHandleCenter(handleCenterX, handleCenterY);
589             // check for need to set timesWrapped
590             if((this._prevAng > 270) && (ang < 90)){ // If wrapping, clockwise
591                 if(this._timesWrapped < this._maxTimesWrapped){
592                     this._timesWrapped = (this._timesWrapped + 1);
593                 }
594             }else if((this._prevAng < 90) && (ang > 270)){ // if un-wrapping, counter-clockwise
595                 if(this._timesWrapped > this._minTimesWrapped){
596                    this._timesWrapped = (this._timesWrapped - 1);
597                 }
598             }
599             newValue = this._getValueFromAngle(ang); // This function needs the current _timesWrapped value. That's why it comes after the _timesWrapped code above
601             // If you've gone past max more than one full revolution, we decrement the _timesWrapped value
602             // This gives the effect of a ratchet mechanism.
603             // It feels like you are never more than one revolution past max
604             // The effect is the same for min, only in reverse.
605             // We can't reset the _timesWrapped to the max or min here.
606             // If we did, the next (continuous) drag would reset the value incorrectly.
607             if(newValue > (this._maxValue + this._stepsPerRevolution) ){
608                 this._timesWrapped --;
609             }else if(newValue < (this._minValue - this._stepsPerRevolution) ){
610                 this._timesWrapped ++;
611             }
612             this._prevAng = ang; // need to keep the previous angle in order to check for wrapping on the next drag, click, or keypress
614             this._handleValuesBeyondMinMax(e, newValue);
615         },
617         /**
618          * handles a mousedown or gesturemovestart event on the ring node
619          *
620          * @method _handleMousedown
621          * @param e {DOMEvent} the event object
622          * @private
623          */
624         _handleMousedown : function(e){ // #2530306
626             if (this._ringNode.compareTo(e.target)) {
627                 var minAng = this._getAngleFromValue(this._minValue),
628                 maxAng = this._getAngleFromValue(this._maxValue),
629                 newValue, oppositeMidRangeAngle,
630                 handleCenterX, handleCenterY,
631                 ang;
635                 // The event was emitted from mousedown on the ring node,
636                 // so the center of the handle should be the XY of mousedown.
637                 if(Y.UA.ios){  // ios adds the scrollLeft and top onto clientX and Y in a native click
638                     handleCenterX = (e.clientX - this._ringNode.getX());
639                     handleCenterY = (e.clientY - this._ringNode.getY());
640                 }else{
641                     handleCenterX = (e.clientX + Y.one('document').get('scrollLeft') - this._ringNode.getX());
642                     handleCenterY = (e.clientY + Y.one('document').get('scrollTop') - this._ringNode.getY());
643                 }
644                 ang = this._getAngleFromHandleCenter(handleCenterX, handleCenterY);
646                 /* ///////////////////////////////////////////////////////////////////////////////////////////////////////
647                 * The next sections of logic
648                 * set this._timesWrapped in the different cases of value range
649                 * and value range position,
650                 * then the Dial value is set at the end of this method
651                 */ ///////////////////////////////////////////////////////////////////////////////////////////////////////
654                 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
655                 if(this._maxValue - this._minValue > this._stepsPerRevolution){
657                 // Case: range min-to-max is greater than stepsPerRevolution (one revolution)
659                     // This checks the shortest way around the dial between the prevAng and this ang.
660                     if(Math.abs(this._prevAng - ang) > 180){ // this crossed a wrapping
662                         // Only change the _timesWrapped if it's between minTimesWrapped and maxTimesWrapped
663                         if((this._timesWrapped > this._minTimesWrapped) &&
664                            (this._timesWrapped < this._maxTimesWrapped)
665                         ){
666                             // this checks which direction, clock wise or CCW and incr or decr _timesWrapped
667                             this._timesWrapped = ((this._prevAng - ang) > 0) ? (this._timesWrapped + 1) : (this._timesWrapped - 1);
668                         }
669                     // special case of getting un-stuck from a min value case
670                     // where timesWrapped is minTimesWrapped but new ang won't trigger a cross wrap boundry
671                     // because prevAng is set to 0 or > 0
672                     }else if(
673                             (this._timesWrapped === this._minTimesWrapped) &&
674                             (ang - this._prevAng < 180)
675                     ){
676                         this._timesWrapped ++;
677                     } //it didn't cross a wrapping boundary
679                 } /////////////////////////////////////////////////////////////////////////////////////////////////////////
680                 else if(this._maxValue - this._minValue === this._stepsPerRevolution){
681                 // Case: range min-to-max === stepsPerRevolution     (one revolution)
682                 // This means min and max will be at same angle
683                 // This does not mean they are at "north"
685                     if(ang < minAng){ // if mousedown angle is < minAng (and maxAng, because they're the same)
686                                       // The only way it can be, is if min and max are not at north
687                         this._timesWrapped = 1;
688                     }else{
689                         this._timesWrapped = 0;
690                     }
692                 } //////////////////////////////////////////////////////////////////////////////////////////////////////////
693                 else if(minAng > maxAng){
694                 // Case: range includes the wrap point (north)
695                 // Because of "else if"...
696                 // range is < stepsPerRevolution
698                     if(
699                        (this._prevAng >= minAng) && // if prev angle was greater than angle of min and...
700                        (ang <= (minAng + maxAng) / 2) // the angle of this click is less than
701                                                       // the angle opposite the mid-range angle, then...
702                     ){
703                         this._timesWrapped ++;
704                     }else if(
705                         (this._prevAng <= maxAng) &&
706                         // if prev angle is < max angle and...
708                         (ang > (minAng + maxAng) / 2)
709                         // the angle of this click is greater than,
710                         // the angle opposite the mid-range angle and...
712                     ){
713                         this._timesWrapped --;
714                     }
716                 } ////////////////////////////////////////////////////////////////////////////////////////////////////
717                 else{
718                 // "else" Case: min-to-max range doesn't include the wrap point
719                 // Because of "else if"...
720                 // range is still < stepsPerRevolution
722                     if ((ang < minAng) || (ang > maxAng)){ // angle is out of range
723                         oppositeMidRangeAngle = (((minAng + maxAng) / 2) + 180) % 360;
724                         // This is the bisection of the min-to-max range + 180.  (opposite the bisection)
726                         if(oppositeMidRangeAngle > 180){
727                             newValue = ((maxAng < ang) && (ang < oppositeMidRangeAngle)) ? this.get('max') : this.get('min');
728                         }else{ //oppositeMidRangeAngle <= 180
729                             newValue = ((minAng > ang) && (ang > oppositeMidRangeAngle)) ? this.get('min') : this.get('max');
730                         }
731                         this._prevAng = this._getAngleFromValue(newValue);
732                         this.set('value', newValue);
733                         this._setTimesWrappedFromValue(newValue);
734                         return;
735                     }
736                 }
738                 // Now that _timesWrapped is set, set newValue .......................................................................
739                 newValue = this._getValueFromAngle(ang); // This function needs the correct, current _timesWrapped value.
742                 /* updating _prevAng (previous angle)
743                  * When past min or max, _prevAng is set to the angle of min or max
744                  * Don't do this in a drag method, or it will affect wrapping,
745                  * causing the marker to stick at min, when min is 0 degrees (north)
746                  * #2532878
747                  */
748                 if (newValue > this._maxValue) {
749                     this._prevAng = this._getAngleFromValue(this._maxValue);  // #2530766 need for mousedown on the ring; causes prob for drag
750                 } else if (newValue < this._minValue) {
751                     this._prevAng = this._getAngleFromValue(this._minValue);
752                 } else {
753                     this._prevAng = ang;
754                 }
756                 this._handleValuesBeyondMinMax(e, newValue);
757             }
758         },
760         /**
761          * handles the case where the value is less than min or greater than max
762          * This is used both when handle is dragged and when the ring is clicked
763          *
764          * @method _handleValuesBeyondMinMax
765          * @param e {DOMEvent} the event object
766          * @param newValue {number} current value of the dial
767          * @protected
768          */
769         _handleValuesBeyondMinMax : function(e, newValue){ // #2530306
770             // If _getValueFromAngle() is passed 0, it increments the _timesWrapped value.
771             // handle hitting max and min and going beyond, stops at max or min
772             if((newValue >= this._minValue) && (newValue <= this._maxValue)) {
773                 this.set('value', newValue);
774                 // [#2530206] transfer the mousedown event from the _ringNode to the _handleNode drag, so we can mousedown, then continue dragging
775                 if(e.currentTarget === this._ringNode){
776                     // Delegate to DD's natural behavior
777                     this._dd1._handleMouseDownEvent(e);
778                 }
779             } else if (newValue > this._maxValue) {
780                 this.set('value', this._maxValue);
781             } else if (newValue < this._minValue) {
782                 this.set('value', this._minValue);
783             }
784         },
786         /**
787          * handles the user starting to drag the handle around the Dial
788          *
789          * @method _handleDragStart
790          * @param e {DOMEvent} the drag event object
791          * @protected
792          */
793         _handleDragStart : function(e){
794             this._markerNode.removeClass(Dial.CSS_CLASSES.hidden);
795         },
797         /*
798          * When handle is handleDragEnd, this animates the return to the fixed dial
799          */
801         /**
802          * handles the end of a user dragging the handle, animates the handle returning to
803          * resting position.
804          *
805          * @method _handleDragEnd
806          * @protected
807          */
808         _handleDragEnd : function(){
809             var node = this._handleNode;
810                 node.transition({
811                     duration: 0.08, // seconds
812                     easing: 'ease-in',
813                     left: this._setNodeToFixedRadius(this._handleNode, true)[0] + 'px',
814                     top: this._setNodeToFixedRadius(this._handleNode, true)[1] + 'px'
815                 }, Y.bind(function(){
816                         var value = this.get('value');
817                         //[#2530206] only hide marker if not at max or min
818                         // more persistant user visibility of when the dial is at max or min
819                         if((value > this._minValue) && (value < this._maxValue)){
820                             this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
821                         }else{
822                             this._setTimesWrappedFromValue(value);  //#2530766 secondary bug when drag past max + cross wrapping boundry
823                             this._prevAng = this._getAngleFromValue(value); //#2530766 secondary bug when drag past max + cross wrapping boundry
824                         }
825                     }, this)
826                 );
827         },
829         /**
830          * returns the XY of the fixed position, handleDistance, from the center of the Dial (resting position).
831          * The XY also represents the angle related to the current value.
832          * If typeArray is true, [X,Y] is returned.
833          * If typeArray is false, the XY of the obj node passed in is set.
834          *
835          * @method _setNodeToFixedRadius
836          * @param obj {Node}
837          * @param typeArray {Boolean} true returns an array [X,Y]
838          * @protected
839          * @return {Array} an array of [XY] is optionally returned
840          */
841          _setNodeToFixedRadius : function(obj, typeArray){
842             var thisAngle = (this._angle - 90),
843             rad = (Math.PI / 180),
844             newY = Math.round(Math.sin(thisAngle * rad) * this._handleDistance),
845             newX = Math.round(Math.cos(thisAngle * rad) * this._handleDistance),
846             dia = obj.get('offsetWidth'); //Ticket #2529852
848             newY = newY - (dia * 0.5);
849             newX = newX - (dia * 0.5);
850             if(typeArray){ // just need the style for css transform left and top to animate the handle drag:end
851                 return [(this._ringNodeRadius + newX), (this._ringNodeRadius + newY)];
852             }else{
853                 obj.setStyle('left', (this._ringNodeRadius + newX) + 'px');
854                 obj.setStyle('top', (this._ringNodeRadius + newY) + 'px');
855             }
856          },
858         /**
859          * Synchronizes the DOM state with the attribute settings.
860          *
861          * @method syncUI
862          */
863         syncUI : function() {
864             // Make the marker and the resetString display so their placement and borderRadius can be calculated, then hide them again.
865             // We would have used visibility:hidden in the css of this class,
866             // but IE8 VML never returns to visible after applying visibility:hidden then removing it.
867             this._setSizes();
868             this._calculateDialCenter(); // #2531111 initialize center of dial
869             this._setBorderRadius();
870             this._uiSetValue(this.get("value"));
871             this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
872             this._resetString.addClass(Dial.CSS_CLASSES.hidden);
873         },
875         /**
876          * sets the sizes of ring, center-button, marker, handle, and VML ovals in pixels.
877          * Needed only because some IE versions
878          * ignore CSS percent sizes/offsets.
879          * so these must be set in pixels.
880          * Normally these are set in % of the ring.
881          *
882          * @method _setSizes
883          * @protected
884          */
885         _setSizes : function(){
886             var dia = this.get('diameter'),
887             offset, offsetResetX, offsetResetY,
888             setSize = function(node, dia, percent){
889                 var suffix = 'px';
890                 node.getElementsByTagName('oval').setStyle('width', (dia * percent) + suffix);
891                 node.getElementsByTagName('oval').setStyle('height', (dia * percent) + suffix);
892                 node.setStyle('width', (dia * percent) + suffix);
893                 node.setStyle('height', (dia * percent) + suffix);
894             };
895             setSize(this._ringNode, dia, 1.0);
896             setSize(this._handleNode, dia, this.get('handleDiameter'));
897             setSize(this._markerNode, dia, this.get('markerDiameter'));
898             setSize(this._centerButtonNode, dia, this.get('centerButtonDiameter'));
900             // Set these (used for trig) this way instead of relative to dia,
901             // in case they have borders, have images etc.
902             this._ringNodeRadius = this._ringNode.get('offsetWidth') * 0.5;
903             this._handleNodeRadius = this._handleNode.get('offsetWidth') * 0.5;
904             this._markerNodeRadius = this._markerNode.get('offsetWidth') * 0.5;
905             this._centerButtonNodeRadius = this._centerButtonNode.get('offsetWidth') * 0.5;
906             this._handleDistance = this._ringNodeRadius * this.get('handleDistance');
907             // place the centerButton
908             offset = (this._ringNodeRadius - this._centerButtonNodeRadius);
909             this._centerButtonNode.setStyle('left', offset + 'px');
910             this._centerButtonNode.setStyle('top', offset + 'px');
911             /*
912             Place the resetString
913             This seems like it should be able to be done with CSS,
914             But since there is also a VML oval in IE that is absolute positioned,
915             The resetString ends up behind the VML oval.
916             */
917             offsetResetX = (this._centerButtonNodeRadius - (this._resetString.get('offsetWidth') * 0.5));
918             offsetResetY = (this._centerButtonNodeRadius - (this._resetString.get('offsetHeight') * 0.5));
919             this._resetString.setStyles({'left':offsetResetX + 'px', 'top':offsetResetY + 'px'});
920         },
923         /**
924          * renders the DOM object for the Dial's label
925          *
926          * @method _renderLabel
927          * @protected
928          */
929         _renderLabel : function() {
930             var contentBox = this.get("contentBox"),
931                 label = contentBox.one("." + Dial.CSS_CLASSES.label);
932             if (!label) {
933                 label = Node.create(Y.Lang.sub(Dial.LABEL_TEMPLATE, this.get('strings')));
934                 contentBox.append(label);
935             }
936             this._labelNode = label;
937             this._valueStringNode = this._labelNode.one("." + Dial.CSS_CLASSES.valueString);
938         },
940         /**
941          * renders the DOM object for the Dial's background ring
942          *
943          * @method _renderRing
944          * @protected
945          */
946         _renderRing : function() {
947             var contentBox = this.get("contentBox"),
948                 ring = contentBox.one("." + Dial.CSS_CLASSES.ring);
949             if (!ring) {
950                 ring = contentBox.appendChild(Dial.RING_TEMPLATE);
951                 ring.setStyles({width:this.get('diameter') + 'px', height:this.get('diameter') + 'px'});
952             }
953             this._ringNode = ring;
954         },
956         /**
957          * renders the DOM object for the Dial's background marker which
958          * tracks the angle of the user dragging the handle
959          *
960          * @method _renderMarker
961          * @protected
962          */
963         _renderMarker : function() {
964             var contentBox = this.get("contentBox"),
965             marker = contentBox.one("." + Dial.CSS_CLASSES.marker);
966             if (!marker) {
967                 marker = contentBox.one('.' + Dial.CSS_CLASSES.ring).appendChild(Dial.MARKER_TEMPLATE);
968             }
969             this._markerNode = marker;
970         },
972         /**
973          * renders the DOM object for the Dial's center
974          *
975          * @method _renderCenterButton
976          * @protected
977          */
978         _renderCenterButton : function() {
979             var contentBox = this.get("contentBox"),
980                 centerButton = contentBox.one("." + Dial.CSS_CLASSES.centerButton);
981             if (!centerButton) {
982                 centerButton = Node.create(Y.Lang.sub(Dial.CENTER_BUTTON_TEMPLATE, this.get('strings')));
983                 contentBox.one('.' + Dial.CSS_CLASSES.ring).append(centerButton);
984             }
985             this._centerButtonNode = centerButton;
986             this._resetString = this._centerButtonNode.one('.' + Dial.CSS_CLASSES.resetString);
987         },
989         /**
990          * renders the DOM object for the Dial's user draggable handle
991          *
992          * @method _renderHandle
993          * @protected
994          */
995         _renderHandle : function() {
996             var labelId = Dial.CSS_CLASSES.label + Y.guid(), //get this unique id once then use for handle and label for ARIA
997                 contentBox = this.get("contentBox"),
998                 handle = contentBox.one("." + Dial.CSS_CLASSES.handle);
999             if (!handle) {
1000                 handle = Node.create(Y.Lang.sub(Dial.HANDLE_TEMPLATE, this.get('strings')));
1001                 handle.setAttribute('aria-labelledby', labelId);  // get unique id for specifying a label & handle for ARIA
1002                 this._labelNode.one('.' + Dial.CSS_CLASSES.labelString).setAttribute('id', labelId);  // When handle gets focus, screen reader will include label text when reading the value.
1003                 contentBox.one('.' + Dial.CSS_CLASSES.ring).append(handle);
1004             }
1005             this._handleNode = handle;
1006         },
1008         /**
1009          * sets the visible UI label HTML string
1010          *
1011          * @method _setLabelString
1012          * @param str {HTML}
1013          * @protected
1014          * @deprecated Use DialObjName.set('strings',{'label':'My new label'});   before DialObjName.render();
1016          */
1017         _setLabelString : function(str) {
1018             this.get("contentBox").one("." + Dial.CSS_CLASSES.labelString).setHTML(str);
1019         },
1021         /**
1022          * sets the visible UI label HTML string
1023          *
1024          * @method _setResetString
1025          * @param str {HTML}
1026          * @protected
1027          * @deprecated Use DialObjName.set('strings',{'resetStr':'My new reset string'});   before DialObjName.render();
1028          */
1029         _setResetString : function(str) {
1030              this.get("contentBox").one("." + Dial.CSS_CLASSES.resetString).setHTML(str);
1031             // this._setXYResetString(); // This used to recenter the string in the button. Done with CSS now. Method has been removed.
1032             // this._resetString.setHTML(''); //We no longer show/hide the reset string with setHTML but by addClass and removeClass .yui3-dial-reset-string-hidden
1033         },
1035         /**
1036          * sets the tooltip HTML string in the Dial's handle
1037          *
1038          * @method _setTooltipString
1039          * @param str {HTML}
1040          * @protected
1041          * @deprecated Use DialObjName.set('strings',{'tooltipHandle':'My new tooltip'});   before DialObjName.render();
1042          */
1043         _setTooltipString : function(str) {
1044             this._handleNode.set('title', str);
1045         },
1047         /**
1048          * sets the Dial's value in response to key events.
1049          * Left and right keys are in a separate method
1050          * in case an implementation wants to increment values
1051          * but needs left and right arrow keys for other purposes.
1052          *
1053          * @method _onDirectionKey
1054          * @param e {Event} the key event
1055          * @protected
1056          */
1057         _onDirectionKey : function(e) {
1058             e.preventDefault();
1059             switch (e.charCode) {
1060                 case 38: // up
1061                     this._incrMinor();
1062                     break;
1063                 case 40: // down
1064                     this._decrMinor();
1065                     break;
1066                 case 36: // home
1067                     this._setToMin();
1068                     break;
1069                 case 35: // end
1070                     this._setToMax();
1071                     break;
1072                 case 33: // page up
1073                     this._incrMajor();
1074                     break;
1075                 case 34: // page down
1076                     this._decrMajor();
1077                     break;
1078             }
1079         },
1081         /**
1082          * sets the Dial's value in response to left or right key events
1083          *
1084          * @method _onLeftRightKey
1085          * @param e {Event} the key event
1086          * @protected
1087          */
1088         _onLeftRightKey : function(e) {
1089             e.preventDefault();
1090             switch (e.charCode) {
1091                 case 37: // left
1092                     this._decrMinor();
1093                     break;
1094                 case 39: // right
1095                     this._incrMinor();
1096                     break;
1097             }
1098         },
1100         /**
1101          * sets the Dial's value in response to left or right key events when a meta (mac command/apple) key is also pressed
1102          *
1103          * @method _onLeftRightKeyMeta
1104          * @param e {Event} the key event
1105          * @protected
1106          */
1107         _onLeftRightKeyMeta : function(e) {
1108             e.preventDefault();
1109             switch (e.charCode) {
1110                 case 37: // left + meta
1111                     this._setToMin();
1112                     break;
1113                 case 39: // right + meta
1114                     this._setToMax();
1115                     break;
1116             }
1117         },
1119         /**
1120          * increments Dial value by a minor increment
1121          *
1122          * @method _incrMinor
1123          * @protected
1124          */
1125         _incrMinor : function(){
1126                 var newVal = (this.get('value') + this.get("minorStep"));
1127                 newVal = Math.min(newVal, this.get("max"));
1128                 // [#2530045] .toFixed returns a string.
1129                 // Dial's value needs a number. -0 makes it a number, but removes trailing zeros.
1130                 // Added toFixed(...) again in _uiSetValue where content of yui3-dial-value-string is set.
1131                 // Removing the toFixed here, loses the feature of "snap-to" when for example, stepsPerRevolution is 10 and decimalPlaces is 0.
1132                 this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
1133         },
1135         /**
1136          * decrements Dial value by a minor increment
1137          *
1138          * @method _decrMinor
1139          * @protected
1140          */
1141         _decrMinor : function(){
1142                 var newVal = (this.get('value') - this.get("minorStep"));
1143                 newVal = Math.max(newVal, this.get("min"));
1144                 this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
1145         },
1147         /**
1148          * increments Dial value by a major increment
1149          *
1150          * @method _incrMajor
1151          * @protected
1152          */
1153         _incrMajor : function(){
1154                 var newVal = (this.get('value') + this.get("majorStep"));
1155                 newVal = Math.min(newVal, this.get("max"));
1156                 this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
1157         },
1159         /**
1160          * decrements Dial value by a major increment
1161          *
1162          * @method _decrMajor
1163          * @protected
1164          */
1165         _decrMajor : function(){
1166                 var newVal = (this.get('value') - this.get("majorStep"));
1167                 newVal = Math.max(newVal, this.get("min"));
1168                 this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
1169         },
1171         /**
1172          * sets Dial value to dial's max attr
1173          *
1174          * @method _setToMax
1175          * @protected
1176          */
1177         _setToMax : function(){
1178                 this.set('value', this.get("max"));
1179         },
1181         /**
1182          * sets Dial value to dial's min attr
1183          *
1184          * @method _setToMin
1185          * @protected
1186          */
1187         _setToMin : function(){
1188                 this.set('value', this.get("min"));
1189         },
1191         /**
1192          * resets Dial value to the orignal initial value.
1193          *
1194          * @method _resetDial
1195          * @protected
1196          */
1197         _resetDial : function(e){
1198             if(e){
1199                 e.stopPropagation(); //[#2530206] need to add so mousedown doesn't propagate to ring and move the handle
1200             }
1201             this.set('value', this._originalValue);
1202             this._resetString.addClass(Dial.CSS_CLASSES.hidden); //[#2530441]
1203             this._handleNode.focus();
1204         },
1206         /**
1207          * returns the handle angle associated with the current value of the Dial.
1208          * Returns a number between 0 and 360.
1209          *
1210          * @method _getAngleFromValue
1211          * @param newVal {Number} the current value of the Dial
1212          * @return {Number} the angle associated with the current Dial value
1213          * @protected
1214          */
1215         _getAngleFromValue : function(newVal){
1216             var nonWrappedPartOfValue = newVal % this._stepsPerRevolution,
1217             angleFromValue = nonWrappedPartOfValue / this._stepsPerRevolution * 360;
1218             return (angleFromValue < 0) ? (angleFromValue + 360) : angleFromValue;
1219         },
1221         /**
1222          * returns the value of the Dial calculated from the current handle angle
1223          *
1224          * @method _getValueFromAngle
1225          * @param angle {Number} the current angle of the Dial's handle
1226          * @return {Number} the current Dial value corresponding to the handle position
1227          * @protected
1228          */
1229         _getValueFromAngle : function(angle){
1230             if(angle < 0){
1231                 angle = (360 + angle);
1232             }else if(angle === 0){
1233                 angle = 360;
1234             }
1235             var value = (angle / 360) * this._stepsPerRevolution;
1236             value = (value + (this._timesWrapped * this._stepsPerRevolution));
1237             //return Math.round(value * 100) / 100;
1238             return value.toFixed(this.get('decimalPlaces')) - 0;
1239         },
1241         /**
1242          * calls the method to update the UI whenever the Dial value changes
1243          *
1244          * @method _afterValueChange
1245          * @param e {Event}
1246          * @protected
1247          */
1248         _afterValueChange : function(e) {
1249             this._uiSetValue(e.newVal);
1250         },
1252         /**
1253          * Changes a value to have the correct decimal places per the attribute decimalPlaces
1254          *
1255          * @method _valueToDecimalPlaces
1256          * @param val {Number} a raw value to set to the Dial
1257          * @return {Number} the input val changed to have the correct decimal places
1258          * @protected
1259          */
1260         _valueToDecimalPlaces : function(val) { // [#2530206] cleaned up and better user feedback of when it's max or min.
1262         },
1264         /**
1265          * Updates the UI display value of the Dial to reflect
1266          * the value passed in.
1267          * Makes all other needed UI display changes
1268          *
1269          * @method _uiSetValue
1270          * @param val {Number} value of the Dial
1271          * @protected
1272          */
1273         _uiSetValue : function(val) { // [#2530206] cleaned up and better user feedback of when it's max or min.
1274             this._angle = this._getAngleFromValue(val);
1275             if(this._handleNode.hasClass(Dial.CSS_CLASSES.dragging) === false){
1276                 this._setTimesWrappedFromValue(val);
1277                 this._setNodeToFixedRadius(this._handleNode, false);
1278                 this._prevAng = this._getAngleFromValue(this.get('value'));
1279             }
1280             this._valueStringNode.setHTML(val.toFixed(this.get('decimalPlaces'))); // [#2530045]
1281             this._handleNode.set('aria-valuenow', val);
1282             this._handleNode.set('aria-valuetext', val);
1283             this._setNodeToFixedRadius(this._markerNode, false);
1284             if((val === this._maxValue) || (val === this._minValue)){
1285                 this._markerNode.addClass(Dial.CSS_CLASSES.markerMaxMin);
1286                 if(supportsVML === true){
1287                     this._markerNode.getElementsByTagName('fill').set('color', '#AB3232');
1288                 }
1289                 this._markerNode.removeClass(Dial.CSS_CLASSES.hidden);
1290             }else{ // not max or min
1291                 if(supportsVML === true){
1292                     this._markerNode.getElementsByTagName('fill').set('color', '#000');
1293                 }
1294                 this._markerNode.removeClass(Dial.CSS_CLASSES.markerMaxMin);
1295                 if(this._handleNode.hasClass(Dial.CSS_CLASSES.dragging) === false){ // if not max || min, and not dragging handle, hide the marker
1296                     this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
1297                 }
1298             }
1299         },
1301         /**
1302          * value attribute default validator. Verifies that
1303          * the value being set lies between the min/max value
1304          *
1305          * @method _validateValue
1306          * @param val {Number} value of the Dial
1307          * @protected
1308          */
1309         _validateValue: function(val) {
1310             var min = this.get("min"),
1311                 max = this.get("max");
1312             return (Lang.isNumber(val) && val >= min && val <= max);
1313         }
1314     });
1315     Y.Dial = Dial;
1318 }, '3.13.0', {
1319     "requires": [
1320         "widget",
1321         "dd-drag",
1322         "event-mouseenter",
1323         "event-move",
1324         "event-key",
1325         "transition",
1326         "intl"
1327     ],
1328     "lang": [
1329         "en",
1330         "es",
1331         "hu"
1332     ],
1333     "skinnable": true