source: trip-planner-front/node_modules/angular-material/modules/closure/panel/panel.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: 96.8 KB
Line 
1/*!
2 * AngularJS Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.2.3
6 */
7goog.provide('ngmaterial.components.panel');
8goog.require('ngmaterial.components.backdrop');
9goog.require('ngmaterial.core');
10/**
11 * @ngdoc module
12 * @name material.components.panel
13 */
14MdPanelService['$inject'] = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
15angular
16 .module('material.components.panel', [
17 'material.core',
18 'material.components.backdrop'
19 ])
20 .provider('$mdPanel', MdPanelProvider);
21
22
23/*****************************************************************************
24 * PUBLIC DOCUMENTATION *
25 *****************************************************************************/
26
27
28/**
29 * @ngdoc service
30 * @name $mdPanelProvider
31 * @module material.components.panel
32 *
33 * @description
34 * `$mdPanelProvider` allows users to create configuration presets that will be
35 * stored within a cached presets object. When the configuration is needed, the
36 * user can request the preset by passing it as the first parameter in the
37 * `$mdPanel.create` or `$mdPanel.open` methods.
38 *
39 * @usage
40 * <hljs lang="js">
41 * (function(angular, undefined) {
42 * 'use strict';
43 *
44 * angular
45 * .module('demoApp', ['ngMaterial'])
46 * .config(DemoConfig)
47 * .controller('DemoCtrl', DemoCtrl)
48 * .controller('DemoMenuCtrl', DemoMenuCtrl);
49 *
50 * function DemoConfig($mdPanelProvider) {
51 * $mdPanelProvider.definePreset('demoPreset', {
52 * attachTo: angular.element(document.body),
53 * controller: DemoMenuCtrl,
54 * controllerAs: 'ctrl',
55 * template: '' +
56 * '<div class="menu-panel" md-whiteframe="4">' +
57 * ' <div class="menu-content">' +
58 * ' <div class="menu-item" ng-repeat="item in ctrl.items">' +
59 * ' <button class="md-button">' +
60 * ' <span>{{item}}</span>' +
61 * ' </button>' +
62 * ' </div>' +
63 * ' <md-divider></md-divider>' +
64 * ' <div class="menu-item">' +
65 * ' <button class="md-button" ng-click="ctrl.closeMenu()">' +
66 * ' <span>Close Menu</span>' +
67 * ' </button>' +
68 * ' </div>' +
69 * ' </div>' +
70 * '</div>',
71 * panelClass: 'menu-panel-container',
72 * focusOnOpen: false,
73 * zIndex: 100,
74 * propagateContainerEvents: true,
75 * groupName: 'menus'
76 * });
77 * }
78 *
79 * function PanelProviderCtrl($mdPanel) {
80 * this.navigation = {
81 * name: 'navigation',
82 * items: [
83 * 'Home',
84 * 'About',
85 * 'Contact'
86 * ]
87 * };
88 * this.favorites = {
89 * name: 'favorites',
90 * items: [
91 * 'Add to Favorites'
92 * ]
93 * };
94 * this.more = {
95 * name: 'more',
96 * items: [
97 * 'Account',
98 * 'Sign Out'
99 * ]
100 * };
101 *
102 * $mdPanel.newPanelGroup('menus', {
103 * maxOpen: 2
104 * });
105 *
106 * this.showMenu = function($event, menu) {
107 * $mdPanel.open('demoPreset', {
108 * id: 'menu_' + menu.name,
109 * position: $mdPanel.newPanelPosition()
110 * .relativeTo($event.target)
111 * .addPanelPosition(
112 * $mdPanel.xPosition.ALIGN_START,
113 * $mdPanel.yPosition.BELOW
114 * ),
115 * locals: {
116 * items: menu.items
117 * },
118 * openFrom: $event
119 * });
120 * };
121 * }
122 *
123 * function PanelMenuCtrl(mdPanelRef) {
124 * // 'mdPanelRef' is injected in the controller.
125 * this.closeMenu = function() {
126 * if (mdPanelRef) {
127 * mdPanelRef.close();
128 * }
129 * };
130 * }
131 * })(angular);
132 * </hljs>
133 */
134
135/**
136 * @ngdoc method
137 * @name $mdPanelProvider#definePreset
138 * @description
139 * Takes the passed in preset name and preset configuration object and adds it
140 * to the `_presets` object of the provider. This `_presets` object is then
141 * passed along to the `$mdPanel` service.
142 *
143 * @param {string} name Preset name.
144 * @param {!Object} preset Specific configuration object that can contain any
145 * and all of the parameters available within the `$mdPanel.create` method.
146 * However, parameters that pertain to id, position, animation, and user
147 * interaction are not allowed and will be removed from the preset
148 * configuration.
149 */
150
151
152/*****************************************************************************
153 * MdPanel Service *
154 *****************************************************************************/
155
156
157/**
158 * @ngdoc service
159 * @name $mdPanel
160 * @module material.components.panel
161 *
162 * @description
163 * `$mdPanel` is a robust, low-level service for creating floating panels on
164 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
165 *
166 * The following types, referenced below, have separate documentation:
167 * - <a ng-href="api/type/MdPanelAnimation">MdPanelAnimation</a> from `$mdPanel.newPanelAnimation()`
168 * - <a ng-href="api/type/MdPanelPosition">MdPanelPosition</a> from `$mdPanel.newPanelPosition()`
169 * - <a ng-href="api/type/MdPanelRef">MdPanelRef</a> from the `$mdPanel.open()` Promise or
170 * injected in the panel's controller
171 *
172 * @usage
173 * <hljs lang="js">
174 * (function(angular, undefined) {
175 * 'use strict';
176 *
177 * angular
178 * .module('demoApp', ['ngMaterial'])
179 * .controller('DemoDialogController', DialogController)
180 * .controller('DemoCtrl', function($mdPanel) {
181 *
182 * var panelRef;
183 *
184 * function showPanel($event) {
185 * var panelPosition = $mdPanel.newPanelPosition()
186 * .absolute()
187 * .top('50%')
188 * .left('50%');
189 *
190 * var panelAnimation = $mdPanel.newPanelAnimation()
191 * .openFrom($event)
192 * .duration(200)
193 * .closeTo('.show-button')
194 * .withAnimation($mdPanel.animation.SCALE);
195 *
196 * var config = {
197 * attachTo: angular.element(document.body),
198 * controller: DialogController,
199 * controllerAs: 'ctrl',
200 * position: panelPosition,
201 * animation: panelAnimation,
202 * targetEvent: $event,
203 * templateUrl: 'dialog-template.html',
204 * clickOutsideToClose: true,
205 * escapeToClose: true,
206 * focusOnOpen: true
207 * };
208 *
209 * $mdPanel.open(config)
210 * .then(function(result) {
211 * panelRef = result;
212 * });
213 * }
214 * }
215 *
216 * function DialogController(MdPanelRef) {
217 * function closeDialog() {
218 * if (MdPanelRef) MdPanelRef.close();
219 * }
220 * }
221 * })(angular);
222 * </hljs>
223 */
224
225/**
226 * @ngdoc method
227 * @name $mdPanel#create
228 * @description
229 * Creates a panel with the specified options.
230 *
231 * @param {!Object=} config Specific configuration object that may contain the
232 * following properties:
233 *
234 * - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
235 * the created panel is added to a tracked panels object. Any subsequent
236 * requests made to create a panel with that ID are ignored. This is useful
237 * in having the panel service not open multiple panels from the same user
238 * interaction when there is no backdrop and events are propagated. Defaults
239 * to an arbitrary string that is not tracked.
240 * - `template` - `{string=}`: HTML template to show in the panel. This
241 * **must** be trusted HTML with respect to AngularJS’s
242 * [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
243 * - `templateUrl` - `{string=}`: The URL that will be used as the content of
244 * the panel.
245 * - `contentElement` - `{(string|!JQLite|!Element)=}`: Pre-compiled
246 * element to be used as the panel's content.
247 * - `controller` - `{(function|string)=}`: The controller to associate with
248 * the panel. The controller can inject a reference to the returned
249 * panelRef, which allows the panel to be closed, hidden, and shown. Any
250 * fields passed in through locals or resolve will be bound to the
251 * controller.
252 * - `controllerAs` - `{string=}`: An alias to assign the controller to on
253 * the scope.
254 * - `bindToController` - `{boolean=}`: Binds locals to the controller
255 * instead of passing them in. Defaults to true, as this is a best
256 * practice.
257 * - `locals` - `{Object=}`: An object containing key/value pairs. The keys
258 * will be used as names of values to inject into the controller. For
259 * example, `locals: {three: 3}` would inject `three` into the controller,
260 * with the value 3. 'mdPanelRef' is a reserved key, and will always
261 * be set to the created `MdPanelRef` instance.
262 * - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
263 * values. The panel will not open until all of the promises resolve.
264 * - `attachTo` - `{(string|!JQLite|!Element)=}`: The element to
265 * attach the panel to. Defaults to appending to the root element of the
266 * application.
267 * - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
268 * events should be allowed to propagate 'go through' the container, aka the
269 * wrapper, of the panel. Defaults to false.
270 * - `panelClass` - `{string=}`: A css class to apply to the panel element.
271 * This class should define any borders, box-shadow, etc. for the panel.
272 * - `zIndex` - `{number=}`: The z-index to place the panel at.
273 * Defaults to 80.
274 * - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
275 * specifies the alignment of the panel. For more information, see
276 * `MdPanelPosition`.
277 * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
278 * outside the panel to close it. Defaults to false.
279 * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
280 * close the panel. Defaults to false.
281 * - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
282 * called after the close successfully finishes. The first parameter passed
283 * into this function is the current panelRef and the 2nd is an optional
284 * string explaining the close reason. The currently supported closeReasons
285 * can be found in the `MdPanelRef.closeReasons` enum. These are by default
286 * passed along by the panel.
287 * - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
288 * panel. If `trapFocus` is true, the user will not be able to interact
289 * with the rest of the page until the panel is dismissed. Defaults to
290 * false.
291 * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
292 * open. Only disable if focusing some other way, as focus management is
293 * required for panels to be accessible. Defaults to true.
294 * - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
295 * Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
296 * to false.
297 * - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
298 * specifies the animation of the panel. For more information, see
299 * `MdPanelAnimation`.
300 * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
301 * behind the panel. Defaults to false.
302 * - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
303 * page behind the panel. Defaults to false.
304 * - `onDomAdded` - `{function=}`: Callback function used to announce when
305 * the panel is added to the DOM.
306 * - `onOpenComplete` - `{function=}`: Callback function used to announce
307 * when the open() action is finished.
308 * - `onRemoving` - `{function=}`: Callback function used to announce the
309 * close/hide() action is starting.
310 * - `onDomRemoved` - `{function=}`: Callback function used to announce when
311 * the panel is removed from the DOM.
312 * - `origin` - `{(string|!JQLite|!Element)=}`: The element to focus
313 * on when the panel closes. This is commonly the element which triggered
314 * the opening of the panel. If you do not use `origin`, you need to control
315 * the focus manually.
316 * - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
317 * group names. The group name is used for creating a group of panels. The
318 * group is used for configuring the number of open panels and identifying
319 * specific behaviors for groups. For instance, all tooltips could be
320 * identified using the same groupName.
321 *
322 * @returns {!MdPanelRef} panelRef
323 */
324
325/**
326 * @ngdoc method
327 * @name $mdPanel#open
328 * @description
329 * Calls the create method above, then opens the panel. This is a shortcut for
330 * creating and then calling open manually. If custom methods need to be
331 * called when the panel is added to the DOM or opened, do not use this method.
332 * Instead create the panel, chain promises on the domAdded and openComplete
333 * methods, and call open from the returned panelRef.
334 *
335 * @param {!Object=} config Specific configuration object that may contain
336 * the properties defined in `$mdPanel.create`.
337 * @returns {!Q.IPromise<!MdPanelRef>} panelRef A promise that resolves
338 * to an instance of the panel.
339 */
340
341/**
342 * @ngdoc method
343 * @name $mdPanel#newPanelPosition
344 * @description
345 * Returns a new instance of the MdPanelPosition object. Use this to create
346 * the position config object.
347 *
348 * @returns {!MdPanelPosition} panelPosition
349 */
350
351/**
352 * @ngdoc method
353 * @name $mdPanel#newPanelAnimation
354 * @description
355 * Returns a new instance of the MdPanelAnimation object. Use this to create
356 * the animation config object.
357 *
358 * @returns {!MdPanelAnimation} panelAnimation
359 */
360
361/**
362 * @ngdoc method
363 * @name $mdPanel#setGroupMaxOpen
364 * @description
365 * Sets the maximum number of panels in a group that can be opened at a given
366 * time.
367 *
368 * @param {string} groupName The name of the group to configure.
369 * @param {number} maxOpen The maximum number of panels that can be
370 * opened. Infinity can be passed in to remove the maxOpen limit.
371 */
372
373
374/*****************************************************************************
375 * MdPanelRef *
376 *****************************************************************************/
377
378
379/**
380 * @ngdoc type
381 * @name MdPanelRef
382 * @module material.components.panel
383 * @description
384 * A reference to a created panel. This reference contains a unique id for the
385 * panel, along with the following properties:
386 *
387 * - `id` - `{string}`: The unique id for the panel. This id is used to track
388 * when a panel was interacted with.
389 * - `config` - `{!Object=}`: The entire config object that was used in
390 * create.
391 * - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
392 * Visibility to the user does not factor into isAttached.
393 * - `panelContainer` - `{JQLite}`: The wrapper element containing the
394 * panel. This property is added in order to have access to the `addClass`,
395 * `removeClass`, `toggleClass`, etc methods.
396 * - `panelEl` - `{JQLite}`: The panel element. This property is added
397 * in order to have access to the `addClass`, `removeClass`, `toggleClass`,
398 * etc methods.
399 */
400
401/**
402 * @ngdoc method
403 * @name MdPanelRef#open
404 * @description
405 * Attaches and shows the panel.
406 *
407 * @returns {!Q.IPromise} A promise that is resolved when the panel is
408 * opened.
409 */
410
411/**
412 * @ngdoc method
413 * @name MdPanelRef#close
414 * @description
415 * Hides and detaches the panel. Note that this will **not** destroy the panel.
416 * If you don't intend on using the panel again, call the {@link #destroy
417 * destroy} method afterwards.
418 *
419 * @returns {!Q.IPromise} A promise that is resolved when the panel is
420 * closed.
421 */
422
423/**
424 * @ngdoc method
425 * @name MdPanelRef#attach
426 * @description
427 * Create the panel elements and attach them to the DOM. The panel will be
428 * hidden by default.
429 *
430 * @returns {!Q.IPromise} A promise that is resolved when the panel is
431 * attached.
432 */
433
434/**
435 * @ngdoc method
436 * @name MdPanelRef#detach
437 * @description
438 * Removes the panel from the DOM. This will NOT hide the panel before removing
439 * it.
440 *
441 * @returns {!Q.IPromise} A promise that is resolved when the panel is
442 * detached.
443 */
444
445/**
446 * @ngdoc method
447 * @name MdPanelRef#show
448 * @description
449 * Shows the panel.
450 *
451 * @returns {!Q.IPromise} A promise that is resolved when the panel has
452 * shown and animations are completed.
453 */
454
455/**
456 * @ngdoc method
457 * @name MdPanelRef#hide
458 * @description
459 * Hides the panel.
460 *
461 * @returns {!Q.IPromise} A promise that is resolved when the panel has
462 * hidden and animations are completed.
463 */
464
465/**
466 * @ngdoc method
467 * @name MdPanelRef#destroy
468 * @description
469 * Destroys the panel. The panel cannot be opened again after this is called.
470 */
471
472/**
473 * @ngdoc method
474 * @name MdPanelRef#updatePosition
475 * @description
476 * Updates the position configuration of a panel. Use this to update the
477 * position of a panel that is open, without having to close and re-open the
478 * panel.
479 *
480 * @param {!MdPanelPosition} position
481 */
482
483/**
484 * @ngdoc method
485 * @name MdPanelRef#addToGroup
486 * @description
487 * Adds a panel to a group if the panel does not exist within the group already.
488 * A panel can only exist within a single group.
489 *
490 * @param {string} groupName The name of the group to add the panel to.
491 */
492
493/**
494 * @ngdoc method
495 * @name MdPanelRef#removeFromGroup
496 * @description
497 * Removes a panel from a group if the panel exists within that group. The group
498 * must be created ahead of time.
499 *
500 * @param {string} groupName The name of the group.
501 */
502
503/**
504 * @ngdoc method
505 * @name MdPanelRef#registerInterceptor
506 * @description
507 * Registers an interceptor with the panel. The callback should return a promise,
508 * which will allow the action to continue when it gets resolved, or will
509 * prevent an action if it is rejected. The interceptors are called sequentially
510 * and it reverse order. `type` must be one of the following
511 * values available on `$mdPanel.interceptorTypes`:
512 * * `CLOSE` - Gets called before the panel begins closing.
513 *
514 * @param {string} type Type of interceptor.
515 * @param {!Q.IPromise<any>} callback Callback to be registered.
516 * @returns {!MdPanelRef}
517 */
518
519/**
520 * @ngdoc method
521 * @name MdPanelRef#removeInterceptor
522 * @description
523 * Removes a registered interceptor.
524 *
525 * @param {string} type Type of interceptor to be removed.
526 * @param {function(): !Q.IPromise<any>} callback Interceptor to be removed.
527 * @returns {!MdPanelRef}
528 */
529
530/**
531 * @ngdoc method
532 * @name MdPanelRef#removeAllInterceptors
533 * @description
534 * Removes all interceptors. If a type is supplied, only the
535 * interceptors of that type will be cleared.
536 *
537 * @param {string=} type Type of interceptors to be removed.
538 * @returns {!MdPanelRef}
539 */
540
541/**
542 * @ngdoc method
543 * @name MdPanelRef#updateAnimation
544 * @description
545 * Updates the animation configuration for a panel. You can use this to change
546 * the panel's animation without having to re-create it.
547 *
548 * @param {!MdPanelAnimation} animation
549 */
550
551
552/*****************************************************************************
553 * MdPanelPosition *
554 *****************************************************************************/
555
556
557/**
558 * @ngdoc type
559 * @name MdPanelPosition
560 * @module material.components.panel
561 * @description
562 *
563 * Object for configuring the position of the panel.
564 *
565 * @usage
566 *
567 * #### Centering the panel
568 *
569 * <hljs lang="js">
570 * new MdPanelPosition().absolute().center();
571 * </hljs>
572 *
573 * #### Overlapping the panel with an element
574 *
575 * <hljs lang="js">
576 * new MdPanelPosition()
577 * .relativeTo(someElement)
578 * .addPanelPosition(
579 * $mdPanel.xPosition.ALIGN_START,
580 * $mdPanel.yPosition.ALIGN_TOPS
581 * );
582 * </hljs>
583 *
584 * #### Aligning the panel with the bottom of an element
585 *
586 * <hljs lang="js">
587 * new MdPanelPosition()
588 * .relativeTo(someElement)
589 * .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
590 * </hljs>
591 */
592
593/**
594 * @ngdoc method
595 * @name MdPanelPosition#absolute
596 * @description
597 * Positions the panel absolutely relative to the parent element. If the parent
598 * is document.body, this is equivalent to positioning the panel absolutely
599 * within the viewport.
600 *
601 * @returns {!MdPanelPosition}
602 */
603
604/**
605 * @ngdoc method
606 * @name MdPanelPosition#relativeTo
607 * @description
608 * Positions the panel relative to a specific element.
609 *
610 * @param {string|!Element|!JQLite} element Query selector, DOM element,
611 * or angular element to position the panel with respect to.
612 * @returns {!MdPanelPosition}
613 */
614
615/**
616 * @ngdoc method
617 * @name MdPanelPosition#top
618 * @description
619 * Sets the value of `top` for the panel. Clears any previously set vertical
620 * position.
621 *
622 * @param {string=} top Value of `top`. Defaults to '0'.
623 * @returns {!MdPanelPosition}
624 */
625
626/**
627 * @ngdoc method
628 * @name MdPanelPosition#bottom
629 * @description
630 * Sets the value of `bottom` for the panel. Clears any previously set vertical
631 * position.
632 *
633 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
634 * @returns {!MdPanelPosition}
635 */
636
637/**
638 * @ngdoc method
639 * @name MdPanelPosition#start
640 * @description
641 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
642 * `rtl`. Clears any previously set horizontal position.
643 *
644 * @param {string=} start Value of position. Defaults to '0'.
645 * @returns {!MdPanelPosition}
646 */
647
648/**
649 * @ngdoc method
650 * @name MdPanelPosition#end
651 * @description
652 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
653 * Clears any previously set horizontal position.
654 *
655 * @param {string=} end Value of position. Defaults to '0'.
656 * @returns {!MdPanelPosition}
657 */
658
659/**
660 * @ngdoc method
661 * @name MdPanelPosition#left
662 * @description
663 * Sets the value of `left` for the panel. Clears any previously set
664 * horizontal position.
665 *
666 * @param {string=} left Value of `left`. Defaults to '0'.
667 * @returns {!MdPanelPosition}
668 */
669
670/**
671 * @ngdoc method
672 * @name MdPanelPosition#right
673 * @description
674 * Sets the value of `right` for the panel. Clears any previously set
675 * horizontal position.
676 *
677 * @param {string=} right Value of `right`. Defaults to '0'.
678 * @returns {!MdPanelPosition}
679 */
680
681/**
682 * @ngdoc method
683 * @name MdPanelPosition#centerHorizontally
684 * @description
685 * Centers the panel horizontally in the viewport. Clears any previously set
686 * horizontal position.
687 *
688 * @returns {!MdPanelPosition}
689 */
690
691/**
692 * @ngdoc method
693 * @name MdPanelPosition#centerVertically
694 * @description
695 * Centers the panel vertically in the viewport. Clears any previously set
696 * vertical position.
697 *
698 * @returns {!MdPanelPosition}
699 */
700
701/**
702 * @ngdoc method
703 * @name MdPanelPosition#center
704 * @description
705 * Centers the panel horizontally and vertically in the viewport. This is
706 * equivalent to calling both `centerHorizontally` and `centerVertically`.
707 * Clears any previously set horizontal and vertical positions.
708 *
709 * @returns {!MdPanelPosition}
710 */
711
712/**
713 * @ngdoc method
714 * @name MdPanelPosition#addPanelPosition
715 * @description
716 * Sets the x and y position for the panel relative to another element. Can be
717 * called multiple times to specify an ordered list of panel positions. The
718 * first position which allows the panel to be completely on-screen will be
719 * chosen; the last position will be chose whether it is on-screen or not.
720 *
721 * xPosition must be one of the following values available on
722 * $mdPanel.xPosition:
723 *
724 *
725 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
726 *
727 * <pre>
728 * *************
729 * * *
730 * * PANEL *
731 * * *
732 * *************
733 * A B C D E
734 *
735 * A: OFFSET_START (for LTR displays)
736 * B: ALIGN_START (for LTR displays)
737 * C: CENTER
738 * D: ALIGN_END (for LTR displays)
739 * E: OFFSET_END (for LTR displays)
740 * </pre>
741 *
742 * yPosition must be one of the following values available on
743 * $mdPanel.yPosition:
744 *
745 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
746 *
747 * <pre>
748 * F
749 * G *************
750 * * *
751 * H * PANEL *
752 * * *
753 * I *************
754 * J
755 *
756 * F: BELOW
757 * G: ALIGN_TOPS
758 * H: CENTER
759 * I: ALIGN_BOTTOMS
760 * J: ABOVE
761 * </pre>
762 *
763 * @param {string} xPosition
764 * @param {string} yPosition
765 * @returns {!MdPanelPosition}
766 */
767
768/**
769 * @ngdoc method
770 * @name MdPanelPosition#withOffsetX
771 * @description
772 * Sets the value of the offset in the x-direction.
773 *
774 * @param {string|number} offsetX
775 * @returns {!MdPanelPosition}
776 */
777
778/**
779 * @ngdoc method
780 * @name MdPanelPosition#withOffsetY
781 * @description
782 * Sets the value of the offset in the y-direction.
783 *
784 * @param {string|number} offsetY
785 * @returns {!MdPanelPosition}
786 */
787
788
789/*****************************************************************************
790 * MdPanelAnimation *
791 *****************************************************************************/
792
793
794/**
795 * @ngdoc type
796 * @name MdPanelAnimation
797 * @module material.components.panel
798 * @description
799 * Animation configuration object. To use, create an MdPanelAnimation with the
800 * desired properties, then pass the object as part of $mdPanel creation.
801 *
802 * @usage
803 *
804 * <hljs lang="js">
805 * var panelAnimation = new MdPanelAnimation()
806 * .openFrom(myButtonEl)
807 * .duration(1337)
808 * .closeTo('.my-button')
809 * .withAnimation($mdPanel.animation.SCALE);
810 *
811 * $mdPanel.create({
812 * animation: panelAnimation
813 * });
814 * </hljs>
815 */
816
817/**
818 * @ngdoc method
819 * @name MdPanelAnimation#openFrom
820 * @description
821 * Specifies where to start the open animation. `openFrom` accepts a
822 * click event object, query selector, DOM element, or a Rect object that
823 * is used to determine the bounds. When passed a click event, the location
824 * of the click will be used as the position to start the animation.
825 *
826 * @param {string|!Element|!Event|{top: number, left: number}}
827 * @returns {!MdPanelAnimation}
828 */
829
830/**
831 * @ngdoc method
832 * @name MdPanelAnimation#closeTo
833 * @description
834 * Specifies where to animate the panel close. `closeTo` accepts a
835 * query selector, DOM element, or a Rect object that is used to determine
836 * the bounds.
837 *
838 * @param {string|!Element|{top: number, left: number}}
839 * @returns {!MdPanelAnimation}
840 */
841
842/**
843 * @ngdoc method
844 * @name MdPanelAnimation#withAnimation
845 * @description
846 * Specifies the animation class.
847 *
848 * There are several default animations that can be used: `$mdPanel.animation.`
849 * - `SLIDE`: The panel slides in and out from the specified
850 * elements. It will not fade in or out.
851 * - `SCALE`: The panel scales in and out. Slide and fade are
852 * included in this animation.
853 * - `FADE`: The panel fades in and out.
854 *
855 * Custom classes will by default fade in and out unless
856 * `transition: opacity 1ms` is added to the to custom class.
857 *
858 * @param {string|{open: string, close: string}} cssClass
859 * @returns {!MdPanelAnimation}
860 */
861
862/**
863 * @ngdoc method
864 * @name MdPanelAnimation#duration
865 * @description
866 * Specifies the duration of the animation in milliseconds. The `duration`
867 * method accepts either a number or an object with separate open and close
868 * durations.
869 *
870 * @param {number|{open: number, close: number}} duration
871 * @returns {!MdPanelAnimation}
872 */
873
874
875/*****************************************************************************
876 * PUBLIC DOCUMENTATION *
877 *****************************************************************************/
878
879
880var MD_PANEL_Z_INDEX = 80;
881var MD_PANEL_HIDDEN = '_md-panel-hidden';
882var FOCUS_TRAP_TEMPLATE = angular.element(
883 '<div class="_md-panel-focus-trap" tabindex="0"></div>');
884
885var _presets = {};
886
887
888/**
889 * A provider that is used for creating presets for the panel API.
890 * @final @constructor ngInject
891 */
892function MdPanelProvider() {
893 return {
894 'definePreset': definePreset,
895 'getAllPresets': getAllPresets,
896 'clearPresets': clearPresets,
897 '$get': $getProvider()
898 };
899}
900
901
902/**
903 * Takes the passed in panel configuration object and adds it to the `_presets`
904 * object at the specified name.
905 * @param {string} name Name of the preset to set.
906 * @param {!Object} preset Specific configuration object that can contain any
907 * and all of the parameters available within the `$mdPanel.create` method.
908 * However, parameters that pertain to id, position, animation, and user
909 * interaction are not allowed and will be removed from the preset
910 * configuration.
911 */
912function definePreset(name, preset) {
913 if (!name || !preset) {
914 throw new Error('mdPanelProvider: The panel preset definition is ' +
915 'malformed. The name and preset object are required.');
916 } else if (_presets.hasOwnProperty(name)) {
917 throw new Error('mdPanelProvider: The panel preset you have requested ' +
918 'has already been defined.');
919 }
920
921 // Delete any property on the preset that is not allowed.
922 delete preset.id;
923 delete preset.position;
924 delete preset.animation;
925
926 _presets[name] = preset;
927}
928
929
930/**
931 * Gets a clone of the `_presets`.
932 * @return {!Object}
933 */
934function getAllPresets() {
935 return angular.copy(_presets);
936}
937
938
939/**
940 * Clears all of the stored presets.
941 */
942function clearPresets() {
943 _presets = {};
944}
945
946
947/**
948 * Represents the `$get` method of the AngularJS provider. From here, a new
949 * reference to the MdPanelService is returned where the needed arguments are
950 * passed in including the MdPanelProvider `_presets`.
951 * @param {!Object} _presets
952 * @param {!JQLite} $rootElement
953 * @param {!angular.Scope} $rootScope
954 * @param {!IInjectorService} $injector
955 * @param {!IWindowService} $window
956 */
957function $getProvider() {
958 return [
959 '$rootElement', '$rootScope', '$injector', '$window',
960 function($rootElement, $rootScope, $injector, $window) {
961 return new MdPanelService(_presets, $rootElement, $rootScope,
962 $injector, $window);
963 }
964 ];
965}
966
967/**
968 * @param {string|[]} value
969 * @returns {[]} the input string wrapped in an Array or the original Array
970 */
971function coerceToArray(value) {
972 if (angular.isString(value)) {
973 value = [value];
974 }
975 return value;
976}
977
978/*****************************************************************************
979 * MdPanel Service *
980 *****************************************************************************/
981
982
983/**
984 * A service that is used for controlling/displaying panels on the screen.
985 * @param {!Object} presets
986 * @param {!JQLite} $rootElement
987 * @param {!angular.Scope} $rootScope
988 * @param {!IInjectorService} $injector
989 * @param {!IWindowService} $window
990 * @final @constructor ngInject
991 */
992function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
993 /**
994 * Default config options for the panel.
995 * Anything angular related needs to be done later. Therefore
996 * scope: $rootScope.$new(true),
997 * attachTo: $rootElement,
998 * are added later.
999 * @private {!Object}
1000 */
1001 this._defaultConfigOptions = {
1002 bindToController: true,
1003 clickOutsideToClose: false,
1004 disableParentScroll: false,
1005 escapeToClose: false,
1006 focusOnOpen: true,
1007 fullscreen: false,
1008 hasBackdrop: false,
1009 propagateContainerEvents: false,
1010 transformTemplate: angular.bind(this, this._wrapTemplate),
1011 trapFocus: false,
1012 zIndex: MD_PANEL_Z_INDEX
1013 };
1014
1015 /** @private {!Object} */
1016 this._config = {};
1017
1018 /** @private {!Object} */
1019 this._presets = presets;
1020
1021 /** @private @const */
1022 this._$rootElement = $rootElement;
1023
1024 /** @private @const */
1025 this._$rootScope = $rootScope;
1026
1027 /** @private @const */
1028 this._$injector = $injector;
1029
1030 /** @private @const */
1031 this._$window = $window;
1032
1033 /** @private @const */
1034 this._$mdUtil = this._$injector.get('$mdUtil');
1035
1036 /** @private {!Object<string, !MdPanelRef>} */
1037 this._trackedPanels = {};
1038
1039 /**
1040 * @private {!Object<string,
1041 * {panels: !Array<!MdPanelRef>,
1042 * openPanels: !Array<!MdPanelRef>,
1043 * maxOpen: number}>}
1044 */
1045 this._groups = Object.create(null);
1046
1047 /**
1048 * Default animations that can be used within the panel.
1049 * @type {enum}
1050 */
1051 this.animation = MdPanelAnimation.animation;
1052
1053 /**
1054 * Possible values of xPosition for positioning the panel relative to
1055 * another element.
1056 * @type {enum}
1057 */
1058 this.xPosition = MdPanelPosition.xPosition;
1059
1060 /**
1061 * Possible values of yPosition for positioning the panel relative to
1062 * another element.
1063 * @type {enum}
1064 */
1065 this.yPosition = MdPanelPosition.yPosition;
1066
1067 /**
1068 * Possible values for the interceptors that can be registered on a panel.
1069 * @type {enum}
1070 */
1071 this.interceptorTypes = MdPanelRef.interceptorTypes;
1072
1073 /**
1074 * Possible values for closing of a panel.
1075 * @type {enum}
1076 */
1077 this.closeReasons = MdPanelRef.closeReasons;
1078
1079 /**
1080 * Possible values of absolute position.
1081 * @type {enum}
1082 */
1083 this.absPosition = MdPanelPosition.absPosition;
1084}
1085
1086
1087/**
1088 * Creates a panel with the specified options.
1089 * @param {string|Object=} preset Name of a preset configuration that can be used to
1090 * extend the panel configuration.
1091 * @param {!Object=} config Configuration object for the panel.
1092 * @returns {!MdPanelRef}
1093 */
1094MdPanelService.prototype.create = function(preset, config) {
1095 if (typeof preset === 'string') {
1096 preset = this._getPresetByName(preset);
1097 } else if (typeof preset === 'object' &&
1098 (angular.isUndefined(config) || !config)) {
1099 config = preset;
1100 preset = {};
1101 }
1102
1103 preset = preset || {};
1104 config = config || {};
1105
1106 // If the passed-in config contains an ID and the ID is within _trackedPanels,
1107 // return the tracked panel after updating its config with the passed-in
1108 // config.
1109 if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
1110 var trackedPanel = this._trackedPanels[config.id];
1111 angular.extend(trackedPanel.config, config);
1112 return trackedPanel;
1113 }
1114
1115 // Combine the passed-in config, the _defaultConfigOptions, and the preset
1116 // configuration into the `_config`.
1117 this._config = angular.extend({
1118 // If no ID is set within the passed-in config, then create an arbitrary ID.
1119 id: config.id || 'panel_' + this._$mdUtil.nextUid(),
1120 scope: this._$rootScope.$new(true),
1121 attachTo: this._$rootElement
1122 }, this._defaultConfigOptions, config, preset);
1123
1124 // Create the panelRef and add it to the `_trackedPanels` object.
1125 var panelRef = new MdPanelRef(this._config, this._$injector);
1126 this._trackedPanels[this._config.id] = panelRef;
1127
1128 // Add the panel to each of its requested groups.
1129 if (this._config.groupName) {
1130 this._config.groupName = coerceToArray(this._config.groupName);
1131 angular.forEach(this._config.groupName, function(group) {
1132 panelRef.addToGroup(group);
1133 });
1134 }
1135
1136 this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
1137
1138 return panelRef;
1139};
1140
1141
1142/**
1143 * Creates and opens a panel with the specified options.
1144 * @param {string=} preset Name of a preset configuration that can be used to
1145 * extend the panel configuration.
1146 * @param {!Object=} config Configuration object for the panel.
1147 * @returns {!Q.IPromise<!MdPanelRef>} The panel created from create.
1148 */
1149MdPanelService.prototype.open = function(preset, config) {
1150 var panelRef = this.create(preset, config);
1151 return panelRef.open().then(function() {
1152 return panelRef;
1153 });
1154};
1155
1156
1157/**
1158 * Gets a specific preset configuration object saved within `_presets`.
1159 * @param {string} preset Name of the preset to search for.
1160 * @returns {!Object} The preset configuration object.
1161 */
1162MdPanelService.prototype._getPresetByName = function(preset) {
1163 if (!this._presets[preset]) {
1164 throw new Error('mdPanel: The panel preset configuration that you ' +
1165 'requested does not exist. Use the $mdPanelProvider to create a ' +
1166 'preset before requesting one.');
1167 }
1168 return this._presets[preset];
1169};
1170
1171
1172/**
1173 * Returns a new instance of the MdPanelPosition. Use this to create the
1174 * positioning object.
1175 * @returns {!MdPanelPosition}
1176 */
1177MdPanelService.prototype.newPanelPosition = function() {
1178 return new MdPanelPosition(this._$injector);
1179};
1180
1181
1182/**
1183 * Returns a new instance of the MdPanelAnimation. Use this to create the
1184 * animation object.
1185 * @returns {!MdPanelAnimation}
1186 */
1187MdPanelService.prototype.newPanelAnimation = function() {
1188 return new MdPanelAnimation(this._$injector);
1189};
1190
1191
1192/**
1193 * @ngdoc method
1194 * @name $mdPanel#newPanelGroup
1195 * @description
1196 * Creates a panel group and adds it to a tracked list of panel groups.
1197 * @param {string} groupName Name of the group to create.
1198 * @param {{maxOpen: number}=} config Configuration object that may contain the following
1199 * properties:
1200 *
1201 * - `maxOpen`: The maximum number of panels that are allowed open within a defined panel group.
1202 *
1203 * @returns {!{panels: !Array<!MdPanelRef>, openPanels: !Array<!MdPanelRef>, maxOpen: number}}
1204 * the new panel group
1205 */
1206MdPanelService.prototype.newPanelGroup = function(groupName, config) {
1207 if (!this._groups[groupName]) {
1208 config = config || {};
1209 this._groups[groupName] = {
1210 panels: [],
1211 openPanels: [],
1212 maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
1213 };
1214 }
1215 return this._groups[groupName];
1216};
1217
1218
1219/**
1220 * Sets the maximum number of panels in a group that can be opened at a given
1221 * time.
1222 * @param {string} groupName The name of the group to configure.
1223 * @param {number} maxOpen The maximum number of panels that can be
1224 * opened. Infinity can be passed in to remove the maxOpen limit.
1225 */
1226MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
1227 if (this._groups[groupName]) {
1228 this._groups[groupName].maxOpen = maxOpen;
1229 } else {
1230 throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
1231 }
1232};
1233
1234
1235/**
1236 * Determines if the current number of open panels within a group exceeds the
1237 * limit of allowed open panels.
1238 * @param {string} groupName The name of the group to check.
1239 * @returns {boolean} true if open count does exceed maxOpen and false if not.
1240 * @private
1241 */
1242MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
1243 if (this._groups[groupName]) {
1244 var group = this._groups[groupName];
1245 return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
1246 }
1247 return false;
1248};
1249
1250
1251/**
1252 * Closes the first open panel within a specific group.
1253 * @param {string} groupName The name of the group.
1254 * @private
1255 */
1256MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
1257 var group = this._groups[groupName];
1258 if (group && group.openPanels.length) {
1259 group.openPanels[0].close();
1260 }
1261};
1262
1263
1264/**
1265 * Wraps the user's template in three elements:
1266 * - md-panel-outer-wrapper - covers the entire `attachTo` element.
1267 * - md-panel-inner-wrapper - handles the positioning.
1268 * - md-panel - contains the user's content and deals with the animations.
1269 * @param {string} origTemplate The original template.
1270 * @returns {string} The wrapped template.
1271 * @private
1272 */
1273MdPanelService.prototype._wrapTemplate = function(origTemplate) {
1274 var template = origTemplate || '';
1275
1276 // The panel should be initially rendered offscreen so we can calculate
1277 // height and width for positioning.
1278 return '' +
1279 '<div class="md-panel-outer-wrapper">' +
1280 '<div class="md-panel-inner-wrapper _md-panel-offscreen">' +
1281 '<div class="md-panel _md-panel-offscreen">' + template + '</div>' +
1282 '</div>' +
1283 '</div>';
1284};
1285
1286
1287/**
1288 * Wraps a content element in a `md-panel-outer-wrapper`, as well as
1289 * a `md-panel-inner-wrapper`, and positions it off-screen. Allows for
1290 * proper control over positioning and animations.
1291 * @param {!JQLite} contentElement Element to be wrapped.
1292 * @return {!JQLite} Wrapper element.
1293 * @private
1294 */
1295MdPanelService.prototype._wrapContentElement = function(contentElement) {
1296 var outerWrapper = angular.element(
1297 '<div class="md-panel-outer-wrapper">' +
1298 '<div class="md-panel-inner-wrapper _md-panel-offscreen"></div>' +
1299 '</div>'
1300 );
1301
1302 contentElement.addClass('md-panel _md-panel-offscreen');
1303 outerWrapper.children().eq(0).append(contentElement);
1304
1305 return outerWrapper;
1306};
1307
1308
1309/*****************************************************************************
1310 * MdPanelRef *
1311 *****************************************************************************/
1312
1313
1314/**
1315 * A reference to a created panel. This reference contains a unique id for the
1316 * panel, along with properties/functions used to control the panel.
1317 * @param {!Object} config
1318 * @param {!IInjectorService} $injector
1319 * @final @constructor
1320 */
1321function MdPanelRef(config, $injector) {
1322 // Injected variables.
1323 /** @private @const {!IQService} */
1324 this._$q = $injector.get('$q');
1325
1326 /** @private @const {!angular.$mdCompiler} */
1327 this._$mdCompiler = $injector.get('$mdCompiler');
1328
1329 /** @private @const {!angular.$mdConstant} */
1330 this._$mdConstant = $injector.get('$mdConstant');
1331
1332 /** @private @const {!angular.$mdUtil} */
1333 this._$mdUtil = $injector.get('$mdUtil');
1334
1335 /** @private @const {!angular.$mdTheming} */
1336 this._$mdTheming = $injector.get('$mdTheming');
1337
1338 /** @private @const {!IRootScopeService} */
1339 this._$rootScope = $injector.get('$rootScope');
1340
1341 /** @private @const {!angular.$animate} */
1342 this._$animate = $injector.get('$animate');
1343
1344 /** @private @const {!MdPanelRef} */
1345 this._$mdPanel = $injector.get('$mdPanel');
1346
1347 /** @private @const {!ILogService} */
1348 this._$log = $injector.get('$log');
1349
1350 /** @private @const {!IWindowService} */
1351 this._$window = $injector.get('$window');
1352
1353 /** @private @const {!Function} */
1354 this._$$rAF = $injector.get('$$rAF');
1355
1356 // Public variables.
1357 /**
1358 * Unique id for the panelRef.
1359 * @type {string}
1360 */
1361 this.id = config.id;
1362
1363 /** @type {!Object} */
1364 this.config = config;
1365
1366 /** @type {!JQLite|undefined} */
1367 this.panelContainer = undefined;
1368
1369 /** @type {!JQLite|undefined} */
1370 this.panelEl = undefined;
1371
1372 /** @type {!JQLite|undefined} */
1373 this.innerWrapper = undefined;
1374
1375 /**
1376 * Whether the panel is attached. This is synchronous. When attach is called,
1377 * isAttached is set to true. When detach is called, isAttached is set to
1378 * false.
1379 * @type {boolean}
1380 */
1381 this.isAttached = false;
1382
1383 // Private variables.
1384 /** @private {Array<function()>} */
1385 this._removeListeners = [];
1386
1387 /** @private {!JQLite|undefined} */
1388 this._topFocusTrap = undefined;
1389
1390 /** @private {!JQLite|undefined} */
1391 this._bottomFocusTrap = undefined;
1392
1393 /** @private {!$mdPanel|undefined} */
1394 this._backdropRef = undefined;
1395
1396 /** @private {Function?} */
1397 this._restoreScroll = null;
1398
1399 /**
1400 * Keeps track of all the panel interceptors.
1401 * @private {!Object}
1402 */
1403 this._interceptors = Object.create(null);
1404
1405 /**
1406 * Cleanup function, provided by `$mdCompiler` and assigned after the element
1407 * has been compiled. When `contentElement` is used, the function is used to
1408 * restore the element to it's proper place in the DOM.
1409 * @private {Function|null}
1410 */
1411 this._compilerCleanup = null;
1412
1413 /**
1414 * Cache for saving and restoring element inline styles, CSS classes etc.
1415 * @type {{styles: string, classes: string}}
1416 */
1417 this._restoreCache = {
1418 styles: '',
1419 classes: ''
1420 };
1421}
1422
1423
1424MdPanelRef.interceptorTypes = {
1425 CLOSE: 'onClose'
1426};
1427
1428
1429/**
1430 * Opens an already created and configured panel. If the panel is already
1431 * visible, does nothing.
1432 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1433 * the panel is opened and animations finish.
1434 */
1435MdPanelRef.prototype.open = function() {
1436 var self = this;
1437 return this._$q(function(resolve, reject) {
1438 var done = self._done(resolve, self);
1439 var show = self._simpleBind(self.show, self);
1440 var checkGroupMaxOpen = function() {
1441 if (self.config.groupName) {
1442 self.config.groupName = coerceToArray(self.config.groupName);
1443 angular.forEach(self.config.groupName, function(group) {
1444 if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
1445 self._$mdPanel._closeFirstOpenedPanel(group);
1446 }
1447 });
1448 }
1449 };
1450
1451 self.attach()
1452 .then(show)
1453 .then(checkGroupMaxOpen)
1454 .then(done)
1455 .catch(reject);
1456 });
1457};
1458
1459
1460/**
1461 * Closes the panel.
1462 * @param {string} closeReason The event type that triggered the close.
1463 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1464 * the panel is closed and animations finish.
1465 */
1466MdPanelRef.prototype.close = function(closeReason) {
1467 var self = this;
1468
1469 return this._$q(function(resolve, reject) {
1470 self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
1471 var done = self._done(resolve, self);
1472 var detach = self._simpleBind(self.detach, self);
1473 var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
1474 onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
1475
1476 self.hide()
1477 .then(detach)
1478 .then(done)
1479 .then(onCloseSuccess)
1480 .catch(reject);
1481 }, reject);
1482 });
1483};
1484
1485
1486/**
1487 * Attaches the panel. The panel will be hidden afterwards.
1488 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1489 * the panel is attached.
1490 */
1491MdPanelRef.prototype.attach = function() {
1492 if (this.isAttached && this.panelEl) {
1493 return this._$q.when(this);
1494 }
1495
1496 var self = this;
1497 return this._$q(function(resolve, reject) {
1498 var done = self._done(resolve, self);
1499 var onDomAdded = self.config['onDomAdded'] || angular.noop;
1500 var addListeners = function(response) {
1501 self.isAttached = true;
1502 self._addEventListeners();
1503 return response;
1504 };
1505
1506 self._$q.all([
1507 self._createBackdrop(),
1508 self._createPanel()
1509 .then(addListeners)
1510 .catch(reject)
1511 ]).then(onDomAdded)
1512 .then(done)
1513 .catch(reject);
1514 });
1515};
1516
1517
1518/**
1519 * Only detaches the panel. Will NOT hide the panel first.
1520 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1521 * the panel is detached.
1522 */
1523MdPanelRef.prototype.detach = function() {
1524 if (!this.isAttached) {
1525 return this._$q.when(this);
1526 }
1527
1528 var self = this;
1529 var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
1530
1531 var detachFn = function() {
1532 self._removeEventListeners();
1533
1534 // Remove the focus traps that we added earlier for keeping focus within
1535 // the panel.
1536 if (self._topFocusTrap && self._topFocusTrap.parentNode) {
1537 self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
1538 }
1539
1540 if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
1541 self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
1542 }
1543
1544 if (self._restoreCache.classes) {
1545 self.panelEl[0].className = self._restoreCache.classes;
1546 }
1547
1548 // Either restore the saved styles or clear the ones set by mdPanel.
1549 self.panelEl[0].style.cssText = self._restoreCache.styles || '';
1550
1551 self._compilerCleanup();
1552 self.panelContainer.remove();
1553 self.isAttached = false;
1554 return self._$q.when(self);
1555 };
1556
1557 if (this._restoreScroll) {
1558 this._restoreScroll();
1559 this._restoreScroll = null;
1560 }
1561
1562 return this._$q(function(resolve, reject) {
1563 var done = self._done(resolve, self);
1564
1565 self._$q.all([
1566 detachFn(),
1567 self._backdropRef ? self._backdropRef.detach() : true
1568 ]).then(onDomRemoved)
1569 .then(done)
1570 .catch(reject);
1571 });
1572};
1573
1574
1575/**
1576 * Destroys the panel. The Panel cannot be opened again after this.
1577 */
1578MdPanelRef.prototype.destroy = function() {
1579 var self = this;
1580 if (this.config.groupName) {
1581 this.config.groupName = coerceToArray(this.config.groupName);
1582 angular.forEach(this.config.groupName, function(group) {
1583 self.removeFromGroup(group);
1584 });
1585 }
1586 this.config.scope.$destroy();
1587 this.config.locals = null;
1588 this.config.onDomAdded = null;
1589 this.config.onDomRemoved = null;
1590 this.config.onRemoving = null;
1591 this.config.onOpenComplete = null;
1592 this._interceptors = undefined;
1593};
1594
1595
1596/**
1597 * Shows the panel.
1598 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1599 * the panel has shown and animations finish.
1600 */
1601MdPanelRef.prototype.show = function() {
1602 if (!this.panelContainer) {
1603 return this._$q(function(resolve, reject) {
1604 reject('mdPanel: Panel does not exist yet. Call open() or attach().');
1605 });
1606 }
1607
1608 if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1609 return this._$q.when(this);
1610 }
1611
1612 var self = this;
1613 var animatePromise = function() {
1614 self.panelContainer.removeClass(MD_PANEL_HIDDEN);
1615 return self._animateOpen();
1616 };
1617
1618 return this._$q(function(resolve, reject) {
1619 var done = self._done(resolve, self);
1620 var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
1621 var addToGroupOpen = function() {
1622 if (self.config.groupName) {
1623 self.config.groupName = coerceToArray(self.config.groupName);
1624 angular.forEach(self.config.groupName, function(group) {
1625 group = self._$mdPanel._groups[group];
1626 if (group) {
1627 group.openPanels.push(self);
1628 }
1629 });
1630 }
1631 };
1632
1633 self._$q.all([
1634 self._backdropRef ? self._backdropRef.show() : self,
1635 animatePromise().then(function() { self._focusOnOpen(); }, reject)
1636 ]).then(onOpenComplete)
1637 .then(addToGroupOpen)
1638 .then(done)
1639 .catch(reject);
1640 });
1641};
1642
1643
1644/**
1645 * Hides the panel.
1646 * @returns {!Q.IPromise<!MdPanelRef>} A promise that is resolved when
1647 * the panel has hidden and animations finish.
1648 */
1649MdPanelRef.prototype.hide = function() {
1650 if (!this.panelContainer) {
1651 return this._$q(function(resolve, reject) {
1652 reject('mdPanel: Panel does not exist yet. Call open() or attach().');
1653 });
1654 }
1655
1656 if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1657 return this._$q.when(this);
1658 }
1659
1660 var self = this;
1661
1662 return this._$q(function(resolve, reject) {
1663 var done = self._done(resolve, self);
1664 var onRemoving = self.config['onRemoving'] || angular.noop;
1665 var hidePanel = function() {
1666 self.panelContainer.addClass(MD_PANEL_HIDDEN);
1667 };
1668 var removeFromGroupOpen = function() {
1669 if (self.config.groupName) {
1670 var index;
1671 self.config.groupName = coerceToArray(self.config.groupName);
1672 angular.forEach(self.config.groupName, function(group) {
1673 group = self._$mdPanel._groups[group];
1674 index = group.openPanels.indexOf(self);
1675 if (index > -1) {
1676 group.openPanels.splice(index, 1);
1677 }
1678 });
1679 }
1680 };
1681 var focusOnOrigin = function() {
1682 var origin = self.config['origin'];
1683 if (origin) {
1684 getElement(origin).focus();
1685 }
1686 };
1687
1688 self._$q.all([
1689 self._backdropRef ? self._backdropRef.hide() : self,
1690 self._animateClose()
1691 .then(onRemoving)
1692 .then(hidePanel)
1693 .then(removeFromGroupOpen)
1694 .then(focusOnOrigin)
1695 .catch(reject)
1696 ]).then(done, reject);
1697 });
1698};
1699
1700/**
1701 * Compiles the panel, according to the passed in config and appends it to
1702 * the DOM. Helps normalize differences in the compilation process between
1703 * using a string template and a content element.
1704 * @returns {!Q.IPromise<!MdPanelRef>} Promise that is resolved when
1705 * the element has been compiled and added to the DOM.
1706 * @private
1707 */
1708MdPanelRef.prototype._compile = function() {
1709 var self = this;
1710
1711 // Compile the element via $mdCompiler. Note that when using a
1712 // contentElement, the element isn't actually being compiled, rather the
1713 // compiler saves it's place in the DOM and provides a way of restoring it.
1714 return self._$mdCompiler.compile(self.config).then(function(compileData) {
1715 var config = self.config;
1716
1717 if (config.contentElement) {
1718 var panelEl = compileData.element;
1719
1720 // Since mdPanel modifies the inline styles and CSS classes, we need
1721 // to save them in order to be able to restore on close.
1722 self._restoreCache.styles = panelEl[0].style.cssText;
1723 self._restoreCache.classes = panelEl[0].className;
1724
1725 self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
1726 self.panelEl = panelEl;
1727 } else {
1728 self.panelContainer = compileData.link(config['scope']);
1729 self.panelEl = angular.element(
1730 self.panelContainer[0].querySelector('.md-panel')
1731 );
1732 }
1733
1734 // Save a reference to the inner wrapper.
1735 self.innerWrapper = angular.element(
1736 self.panelContainer[0].querySelector('.md-panel-inner-wrapper')
1737 );
1738
1739 // Save a reference to the cleanup function from the compiler.
1740 self._compilerCleanup = compileData.cleanup;
1741
1742 // Attach the panel to the proper place in the DOM.
1743 getElement(self.config['attachTo']).append(self.panelContainer);
1744
1745 return self;
1746 });
1747};
1748
1749
1750/**
1751 * Creates a panel and adds it to the dom.
1752 * @returns {!Q.IPromise} A promise that is resolved when the panel is
1753 * created.
1754 * @private
1755 */
1756MdPanelRef.prototype._createPanel = function() {
1757 var self = this;
1758
1759 return this._$q(function(resolve, reject) {
1760 if (!self.config.locals) {
1761 self.config.locals = {};
1762 }
1763
1764 self.config.locals.mdPanelRef = self;
1765
1766 self._compile().then(function() {
1767 if (self.config['disableParentScroll']) {
1768 self._restoreScroll = self._$mdUtil.disableScrollAround(
1769 null,
1770 self.panelContainer,
1771 { disableScrollMask: true }
1772 );
1773 }
1774
1775 // Add a custom CSS class to the panel element.
1776 if (self.config['panelClass']) {
1777 self.panelEl.addClass(self.config['panelClass']);
1778 }
1779
1780 // Handle click and touch events for the panel container.
1781 if (self.config['propagateContainerEvents']) {
1782 self.panelContainer.css('pointer-events', 'none');
1783 self.panelEl.css('pointer-events', 'all');
1784 }
1785
1786 // Panel may be outside the $rootElement, tell ngAnimate to animate
1787 // regardless.
1788 if (self._$animate.pin) {
1789 self._$animate.pin(
1790 self.panelContainer,
1791 getElement(self.config['attachTo'])
1792 );
1793 }
1794
1795 self._configureTrapFocus();
1796 self._addStyles().then(function() {
1797 resolve(self);
1798 }, reject);
1799 }, reject);
1800
1801 });
1802};
1803
1804
1805/**
1806 * Adds the styles for the panel, such as positioning and z-index. Also,
1807 * themes the panel element and panel container using `$mdTheming`.
1808 * @returns {!Q.IPromise<!MdPanelRef>}
1809 * @private
1810 */
1811MdPanelRef.prototype._addStyles = function() {
1812 var self = this;
1813 return this._$q(function(resolve) {
1814 self.panelContainer.css('z-index', self.config['zIndex']);
1815 self.innerWrapper.css('z-index', self.config['zIndex'] + 1);
1816
1817 var hideAndResolve = function() {
1818 // Theme the element and container.
1819 self._setTheming();
1820
1821 // Remove offscreen classes and add hidden class.
1822 self.panelEl.removeClass('_md-panel-offscreen');
1823 self.innerWrapper.removeClass('_md-panel-offscreen');
1824 self.panelContainer.addClass(MD_PANEL_HIDDEN);
1825
1826 resolve(self);
1827 };
1828
1829 if (self.config['fullscreen']) {
1830 self.panelEl.addClass('_md-panel-fullscreen');
1831 hideAndResolve();
1832 return; // Don't setup positioning.
1833 }
1834
1835 var positionConfig = self.config['position'];
1836 if (!positionConfig) {
1837 hideAndResolve();
1838 return; // Don't setup positioning.
1839 }
1840
1841 // Wait for angular to finish processing the template
1842 self._$rootScope['$$postDigest'](function() {
1843 // Position it correctly. This is necessary so that the panel will have a
1844 // defined height and width.
1845 self._updatePosition(true);
1846
1847 // Theme the element and container.
1848 self._setTheming();
1849
1850 resolve(self);
1851 });
1852 });
1853};
1854
1855
1856/**
1857 * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
1858 * @private
1859 */
1860MdPanelRef.prototype._setTheming = function() {
1861 this._$mdTheming(this.panelEl);
1862 this._$mdTheming(this.panelContainer);
1863};
1864
1865
1866/**
1867 * Updates the position configuration of a panel
1868 * @param {!MdPanelPosition} position
1869 */
1870MdPanelRef.prototype.updatePosition = function(position) {
1871 if (!this.panelContainer) {
1872 throw new Error(
1873 'mdPanel: Panel does not exist yet. Call open() or attach().');
1874 }
1875
1876 this.config['position'] = position;
1877 this._updatePosition();
1878};
1879
1880
1881/**
1882 * Calculates and updates the position of the panel.
1883 * @param {boolean=} init
1884 * @private
1885 */
1886MdPanelRef.prototype._updatePosition = function(init) {
1887 var positionConfig = this.config['position'];
1888
1889 if (positionConfig) {
1890 positionConfig._setPanelPosition(this.innerWrapper);
1891
1892 // Hide the panel now that position is known.
1893 if (init) {
1894 this.panelEl.removeClass('_md-panel-offscreen');
1895 this.innerWrapper.removeClass('_md-panel-offscreen');
1896 this.panelContainer.addClass(MD_PANEL_HIDDEN);
1897 }
1898
1899 this.innerWrapper.css(
1900 MdPanelPosition.absPosition.TOP,
1901 positionConfig.getTop()
1902 );
1903 this.innerWrapper.css(
1904 MdPanelPosition.absPosition.BOTTOM,
1905 positionConfig.getBottom()
1906 );
1907 this.innerWrapper.css(
1908 MdPanelPosition.absPosition.LEFT,
1909 positionConfig.getLeft()
1910 );
1911 this.innerWrapper.css(
1912 MdPanelPosition.absPosition.RIGHT,
1913 positionConfig.getRight()
1914 );
1915 }
1916};
1917
1918
1919/**
1920 * Focuses on the panel or the first focus target.
1921 * @private
1922 */
1923MdPanelRef.prototype._focusOnOpen = function() {
1924 if (this.config['focusOnOpen']) {
1925 // Wait for the template to finish rendering to guarantee md-autofocus has
1926 // finished adding the class md-autofocus, otherwise the focusable element
1927 // isn't available to focus.
1928 var self = this;
1929 this._$rootScope['$$postDigest'](function() {
1930 var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
1931 self.panelEl;
1932 target.focus();
1933 });
1934 }
1935};
1936
1937
1938/**
1939 * Shows the backdrop.
1940 * @returns {!Q.IPromise} A promise that is resolved when the backdrop
1941 * is created and attached.
1942 * @private
1943 */
1944MdPanelRef.prototype._createBackdrop = function() {
1945 if (this.config.hasBackdrop) {
1946 if (!this._backdropRef) {
1947 var backdropAnimation = this._$mdPanel.newPanelAnimation()
1948 .openFrom(this.config.attachTo)
1949 .withAnimation({
1950 open: '_md-opaque-enter',
1951 close: '_md-opaque-leave'
1952 });
1953
1954 if (this.config.animation) {
1955 backdropAnimation.duration(this.config.animation._rawDuration);
1956 }
1957
1958 var backdropConfig = {
1959 animation: backdropAnimation,
1960 attachTo: this.config.attachTo,
1961 focusOnOpen: false,
1962 panelClass: '_md-panel-backdrop',
1963 zIndex: this.config.zIndex - 1
1964 };
1965
1966 this._backdropRef = this._$mdPanel.create(backdropConfig);
1967 }
1968 if (!this._backdropRef.isAttached) {
1969 return this._backdropRef.attach();
1970 }
1971 }
1972};
1973
1974
1975/**
1976 * Listen for escape keys and outside clicks to auto close.
1977 * @private
1978 */
1979MdPanelRef.prototype._addEventListeners = function() {
1980 this._configureEscapeToClose();
1981 this._configureClickOutsideToClose();
1982 this._configureScrollListener();
1983};
1984
1985
1986/**
1987 * Remove event listeners added in _addEventListeners.
1988 * @private
1989 */
1990MdPanelRef.prototype._removeEventListeners = function() {
1991 this._removeListeners && this._removeListeners.forEach(function(removeFn) {
1992 removeFn();
1993 });
1994 this._removeListeners = [];
1995};
1996
1997
1998/**
1999 * Setup the escapeToClose event listeners.
2000 * @private
2001 */
2002MdPanelRef.prototype._configureEscapeToClose = function() {
2003 if (this.config['escapeToClose']) {
2004 var parentTarget = getElement(this.config['attachTo']);
2005 var self = this;
2006
2007 var keyHandlerFn = function(ev) {
2008 if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
2009 ev.stopPropagation();
2010 ev.preventDefault();
2011
2012 self.close(MdPanelRef.closeReasons.ESCAPE);
2013 }
2014 };
2015
2016 // Add keydown listeners
2017 this.panelContainer.on('keydown', keyHandlerFn);
2018 parentTarget.on('keydown', keyHandlerFn);
2019
2020 // Queue remove listeners function
2021 this._removeListeners.push(function() {
2022 self.panelContainer.off('keydown', keyHandlerFn);
2023 parentTarget.off('keydown', keyHandlerFn);
2024 });
2025 }
2026};
2027
2028
2029/**
2030 * Setup the clickOutsideToClose event listeners.
2031 * @private
2032 */
2033MdPanelRef.prototype._configureClickOutsideToClose = function() {
2034 if (this.config['clickOutsideToClose']) {
2035 var target = this.config['propagateContainerEvents'] ?
2036 angular.element(document.body) :
2037 this.panelContainer;
2038 var sourceEl;
2039
2040 // Keep track of the element on which the mouse originally went down
2041 // so that we can only close the backdrop when the 'click' started on it.
2042 // A simple 'click' handler does not work, it sets the target object as the
2043 // element the mouse went down on.
2044 var mousedownHandler = function(ev) {
2045 sourceEl = ev.target;
2046 };
2047
2048 // We check if our original element and the target is the backdrop
2049 // because if the original was the backdrop and the target was inside the
2050 // panel we don't want to panel to close.
2051 var self = this;
2052 var mouseupHandler = function(ev) {
2053 if (self.config['propagateContainerEvents']) {
2054
2055 // We check if the sourceEl of the event is the panel element or one
2056 // of it's children. If it is not, then close the panel.
2057 if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) {
2058 self.close();
2059 }
2060
2061 } else if (sourceEl === target[0] && ev.target === target[0]) {
2062 ev.stopPropagation();
2063 ev.preventDefault();
2064
2065 self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
2066 }
2067 };
2068
2069 // Add listeners
2070 target.on('mousedown', mousedownHandler);
2071 target.on('mouseup', mouseupHandler);
2072
2073 // Queue remove listeners function
2074 this._removeListeners.push(function() {
2075 target.off('mousedown', mousedownHandler);
2076 target.off('mouseup', mouseupHandler);
2077 });
2078 }
2079};
2080
2081
2082/**
2083 * Configures the listeners for updating the panel position on scroll.
2084 * @private
2085*/
2086MdPanelRef.prototype._configureScrollListener = function() {
2087 // No need to bind the event if scrolling is disabled.
2088 if (!this.config['disableParentScroll']) {
2089 var updatePosition = angular.bind(this, this._updatePosition);
2090 var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
2091 var self = this;
2092
2093 var onScroll = function() {
2094 debouncedUpdatePosition();
2095 };
2096
2097 // Add listeners.
2098 this._$window.addEventListener('scroll', onScroll, true);
2099
2100 // Queue remove listeners function.
2101 this._removeListeners.push(function() {
2102 self._$window.removeEventListener('scroll', onScroll, true);
2103 });
2104 }
2105};
2106
2107
2108/**
2109 * Setup the focus traps. These traps will wrap focus when tabbing past the
2110 * panel. When shift-tabbing, the focus will stick in place.
2111 * @private
2112 */
2113MdPanelRef.prototype._configureTrapFocus = function() {
2114 // Focus doesn't remain inside of the panel without this.
2115 this.panelEl.attr('tabIndex', '-1');
2116 if (this.config['trapFocus']) {
2117 var element = this.panelEl;
2118 // Set up elements before and after the panel to capture focus and
2119 // redirect back into the panel.
2120 this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
2121 this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
2122
2123 // When focus is about to move out of the panel, we want to intercept it
2124 // and redirect it back to the panel element.
2125 var focusHandler = function() {
2126 element.focus();
2127 };
2128 this._topFocusTrap.addEventListener('focus', focusHandler);
2129 this._bottomFocusTrap.addEventListener('focus', focusHandler);
2130
2131 // Queue remove listeners function
2132 this._removeListeners.push(this._simpleBind(function() {
2133 this._topFocusTrap.removeEventListener('focus', focusHandler);
2134 this._bottomFocusTrap.removeEventListener('focus', focusHandler);
2135 }, this));
2136
2137 // The top focus trap inserted immediately before the md-panel element (as
2138 // a sibling). The bottom focus trap inserted immediately after the
2139 // md-panel element (as a sibling).
2140 element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
2141 element.after(this._bottomFocusTrap);
2142 }
2143};
2144
2145
2146/**
2147 * Updates the animation of a panel.
2148 * @param {!MdPanelAnimation} animation
2149 */
2150MdPanelRef.prototype.updateAnimation = function(animation) {
2151 this.config['animation'] = animation;
2152
2153 if (this._backdropRef) {
2154 this._backdropRef.config.animation.duration(animation._rawDuration);
2155 }
2156};
2157
2158
2159/**
2160 * Animate the panel opening.
2161 * @returns {!Q.IPromise} A promise that is resolved when the panel has
2162 * animated open.
2163 * @private
2164 */
2165MdPanelRef.prototype._animateOpen = function() {
2166 this.panelContainer.addClass('md-panel-is-showing');
2167 var animationConfig = this.config['animation'];
2168 if (!animationConfig) {
2169 // Promise is in progress, return it.
2170 this.panelContainer.addClass('_md-panel-shown');
2171 return this._$q.when(this);
2172 }
2173
2174 var self = this;
2175 return this._$q(function(resolve) {
2176 var done = self._done(resolve, self);
2177 var warnAndOpen = function() {
2178 self._$log.warn(
2179 'mdPanel: MdPanel Animations failed. ' +
2180 'Showing panel without animating.');
2181 done();
2182 };
2183
2184 animationConfig.animateOpen(self.panelEl)
2185 .then(done, warnAndOpen);
2186 });
2187};
2188
2189
2190/**
2191 * Animate the panel closing.
2192 * @returns {!Q.IPromise} A promise that is resolved when the panel has animated closed.
2193 * @private
2194 */
2195MdPanelRef.prototype._animateClose = function() {
2196 var self = this;
2197 var animationConfig = this.config['animation'];
2198
2199 if (!animationConfig) {
2200 this.panelContainer.removeClass('md-panel-is-showing');
2201 this.panelContainer.removeClass('_md-panel-shown');
2202 return this._$q.when(this);
2203 } else {
2204 return this._$q(function (resolve) {
2205 var done = function () {
2206 self.panelContainer.removeClass('md-panel-is-showing');
2207 // Remove the transform so that re-used panels don't accumulate transforms.
2208 self.panelEl.css('transform', '');
2209 resolve(self);
2210 };
2211 var warnAndClose = function () {
2212 self._$log.warn(
2213 'mdPanel: MdPanel Animations failed. Hiding panel without animating.');
2214 done();
2215 };
2216
2217 animationConfig.animateClose(self.panelEl).then(done, warnAndClose);
2218 });
2219 }
2220};
2221
2222
2223/**
2224 * Registers a interceptor with the panel. The callback should return a promise,
2225 * which will allow the action to continue when it gets resolved, or will
2226 * prevent an action if it is rejected.
2227 * @param {string} type Type of interceptor.
2228 * @param {!Q.IPromise<!any>} callback Callback to be registered.
2229 * @returns {!MdPanelRef}
2230 */
2231MdPanelRef.prototype.registerInterceptor = function(type, callback) {
2232 var error = null;
2233
2234 if (!angular.isString(type)) {
2235 error = 'Interceptor type must be a string, instead got ' + typeof type;
2236 } else if (!angular.isFunction(callback)) {
2237 error = 'Interceptor callback must be a function, instead got ' + typeof callback;
2238 }
2239
2240 if (error) {
2241 throw new Error('MdPanel: ' + error);
2242 }
2243
2244 var interceptors = this._interceptors[type] = this._interceptors[type] || [];
2245
2246 if (interceptors.indexOf(callback) === -1) {
2247 interceptors.push(callback);
2248 }
2249
2250 return this;
2251};
2252
2253
2254/**
2255 * Removes a registered interceptor.
2256 * @param {string} type Type of interceptor to be removed.
2257 * @param {Function} callback Interceptor to be removed.
2258 * @returns {!MdPanelRef}
2259 */
2260MdPanelRef.prototype.removeInterceptor = function(type, callback) {
2261 var index = this._interceptors[type] ?
2262 this._interceptors[type].indexOf(callback) : -1;
2263
2264 if (index > -1) {
2265 this._interceptors[type].splice(index, 1);
2266 }
2267
2268 return this;
2269};
2270
2271
2272/**
2273 * Removes all interceptors.
2274 * @param {string=} type Type of interceptors to be removed.
2275 * If ommited, all interceptors types will be removed.
2276 * @returns {!MdPanelRef}
2277 */
2278MdPanelRef.prototype.removeAllInterceptors = function(type) {
2279 if (type) {
2280 this._interceptors[type] = [];
2281 } else {
2282 this._interceptors = Object.create(null);
2283 }
2284
2285 return this;
2286};
2287
2288
2289/**
2290 * Invokes all the interceptors of a certain type sequantially in
2291 * reverse order. Works in a similar way to `$q.all`, except it
2292 * respects the order of the functions.
2293 * @param {string} type Type of interceptors to be invoked.
2294 * @returns {!Q.IPromise<!MdPanelRef>}
2295 * @private
2296 */
2297MdPanelRef.prototype._callInterceptors = function(type) {
2298 var self = this;
2299 var $q = self._$q;
2300 var interceptors = self._interceptors && self._interceptors[type] || [];
2301
2302 return interceptors.reduceRight(function(promise, interceptor) {
2303 var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
2304 var response = isPromiseLike ? interceptor : null;
2305
2306 /**
2307 * For interceptors to reject/cancel subsequent portions of the chain, simply
2308 * return a `$q.reject(<value>)`
2309 */
2310 return promise.then(function() {
2311 if (!response) {
2312 try {
2313 response = interceptor(self);
2314 } catch (e) {
2315 response = $q.reject(e);
2316 }
2317 }
2318
2319 return response;
2320 });
2321 }, $q.resolve(self));
2322};
2323
2324
2325/**
2326 * Faster, more basic than angular.bind
2327 * http://jsperf.com/angular-bind-vs-custom-vs-native
2328 * @param {function} callback
2329 * @param {!Object} self
2330 * @return {function} Callback function with a bound self.
2331 */
2332MdPanelRef.prototype._simpleBind = function(callback, self) {
2333 return function(value) {
2334 return callback.apply(self, value);
2335 };
2336};
2337
2338
2339/**
2340 * @param {function|IQResolveReject} callback
2341 * @param {!Object} self
2342 * @return {function} Callback function with a self param.
2343 */
2344MdPanelRef.prototype._done = function(callback, self) {
2345 return function() {
2346 callback(self);
2347 };
2348};
2349
2350
2351/**
2352 * Adds a panel to a group if the panel does not exist within the group already.
2353 * A panel can only exist within a single group.
2354 * @param {string} groupName The name of the group.
2355 */
2356MdPanelRef.prototype.addToGroup = function(groupName) {
2357 if (!this._$mdPanel._groups[groupName]) {
2358 this._$mdPanel.newPanelGroup(groupName);
2359 }
2360
2361 var group = this._$mdPanel._groups[groupName];
2362 var index = group.panels.indexOf(this);
2363
2364 if (index < 0) {
2365 group.panels.push(this);
2366 }
2367};
2368
2369
2370/**
2371 * Removes a panel from a group if the panel exists within that group. The group
2372 * must be created ahead of time.
2373 * @param {string} groupName The name of the group.
2374 */
2375MdPanelRef.prototype.removeFromGroup = function(groupName) {
2376 if (!this._$mdPanel._groups[groupName]) {
2377 throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
2378 }
2379
2380 var group = this._$mdPanel._groups[groupName];
2381 var index = group.panels.indexOf(this);
2382
2383 if (index > -1) {
2384 group.panels.splice(index, 1);
2385 }
2386};
2387
2388
2389/**
2390 * Possible default closeReasons for the close function.
2391 * @enum {string}
2392 */
2393MdPanelRef.closeReasons = {
2394 CLICK_OUTSIDE: 'clickOutsideToClose',
2395 ESCAPE: 'escapeToClose',
2396};
2397
2398
2399/*****************************************************************************
2400 * MdPanelPosition *
2401 *****************************************************************************/
2402
2403
2404/**
2405 * Position configuration object. To use, create an MdPanelPosition with the
2406 * desired properties, then pass the object as part of $mdPanel creation.
2407 *
2408 * Example:
2409 *
2410 * var panelPosition = new MdPanelPosition()
2411 * .relativeTo(myButtonEl)
2412 * .addPanelPosition(
2413 * $mdPanel.xPosition.CENTER,
2414 * $mdPanel.yPosition.ALIGN_TOPS
2415 * );
2416 *
2417 * $mdPanel.create({
2418 * position: panelPosition
2419 * });
2420 *
2421 * @param {!IInjectorService} $injector
2422 * @final @constructor
2423 */
2424function MdPanelPosition($injector) {
2425 /** @private @const {!IWindowService} */
2426 this._$window = $injector.get('$window');
2427
2428 /** @private {boolean} */
2429 this._isRTL = $injector.get('$mdUtil').isRtl();
2430
2431 /** @private @const {!angular.$mdConstant} */
2432 this._$mdConstant = $injector.get('$mdConstant');
2433
2434 /** @private {boolean} */
2435 this._absolute = false;
2436
2437 /** @private {!JQLite} */
2438 this._relativeToEl = undefined;
2439
2440 /** @private {string} */
2441 this._top = '';
2442
2443 /** @private {string} */
2444 this._bottom = '';
2445
2446 /** @private {string} */
2447 this._left = '';
2448
2449 /** @private {string} */
2450 this._right = '';
2451
2452 /** @private {!Array<string>} */
2453 this._translateX = [];
2454
2455 /** @private {!Array<string>} */
2456 this._translateY = [];
2457
2458 /** @private {!Array<{x:string, y:string}>} */
2459 this._positions = [];
2460
2461 /** @private {?{x:string, y:string}} */
2462 this._actualPosition = undefined;
2463}
2464
2465
2466/**
2467 * Possible values of xPosition.
2468 * @enum {string}
2469 */
2470MdPanelPosition.xPosition = {
2471 CENTER: 'center',
2472 ALIGN_START: 'align-start',
2473 ALIGN_END: 'align-end',
2474 OFFSET_START: 'offset-start',
2475 OFFSET_END: 'offset-end'
2476};
2477
2478
2479/**
2480 * Possible values of yPosition.
2481 * @enum {string}
2482 */
2483MdPanelPosition.yPosition = {
2484 CENTER: 'center',
2485 ALIGN_TOPS: 'align-tops',
2486 ALIGN_BOTTOMS: 'align-bottoms',
2487 ABOVE: 'above',
2488 BELOW: 'below'
2489};
2490
2491
2492/**
2493 * Possible values of absolute position.
2494 * @enum {string}
2495 */
2496MdPanelPosition.absPosition = {
2497 TOP: 'top',
2498 RIGHT: 'right',
2499 BOTTOM: 'bottom',
2500 LEFT: 'left'
2501};
2502
2503/**
2504 * Margin between the edges of a panel and the viewport.
2505 * @const {number}
2506 */
2507MdPanelPosition.viewportMargin = 8;
2508
2509
2510/**
2511 * Sets absolute positioning for the panel.
2512 * @return {!MdPanelPosition}
2513 */
2514MdPanelPosition.prototype.absolute = function() {
2515 this._absolute = true;
2516 return this;
2517};
2518
2519
2520/**
2521 * Sets the value of a position for the panel. Clears any previously set
2522 * position.
2523 * @param {string} position Position to set
2524 * @param {string=} value Value of the position. Defaults to '0'.
2525 * @returns {!MdPanelPosition}
2526 * @private
2527 */
2528MdPanelPosition.prototype._setPosition = function(position, value) {
2529 if (position === MdPanelPosition.absPosition.RIGHT ||
2530 position === MdPanelPosition.absPosition.LEFT) {
2531 this._left = this._right = '';
2532 } else if (
2533 position === MdPanelPosition.absPosition.BOTTOM ||
2534 position === MdPanelPosition.absPosition.TOP) {
2535 this._top = this._bottom = '';
2536 } else {
2537 var positions = Object.keys(MdPanelPosition.absPosition).join()
2538 .toLowerCase();
2539
2540 throw new Error('mdPanel: Position must be one of ' + positions + '.');
2541 }
2542
2543 this['_' + position] = angular.isString(value) ? value : '0';
2544
2545 return this;
2546};
2547
2548
2549/**
2550 * Sets the value of `top` for the panel. Clears any previously set vertical
2551 * position.
2552 * @param {string=} top Value of `top`. Defaults to '0'.
2553 * @returns {!MdPanelPosition}
2554 */
2555MdPanelPosition.prototype.top = function(top) {
2556 return this._setPosition(MdPanelPosition.absPosition.TOP, top);
2557};
2558
2559
2560/**
2561 * Sets the value of `bottom` for the panel. Clears any previously set vertical
2562 * position.
2563 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
2564 * @returns {!MdPanelPosition}
2565 */
2566MdPanelPosition.prototype.bottom = function(bottom) {
2567 return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
2568};
2569
2570
2571/**
2572 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
2573 * `rtl`. Clears any previously set horizontal position.
2574 * @param {string=} start Value of position. Defaults to '0'.
2575 * @returns {!MdPanelPosition}
2576 */
2577MdPanelPosition.prototype.start = function(start) {
2578 var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
2579 return this._setPosition(position, start);
2580};
2581
2582
2583/**
2584 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
2585 * Clears any previously set horizontal position.
2586 * @param {string=} end Value of position. Defaults to '0'.
2587 * @returns {!MdPanelPosition}
2588 */
2589MdPanelPosition.prototype.end = function(end) {
2590 var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
2591 return this._setPosition(position, end);
2592};
2593
2594
2595/**
2596 * Sets the value of `left` for the panel. Clears any previously set
2597 * horizontal position.
2598 * @param {string=} left Value of `left`. Defaults to '0'.
2599 * @returns {!MdPanelPosition}
2600 */
2601MdPanelPosition.prototype.left = function(left) {
2602 return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
2603};
2604
2605
2606/**
2607 * Sets the value of `right` for the panel. Clears any previously set
2608 * horizontal position.
2609 * @param {string=} right Value of `right`. Defaults to '0'.
2610 * @returns {!MdPanelPosition}
2611*/
2612MdPanelPosition.prototype.right = function(right) {
2613 return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
2614};
2615
2616
2617/**
2618 * Centers the panel horizontally in the viewport. Clears any previously set
2619 * horizontal position.
2620 * @returns {!MdPanelPosition}
2621 */
2622MdPanelPosition.prototype.centerHorizontally = function() {
2623 this._left = '50%';
2624 this._right = '';
2625 this._translateX = ['-50%'];
2626 return this;
2627};
2628
2629
2630/**
2631 * Centers the panel vertically in the viewport. Clears any previously set
2632 * vertical position.
2633 * @returns {!MdPanelPosition}
2634 */
2635MdPanelPosition.prototype.centerVertically = function() {
2636 this._top = '50%';
2637 this._bottom = '';
2638 this._translateY = ['-50%'];
2639 return this;
2640};
2641
2642
2643/**
2644 * Centers the panel horizontally and vertically in the viewport. This is
2645 * equivalent to calling both `centerHorizontally` and `centerVertically`.
2646 * Clears any previously set horizontal and vertical positions.
2647 * @returns {!MdPanelPosition}
2648 */
2649MdPanelPosition.prototype.center = function() {
2650 return this.centerHorizontally().centerVertically();
2651};
2652
2653
2654/**
2655 * Sets element for relative positioning.
2656 * @param {string|!Element|!JQLite} element Query selector, DOM element,
2657 * or angular element to set the panel relative to.
2658 * @returns {!MdPanelPosition}
2659 */
2660MdPanelPosition.prototype.relativeTo = function(element) {
2661 this._absolute = false;
2662 this._relativeToEl = getElement(element);
2663 return this;
2664};
2665
2666
2667/**
2668 * Sets the x and y positions for the panel relative to another element.
2669 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
2670 * values.
2671 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
2672 * values.
2673 * @returns {!MdPanelPosition}
2674 */
2675MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
2676 if (!this._relativeToEl) {
2677 throw new Error('mdPanel: addPanelPosition can only be used with ' +
2678 'relative positioning. Set relativeTo first.');
2679 }
2680
2681 validatePosition(MdPanelPosition.xPosition, xPosition);
2682 validatePosition(MdPanelPosition.yPosition, yPosition);
2683
2684 this._positions.push({
2685 x: xPosition,
2686 y: yPosition
2687 });
2688
2689 return this;
2690};
2691
2692
2693/**
2694 * Sets the value of the offset in the x-direction. This will add to any
2695 * previously set offsets.
2696 * @param {string|number|function(MdPanelPosition): string} offsetX
2697 * @returns {!MdPanelPosition}
2698 */
2699MdPanelPosition.prototype.withOffsetX = function(offsetX) {
2700 this._translateX.push(addUnits(offsetX));
2701 return this;
2702};
2703
2704
2705/**
2706 * Sets the value of the offset in the y-direction. This will add to any
2707 * previously set offsets.
2708 * @param {string|number|function(MdPanelPosition): string} offsetY
2709 * @returns {!MdPanelPosition}
2710 */
2711MdPanelPosition.prototype.withOffsetY = function(offsetY) {
2712 this._translateY.push(addUnits(offsetY));
2713 return this;
2714};
2715
2716
2717/**
2718 * Gets the value of `top` for the panel.
2719 * @returns {string}
2720 */
2721MdPanelPosition.prototype.getTop = function() {
2722 return this._top;
2723};
2724
2725
2726/**
2727 * Gets the value of `bottom` for the panel.
2728 * @returns {string}
2729 */
2730MdPanelPosition.prototype.getBottom = function() {
2731 return this._bottom;
2732};
2733
2734
2735/**
2736 * Gets the value of `left` for the panel.
2737 * @returns {string}
2738 */
2739MdPanelPosition.prototype.getLeft = function() {
2740 return this._left;
2741};
2742
2743
2744/**
2745 * Gets the value of `right` for the panel.
2746 * @returns {string}
2747 */
2748MdPanelPosition.prototype.getRight = function() {
2749 return this._right;
2750};
2751
2752
2753/**
2754 * Gets the value of `transform` for the panel.
2755 * @returns {string} representation of the translateX and translateY rules and values
2756 */
2757MdPanelPosition.prototype.getTransform = function() {
2758 var translateX = this._reduceTranslateValues('translateX', this._translateX);
2759 var translateY = this._reduceTranslateValues('translateY', this._translateY);
2760
2761 // It's important to trim the result, because the browser will ignore the set
2762 // operation if the string contains only whitespace.
2763 return (translateX + ' ' + translateY).trim();
2764};
2765
2766
2767/**
2768 * Sets the `transform` value for an element.
2769 * @param {!JQLite} el
2770 * @returns {!JQLite}
2771 * @private
2772 */
2773MdPanelPosition.prototype._setTransform = function(el) {
2774 return el.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
2775};
2776
2777
2778/**
2779 * True if the panel is completely on-screen with this positioning; false
2780 * otherwise.
2781 * @param {!JQLite} el
2782 * @return {boolean}
2783 * @private
2784 */
2785MdPanelPosition.prototype._isOnscreen = function(el) {
2786 // this works because we always use fixed positioning for the panel,
2787 // which is relative to the viewport.
2788 var left = parseInt(this.getLeft());
2789 var top = parseInt(this.getTop());
2790
2791 if (this._translateX.length || this._translateY.length) {
2792 var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
2793 var offsets = getComputedTranslations(el, prefixedTransform);
2794 left += offsets.x;
2795 top += offsets.y;
2796 }
2797
2798 var right = left + el[0].offsetWidth;
2799 var bottom = top + el[0].offsetHeight;
2800
2801 return (left >= 0) &&
2802 (top >= 0) &&
2803 (bottom <= this._$window.innerHeight) &&
2804 (right <= this._$window.innerWidth);
2805};
2806
2807
2808/**
2809 * Gets the first x/y position that can fit on-screen.
2810 * @returns {{x: string, y: string}}
2811 */
2812MdPanelPosition.prototype.getActualPosition = function() {
2813 return this._actualPosition;
2814};
2815
2816
2817/**
2818 * Reduces a list of translate values to a string that can be used within
2819 * transform.
2820 * @param {string} translateFn
2821 * @param {!Array<string>} values
2822 * @returns {string}
2823 * @private
2824 */
2825MdPanelPosition.prototype._reduceTranslateValues =
2826 function(translateFn, values) {
2827 return values.map(function(translation) {
2828 var translationValue = angular.isFunction(translation) ?
2829 addUnits(translation(this)) : translation;
2830 return translateFn + '(' + translationValue + ')';
2831 }, this).join(' ');
2832 };
2833
2834
2835/**
2836 * Sets the panel position based on the created panel element and best x/y
2837 * positioning.
2838 * @param {!JQLite} el
2839 * @private
2840 */
2841MdPanelPosition.prototype._setPanelPosition = function(el) {
2842 // Remove the class in case it has been added before.
2843 el.removeClass('_md-panel-position-adjusted');
2844
2845 // Only calculate the position if necessary.
2846 if (this._absolute) {
2847 this._setTransform(el);
2848 return;
2849 }
2850
2851 if (this._actualPosition) {
2852 this._calculatePanelPosition(el, this._actualPosition);
2853 this._setTransform(el);
2854 this._constrainToViewport(el);
2855 return;
2856 }
2857
2858 for (var i = 0; i < this._positions.length; i++) {
2859 this._actualPosition = this._positions[i];
2860 this._calculatePanelPosition(el, this._actualPosition);
2861 this._setTransform(el);
2862
2863 if (this._isOnscreen(el)) {
2864 return;
2865 }
2866 }
2867
2868 this._constrainToViewport(el);
2869};
2870
2871
2872/**
2873 * Constrains a panel's position to the viewport.
2874 * @param {!JQLite} el
2875 * @private
2876 */
2877MdPanelPosition.prototype._constrainToViewport = function(el) {
2878 var margin = MdPanelPosition.viewportMargin;
2879 var initialTop = this._top;
2880 var initialLeft = this._left;
2881
2882 if (this.getTop()) {
2883 var top = parseInt(this.getTop());
2884 var bottom = el[0].offsetHeight + top;
2885 var viewportHeight = this._$window.innerHeight;
2886
2887 if (top < margin) {
2888 this._top = margin + 'px';
2889 } else if (bottom > viewportHeight) {
2890 this._top = top - (bottom - viewportHeight + margin) + 'px';
2891 }
2892 }
2893
2894 if (this.getLeft()) {
2895 var left = parseInt(this.getLeft());
2896 var right = el[0].offsetWidth + left;
2897 var viewportWidth = this._$window.innerWidth;
2898
2899 if (left < margin) {
2900 this._left = margin + 'px';
2901 } else if (right > viewportWidth) {
2902 this._left = left - (right - viewportWidth + margin) + 'px';
2903 }
2904 }
2905
2906 // Class that can be used to re-style the panel if it was repositioned.
2907 el.toggleClass(
2908 '_md-panel-position-adjusted',
2909 this._top !== initialTop || this._left !== initialLeft
2910 );
2911};
2912
2913
2914/**
2915 * Switches between 'start' and 'end'.
2916 * @param {string} position Horizontal position of the panel
2917 * @returns {string} Reversed position
2918 * @private
2919 */
2920MdPanelPosition.prototype._reverseXPosition = function(position) {
2921 if (position === MdPanelPosition.xPosition.CENTER) {
2922 return position;
2923 }
2924
2925 var start = 'start';
2926 var end = 'end';
2927
2928 return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
2929};
2930
2931
2932/**
2933 * Handles horizontal positioning in rtl or ltr environments.
2934 * @param {string} position Horizontal position of the panel
2935 * @returns {string} The correct position according the page direction
2936 * @private
2937 */
2938MdPanelPosition.prototype._bidi = function(position) {
2939 return this._isRTL ? this._reverseXPosition(position) : position;
2940};
2941
2942
2943/**
2944 * Calculates the panel position based on the created panel element and the
2945 * provided positioning.
2946 * @param {!JQLite} el
2947 * @param {!{x:string, y:string}} position
2948 * @private
2949 */
2950MdPanelPosition.prototype._calculatePanelPosition = function(el, position) {
2951
2952 var panelBounds = el[0].getBoundingClientRect();
2953 var panelWidth = Math.max(panelBounds.width, el[0].clientWidth);
2954 var panelHeight = Math.max(panelBounds.height, el[0].clientHeight);
2955
2956 var targetBounds = this._relativeToEl[0].getBoundingClientRect();
2957
2958 var targetLeft = targetBounds.left;
2959 var targetRight = targetBounds.right;
2960 var targetWidth = targetBounds.width;
2961
2962 switch (this._bidi(position.x)) {
2963 case MdPanelPosition.xPosition.OFFSET_START:
2964 this._left = targetLeft - panelWidth + 'px';
2965 break;
2966 case MdPanelPosition.xPosition.ALIGN_END:
2967 this._left = targetRight - panelWidth + 'px';
2968 break;
2969 case MdPanelPosition.xPosition.CENTER:
2970 var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
2971 this._left = left + 'px';
2972 break;
2973 case MdPanelPosition.xPosition.ALIGN_START:
2974 this._left = targetLeft + 'px';
2975 break;
2976 case MdPanelPosition.xPosition.OFFSET_END:
2977 this._left = targetRight + 'px';
2978 break;
2979 }
2980
2981 var targetTop = targetBounds.top;
2982 var targetBottom = targetBounds.bottom;
2983 var targetHeight = targetBounds.height;
2984
2985 switch (position.y) {
2986 case MdPanelPosition.yPosition.ABOVE:
2987 this._top = targetTop - panelHeight + 'px';
2988 break;
2989 case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
2990 this._top = targetBottom - panelHeight + 'px';
2991 break;
2992 case MdPanelPosition.yPosition.CENTER:
2993 var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
2994 this._top = top + 'px';
2995 break;
2996 case MdPanelPosition.yPosition.ALIGN_TOPS:
2997 this._top = targetTop + 'px';
2998 break;
2999 case MdPanelPosition.yPosition.BELOW:
3000 this._top = targetBottom + 'px';
3001 break;
3002 }
3003};
3004
3005
3006/*****************************************************************************
3007 * MdPanelAnimation *
3008 *****************************************************************************/
3009
3010
3011/**
3012 * Animation configuration object. To use, create an MdPanelAnimation with the
3013 * desired properties, then pass the object as part of $mdPanel creation.
3014 *
3015 * Example:
3016 *
3017 * var panelAnimation = new MdPanelAnimation()
3018 * .openFrom(myButtonEl)
3019 * .closeTo('.my-button')
3020 * .withAnimation($mdPanel.animation.SCALE);
3021 *
3022 * $mdPanel.create({
3023 * animation: panelAnimation
3024 * });
3025 *
3026 * @param {!IInjectorService} $injector
3027 * @final @constructor
3028 */
3029function MdPanelAnimation($injector) {
3030 /** @private @const {!angular.$mdUtil} */
3031 this._$mdUtil = $injector.get('$mdUtil');
3032
3033 /**
3034 * @private {{element: !JQLite|undefined, bounds: !DOMRect}|
3035 * undefined}
3036 */
3037 this._openFrom;
3038
3039 /**
3040 * @private {{element: !JQLite|undefined, bounds: !DOMRect}|
3041 * undefined}
3042 */
3043 this._closeTo;
3044
3045 /** @private {string|{open: string, close: string}} */
3046 this._animationClass = '';
3047
3048 /** @private {number} */
3049 this._openDuration;
3050
3051 /** @private {number} */
3052 this._closeDuration;
3053
3054 /** @private {number|{open: number, close: number}} */
3055 this._rawDuration;
3056}
3057
3058
3059/**
3060 * Possible default animations.
3061 * @enum {string}
3062 */
3063MdPanelAnimation.animation = {
3064 SLIDE: 'md-panel-animate-slide',
3065 SCALE: 'md-panel-animate-scale',
3066 FADE: 'md-panel-animate-fade'
3067};
3068
3069
3070/**
3071 * Specifies where to start the open animation. `openFrom` accepts a
3072 * click event object, query selector, DOM element, or a Rect object that
3073 * is used to determine the bounds. When passed a click event, the location
3074 * of the click will be used as the position to start the animation.
3075 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
3076 * @returns {!MdPanelAnimation}
3077 */
3078MdPanelAnimation.prototype.openFrom = function(openFrom) {
3079 // Check if 'openFrom' is an Event.
3080 openFrom = openFrom.target ? openFrom.target : openFrom;
3081
3082 this._openFrom = this._getPanelAnimationTarget(openFrom);
3083
3084 if (!this._closeTo) {
3085 this._closeTo = this._openFrom;
3086 }
3087 return this;
3088};
3089
3090
3091/**
3092 * Specifies where to animate the panel close. `closeTo` accepts a
3093 * query selector, DOM element, or a Rect object that is used to determine
3094 * the bounds.
3095 * @param {string|!Element|{top: number, left: number}} closeTo
3096 * @returns {!MdPanelAnimation}
3097 */
3098MdPanelAnimation.prototype.closeTo = function(closeTo) {
3099 this._closeTo = this._getPanelAnimationTarget(closeTo);
3100 return this;
3101};
3102
3103
3104/**
3105 * Specifies the duration of the animation in milliseconds.
3106 * @param {number|{open: number, close: number}} duration
3107 * @returns {!MdPanelAnimation}
3108 */
3109MdPanelAnimation.prototype.duration = function(duration) {
3110 if (duration) {
3111 if (angular.isNumber(duration)) {
3112 this._openDuration = this._closeDuration = toSeconds(duration);
3113 } else if (angular.isObject(duration)) {
3114 this._openDuration = toSeconds(duration.open);
3115 this._closeDuration = toSeconds(duration.close);
3116 }
3117 }
3118
3119 // Save the original value so it can be passed to the backdrop.
3120 this._rawDuration = duration;
3121
3122 return this;
3123
3124 function toSeconds(value) {
3125 if (angular.isNumber(value)) return value / 1000;
3126 }
3127};
3128
3129
3130/**
3131 * Returns the element and bounds for the animation target.
3132 * @param {string|!Element|{top: number, left: number}} location
3133 * @returns {{element: !JQLite|undefined, bounds: !DOMRect}}
3134 * @private
3135 */
3136MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
3137 if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
3138 return {
3139 element: undefined,
3140 bounds: {
3141 top: location.top || 0,
3142 left: location.left || 0
3143 }
3144 };
3145 } else {
3146 return this._getBoundingClientRect(getElement(location));
3147 }
3148};
3149
3150
3151/**
3152 * Specifies the animation class.
3153 *
3154 * There are several default animations that can be used:
3155 * (MdPanelAnimation.animation)
3156 * SLIDE: The panel slides in and out from the specified
3157 * elements.
3158 * SCALE: The panel scales in and out.
3159 * FADE: The panel fades in and out.
3160 *
3161 * @param {string|{open: string, close: string}} cssClass
3162 * @returns {!MdPanelAnimation}
3163 */
3164MdPanelAnimation.prototype.withAnimation = function(cssClass) {
3165 this._animationClass = cssClass;
3166 return this;
3167};
3168
3169
3170/**
3171 * Animate the panel open.
3172 * @param {!JQLite} panelEl
3173 * @returns {!Q.IPromise} A promise that is resolved when the open
3174 * animation is complete.
3175 */
3176MdPanelAnimation.prototype.animateOpen = function(panelEl) {
3177 var animator = this._$mdUtil.dom.animator;
3178
3179 this._fixBounds(panelEl);
3180 var animationOptions = {};
3181
3182 // Include the panel transformations when calculating the animations.
3183 var panelTransform = panelEl[0].style.transform || '';
3184
3185 var openFrom = animator.toTransformCss(panelTransform);
3186 var openTo = animator.toTransformCss(panelTransform);
3187
3188 switch (this._animationClass) {
3189 case MdPanelAnimation.animation.SLIDE:
3190 // Slide should start with opacity: 1.
3191 panelEl.css('opacity', '1');
3192
3193 animationOptions = {
3194 transitionInClass: '_md-panel-animate-enter',
3195 transitionOutClass: '_md-panel-animate-leave',
3196 };
3197
3198 var openSlide = animator.calculateSlideToOrigin(
3199 panelEl, this._openFrom) || '';
3200 openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
3201 break;
3202
3203 case MdPanelAnimation.animation.SCALE:
3204 animationOptions = {
3205 transitionInClass: '_md-panel-animate-enter'
3206 };
3207
3208 var openScale = animator.calculateZoomToOrigin(
3209 panelEl, this._openFrom) || '';
3210 openFrom = animator.toTransformCss(panelTransform + ' ' + openScale);
3211 break;
3212
3213 case MdPanelAnimation.animation.FADE:
3214 animationOptions = {
3215 transitionInClass: '_md-panel-animate-enter'
3216 };
3217 break;
3218
3219 default:
3220 if (angular.isString(this._animationClass)) {
3221 animationOptions = {
3222 transitionInClass: this._animationClass
3223 };
3224 } else {
3225 animationOptions = {
3226 transitionInClass: this._animationClass['open'],
3227 transitionOutClass: this._animationClass['close'],
3228 };
3229 }
3230 }
3231
3232 animationOptions.duration = this._openDuration;
3233
3234 return animator
3235 .translate3d(panelEl, openFrom, openTo, animationOptions);
3236};
3237
3238
3239/**
3240 * Animate the panel close.
3241 * @param {!JQLite} panelEl
3242 * @returns {!Q.IPromise} A promise that resolves when the close animation is complete.
3243 */
3244MdPanelAnimation.prototype.animateClose = function(panelEl) {
3245 var animator = this._$mdUtil.dom.animator;
3246 var reverseAnimationOptions = {};
3247
3248 // Include the panel transformations when calculating the animations.
3249 var panelTransform = panelEl[0].style.transform || '';
3250
3251 var closeFrom = animator.toTransformCss(panelTransform);
3252 var closeTo = animator.toTransformCss(panelTransform);
3253
3254 switch (this._animationClass) {
3255 case MdPanelAnimation.animation.SLIDE:
3256 // Slide should start with opacity: 1.
3257 panelEl.css('opacity', '1');
3258 reverseAnimationOptions = {
3259 transitionInClass: '_md-panel-animate-leave',
3260 transitionOutClass: '_md-panel-animate-enter _md-panel-animate-leave'
3261 };
3262
3263 var closeSlide = animator.calculateSlideToOrigin(panelEl, this._closeTo) || '';
3264 closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
3265 break;
3266
3267 case MdPanelAnimation.animation.SCALE:
3268 reverseAnimationOptions = {
3269 transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave',
3270 transitionOutClass: '_md-panel-animate-scale-out _md-panel-animate-enter _md-panel-animate-leave'
3271 };
3272
3273 var closeScale = animator.calculateZoomToOrigin(panelEl, this._closeTo) || '';
3274 closeTo = animator.toTransformCss(panelTransform + ' ' + closeScale);
3275 break;
3276
3277 case MdPanelAnimation.animation.FADE:
3278 reverseAnimationOptions = {
3279 transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave',
3280 transitionOutClass: '_md-panel-animate-fade-out _md-panel-animate-enter _md-panel-animate-leave'
3281 };
3282 break;
3283
3284 default:
3285 if (angular.isString(this._animationClass)) {
3286 reverseAnimationOptions = {
3287 transitionOutClass: this._animationClass
3288 };
3289 } else {
3290 reverseAnimationOptions = {
3291 transitionInClass: this._animationClass['close'],
3292 transitionOutClass: this._animationClass['open']
3293 };
3294 }
3295 }
3296
3297 reverseAnimationOptions.duration = this._closeDuration;
3298
3299 return animator
3300 .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
3301};
3302
3303
3304/**
3305 * Set the height and width to match the panel if not provided.
3306 * @param {!JQLite} panelEl
3307 * @private
3308 */
3309MdPanelAnimation.prototype._fixBounds = function(panelEl) {
3310 var panelWidth = panelEl[0].offsetWidth;
3311 var panelHeight = panelEl[0].offsetHeight;
3312
3313 if (this._openFrom && this._openFrom.bounds.height == null) {
3314 this._openFrom.bounds.height = panelHeight;
3315 }
3316 if (this._openFrom && this._openFrom.bounds.width == null) {
3317 this._openFrom.bounds.width = panelWidth;
3318 }
3319 if (this._closeTo && this._closeTo.bounds.height == null) {
3320 this._closeTo.bounds.height = panelHeight;
3321 }
3322 if (this._closeTo && this._closeTo.bounds.width == null) {
3323 this._closeTo.bounds.width = panelWidth;
3324 }
3325};
3326
3327
3328/**
3329 * Identify the bounding RECT for the target element.
3330 * @param {!JQLite} element
3331 * @returns {{element: !JQLite|undefined, bounds: !DOMRect}}
3332 * @private
3333 */
3334MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
3335 if (element instanceof angular.element) {
3336 return {
3337 element: element,
3338 bounds: element[0].getBoundingClientRect()
3339 };
3340 }
3341};
3342
3343
3344/*****************************************************************************
3345 * Util Methods *
3346 *****************************************************************************/
3347
3348
3349/**
3350 * Returns the angular element associated with a css selector or element.
3351 * @param el {string|!JQLite|!Element}
3352 * @returns {!JQLite}
3353 */
3354function getElement(el) {
3355 var queryResult = angular.isString(el) ?
3356 document.querySelector(el) : el;
3357 return angular.element(queryResult);
3358}
3359
3360/**
3361 * Gets the computed values for an element's translateX and translateY in px.
3362 * @param {!JQLite|!Element} el the element to evaluate
3363 * @param {string} property
3364 * @return {{x: number, y: number}} an element's translateX and translateY in px
3365 */
3366function getComputedTranslations(el, property) {
3367 // The transform being returned by `getComputedStyle` is in the format:
3368 // `matrix(a, b, c, d, translateX, translateY)` if defined and `none`
3369 // if the element doesn't have a transform.
3370 var transform = getComputedStyle(el[0] || el)[property];
3371 var openIndex = transform.indexOf('(');
3372 var closeIndex = transform.lastIndexOf(')');
3373 var output = { x: 0, y: 0 };
3374
3375 if (openIndex > -1 && closeIndex > -1) {
3376 var parsedValues = transform
3377 .substring(openIndex + 1, closeIndex)
3378 .split(', ')
3379 .slice(-2);
3380
3381 output.x = parseInt(parsedValues[0]);
3382 output.y = parseInt(parsedValues[1]);
3383 }
3384
3385 return output;
3386}
3387
3388/*
3389 * Ensures that a value is a valid position name. Throw an exception if not.
3390 * @param {Object} positionMap Object against which the value will be checked.
3391 * @param {string} value
3392 */
3393function validatePosition(positionMap, value) {
3394 // empty is ok
3395 if (value === null || angular.isUndefined(value)) {
3396 return;
3397 }
3398
3399 var positionKeys = Object.keys(positionMap);
3400 var positionValues = [];
3401
3402 for (var key, i = 0; key = positionKeys[i]; i++) {
3403 var position = positionMap[key];
3404 positionValues.push(position);
3405
3406 if (position === value) {
3407 return;
3408 }
3409 }
3410
3411 throw new Error('Panel position only accepts the following values:\n' +
3412 positionValues.join(' | '));
3413}
3414
3415/**
3416 * Adds units to a number value.
3417 * @param {string|number} value
3418 * @return {string}
3419 */
3420function addUnits(value) {
3421 return angular.isNumber(value) ? value + 'px' : value;
3422}
3423
3424ngmaterial.components.panel = angular.module("material.components.panel");
Note: See TracBrowser for help on using the repository browser.