6df10cec07aeaa05fdc45259ef1b3ffe8f2b17ad
[vnfsdk/refrepo.git] /
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v1.1.3
6  */
7 (function( window, angular, undefined ){
8 "use strict";
9
10 (function() {
11   'use strict';
12
13   MdFabController['$inject'] = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"];
14   angular.module('material.components.fabShared', ['material.core'])
15     .controller('MdFabController', MdFabController);
16
17   function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
18     var vm = this;
19     var initialAnimationAttempts = 0;
20
21     // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
22
23     vm.open = function() {
24       $scope.$evalAsync("vm.isOpen = true");
25     };
26
27     vm.close = function() {
28       // Async eval to avoid conflicts with existing digest loops
29       $scope.$evalAsync("vm.isOpen = false");
30
31       // Focus the trigger when the element closes so users can still tab to the next item
32       $element.find('md-fab-trigger')[0].focus();
33     };
34
35     // Toggle the open/close state when the trigger is clicked
36     vm.toggle = function() {
37       $scope.$evalAsync("vm.isOpen = !vm.isOpen");
38     };
39
40     /*
41      * Angular Lifecycle hook for newer Angular versions.
42      * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
43      */
44     vm.$onInit = function() {
45       setupDefaults();
46       setupListeners();
47       setupWatchers();
48
49       fireInitialAnimations();
50     };
51
52     // For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
53     // manually call the $onInit hook.
54     if (angular.version.major === 1 && angular.version.minor <= 4) {
55       this.$onInit();
56     }
57
58     function setupDefaults() {
59       // Set the default direction to 'down' if none is specified
60       vm.direction = vm.direction || 'down';
61
62       // Set the default to be closed
63       vm.isOpen = vm.isOpen || false;
64
65       // Start the keyboard interaction at the first action
66       resetActionIndex();
67
68       // Add an animations waiting class so we know not to run
69       $element.addClass('md-animations-waiting');
70     }
71
72     function setupListeners() {
73       var eventTypes = [
74         'click', 'focusin', 'focusout'
75       ];
76
77       // Add our listeners
78       angular.forEach(eventTypes, function(eventType) {
79         $element.on(eventType, parseEvents);
80       });
81
82       // Remove our listeners when destroyed
83       $scope.$on('$destroy', function() {
84         angular.forEach(eventTypes, function(eventType) {
85           $element.off(eventType, parseEvents);
86         });
87
88         // remove any attached keyboard handlers in case element is removed while
89         // speed dial is open
90         disableKeyboard();
91       });
92     }
93
94     var closeTimeout;
95     function parseEvents(event) {
96       // If the event is a click, just handle it
97       if (event.type == 'click') {
98         handleItemClick(event);
99       }
100
101       // If we focusout, set a timeout to close the element
102       if (event.type == 'focusout' && !closeTimeout) {
103         closeTimeout = $timeout(function() {
104           vm.close();
105         }, 100, false);
106       }
107
108       // If we see a focusin and there is a timeout about to run, cancel it so we stay open
109       if (event.type == 'focusin' && closeTimeout) {
110         $timeout.cancel(closeTimeout);
111         closeTimeout = null;
112       }
113     }
114
115     function resetActionIndex() {
116       vm.currentActionIndex = -1;
117     }
118
119     function setupWatchers() {
120       // Watch for changes to the direction and update classes/attributes
121       $scope.$watch('vm.direction', function(newDir, oldDir) {
122         // Add the appropriate classes so we can target the direction in the CSS
123         $animate.removeClass($element, 'md-' + oldDir);
124         $animate.addClass($element, 'md-' + newDir);
125
126         // Reset the action index since it may have changed
127         resetActionIndex();
128       });
129
130       var trigger, actions;
131
132       // Watch for changes to md-open
133       $scope.$watch('vm.isOpen', function(isOpen) {
134         // Reset the action index since it may have changed
135         resetActionIndex();
136
137         // We can't get the trigger/actions outside of the watch because the component hasn't been
138         // linked yet, so we wait until the first watch fires to cache them.
139         if (!trigger || !actions) {
140           trigger = getTriggerElement();
141           actions = getActionsElement();
142         }
143
144         if (isOpen) {
145           enableKeyboard();
146         } else {
147           disableKeyboard();
148         }
149
150         var toAdd = isOpen ? 'md-is-open' : '';
151         var toRemove = isOpen ? '' : 'md-is-open';
152
153         // Set the proper ARIA attributes
154         trigger.attr('aria-haspopup', true);
155         trigger.attr('aria-expanded', isOpen);
156         actions.attr('aria-hidden', !isOpen);
157
158         // Animate the CSS classes
159         $animate.setClass($element, toAdd, toRemove);
160       });
161     }
162
163     function fireInitialAnimations() {
164       // If the element is actually visible on the screen
165       if ($element[0].scrollHeight > 0) {
166         // Fire our animation
167         $animate.addClass($element, '_md-animations-ready').then(function() {
168           // Remove the waiting class
169           $element.removeClass('md-animations-waiting');
170         });
171       }
172
173       // Otherwise, try for up to 1 second before giving up
174       else if (initialAnimationAttempts < 10) {
175         $timeout(fireInitialAnimations, 100);
176
177         // Increment our counter
178         initialAnimationAttempts = initialAnimationAttempts + 1;
179       }
180     }
181
182     function enableKeyboard() {
183       $element.on('keydown', keyPressed);
184
185       // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid
186       // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button)
187       $mdUtil.nextTick(function() {
188         angular.element(document).on('click touchend', checkForOutsideClick);
189       });
190
191       // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
192       // this breaks accessibility, especially on mobile, since you have no arrow keys to press
193       //resetActionTabIndexes();
194     }
195
196     function disableKeyboard() {
197       $element.off('keydown', keyPressed);
198       angular.element(document).off('click touchend', checkForOutsideClick);
199     }
200
201     function checkForOutsideClick(event) {
202       if (event.target) {
203         var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger');
204         var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
205
206         if (!closestTrigger && !closestActions) {
207           vm.close();
208         }
209       }
210     }
211
212     function keyPressed(event) {
213       switch (event.which) {
214         case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
215         case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
216         case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
217         case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
218         case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false;
219       }
220     }
221
222     function doActionPrev(event) {
223       focusAction(event, -1);
224     }
225
226     function doActionNext(event) {
227       focusAction(event, 1);
228     }
229
230     function focusAction(event, direction) {
231       var actions = resetActionTabIndexes();
232
233       // Increment/decrement the counter with restrictions
234       vm.currentActionIndex = vm.currentActionIndex + direction;
235       vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex);
236       vm.currentActionIndex = Math.max(0, vm.currentActionIndex);
237
238       // Focus the element
239       var focusElement =  angular.element(actions[vm.currentActionIndex]).children()[0];
240       angular.element(focusElement).attr('tabindex', 0);
241       focusElement.focus();
242
243       // Make sure the event doesn't bubble and cause something else
244       event.preventDefault();
245       event.stopImmediatePropagation();
246     }
247
248     function resetActionTabIndexes() {
249       // Grab all of the actions
250       var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');
251
252       // Disable all other actions for tabbing
253       angular.forEach(actions, function(action) {
254         angular.element(angular.element(action).children()[0]).attr('tabindex', -1);
255       });
256
257       return actions;
258     }
259
260     function doKeyLeft(event) {
261       if (vm.direction === 'left') {
262         doActionNext(event);
263       } else {
264         doActionPrev(event);
265       }
266     }
267
268     function doKeyUp(event) {
269       if (vm.direction === 'down') {
270         doActionPrev(event);
271       } else {
272         doActionNext(event);
273       }
274     }
275
276     function doKeyRight(event) {
277       if (vm.direction === 'left') {
278         doActionPrev(event);
279       } else {
280         doActionNext(event);
281       }
282     }
283
284     function doKeyDown(event) {
285       if (vm.direction === 'up') {
286         doActionPrev(event);
287       } else {
288         doActionNext(event);
289       }
290     }
291
292     function isTrigger(element) {
293       return $mdUtil.getClosest(element, 'md-fab-trigger');
294     }
295
296     function isAction(element) {
297       return $mdUtil.getClosest(element, 'md-fab-actions');
298     }
299
300     function handleItemClick(event) {
301       if (isTrigger(event.target)) {
302         vm.toggle();
303       }
304
305       if (isAction(event.target)) {
306         vm.close();
307       }
308     }
309
310     function getTriggerElement() {
311       return $element.find('md-fab-trigger');
312     }
313
314     function getActionsElement() {
315       return $element.find('md-fab-actions');
316     }
317   }
318 })();
319
320 (function() {
321   'use strict';
322
323   /**
324    * The duration of the CSS animation in milliseconds.
325    *
326    * @type {number}
327    */
328   MdFabSpeedDialFlingAnimation['$inject'] = ["$timeout"];
329   MdFabSpeedDialScaleAnimation['$inject'] = ["$timeout"];
330   var cssAnimationDuration = 300;
331
332   /**
333    * @ngdoc module
334    * @name material.components.fabSpeedDial
335    */
336   angular
337     // Declare our module
338     .module('material.components.fabSpeedDial', [
339       'material.core',
340       'material.components.fabShared',
341       'material.components.fabActions'
342     ])
343
344     // Register our directive
345     .directive('mdFabSpeedDial', MdFabSpeedDialDirective)
346
347     // Register our custom animations
348     .animation('.md-fling', MdFabSpeedDialFlingAnimation)
349     .animation('.md-scale', MdFabSpeedDialScaleAnimation)
350
351     // Register a service for each animation so that we can easily inject them into unit tests
352     .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation)
353     .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation);
354
355   /**
356    * @ngdoc directive
357    * @name mdFabSpeedDial
358    * @module material.components.fabSpeedDial
359    *
360    * @restrict E
361    *
362    * @description
363    * The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually
364    * `<md-button>`s) for quick access to common actions.
365    *
366    * There are currently two animations available by applying one of the following classes to
367    * the component:
368    *
369    *  - `md-fling` - The speed dial items appear from underneath the trigger and move into their
370    *    appropriate positions.
371    *  - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%.
372    *
373    * You may also easily position the trigger by applying one one of the following classes to the
374    * `<md-fab-speed-dial>` element:
375    *  - `md-fab-top-left`
376    *  - `md-fab-top-right`
377    *  - `md-fab-bottom-left`
378    *  - `md-fab-bottom-right`
379    *
380    * These CSS classes use `position: absolute`, so you need to ensure that the container element
381    * also uses `position: absolute` or `position: relative` in order for them to work.
382    *
383    * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
384    * open or close the speed dial. However, if you wish to allow users to hover over the empty
385    * space where the actions will appear, you must also add the `md-hover-full` class to the speed
386    * dial element. Without this, the hover effect will only occur on top of the trigger.
387    *
388    * See the demos for more information.
389    *
390    * ## Troubleshooting
391    *
392    * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on
393    * the parent container to ensure that it is only visible once ready. We have plans to remove this
394    * necessity in the future.
395    *
396    * @usage
397    * <hljs lang="html">
398    * <md-fab-speed-dial md-direction="up" class="md-fling">
399    *   <md-fab-trigger>
400    *     <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
401    *   </md-fab-trigger>
402    *
403    *   <md-fab-actions>
404    *     <md-button aria-label="Add User">
405    *       <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
406    *     </md-button>
407    *
408    *     <md-button aria-label="Add Group">
409    *       <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
410    *     </md-button>
411    *   </md-fab-actions>
412    * </md-fab-speed-dial>
413    * </hljs>
414    *
415    * @param {string} md-direction From which direction you would like the speed dial to appear
416    * relative to the trigger element.
417    * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible.
418    */
419   function MdFabSpeedDialDirective() {
420     return {
421       restrict: 'E',
422
423       scope: {
424         direction: '@?mdDirection',
425         isOpen: '=?mdOpen'
426       },
427
428       bindToController: true,
429       controller: 'MdFabController',
430       controllerAs: 'vm',
431
432       link: FabSpeedDialLink
433     };
434
435     function FabSpeedDialLink(scope, element) {
436       // Prepend an element to hold our CSS variables so we can use them in the animations below
437       element.prepend('<div class="_md-css-variables"></div>');
438     }
439   }
440
441   function MdFabSpeedDialFlingAnimation($timeout) {
442     function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
443
444     function runAnimation(element) {
445       // Don't run if we are still waiting and we are not ready
446       if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) {
447         return;
448       }
449
450       var el = element[0];
451       var ctrl = element.controller('mdFabSpeedDial');
452       var items = el.querySelectorAll('.md-fab-action-item');
453
454       // Grab our trigger element
455       var triggerElement = el.querySelector('md-fab-trigger');
456
457       // Grab our element which stores CSS variables
458       var variablesElement = el.querySelector('._md-css-variables');
459
460       // Setup JS variables based on our CSS variables
461       var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
462
463       // Always reset the items to their natural position/state
464       angular.forEach(items, function(item, index) {
465         var styles = item.style;
466
467         styles.transform = styles.webkitTransform = '';
468         styles.transitionDelay = '';
469         styles.opacity = 1;
470
471         // Make the items closest to the trigger have the highest z-index
472         styles.zIndex = (items.length - index) + startZIndex;
473       });
474
475       // Set the trigger to be above all of the actions so they disappear behind it.
476       triggerElement.style.zIndex = startZIndex + items.length + 1;
477
478       // If the control is closed, hide the items behind the trigger
479       if (!ctrl.isOpen) {
480         angular.forEach(items, function(item, index) {
481           var newPosition, axis;
482           var styles = item.style;
483
484           // Make sure to account for differences in the dimensions of the trigger verses the items
485           // so that we can properly center everything; this helps hide the item's shadows behind
486           // the trigger.
487           var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2;
488           var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2;
489
490           switch (ctrl.direction) {
491             case 'up':
492               newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset);
493               axis = 'Y';
494               break;
495             case 'down':
496               newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset);
497               axis = 'Y';
498               break;
499             case 'left':
500               newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset);
501               axis = 'X';
502               break;
503             case 'right':
504               newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset);
505               axis = 'X';
506               break;
507           }
508
509           var newTranslate = 'translate' + axis + '(' + newPosition + 'px)';
510
511           styles.transform = styles.webkitTransform = newTranslate;
512         });
513       }
514     }
515
516     return {
517       addClass: function(element, className, done) {
518         if (element.hasClass('md-fling')) {
519           runAnimation(element);
520           delayDone(done);
521         } else {
522           done();
523         }
524       },
525       removeClass: function(element, className, done) {
526         runAnimation(element);
527         delayDone(done);
528       }
529     };
530   }
531
532   function MdFabSpeedDialScaleAnimation($timeout) {
533     function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
534
535     var delay = 65;
536
537     function runAnimation(element) {
538       var el = element[0];
539       var ctrl = element.controller('mdFabSpeedDial');
540       var items = el.querySelectorAll('.md-fab-action-item');
541
542       // Grab our element which stores CSS variables
543       var variablesElement = el.querySelector('._md-css-variables');
544
545       // Setup JS variables based on our CSS variables
546       var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
547
548       // Always reset the items to their natural position/state
549       angular.forEach(items, function(item, index) {
550         var styles = item.style,
551           offsetDelay = index * delay;
552
553         styles.opacity = ctrl.isOpen ? 1 : 0;
554         styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
555         styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';
556
557         // Make the items closest to the trigger have the highest z-index
558         styles.zIndex = (items.length - index) + startZIndex;
559       });
560     }
561
562     return {
563       addClass: function(element, className, done) {
564         runAnimation(element);
565         delayDone(done);
566       },
567
568       removeClass: function(element, className, done) {
569         runAnimation(element);
570         delayDone(done);
571       }
572     };
573   }
574 })();
575
576 })(window, window.angular);