source: trip-planner-front/node_modules/angular-material/modules/js/fabSpeedDial/fabSpeedDial.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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