Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / transition-native / transition-native.js
blob2475ba2f1417d92191b058ad437c83aa8e4cb526
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('transition-native', function(Y) {
9 /**
10 * Provides the transition method for Node.
11 * Transition has no API of its own, but adds the transition method to Node.
13 * @module transition
14 * @requires node-style
17 var CAMEL_VENDOR_PREFIX = '',
18     VENDOR_PREFIX = '',
19     DOCUMENT = Y.config.doc,
20     DOCUMENT_ELEMENT = 'documentElement',
21     TRANSITION = 'transition',
22     TRANSITION_CAMEL = 'Transition',
23     TRANSITION_PROPERTY_CAMEL,
24     TRANSITION_PROPERTY,
25     TRANSITION_DURATION,
26     TRANSITION_TIMING_FUNCTION,
27     TRANSITION_DELAY,
28     TRANSITION_END,
29     ON_TRANSITION_END,
30     TRANSFORM_CAMEL,
32     EMPTY_OBJ = {},
34     VENDORS = [
35         'Webkit',
36         'Moz'
37     ],
39     VENDOR_TRANSITION_END = {
40         Webkit: 'webkitTransitionEnd'
41     },
43 /**
44  * A class for constructing transition instances.
45  * Adds the "transition" method to Node.
46  * @class Transition
47  * @constructor
48  */
50 Transition = function() {
51     this.init.apply(this, arguments);
54 Transition._toCamel = function(property) {
55     property = property.replace(/-([a-z])/gi, function(m0, m1) {
56         return m1.toUpperCase();
57     });
59     return property;
62 Transition._toHyphen = function(property) {
63     property = property.replace(/([A-Z]?)([a-z]+)([A-Z]?)/g, function(m0, m1, m2, m3) {
64         var str = ((m1) ? '-' + m1.toLowerCase() : '') + m2;
65         
66         if (m3) {
67             str += '-' + m3.toLowerCase();
68         }
70         return str;
71     }); 
73     return property;
76 Transition.SHOW_TRANSITION = 'fadeIn';
77 Transition.HIDE_TRANSITION = 'fadeOut';
79 Transition.useNative = false;
81 Y.Array.each(VENDORS, function(val) { // then vendor specific
82     var property = val + TRANSITION_CAMEL;
83     if (property in DOCUMENT[DOCUMENT_ELEMENT].style) {
84         CAMEL_VENDOR_PREFIX = val;
85         VENDOR_PREFIX = Transition._toHyphen(val) + '-';
87         Transition.useNative = true;
88         Transition.supported = true; // TODO: remove
89     }
90 });
92 TRANSITION_CAMEL = CAMEL_VENDOR_PREFIX + TRANSITION_CAMEL;
93 TRANSITION_PROPERTY_CAMEL = CAMEL_VENDOR_PREFIX + 'TransitionProperty';
94 TRANSITION_PROPERTY = VENDOR_PREFIX + 'transition-property';
95 TRANSITION_DURATION = VENDOR_PREFIX + 'transition-duration';
96 TRANSITION_TIMING_FUNCTION = VENDOR_PREFIX + 'transition-timing-function';
97 TRANSITION_DELAY = VENDOR_PREFIX + 'transition-delay';
98 TRANSITION_END = 'transitionend';
99 ON_TRANSITION_END = 'on' + CAMEL_VENDOR_PREFIX.toLowerCase() + 'transitionend';
101 TRANSITION_END = VENDOR_TRANSITION_END[CAMEL_VENDOR_PREFIX] || TRANSITION_END;
103 TRANSFORM_CAMEL = CAMEL_VENDOR_PREFIX + 'Transform';
105 Transition.fx = {};
106 Transition.toggles = {};
108 Transition._hasEnd = {};
110 Transition._reKeywords = /^(?:node|duration|iterations|easing|delay|on|onstart|onend)$/i;
112 Y.Node.DOM_EVENTS[TRANSITION_END] = 1; 
114 Transition.NAME = 'transition';
116 Transition.DEFAULT_EASING = 'ease';
117 Transition.DEFAULT_DURATION = 0.5;
118 Transition.DEFAULT_DELAY = 0;
120 Transition._nodeAttrs = {};
122 Transition.prototype = {
123     constructor: Transition,
124     init: function(node, config) {
125         var anim = this;
126         anim._node = node;
127         if (!anim._running && config) {
128             anim._config = config;
129             node._transition = anim; // cache for reuse
131             anim._duration = ('duration' in config) ?
132                 config.duration: anim.constructor.DEFAULT_DURATION;
134             anim._delay = ('delay' in config) ?
135                 config.delay: anim.constructor.DEFAULT_DELAY;
137             anim._easing = config.easing || anim.constructor.DEFAULT_EASING;
138             anim._count = 0; // track number of animated properties
139             anim._running = false;
141         }
143         return anim;
144     },
146     addProperty: function(prop, config) {
147         var anim = this,
148             node = this._node,
149             uid = Y.stamp(node),
150             nodeInstance = Y.one(node),
151             attrs = Transition._nodeAttrs[uid],
152             computed,
153             compareVal,
154             dur,
155             attr,
156             val;
158         if (!attrs) {
159             attrs = Transition._nodeAttrs[uid] = {};
160         }
162         attr = attrs[prop];
164         // might just be a value
165         if (config && config.value !== undefined) {
166             val = config.value;
167         } else if (config !== undefined) {
168             val = config; 
169             config = EMPTY_OBJ;
170         }
172         if (typeof val === 'function') {
173             val = val.call(nodeInstance, nodeInstance);
174         }
176         if (attr && attr.transition) {
177             // take control if another transition owns this property
178             if (attr.transition !== anim) {
179                 attr.transition._count--; // remapping attr to this transition
180             }
181         } 
183         anim._count++; // properties per transition
185         // make 0 async and fire events
186         dur = ((typeof config.duration != 'undefined') ? config.duration :
187                     anim._duration) || 0.0001;
189         attrs[prop] = {
190             value: val,
191             duration: dur,
192             delay: (typeof config.delay != 'undefined') ? config.delay :
193                     anim._delay,
195             easing: config.easing || anim._easing,
197             transition: anim
198         };
200         // native end event doesnt fire when setting to same value
201         // supplementing with timer
202         // val may be a string or number (height: 0, etc), but computedStyle is always string
203         computed = Y.DOM.getComputedStyle(node, prop);
204         compareVal = (typeof val === 'string') ? computed : parseFloat(computed);
206         if (Transition.useNative && compareVal === val) {
207             setTimeout(function() {
208                 anim._onNativeEnd.call(node, {
209                     propertyName: prop,
210                     elapsedTime: dur
211                 });
212             }, dur * 1000);
213         }
214     },
216     removeProperty: function(prop) {
217         var anim = this,
218             attrs = Transition._nodeAttrs[Y.stamp(anim._node)];
220         if (attrs && attrs[prop]) {
221             delete attrs[prop];
222             anim._count--;
223         }
225     },
227     initAttrs: function(config) {
228         var attr,
229             node = this._node;
231         if (config.transform && !config[TRANSFORM_CAMEL]) {
232             config[TRANSFORM_CAMEL] = config.transform;
233             delete config.transform; // TODO: copy
234         }
236         for (attr in config) {
237             if (config.hasOwnProperty(attr) && !Transition._reKeywords.test(attr)) {
238                 this.addProperty(attr, config[attr]);
240                 // when size is auto or % webkit starts from zero instead of computed 
241                 // (https://bugs.webkit.org/show_bug.cgi?id=16020)
242                 // TODO: selective set
243                 if (node.style[attr] === '') {
244                     Y.DOM.setStyle(node, attr, Y.DOM.getComputedStyle(node, attr));
245                 }
246             }
247         }
248     },
250     /**
251      * Starts or an animation.
252      * @method run
253      * @chainable
254      * @private
255      */    
256     run: function(callback) {
257         var anim = this,
258             node = anim._node,
259             config = anim._config,
260             data = {
261                 type: 'transition:start',
262                 config: config
263             };
266         if (!anim._running) {
267             anim._running = true;
269             //anim._node.fire('transition:start', data);
271             if (config.on && config.on.start) {
272                 config.on.start.call(Y.one(node), data);
273             }
275             anim.initAttrs(anim._config);
277             anim._callback = callback;
278             anim._start();
279         }
282         return anim;
283     },
285     _start: function() {
286         this._runNative();
287     },
289     _prepDur: function(dur) {
290         dur = parseFloat(dur);
292         return dur + 's';
293     },
295     _runNative: function(time) {
296         var anim = this,
297             node = anim._node,
298             uid = Y.stamp(node),
299             style = node.style,
300             computed = getComputedStyle(node),
301             attrs = Transition._nodeAttrs[uid],
302             cssText = '',
303             cssTransition = computed[Transition._toCamel(TRANSITION_PROPERTY)],
305             transitionText = TRANSITION_PROPERTY + ': ',
306             duration = TRANSITION_DURATION + ': ',
307             easing = TRANSITION_TIMING_FUNCTION + ': ',
308             delay = TRANSITION_DELAY + ': ',
309             hyphy,
310             attr,
311             name;
313         // preserve existing transitions
314         if (cssTransition !== 'all') {
315             transitionText += cssTransition + ',';
316             duration += computed[Transition._toCamel(TRANSITION_DURATION)] + ',';
317             easing += computed[Transition._toCamel(TRANSITION_TIMING_FUNCTION)] + ',';
318             delay += computed[Transition._toCamel(TRANSITION_DELAY)] + ',';
320         }
322         // run transitions mapped to this instance
323         for (name in attrs) {
324             hyphy = Transition._toHyphen(name);
325             attr = attrs[name];
326             if ((attr = attrs[name]) && attr.transition === anim) {
327                 if (name in node.style) { // only native styles allowed
328                     duration += anim._prepDur(attr.duration) + ',';
329                     delay += anim._prepDur(attr.delay) + ',';
330                     easing += (attr.easing) + ',';
332                     transitionText += hyphy + ',';
333                     cssText += hyphy + ': ' + attr.value + '; ';
334                 } else {
335                     this.removeProperty(name);
336                 }
337             }
338         }
340         transitionText = transitionText.replace(/,$/, ';');
341         duration = duration.replace(/,$/, ';');
342         easing = easing.replace(/,$/, ';');
343         delay = delay.replace(/,$/, ';');
345         // only one native end event per node
346         if (!Transition._hasEnd[uid]) {
347             //anim._detach = Y.on(TRANSITION_END, anim._onNativeEnd, node);
348             //node[ON_TRANSITION_END] = anim._onNativeEnd;
349             node.addEventListener(TRANSITION_END, anim._onNativeEnd, '');
350             Transition._hasEnd[uid] = true;
352         }
353         
354         //setTimeout(function() { // allow updates to apply (size fix, onstart, etc)
355             style.cssText += transitionText + duration + easing + delay + cssText;
356         //}, 1);
358     },
360     _end: function(elapsed) {
361         var anim = this,
362             node = anim._node,
363             callback = anim._callback,
364             config = anim._config,
365             data = {
366                 type: 'transition:end',
367                 config: config,
368                 elapsedTime: elapsed 
369             },
371             nodeInstance = Y.one(node); 
373         anim._running = false;
374         anim._callback = null;
376         if (node) {
377             if (config.on && config.on.end) {
378                 setTimeout(function() { // IE: allow previous update to finish
379                     config.on.end.call(nodeInstance, data);
381                     // nested to ensure proper fire order
382                     if (callback) {
383                         callback.call(nodeInstance, data);
384                     }
386                 }, 1);
387             } else if (callback) {
388                 setTimeout(function() { // IE: allow previous update to finish
389                     callback.call(nodeInstance, data);
390                 }, 1);
391             }
392             //node.fire('transition:end', data);
393         }
395     },
397     _endNative: function(name) {
398         var node = this._node,
399             value = node.ownerDocument.defaultView.getComputedStyle(node, '')[Transition._toCamel(TRANSITION_PROPERTY)];
401         if (typeof value === 'string') {
402             value = value.replace(new RegExp('(?:^|,\\s)' + name + ',?'), ',');
403             value = value.replace(/^,|,$/, '');
404             node.style[TRANSITION_CAMEL] = value;
405         }
406     },
408     _onNativeEnd: function(e) {
409         var node = this,
410             uid = Y.stamp(node),
411             event = e,//e._event,
412             name = Transition._toCamel(event.propertyName),
413             elapsed = event.elapsedTime,
414             attrs = Transition._nodeAttrs[uid],
415             attr = attrs[name],
416             anim = (attr) ? attr.transition : null,
417             data,
418             config;
420         if (anim) {
421             anim.removeProperty(name);
422             anim._endNative(name);
423             config = anim._config[name];
425             data = {
426                 type: 'propertyEnd',
427                 propertyName: name,
428                 elapsedTime: elapsed,
429                 config: config
430             };
432             if (config && config.on && config.on.end) {
433                 config.on.end.call(Y.one(node), data);
434             }
436             //node.fire('transition:propertyEnd', data);
438             if (anim._count <= 0)  { // after propertyEnd fires
439                 anim._end(elapsed);
440             }
441         }
442     },
444     destroy: function() {
445         var anim = this,
446             node = anim._node;
447         /*
448         if (anim._detach) {
449             anim._detach.detach();
450         }
451         */
452         //anim._node[ON_TRANSITION_END] = null;
453         if (node) {
454             node.removeEventListener(TRANSITION_END, anim._onNativeEnd, false);
455             anim._node = null;
456         }
457     }
460 Y.Transition = Transition;
461 Y.TransitionNative = Transition; // TODO: remove
463 /** 
464  *   Animate one or more css properties to a given value. Requires the "transition" module.
465  *   <pre>example usage:
466  *       Y.one('#demo').transition({
467  *           duration: 1, // in seconds, default is 0.5
468  *           easing: 'ease-out', // default is 'ease'
469  *           delay: '1', // delay start for 1 second, default is 0
471  *           height: '10px',
472  *           width: '10px',
474  *           opacity: { // per property
475  *               value: 0,
476  *               duration: 2,
477  *               delay: 2,
478  *               easing: 'ease-in'
479  *           }
480  *       });
481  *   </pre>
482  *   @for Node
483  *   @method transition
484  *   @param {Object} config An object containing one or more style properties, a duration and an easing.
485  *   @param {Function} callback A function to run after the transition has completed. 
486  *   @chainable
488 Y.Node.prototype.transition = function(name, config, callback) {
489     var 
490         transitionAttrs = Transition._nodeAttrs[Y.stamp(this._node)],
491         anim = (transitionAttrs) ? transitionAttrs.transition || null : null,
492         fxConfig,
493         prop;
494     
495     if (typeof name === 'string') { // named effect, pull config from registry
496         if (typeof config === 'function') {
497             callback = config;
498             config = null;
499         }
501         fxConfig = Transition.fx[name];
503         if (config && typeof config !== 'boolean') {
504             config = Y.clone(config);
506             for (prop in fxConfig) {
507                 if (fxConfig.hasOwnProperty(prop)) {
508                     if (! (prop in config)) {
509                         config[prop] = fxConfig[prop]; 
510                     }
511                 }
512             }
513         } else {
514             config = fxConfig;
515         }
517     } else { // name is a config, config is a callback or undefined
518         callback = config;
519         config = name;
520     }
522     if (anim && !anim._running) {
523         anim.init(this, config);
524     } else {
525         anim = new Transition(this._node, config);
526     }
528     anim.run(callback);
529     return this;
532 Y.Node.prototype.show = function(name, config, callback) {
533     this._show(); // show prior to transition
534     if (name && Y.Transition) {
535         if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
536             if (typeof config === 'function') {
537                 callback = config;
538                 config = name;
539             }
540             name = Transition.SHOW_TRANSITION; 
541         }    
542         this.transition(name, config, callback);
543     }    
544     return this;
547 var _wrapCallBack = function(anim, fn, callback) {
548     return function() {
549         if (fn) {
550             fn.call(anim);
551         }
552         if (callback) {
553             callback.apply(anim._node, arguments);
554         }
555     };
558 Y.Node.prototype.hide = function(name, config, callback) {
559     if (name && Y.Transition) {
560         if (typeof config === 'function') {
561             callback = config;
562             config = null;
563         }
565         callback = _wrapCallBack(this, this._hide, callback); // wrap with existing callback
566         if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
567             if (typeof config === 'function') {
568                 callback = config;
569                 config = name;
570             }
571             name = Transition.HIDE_TRANSITION; 
572         }    
573         this.transition(name, config, callback);
574     } else {
575         this._hide();
576     }    
577     return this;
578 }; 
580 /** 
581  *   Animate one or more css properties to a given value. Requires the "transition" module.
582  *   <pre>example usage:
583  *       Y.all('.demo').transition({
584  *           duration: 1, // in seconds, default is 0.5
585  *           easing: 'ease-out', // default is 'ease'
586  *           delay: '1', // delay start for 1 second, default is 0
588  *           height: '10px',
589  *           width: '10px',
591  *           opacity: { // per property
592  *               value: 0,
593  *               duration: 2,
594  *               delay: 2,
595  *               easing: 'ease-in'
596  *           }
597  *       });
598  *   </pre>
599  *   @for NodeList
600  *   @method transition
601  *   @param {Object} config An object containing one or more style properties, a duration and an easing.
602  *   @param {Function} callback A function to run after the transition has completed. The callback fires
603  *       once per item in the NodeList.
604  *   @chainable
606 Y.NodeList.prototype.transition = function(config, callback) {
607     var nodes = this._nodes,
608         i = 0,
609         node;
611     while ((node = nodes[i++])) {
612         Y.one(node).transition(config, callback);
613     }
615     return this;
618 Y.Node.prototype.toggleView = function(name, on, callback) {
619     this._toggles = this._toggles || [];
620     callback = arguments[arguments.length - 1];
622     if (typeof name == 'boolean') { // no transition, just toggle
623         on = name;
624         name = null;
625     }
627     name = name || Y.Transition.DEFAULT_TOGGLE;
629     if (typeof on == 'undefined' && name in this._toggles) { // reverse current toggle
630         on = ! this._toggles[name];
631     }
633     on = (on) ? 1 : 0;
634     if (on) {
635         this._show();
636     }  else {
637         callback = _wrapCallBack(this, this._hide, callback);
638     }
640     this._toggles[name] = on;
641     this.transition(Y.Transition.toggles[name][on], callback);
643     return this;
646 Y.NodeList.prototype.toggleView = function(name, on, callback) {
647     var nodes = this._nodes,
648         i = 0,
649         node;
651     while ((node = nodes[i++])) {
652         Y.one(node).toggleView(name, on, callback);
653     }
655     return this;
658 Y.mix(Transition.fx, {
659     fadeOut: {
660         opacity: 0,
661         duration: 0.5,
662         easing: 'ease-out'
663     },
665     fadeIn: {
666         opacity: 1,
667         duration: 0.5,
668         easing: 'ease-in'
669     },
671     sizeOut: {
672         height: 0,
673         width: 0,
674         duration: 0.75,
675         easing: 'ease-out'
676     },
678     sizeIn: {
679         height: function(node) {
680             return node.get('scrollHeight') + 'px';
681         },
682         width: function(node) {
683             return node.get('scrollWidth') + 'px';
684         },
685         duration: 0.5,
686         easing: 'ease-in',
687         
688         on: {
689             start: function() {
690                 var overflow = this.getStyle('overflow');
691                 if (overflow !== 'hidden') { // enable scrollHeight/Width
692                     this.setStyle('overflow', 'hidden');
693                     this._transitionOverflow = overflow;
694                 }
695             },
697             end: function() {
698                 if (this._transitionOverflow) { // revert overridden value
699                     this.setStyle('overflow', this._transitionOverflow);
700                     delete this._transitionOverflow;
701                 }
702             }
703         } 
704     }
707 Y.mix(Transition.toggles, {
708     size: ['sizeOut', 'sizeIn'],
709     fade: ['fadeOut', 'fadeIn']
712 Transition.DEFAULT_TOGGLE = 'fade';
716 }, '3.5.0' ,{requires:['node-base']});