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);