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