arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
socketioMEANseed/www/bower_components/angular-material/modules/js/fabSpeedDial/fabSpeedDial.js
www/bower_components/angular-material/modules/js/fabSpeedDial/fabSpeedDial.jsRaw
  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);