2  * Angular Material Design
 
   3  * https://github.com/angular/material
 
   7 goog.provide('ngmaterial.components.panel');
 
   8 goog.require('ngmaterial.components.backdrop');
 
   9 goog.require('ngmaterial.core');
 
  12  * @name material.components.panel
 
  14 MdPanelService['$inject'] = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
 
  16   .module('material.components.panel', [
 
  18     'material.components.backdrop'
 
  20   .provider('$mdPanel', MdPanelProvider);
 
  23 /*****************************************************************************
 
  24  *                            PUBLIC DOCUMENTATION                           *
 
  25  *****************************************************************************/
 
  30  * @name $mdPanelProvider
 
  31  * @module material.components.panel
 
  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.
 
  41  * (function(angular, undefined) {
 
  45  *       .module('demoApp', ['ngMaterial'])
 
  47  *       .controller('DemoCtrl', DemoCtrl)
 
  48  *       .controller('DemoMenuCtrl', DemoMenuCtrl);
 
  50  *   function DemoConfig($mdPanelProvider) {
 
  51  *     $mdPanelProvider.definePreset('demoPreset', {
 
  52  *       attachTo: angular.element(document.body),
 
  53  *       controller: DemoMenuCtrl,
 
  54  *       controllerAs: 'ctrl',
 
  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>' +
 
  63  *           '    <md-divider></md-divider>' +
 
  64  *           '    <div class="menu-item">' +
 
  65  *           '      <button class="md-button" ng-click="ctrl.closeMenu()">' +
 
  66  *           '        <span>Close Menu</span>' +
 
  71  *       panelClass: 'menu-panel-container',
 
  74  *       propagateContainerEvents: true,
 
  79  *   function PanelProviderCtrl($mdPanel) {
 
 102  *     $mdPanel.newPanelGroup('menus', {
 
 106  *     this.showMenu = function($event, menu) {
 
 107  *       $mdPanel.open('demoPreset', {
 
 108  *         id: 'menu_' + menu.name,
 
 109  *         position: $mdPanel.newPanelPosition()
 
 110  *             .relativeTo($event.srcElement)
 
 112  *               $mdPanel.xPosition.ALIGN_START,
 
 113  *               $mdPanel.yPosition.BELOW
 
 123  *   function PanelMenuCtrl(mdPanelRef) {
 
 124  *     this.closeMenu = function() {
 
 125  *       mdPanelRef && mdPanelRef.close();
 
 134  * @name $mdPanelProvider#definePreset
 
 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.
 
 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
 
 149 /*****************************************************************************
 
 151  *****************************************************************************/
 
 157  * @module material.components.panel
 
 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.
 
 165  * (function(angular, undefined) {
 
 169  *       .module('demoApp', ['ngMaterial'])
 
 170  *       .controller('DemoDialogController', DialogController);
 
 174  *   function showPanel($event) {
 
 175  *     var panelPosition = $mdPanel.newPanelPosition()
 
 180  *     var panelAnimation = $mdPanel.newPanelAnimation()
 
 181  *         .targetEvent($event)
 
 182  *         .defaultAnimation('md-panel-animate-fly')
 
 183  *         .closeTo('.show-button');
 
 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,
 
 198  *     $mdPanel.open(config)
 
 199  *         .then(function(result) {
 
 204  *   function DialogController(MdPanelRef) {
 
 205  *     function closeDialog() {
 
 206  *       if (MdPanelRef) MdPanelRef.close();
 
 215  * @name $mdPanel#create
 
 217  * Creates a panel with the specified options.
 
 219  * @param config {!Object=} Specific configuration object that may contain the
 
 220  *     following properties:
 
 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
 
 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
 
 240  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on
 
 242  *   - `bindToController` - `{boolean=}`: Binds locals to the controller
 
 243  *     instead of passing them in. Defaults to true, as this is a best
 
 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,
 
 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
 
 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.
 
 261  *   - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
 
 262  *     specifies the alignment of the panel. For more information, see
 
 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
 
 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
 
 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.
 
 309  * @returns {!MdPanelRef} panelRef
 
 314  * @name $mdPanel#open
 
 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.
 
 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.
 
 330  * @name $mdPanel#newPanelPosition
 
 332  * Returns a new instance of the MdPanelPosition object. Use this to create
 
 333  * the position config object.
 
 335  * @returns {!MdPanelPosition} panelPosition
 
 340  * @name $mdPanel#newPanelAnimation
 
 342  * Returns a new instance of the MdPanelAnimation object. Use this to create
 
 343  * the animation config object.
 
 345  * @returns {!MdPanelAnimation} panelAnimation
 
 350  * @name $mdPanel#newPanelGroup
 
 352  * Creates a panel group and adds it to a tracked list of panel groups.
 
 354  * @param {string} groupName Name of the group to create.
 
 355  * @param {!Object=} config Specific configuration object that may contain the
 
 356  *     following properties:
 
 358  *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
 
 359  *     be open within a defined panel group.
 
 361  * @returns {!Object<string,
 
 362  *     {panels: !Array<!MdPanelRef>,
 
 363  *     openPanels: !Array<!MdPanelRef>,
 
 364  *     maxOpen: number}>} panelGroup
 
 369  * @name $mdPanel#setGroupMaxOpen
 
 371  * Sets the maximum number of panels in a group that can be opened at a given
 
 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.
 
 380 /*****************************************************************************
 
 382  *****************************************************************************/
 
 388  * @module material.components.panel
 
 390  * A reference to a created panel. This reference contains a unique id for the
 
 391  * panel, along with the following properties:
 
 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
 
 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`,
 
 409  * @name MdPanelRef#open
 
 411  * Attaches and shows the panel.
 
 413  * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 
 419  * @name MdPanelRef#close
 
 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.
 
 425  * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 
 431  * @name MdPanelRef#attach
 
 433  * Create the panel elements and attach them to the DOM. The panel will be
 
 436  * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 
 442  * @name MdPanelRef#detach
 
 444  * Removes the panel from the DOM. This will NOT hide the panel before removing
 
 447  * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 
 453  * @name MdPanelRef#show
 
 457  * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 
 458  *     shown and animations are completed.
 
 463  * @name MdPanelRef#hide
 
 467  * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 
 468  *     hidden and animations are completed.
 
 473  * @name MdPanelRef#destroy
 
 475  * Destroys the panel. The panel cannot be opened again after this is called.
 
 480  * @name MdPanelRef#addClass
 
 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.
 
 486  * Adds a class to the panel. DO NOT use this hide/show the panel.
 
 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.
 
 495  * @name MdPanelRef#removeClass
 
 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.
 
 501  * Removes a class from the panel. DO NOT use this to hide/show the panel.
 
 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.
 
 510  * @name MdPanelRef#toggleClass
 
 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.
 
 516  * Toggles a class on the panel. DO NOT use this to hide/show the panel.
 
 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.
 
 525  * @name MdPanelRef#updatePosition
 
 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
 
 531  * @param {!MdPanelPosition} position
 
 536  * @name MdPanelRef#addToGroup
 
 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.
 
 541  * @param {string} groupName The name of the group to add the panel to.
 
 546  * @name MdPanelRef#removeFromGroup
 
 548  * Removes a panel from a group if the panel exists within that group. The group
 
 549  * must be created ahead of time.
 
 551  * @param {string} groupName The name of the group.
 
 556  * @name MdPanelRef#registerInterceptor
 
 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.
 
 565  * @param {string} type Type of interceptor.
 
 566  * @param {!angular.$q.Promise<any>} callback Callback to be registered.
 
 567  * @returns {!MdPanelRef}
 
 572  * @name MdPanelRef#removeInterceptor
 
 574  * Removes a registered interceptor.
 
 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}
 
 583  * @name MdPanelRef#removeAllInterceptors
 
 585  * Removes all interceptors. If a type is supplied, only the
 
 586  * interceptors of that type will be cleared.
 
 588  * @param {string=} type Type of interceptors to be removed.
 
 589  * @returns {!MdPanelRef}
 
 594  * @name MdPanelRef#updateAnimation
 
 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.
 
 599  * @param {!MdPanelAnimation} animation
 
 603 /*****************************************************************************
 
 605  *****************************************************************************/
 
 610  * @name MdPanelPosition
 
 611  * @module material.components.panel
 
 614  * Object for configuring the position of the panel.
 
 618  * #### Centering the panel
 
 621  * new MdPanelPosition().absolute().center();
 
 624  * #### Overlapping the panel with an element
 
 627  * new MdPanelPosition()
 
 628  *     .relativeTo(someElement)
 
 630  *       $mdPanel.xPosition.ALIGN_START,
 
 631  *       $mdPanel.yPosition.ALIGN_TOPS
 
 635  * #### Aligning the panel with the bottom of an element
 
 638  * new MdPanelPosition()
 
 639  *     .relativeTo(someElement)
 
 640  *     .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
 
 646  * @name MdPanelPosition#absolute
 
 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.
 
 652  * @returns {!MdPanelPosition}
 
 657  * @name MdPanelPosition#relativeTo
 
 659  * Positions the panel relative to a specific element.
 
 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}
 
 668  * @name MdPanelPosition#top
 
 670  * Sets the value of `top` for the panel. Clears any previously set vertical
 
 673  * @param {string=} top Value of `top`. Defaults to '0'.
 
 674  * @returns {!MdPanelPosition}
 
 679  * @name MdPanelPosition#bottom
 
 681  * Sets the value of `bottom` for the panel. Clears any previously set vertical
 
 684  * @param {string=} bottom Value of `bottom`. Defaults to '0'.
 
 685  * @returns {!MdPanelPosition}
 
 690  * @name MdPanelPosition#start
 
 692  * Sets the panel to the start of the page - `left` if `ltr` or `right` for
 
 693  * `rtl`. Clears any previously set horizontal position.
 
 695  * @param {string=} start Value of position. Defaults to '0'.
 
 696  * @returns {!MdPanelPosition}
 
 701  * @name MdPanelPosition#end
 
 703  * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
 
 704  * Clears any previously set horizontal position.
 
 706  * @param {string=} end Value of position. Defaults to '0'.
 
 707  * @returns {!MdPanelPosition}
 
 712  * @name MdPanelPosition#left
 
 714  * Sets the value of `left` for the panel. Clears any previously set
 
 715  * horizontal position.
 
 717  * @param {string=} left Value of `left`. Defaults to '0'.
 
 718  * @returns {!MdPanelPosition}
 
 723  * @name MdPanelPosition#right
 
 725  * Sets the value of `right` for the panel. Clears any previously set
 
 726  * horizontal position.
 
 728  * @param {string=} right Value of `right`. Defaults to '0'.
 
 729  * @returns {!MdPanelPosition}
 
 734  * @name MdPanelPosition#centerHorizontally
 
 736  * Centers the panel horizontally in the viewport. Clears any previously set
 
 737  * horizontal position.
 
 739  * @returns {!MdPanelPosition}
 
 744  * @name MdPanelPosition#centerVertically
 
 746  * Centers the panel vertically in the viewport. Clears any previously set
 
 749  * @returns {!MdPanelPosition}
 
 754  * @name MdPanelPosition#center
 
 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.
 
 760  * @returns {!MdPanelPosition}
 
 765  * @name MdPanelPosition#addPanelPosition
 
 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.
 
 772  * xPosition must be one of the following values available on
 
 773  * $mdPanel.xPosition:
 
 776  * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
 
 786  * A: OFFSET_START (for LTR displays)
 
 787  * B: ALIGN_START (for LTR displays)
 
 789  * D: ALIGN_END (for LTR displays)
 
 790  * E: OFFSET_END (for LTR displays)
 
 793  * yPosition must be one of the following values available on
 
 794  * $mdPanel.yPosition:
 
 796  * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
 
 814  * @param {string} xPosition
 
 815  * @param {string} yPosition
 
 816  * @returns {!MdPanelPosition}
 
 821  * @name MdPanelPosition#withOffsetX
 
 823  * Sets the value of the offset in the x-direction.
 
 825  * @param {string} offsetX
 
 826  * @returns {!MdPanelPosition}
 
 831  * @name MdPanelPosition#withOffsetY
 
 833  * Sets the value of the offset in the y-direction.
 
 835  * @param {string} offsetY
 
 836  * @returns {!MdPanelPosition}
 
 840 /*****************************************************************************
 
 842  *****************************************************************************/
 
 847  * @name MdPanelAnimation
 
 848  * @module material.components.panel
 
 850  * Animation configuration object. To use, create an MdPanelAnimation with the
 
 851  * desired properties, then pass the object as part of $mdPanel creation.
 
 856  * var panelAnimation = new MdPanelAnimation()
 
 857  *     .openFrom(myButtonEl)
 
 859  *     .closeTo('.my-button')
 
 860  *     .withAnimation($mdPanel.animation.SCALE);
 
 863  *   animation: panelAnimation
 
 870  * @name MdPanelAnimation#openFrom
 
 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.
 
 877  * @param {string|!Element|!Event|{top: number, left: number}}
 
 878  * @returns {!MdPanelAnimation}
 
 883  * @name MdPanelAnimation#closeTo
 
 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
 
 889  * @param {string|!Element|{top: number, left: number}}
 
 890  * @returns {!MdPanelAnimation}
 
 895  * @name MdPanelAnimation#withAnimation
 
 897  * Specifies the animation class.
 
 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.
 
 907  * Custom classes will by default fade in and out unless
 
 908  * "transition: opacity 1ms" is added to the to custom class.
 
 910  * @param {string|{open: string, close: string}} cssClass
 
 911  * @returns {!MdPanelAnimation}
 
 916  * @name MdPanelAnimation#duration
 
 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
 
 922  * @param {number|{open: number, close: number}} duration
 
 923  * @returns {!MdPanelAnimation}
 
 927 /*****************************************************************************
 
 928  *                            PUBLIC DOCUMENTATION                           *
 
 929  *****************************************************************************/
 
 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>');
 
 941  * A provider that is used for creating presets for the panel API.
 
 942  * @final @constructor ngInject
 
 944 function MdPanelProvider() {
 
 946     'definePreset': definePreset,
 
 947     'getAllPresets': getAllPresets,
 
 948     'clearPresets': clearPresets,
 
 949     '$get': $getProvider()
 
 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
 
 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.');
 
 973   // Delete any property on the preset that is not allowed.
 
 975   delete preset.position;
 
 976   delete preset.animation;
 
 978   _presets[name] = preset;
 
 983  * Gets a clone of the `_presets`.
 
 986 function getAllPresets() {
 
 987   return angular.copy(_presets);
 
 992  * Clears all of the stored presets.
 
 994 function clearPresets() {
 
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
 
1009 function $getProvider() {
 
1011     '$rootElement', '$rootScope', '$injector', '$window',
 
1012     function($rootElement, $rootScope, $injector, $window) {
 
1013       return new MdPanelService(_presets, $rootElement, $rootScope,
 
1014           $injector, $window);
 
1020 /*****************************************************************************
 
1022  *****************************************************************************/
 
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
 
1034 function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
 
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,
 
1041    * @private {!Object}
 
1043   this._defaultConfigOptions = {
 
1044     bindToController: true,
 
1045     clickOutsideToClose: false,
 
1046     disableParentScroll: false,
 
1047     escapeToClose: false,
 
1051     propagateContainerEvents: false,
 
1052     transformTemplate: angular.bind(this, this._wrapTemplate),
 
1054     zIndex: MD_PANEL_Z_INDEX
 
1057   /** @private {!Object} */
 
1060   /** @private {!Object} */
 
1061   this._presets = presets;
 
1063   /** @private @const */
 
1064   this._$rootElement = $rootElement;
 
1066   /** @private @const */
 
1067   this._$rootScope = $rootScope;
 
1069   /** @private @const */
 
1070   this._$injector = $injector;
 
1072   /** @private @const */
 
1073   this._$window = $window;
 
1075   /** @private @const */
 
1076   this._$mdUtil = this._$injector.get('$mdUtil');
 
1078   /** @private {!Object<string, !MdPanelRef>} */
 
1079   this._trackedPanels = {};
 
1082    * @private {!Object<string,
 
1083    *     {panels: !Array<!MdPanelRef>,
 
1084    *     openPanels: !Array<!MdPanelRef>,
 
1085    *     maxOpen: number}>}
 
1087   this._groups = Object.create(null);
 
1090    * Default animations that can be used within the panel.
 
1093   this.animation = MdPanelAnimation.animation;
 
1096    * Possible values of xPosition for positioning the panel relative to
 
1100   this.xPosition = MdPanelPosition.xPosition;
 
1103    * Possible values of yPosition for positioning the panel relative to
 
1107   this.yPosition = MdPanelPosition.yPosition;
 
1110    * Possible values for the interceptors that can be registered on a panel.
 
1113   this.interceptorTypes = MdPanelRef.interceptorTypes;
 
1116    * Possible values for closing of a panel.
 
1119   this.closeReasons = MdPanelRef.closeReasons;
 
1122    * Possible values of absolute position.
 
1125   this.absPosition = MdPanelPosition.absPosition;
 
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}
 
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)) {
 
1145   preset = preset || {};
 
1146   config = config || {};
 
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
 
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;
 
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);
 
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;
 
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];
 
1175     angular.forEach(this._config.groupName, function(group) {
 
1176       panelRef.addToGroup(group);
 
1180   this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
 
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.
 
1193 MdPanelService.prototype.open = function(preset, config) {
 
1194   var panelRef = this.create(preset, config);
 
1195   return panelRef.open().then(function() {
 
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.
 
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.');
 
1212   return this._presets[preset];
 
1217  * Returns a new instance of the MdPanelPosition. Use this to create the
 
1218  * positioning object.
 
1219  * @returns {!MdPanelPosition}
 
1221 MdPanelService.prototype.newPanelPosition = function() {
 
1222   return new MdPanelPosition(this._$injector);
 
1227  * Returns a new instance of the MdPanelAnimation. Use this to create the
 
1229  * @returns {!MdPanelAnimation}
 
1231 MdPanelService.prototype.newPanelAnimation = function() {
 
1232   return new MdPanelAnimation(this._$injector);
 
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:
 
1242  *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
 
1243  *     open within a defined panel group.
 
1245  * @returns {!Object<string,
 
1246  *     {panels: !Array<!MdPanelRef>,
 
1247  *     openPanels: !Array<!MdPanelRef>,
 
1248  *     maxOpen: number}>} panelGroup
 
1250 MdPanelService.prototype.newPanelGroup = function(groupName, config) {
 
1251   if (!this._groups[groupName]) {
 
1252     config = config || {};
 
1256       maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
 
1258     this._groups[groupName] = group;
 
1260   return this._groups[groupName];
 
1265  * Sets the maximum number of panels in a group that can be opened at a given
 
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.
 
1271 MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
 
1272   if (this._groups[groupName]) {
 
1273     this._groups[groupName].maxOpen = maxOpen;
 
1275     throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
 
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.
 
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;
 
1297  * Closes the first open panel within a specific group.
 
1298  * @param {string} groupName The name of the group.
 
1301 MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
 
1302   this._groups[groupName].openPanels[0].close();
 
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.
 
1315 MdPanelService.prototype._wrapTemplate = function(origTemplate) {
 
1316   var template = origTemplate || '';
 
1318   // The panel should be initially rendered offscreen so we can calculate
 
1319   // height and width for positioning.
 
1321       '<div class="md-panel-outer-wrapper">' +
 
1322       '  <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
 
1328  * Wraps a content element in a md-panel-outer wrapper and
 
1329  * positions it off-screen. Allows for proper control over positoning
 
1331  * @param {!angular.JQLite} contentElement Element to be wrapped.
 
1332  * @return {!angular.JQLite} Wrapper element.
 
1335 MdPanelService.prototype._wrapContentElement = function(contentElement) {
 
1336   var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
 
1338   contentElement.addClass('md-panel').css('left', '-9999px');
 
1339   wrapper.append(contentElement);
 
1345 /*****************************************************************************
 
1347  *****************************************************************************/
 
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
 
1357 function MdPanelRef(config, $injector) {
 
1358   // Injected variables.
 
1359   /** @private @const {!angular.$q} */
 
1360   this._$q = $injector.get('$q');
 
1362   /** @private @const {!angular.$mdCompiler} */
 
1363   this._$mdCompiler = $injector.get('$mdCompiler');
 
1365   /** @private @const {!angular.$mdConstant} */
 
1366   this._$mdConstant = $injector.get('$mdConstant');
 
1368   /** @private @const {!angular.$mdUtil} */
 
1369   this._$mdUtil = $injector.get('$mdUtil');
 
1371   /** @private @const {!angular.$mdTheming} */
 
1372   this._$mdTheming = $injector.get('$mdTheming');
 
1374   /** @private @const {!angular.Scope} */
 
1375   this._$rootScope = $injector.get('$rootScope');
 
1377   /** @private @const {!angular.$animate} */
 
1378   this._$animate = $injector.get('$animate');
 
1380   /** @private @const {!MdPanelRef} */
 
1381   this._$mdPanel = $injector.get('$mdPanel');
 
1383   /** @private @const {!angular.$log} */
 
1384   this._$log = $injector.get('$log');
 
1386   /** @private @const {!angular.$window} */
 
1387   this._$window = $injector.get('$window');
 
1389   /** @private @const {!Function} */
 
1390   this._$$rAF = $injector.get('$$rAF');
 
1392   // Public variables.
 
1394    * Unique id for the panelRef.
 
1397   this.id = config.id;
 
1399   /** @type {!Object} */
 
1400   this.config = config;
 
1402   /** @type {!angular.JQLite|undefined} */
 
1403   this.panelContainer;
 
1405   /** @type {!angular.JQLite|undefined} */
 
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
 
1414   this.isAttached = false;
 
1416   // Private variables.
 
1417   /** @private {Array<function()>} */
 
1418   this._removeListeners = [];
 
1420   /** @private {!angular.JQLite|undefined} */
 
1423   /** @private {!angular.JQLite|undefined} */
 
1424   this._bottomFocusTrap;
 
1426   /** @private {!$mdPanel|undefined} */
 
1429   /** @private {Function?} */
 
1430   this._restoreScroll = null;
 
1433    * Keeps track of all the panel interceptors.
 
1434    * @private {!Object}
 
1436   this._interceptors = Object.create(null);
 
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}
 
1444   this._compilerCleanup = null;
 
1447    * Cache for saving and restoring element inline styles, CSS classes etc.
 
1448    * @type {{styles: string, classes: string}}
 
1450   this._restoreCache = {
 
1457 MdPanelRef.interceptorTypes = {
 
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.
 
1468 MdPanelRef.prototype.open = function() {
 
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);
 
1485         .then(checkGroupMaxOpen)
 
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.
 
1498 MdPanelRef.prototype.close = function(closeReason) {
 
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);
 
1511           .then(onCloseSuccess)
 
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.
 
1523 MdPanelRef.prototype.attach = function() {
 
1524   if (this.isAttached && this.panelEl) {
 
1525     return this._$q.when(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();
 
1539         self._createBackdrop(),
 
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.
 
1555 MdPanelRef.prototype.detach = function() {
 
1556   if (!this.isAttached) {
 
1557     return this._$q.when(this);
 
1561   var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
 
1563   var detachFn = function() {
 
1564     self._removeEventListeners();
 
1566     // Remove the focus traps that we added earlier for keeping focus within
 
1568     if (self._topFocusTrap && self._topFocusTrap.parentNode) {
 
1569       self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
 
1572     if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
 
1573       self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
 
1576     if (self._restoreCache.classes) {
 
1577       self.panelEl[0].className = self._restoreCache.classes;
 
1580     // Either restore the saved styles or clear the ones set by mdPanel.
 
1581     self.panelEl[0].style.cssText = self._restoreCache.styles || '';
 
1583     self._compilerCleanup();
 
1584     self.panelContainer.remove();
 
1585     self.isAttached = false;
 
1586     return self._$q.when(self);
 
1589   if (this._restoreScroll) {
 
1590     this._restoreScroll();
 
1591     this._restoreScroll = null;
 
1594   return this._$q(function(resolve, reject) {
 
1595     var done = self._done(resolve, self);
 
1599       self._backdropRef ? self._backdropRef.detach() : true
 
1600     ]).then(onDomRemoved)
 
1608  * Destroys the panel. The Panel cannot be opened again after this.
 
1610 MdPanelRef.prototype.destroy = function() {
 
1612   if (this.config.groupName) {
 
1613     angular.forEach(this.config.groupName, function(group) {
 
1614       self.removeFromGroup(group);
 
1617   this.config.scope.$destroy();
 
1618   this.config.locals = null;
 
1619   this._interceptors = null;
 
1625  * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
 
1626  *     the panel has shown and animations finish.
 
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().');
 
1635   if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
 
1636     return this._$q.when(this);
 
1640   var animatePromise = function() {
 
1641     self.panelContainer.removeClass(MD_PANEL_HIDDEN);
 
1642     return self._animateOpen();
 
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);
 
1657       self._backdropRef ? self._backdropRef.show() : self,
 
1658       animatePromise().then(function() { self._focusOnOpen(); }, reject)
 
1659     ]).then(onOpenComplete)
 
1660       .then(addToGroupOpen)
 
1669  * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
 
1670  *     the panel has hidden and animations finish.
 
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().');
 
1679   if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
 
1680     return this._$q.when(this);
 
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);
 
1691     var removeFromGroupOpen = function() {
 
1692       if (self.config.groupName) {
 
1694         angular.forEach(self.config.groupName, function(group) {
 
1695           group = self._$mdPanel._groups[group];
 
1696           index = group.openPanels.indexOf(self);
 
1698             group.openPanels.splice(index, 1);
 
1703     var focusOnOrigin = function() {
 
1704       var origin = self.config['origin'];
 
1706         getElement(origin).focus();
 
1711       self._backdropRef ? self._backdropRef.hide() : self,
 
1712       self._animateClose()
 
1715           .then(removeFromGroupOpen)
 
1716           .then(focusOnOrigin)
 
1718     ]).then(done, reject);
 
1724  * Add a class to the panel. DO NOT use this to hide/show the panel.
 
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.
 
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.
 
1734 MdPanelRef.prototype.addClass = function(newClass, toElement) {
 
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.');
 
1741   if (!this.panelContainer) {
 
1743         'mdPanel: Panel does not exist yet. Call open() or attach().');
 
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);
 
1755  * Remove a class from the panel. DO NOT use this to hide/show the panel.
 
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.
 
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.
 
1765 MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
 
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.');
 
1772   if (!this.panelContainer) {
 
1774         'mdPanel: Panel does not exist yet. Call open() or attach().');
 
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);
 
1786  * Toggle a class on the panel. DO NOT use this to hide/show the panel.
 
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.
 
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.
 
1796 MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
 
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.');
 
1803   if (!this.panelContainer) {
 
1805         'mdPanel: Panel does not exist yet. Call open() or attach().');
 
1809     this.panelContainer.toggleClass(toggleClass);
 
1811     this.panelEl.toggleClass(toggleClass);
 
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.
 
1824 MdPanelRef.prototype._compile = function() {
 
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;
 
1833     if (config.contentElement) {
 
1834       var panelEl = compileData.element;
 
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;
 
1841       self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
 
1842       self.panelEl = panelEl;
 
1844       self.panelContainer = compileData.link(config['scope']);
 
1845       self.panelEl = angular.element(
 
1846         self.panelContainer[0].querySelector('.md-panel')
 
1850     // Save a reference to the cleanup function from the compiler.
 
1851     self._compilerCleanup = compileData.cleanup;
 
1853     // Attach the panel to the proper place in the DOM.
 
1854     getElement(self.config['attachTo']).append(self.panelContainer);
 
1862  * Creates a panel and adds it to the dom.
 
1863  * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 
1867 MdPanelRef.prototype._createPanel = function() {
 
1870   return this._$q(function(resolve, reject) {
 
1871     if (!self.config.locals) {
 
1872       self.config.locals = {};
 
1875     self.config.locals.mdPanelRef = self;
 
1877     self._compile().then(function() {
 
1878       if (self.config['disableParentScroll']) {
 
1879         self._restoreScroll = self._$mdUtil.disableScrollAround(
 
1881           self.panelContainer,
 
1882           { disableScrollMask: true }
 
1886       // Add a custom CSS class to the panel element.
 
1887       if (self.config['panelClass']) {
 
1888         self.panelEl.addClass(self.config['panelClass']);
 
1891       // Handle click and touch events for the panel container.
 
1892       if (self.config['propagateContainerEvents']) {
 
1893         self.panelContainer.css('pointer-events', 'none');
 
1896       // Panel may be outside the $rootElement, tell ngAnimate to animate
 
1898       if (self._$animate.pin) {
 
1900           self.panelContainer,
 
1901           getElement(self.config['attachTo'])
 
1905       self._configureTrapFocus();
 
1906       self._addStyles().then(function() {
 
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>}
 
1921 MdPanelRef.prototype._addStyles = function() {
 
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);
 
1927     var hideAndResolve = function() {
 
1928       // Theme the element and container.
 
1931       // Remove left: -9999px and add hidden class.
 
1932       self.panelEl.css('left', '');
 
1933       self.panelContainer.addClass(MD_PANEL_HIDDEN);
 
1938     if (self.config['fullscreen']) {
 
1939       self.panelEl.addClass('_md-panel-fullscreen');
 
1941       return; // Don't setup positioning.
 
1944     var positionConfig = self.config['position'];
 
1945     if (!positionConfig) {
 
1947       return; // Don't setup positioning.
 
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);
 
1956       // Theme the element and container.
 
1966  * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
 
1969 MdPanelRef.prototype._setTheming = function() {
 
1970   this._$mdTheming(this.panelEl);
 
1971   this._$mdTheming(this.panelContainer);
 
1976  * Updates the position configuration of a panel
 
1977  * @param {!MdPanelPosition} position
 
1979 MdPanelRef.prototype.updatePosition = function(position) {
 
1980   if (!this.panelContainer) {
 
1982         'mdPanel: Panel does not exist yet. Call open() or attach().');
 
1985   this.config['position'] = position;
 
1986   this._updatePosition();
 
1991  * Calculates and updates the position of the panel.
 
1992  * @param {boolean=} init
 
1995 MdPanelRef.prototype._updatePosition = function(init) {
 
1996   var positionConfig = this.config['position'];
 
1998   if (positionConfig) {
 
1999     positionConfig._setPanelPosition(this.panelEl);
 
2001     // Hide the panel now that position is known.
 
2003       this.panelContainer.addClass(MD_PANEL_HIDDEN);
 
2007       MdPanelPosition.absPosition.TOP,
 
2008       positionConfig.getTop()
 
2011       MdPanelPosition.absPosition.BOTTOM,
 
2012       positionConfig.getBottom()
 
2015       MdPanelPosition.absPosition.LEFT,
 
2016       positionConfig.getLeft()
 
2019       MdPanelPosition.absPosition.RIGHT,
 
2020       positionConfig.getRight()
 
2027  * Focuses on the panel or the first focus target.
 
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.
 
2036     this._$rootScope['$$postDigest'](function() {
 
2037       var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
 
2046  * Shows the backdrop.
 
2047  * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
 
2048  *     is created and attached.
 
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)
 
2057             open: '_md-opaque-enter',
 
2058             close: '_md-opaque-leave'
 
2061       if (this.config.animation) {
 
2062         backdropAnimation.duration(this.config.animation._rawDuration);
 
2065       var backdropConfig = {
 
2066         animation: backdropAnimation,
 
2067         attachTo: this.config.attachTo,
 
2069         panelClass: '_md-panel-backdrop',
 
2070         zIndex: this.config.zIndex - 1
 
2073       this._backdropRef = this._$mdPanel.create(backdropConfig);
 
2075     if (!this._backdropRef.isAttached) {
 
2076       return this._backdropRef.attach();
 
2083  * Listen for escape keys and outside clicks to auto close.
 
2086 MdPanelRef.prototype._addEventListeners = function() {
 
2087   this._configureEscapeToClose();
 
2088   this._configureClickOutsideToClose();
 
2089   this._configureScrollListener();
 
2094  * Remove event listeners added in _addEventListeners.
 
2097 MdPanelRef.prototype._removeEventListeners = function() {
 
2098   this._removeListeners && this._removeListeners.forEach(function(removeFn) {
 
2101   this._removeListeners = [];
 
2106  * Setup the escapeToClose event listeners.
 
2109 MdPanelRef.prototype._configureEscapeToClose = function() {
 
2110   if (this.config['escapeToClose']) {
 
2111     var parentTarget = getElement(this.config['attachTo']);
 
2114     var keyHandlerFn = function(ev) {
 
2115       if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
 
2116         ev.stopPropagation();
 
2117         ev.preventDefault();
 
2119         self.close(MdPanelRef.closeReasons.ESCAPE);
 
2123     // Add keydown listeners
 
2124     this.panelContainer.on('keydown', keyHandlerFn);
 
2125     parentTarget.on('keydown', keyHandlerFn);
 
2127     // Queue remove listeners function
 
2128     this._removeListeners.push(function() {
 
2129       self.panelContainer.off('keydown', keyHandlerFn);
 
2130       parentTarget.off('keydown', keyHandlerFn);
 
2137  * Setup the clickOutsideToClose event listeners.
 
2140 MdPanelRef.prototype._configureClickOutsideToClose = function() {
 
2141   if (this.config['clickOutsideToClose']) {
 
2142     var target = this.config['propagateContainerEvents'] ?
 
2143         angular.element(document.body) :
 
2144         this.panelContainer;
 
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;
 
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.
 
2159     var mouseupHandler = function(ev) {
 
2160       if (self.config['propagateContainerEvents']) {
 
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)) {
 
2168       } else if (sourceEl === target[0] && ev.target === target[0]) {
 
2169         ev.stopPropagation();
 
2170         ev.preventDefault();
 
2172         self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
 
2177     target.on('mousedown', mousedownHandler);
 
2178     target.on('mouseup', mouseupHandler);
 
2180     // Queue remove listeners function
 
2181     this._removeListeners.push(function() {
 
2182       target.off('mousedown', mousedownHandler);
 
2183       target.off('mouseup', mouseupHandler);
 
2190  * Configures the listeners for updating the panel position on scroll.
 
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);
 
2200     var onScroll = function() {
 
2201       debouncedUpdatePosition();
 
2205     this._$window.addEventListener('scroll', onScroll, true);
 
2207     // Queue remove listeners function.
 
2208     this._removeListeners.push(function() {
 
2209       self._$window.removeEventListener('scroll', onScroll, true);
 
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.
 
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];
 
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() {
 
2235     this._topFocusTrap.addEventListener('focus', focusHandler);
 
2236     this._bottomFocusTrap.addEventListener('focus', focusHandler);
 
2238     // Queue remove listeners function
 
2239     this._removeListeners.push(this._simpleBind(function() {
 
2240       this._topFocusTrap.removeEventListener('focus', focusHandler);
 
2241       this._bottomFocusTrap.removeEventListener('focus', focusHandler);
 
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);
 
2254  * Updates the animation of a panel.
 
2255  * @param {!MdPanelAnimation} animation
 
2257 MdPanelRef.prototype.updateAnimation = function(animation) {
 
2258   this.config['animation'] = animation;
 
2260   if (this._backdropRef) {
 
2261     this._backdropRef.config.animation.duration(animation._rawDuration);
 
2267  * Animate the panel opening.
 
2268  * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 
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);
 
2282   return this._$q(function(resolve) {
 
2283     var done = self._done(resolve, self);
 
2284     var warnAndOpen = function() {
 
2286           'mdPanel: MdPanel Animations failed. ' +
 
2287           'Showing panel without animating.');
 
2291     animationConfig.animateOpen(self.panelEl)
 
2292         .then(done, warnAndOpen);
 
2298  * Animate the panel closing.
 
2299  * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 
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);
 
2312   return this._$q(function(resolve) {
 
2313     var done = function() {
 
2314       self.panelContainer.removeClass('md-panel-is-showing');
 
2317     var warnAndClose = function() {
 
2319           'mdPanel: MdPanel Animations failed. ' +
 
2320           'Hiding panel without animating.');
 
2324     animationConfig.animateClose(self.panelEl)
 
2325         .then(done, warnAndClose);
 
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}
 
2338 MdPanelRef.prototype.registerInterceptor = function(type, callback) {
 
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;
 
2348     throw new Error('MdPanel: ' + error);
 
2351   var interceptors = this._interceptors[type] = this._interceptors[type] || [];
 
2353   if (interceptors.indexOf(callback) === -1) {
 
2354     interceptors.push(callback);
 
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}
 
2367 MdPanelRef.prototype.removeInterceptor = function(type, callback) {
 
2368   var index = this._interceptors[type] ?
 
2369     this._interceptors[type].indexOf(callback) : -1;
 
2372     this._interceptors[type].splice(index, 1);
 
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}
 
2385 MdPanelRef.prototype.removeAllInterceptors = function(type) {
 
2387     this._interceptors[type] = [];
 
2389     this._interceptors = Object.create(null);
 
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>}
 
2404 MdPanelRef.prototype._callInterceptors = function(type) {
 
2407   var interceptors = self._interceptors && self._interceptors[type] || [];
 
2409   return interceptors.reduceRight(function(promise, interceptor) {
 
2410     var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
 
2411     var response = isPromiseLike ? interceptor : null;
 
2414     * For interceptors to reject/cancel subsequent portions of the chain, simply
 
2415     * return a `$q.reject(<value>)`
 
2417     return promise.then(function() {
 
2420           response = interceptor(self);
 
2422           response = $q.reject(e);
 
2428   }, $q.resolve(self));
 
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.
 
2439 MdPanelRef.prototype._simpleBind = function(callback, self) {
 
2440   return function(value) {
 
2441     return callback.apply(self, value);
 
2447  * @param {function} callback
 
2448  * @param {!Object} self
 
2449  * @return {function} Callback function with a self param.
 
2451 MdPanelRef.prototype._done = function(callback, self) {
 
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.
 
2463 MdPanelRef.prototype.addToGroup = function(groupName) {
 
2464   if (!this._$mdPanel._groups[groupName]) {
 
2465     this._$mdPanel.newPanelGroup(groupName);
 
2468   var group = this._$mdPanel._groups[groupName];
 
2469   var index = group.panels.indexOf(this);
 
2472     group.panels.push(this);
 
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.
 
2482 MdPanelRef.prototype.removeFromGroup = function(groupName) {
 
2483   if (!this._$mdPanel._groups[groupName]) {
 
2484     throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
 
2487   var group = this._$mdPanel._groups[groupName];
 
2488   var index = group.panels.indexOf(this);
 
2491     group.panels.splice(index, 1);
 
2497  * Possible default closeReasons for the close function.
 
2500 MdPanelRef.closeReasons = {
 
2501   CLICK_OUTSIDE: 'clickOutsideToClose',
 
2502   ESCAPE: 'escapeToClose',
 
2506 /*****************************************************************************
 
2508  *****************************************************************************/
 
2512  * Position configuration object. To use, create an MdPanelPosition with the
 
2513  * desired properties, then pass the object as part of $mdPanel creation.
 
2517  * var panelPosition = new MdPanelPosition()
 
2518  *     .relativeTo(myButtonEl)
 
2519  *     .addPanelPosition(
 
2520  *       $mdPanel.xPosition.CENTER,
 
2521  *       $mdPanel.yPosition.ALIGN_TOPS
 
2525  *   position: panelPosition
 
2528  * @param {!angular.$injector} $injector
 
2529  * @final @constructor
 
2531 function MdPanelPosition($injector) {
 
2532   /** @private @const {!angular.$window} */
 
2533   this._$window = $injector.get('$window');
 
2535   /** @private {boolean} */
 
2536   this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
 
2538   /** @private @const {!angular.$mdConstant} */
 
2539   this._$mdConstant = $injector.get('$mdConstant');
 
2541   /** @private {boolean} */
 
2542   this._absolute = false;
 
2544   /** @private {!angular.JQLite} */
 
2547   /** @private {string} */
 
2550   /** @private {string} */
 
2553   /** @private {string} */
 
2556   /** @private {string} */
 
2559   /** @private {!Array<string>} */
 
2560   this._translateX = [];
 
2562   /** @private {!Array<string>} */
 
2563   this._translateY = [];
 
2565   /** @private {!Array<{x:string, y:string}>} */
 
2566   this._positions = [];
 
2568   /** @private {?{x:string, y:string}} */
 
2569   this._actualPosition;
 
2574  * Possible values of xPosition.
 
2577 MdPanelPosition.xPosition = {
 
2579   ALIGN_START: 'align-start',
 
2580   ALIGN_END: 'align-end',
 
2581   OFFSET_START: 'offset-start',
 
2582   OFFSET_END: 'offset-end'
 
2587  * Possible values of yPosition.
 
2590 MdPanelPosition.yPosition = {
 
2592   ALIGN_TOPS: 'align-tops',
 
2593   ALIGN_BOTTOMS: 'align-bottoms',
 
2600  * Possible values of absolute position.
 
2603 MdPanelPosition.absPosition = {
 
2611  * Margin between the edges of a panel and the viewport.
 
2614 MdPanelPosition.viewportMargin = 8;
 
2618  * Sets absolute positioning for the panel.
 
2619  * @return {!MdPanelPosition}
 
2621 MdPanelPosition.prototype.absolute = function() {
 
2622   this._absolute = true;
 
2628  * Sets the value of a position for the panel. Clears any previously set
 
2630  * @param {string} position Position to set
 
2631  * @param {string=} value Value of the position. Defaults to '0'.
 
2632  * @returns {!MdPanelPosition}
 
2635 MdPanelPosition.prototype._setPosition = function(position, value) {
 
2636   if (position === MdPanelPosition.absPosition.RIGHT ||
 
2637       position === MdPanelPosition.absPosition.LEFT) {
 
2638     this._left = this._right = '';
 
2640       position === MdPanelPosition.absPosition.BOTTOM ||
 
2641       position === MdPanelPosition.absPosition.TOP) {
 
2642     this._top = this._bottom = '';
 
2644     var positions = Object.keys(MdPanelPosition.absPosition).join()
 
2647     throw new Error('mdPanel: Position must be one of ' + positions + '.');
 
2650   this['_' +  position] = angular.isString(value) ? value : '0';
 
2657  * Sets the value of `top` for the panel. Clears any previously set vertical
 
2659  * @param {string=} top Value of `top`. Defaults to '0'.
 
2660  * @returns {!MdPanelPosition}
 
2662 MdPanelPosition.prototype.top = function(top) {
 
2663   return this._setPosition(MdPanelPosition.absPosition.TOP, top);
 
2668  * Sets the value of `bottom` for the panel. Clears any previously set vertical
 
2670  * @param {string=} bottom Value of `bottom`. Defaults to '0'.
 
2671  * @returns {!MdPanelPosition}
 
2673 MdPanelPosition.prototype.bottom = function(bottom) {
 
2674   return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
 
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}
 
2684 MdPanelPosition.prototype.start = function(start) {
 
2685   var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
 
2686   return this._setPosition(position, start);
 
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}
 
2696 MdPanelPosition.prototype.end = function(end) {
 
2697   var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
 
2698   return this._setPosition(position, end);
 
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}
 
2708 MdPanelPosition.prototype.left = function(left) {
 
2709   return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
 
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}
 
2719 MdPanelPosition.prototype.right = function(right) {
 
2720   return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
 
2725  * Centers the panel horizontally in the viewport. Clears any previously set
 
2726  * horizontal position.
 
2727  * @returns {!MdPanelPosition}
 
2729 MdPanelPosition.prototype.centerHorizontally = function() {
 
2732   this._translateX = ['-50%'];
 
2738  * Centers the panel vertically in the viewport. Clears any previously set
 
2739  * vertical position.
 
2740  * @returns {!MdPanelPosition}
 
2742 MdPanelPosition.prototype.centerVertically = function() {
 
2745   this._translateY = ['-50%'];
 
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}
 
2756 MdPanelPosition.prototype.center = function() {
 
2757   return this.centerHorizontally().centerVertically();
 
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}
 
2767 MdPanelPosition.prototype.relativeTo = function(element) {
 
2768   this._absolute = false;
 
2769   this._relativeToEl = getElement(element);
 
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
 
2778  * @param {string} yPosition must be one of the MdPanelPosition.yPosition
 
2780  * @returns {!MdPanelPosition}
 
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.');
 
2788   this._validateXPosition(xPosition);
 
2789   this._validateYPosition(yPosition);
 
2791   this._positions.push({
 
2800  * Ensures that yPosition is a valid position name. Throw an exception if not.
 
2801  * @param {string} yPosition
 
2803 MdPanelPosition.prototype._validateYPosition = function(yPosition) {
 
2805   if (yPosition == null) {
 
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);
 
2815     if (position === yPosition) {
 
2820   throw new Error('mdPanel: Panel y position only accepts the following ' +
 
2821       'values:\n' + positionValues.join(' | '));
 
2826  * Ensures that xPosition is a valid position name. Throw an exception if not.
 
2827  * @param {string} xPosition
 
2829 MdPanelPosition.prototype._validateXPosition = function(xPosition) {
 
2831   if (xPosition == null) {
 
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) {
 
2845   throw new Error('mdPanel: Panel x Position only accepts the following ' +
 
2846       'values:\n' + positionValues.join(' | '));
 
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}
 
2856 MdPanelPosition.prototype.withOffsetX = function(offsetX) {
 
2857   this._translateX.push(offsetX);
 
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}
 
2868 MdPanelPosition.prototype.withOffsetY = function(offsetY) {
 
2869   this._translateY.push(offsetY);
 
2875  * Gets the value of `top` for the panel.
 
2878 MdPanelPosition.prototype.getTop = function() {
 
2884  * Gets the value of `bottom` for the panel.
 
2887 MdPanelPosition.prototype.getBottom = function() {
 
2888   return this._bottom;
 
2893  * Gets the value of `left` for the panel.
 
2896 MdPanelPosition.prototype.getLeft = function() {
 
2902  * Gets the value of `right` for the panel.
 
2905 MdPanelPosition.prototype.getRight = function() {
 
2911  * Gets the value of `transform` for the panel.
 
2914 MdPanelPosition.prototype.getTransform = function() {
 
2915   var translateX = this._reduceTranslateValues('translateX', this._translateX);
 
2916   var translateY = this._reduceTranslateValues('translateY', this._translateY);
 
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();
 
2925  * Sets the `transform` value for a panel element.
 
2926  * @param {!angular.JQLite} panelEl
 
2927  * @returns {!angular.JQLite}
 
2930 MdPanelPosition.prototype._setTransform = function(panelEl) {
 
2931   return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
 
2936  * True if the panel is completely on-screen with this positioning; false
 
2938  * @param {!angular.JQLite} panelEl
 
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());
 
2948   if (this._translateX.length || this._translateY.length) {
 
2949     var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
 
2950     var offsets = getComputedTranslations(panelEl, prefixedTransform);
 
2955   var right = left + panelEl[0].offsetWidth;
 
2956   var bottom = top + panelEl[0].offsetHeight;
 
2958   return (left >= 0) &&
 
2960     (bottom <= this._$window.innerHeight) &&
 
2961     (right <= this._$window.innerWidth);
 
2966  * Gets the first x/y position that can fit on-screen.
 
2967  * @returns {{x: string, y: string}}
 
2969 MdPanelPosition.prototype.getActualPosition = function() {
 
2970   return this._actualPosition;
 
2975  * Reduces a list of translate values to a string that can be used within
 
2977  * @param {string} translateFn
 
2978  * @param {!Array<string>} values
 
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 + ')';
 
2994  * Sets the panel position based on the created panel element and best x/y
 
2996  * @param {!angular.JQLite} panelEl
 
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');
 
3003   // Only calculate the position if necessary.
 
3004   if (this._absolute) {
 
3005     this._setTransform(panelEl);
 
3009   if (this._actualPosition) {
 
3010     this._calculatePanelPosition(panelEl, this._actualPosition);
 
3011     this._setTransform(panelEl);
 
3012     this._constrainToViewport(panelEl);
 
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);
 
3021     if (this._isOnscreen(panelEl)) {
 
3026   this._constrainToViewport(panelEl);
 
3031  * Constrains a panel's position to the viewport.
 
3032  * @param {!angular.JQLite} panelEl
 
3035 MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
 
3036   var margin = MdPanelPosition.viewportMargin;
 
3037   var initialTop = this._top;
 
3038   var initialLeft = this._left;
 
3040   if (this.getTop()) {
 
3041     var top = parseInt(this.getTop());
 
3042     var bottom = panelEl[0].offsetHeight + top;
 
3043     var viewportHeight = this._$window.innerHeight;
 
3046       this._top = margin + 'px';
 
3047     } else if (bottom > viewportHeight) {
 
3048       this._top = top - (bottom - viewportHeight + margin) + 'px';
 
3052   if (this.getLeft()) {
 
3053     var left = parseInt(this.getLeft());
 
3054     var right = panelEl[0].offsetWidth + left;
 
3055     var viewportWidth = this._$window.innerWidth;
 
3057     if (left < margin) {
 
3058       this._left = margin + 'px';
 
3059     } else if (right > viewportWidth) {
 
3060       this._left = left - (right - viewportWidth + margin) + 'px';
 
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
 
3073  * Switches between 'start' and 'end'.
 
3074  * @param {string} position Horizontal position of the panel
 
3075  * @returns {string} Reversed position
 
3078 MdPanelPosition.prototype._reverseXPosition = function(position) {
 
3079   if (position === MdPanelPosition.xPosition.CENTER) {
 
3083   var start = 'start';
 
3086   return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
 
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
 
3096 MdPanelPosition.prototype._bidi = function(position) {
 
3097   return this._isRTL ? this._reverseXPosition(position) : position;
 
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
 
3108 MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
 
3110   var panelBounds = panelEl[0].getBoundingClientRect();
 
3111   var panelWidth = panelBounds.width;
 
3112   var panelHeight = panelBounds.height;
 
3114   var targetBounds = this._relativeToEl[0].getBoundingClientRect();
 
3116   var targetLeft = targetBounds.left;
 
3117   var targetRight = targetBounds.right;
 
3118   var targetWidth = targetBounds.width;
 
3120   switch (this._bidi(position.x)) {
 
3121     case MdPanelPosition.xPosition.OFFSET_START:
 
3122       this._left = targetLeft - panelWidth + 'px';
 
3124     case MdPanelPosition.xPosition.ALIGN_END:
 
3125       this._left = targetRight - panelWidth + 'px';
 
3127     case MdPanelPosition.xPosition.CENTER:
 
3128       var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
 
3129       this._left = left + 'px';
 
3131     case MdPanelPosition.xPosition.ALIGN_START:
 
3132       this._left = targetLeft + 'px';
 
3134     case MdPanelPosition.xPosition.OFFSET_END:
 
3135       this._left = targetRight + 'px';
 
3139   var targetTop = targetBounds.top;
 
3140   var targetBottom = targetBounds.bottom;
 
3141   var targetHeight = targetBounds.height;
 
3143   switch (position.y) {
 
3144     case MdPanelPosition.yPosition.ABOVE:
 
3145       this._top = targetTop - panelHeight + 'px';
 
3147     case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
 
3148       this._top = targetBottom - panelHeight + 'px';
 
3150     case MdPanelPosition.yPosition.CENTER:
 
3151       var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
 
3152       this._top = top + 'px';
 
3154     case MdPanelPosition.yPosition.ALIGN_TOPS:
 
3155       this._top = targetTop + 'px';
 
3157     case MdPanelPosition.yPosition.BELOW:
 
3158       this._top = targetBottom + 'px';
 
3164 /*****************************************************************************
 
3165  *                               MdPanelAnimation                            *
 
3166  *****************************************************************************/
 
3170  * Animation configuration object. To use, create an MdPanelAnimation with the
 
3171  * desired properties, then pass the object as part of $mdPanel creation.
 
3175  * var panelAnimation = new MdPanelAnimation()
 
3176  *     .openFrom(myButtonEl)
 
3177  *     .closeTo('.my-button')
 
3178  *     .withAnimation($mdPanel.animation.SCALE);
 
3181  *   animation: panelAnimation
 
3184  * @param {!angular.$injector} $injector
 
3185  * @final @constructor
 
3187 function MdPanelAnimation($injector) {
 
3188   /** @private @const {!angular.$mdUtil} */
 
3189   this._$mdUtil = $injector.get('$mdUtil');
 
3192    * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
 
3198    * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
 
3203   /** @private {string|{open: string, close: string}} */
 
3204   this._animationClass = '';
 
3206   /** @private {number} */
 
3209   /** @private {number} */
 
3210   this._closeDuration;
 
3212   /** @private {number|{open: number, close: number}} */
 
3218  * Possible default animations.
 
3221 MdPanelAnimation.animation = {
 
3222   SLIDE: 'md-panel-animate-slide',
 
3223   SCALE: 'md-panel-animate-scale',
 
3224   FADE: 'md-panel-animate-fade'
 
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}
 
3236 MdPanelAnimation.prototype.openFrom = function(openFrom) {
 
3237   // Check if 'openFrom' is an Event.
 
3238   openFrom = openFrom.target ? openFrom.target : openFrom;
 
3240   this._openFrom = this._getPanelAnimationTarget(openFrom);
 
3242   if (!this._closeTo) {
 
3243     this._closeTo = this._openFrom;
 
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
 
3253  * @param {string|!Element|{top: number, left: number}} closeTo
 
3254  * @returns {!MdPanelAnimation}
 
3256 MdPanelAnimation.prototype.closeTo = function(closeTo) {
 
3257   this._closeTo = this._getPanelAnimationTarget(closeTo);
 
3263  * Specifies the duration of the animation in milliseconds.
 
3264  * @param {number|{open: number, close: number}} duration
 
3265  * @returns {!MdPanelAnimation}
 
3267 MdPanelAnimation.prototype.duration = function(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);
 
3277   // Save the original value so it can be passed to the backdrop.
 
3278   this._rawDuration = duration;
 
3282   function toSeconds(value) {
 
3283     if (angular.isNumber(value)) return value / 1000;
 
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}}
 
3294 MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
 
3295   if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
 
3299         top: location.top || 0,
 
3300         left: location.left || 0
 
3304     return this._getBoundingClientRect(getElement(location));
 
3310  * Specifies the animation class.
 
3312  * There are several default animations that can be used:
 
3313  * (MdPanelAnimation.animation)
 
3314  *   SLIDE: The panel slides in and out from the specified
 
3316  *   SCALE: The panel scales in and out.
 
3317  *   FADE: The panel fades in and out.
 
3319  * @param {string|{open: string, close: string}} cssClass
 
3320  * @returns {!MdPanelAnimation}
 
3322 MdPanelAnimation.prototype.withAnimation = function(cssClass) {
 
3323   this._animationClass = cssClass;
 
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.
 
3334 MdPanelAnimation.prototype.animateOpen = function(panelEl) {
 
3335   var animator = this._$mdUtil.dom.animator;
 
3337   this._fixBounds(panelEl);
 
3338   var animationOptions = {};
 
3340   // Include the panel transformations when calculating the animations.
 
3341   var panelTransform = panelEl[0].style.transform || '';
 
3343   var openFrom = animator.toTransformCss(panelTransform);
 
3344   var openTo = animator.toTransformCss(panelTransform);
 
3346   switch (this._animationClass) {
 
3347     case MdPanelAnimation.animation.SLIDE:
 
3348       // Slide should start with opacity: 1.
 
3349       panelEl.css('opacity', '1');
 
3351       animationOptions = {
 
3352         transitionInClass: '_md-panel-animate-enter'
 
3355       var openSlide = animator.calculateSlideToOrigin(
 
3356               panelEl, this._openFrom) || '';
 
3357       openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
 
3360     case MdPanelAnimation.animation.SCALE:
 
3361       animationOptions = {
 
3362         transitionInClass: '_md-panel-animate-enter'
 
3365       var openScale = animator.calculateZoomToOrigin(
 
3366               panelEl, this._openFrom) || '';
 
3367       openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
 
3370     case MdPanelAnimation.animation.FADE:
 
3371       animationOptions = {
 
3372         transitionInClass: '_md-panel-animate-enter'
 
3377       if (angular.isString(this._animationClass)) {
 
3378         animationOptions = {
 
3379           transitionInClass: this._animationClass
 
3382         animationOptions = {
 
3383           transitionInClass: this._animationClass['open'],
 
3384           transitionOutClass: this._animationClass['close'],
 
3389   animationOptions.duration = this._openDuration;
 
3392       .translate3d(panelEl, openFrom, openTo, animationOptions);
 
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.
 
3402 MdPanelAnimation.prototype.animateClose = function(panelEl) {
 
3403   var animator = this._$mdUtil.dom.animator;
 
3404   var reverseAnimationOptions = {};
 
3406   // Include the panel transformations when calculating the animations.
 
3407   var panelTransform = panelEl[0].style.transform || '';
 
3409   var closeFrom = animator.toTransformCss(panelTransform);
 
3410   var closeTo = animator.toTransformCss(panelTransform);
 
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'
 
3420       var closeSlide = animator.calculateSlideToOrigin(
 
3421               panelEl, this._closeTo) || '';
 
3422       closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
 
3425     case MdPanelAnimation.animation.SCALE:
 
3426       reverseAnimationOptions = {
 
3427         transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
 
3430       var closeScale = animator.calculateZoomToOrigin(
 
3431               panelEl, this._closeTo) || '';
 
3432       closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
 
3435     case MdPanelAnimation.animation.FADE:
 
3436       reverseAnimationOptions = {
 
3437         transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
 
3442       if (angular.isString(this._animationClass)) {
 
3443         reverseAnimationOptions = {
 
3444           transitionOutClass: this._animationClass
 
3447         reverseAnimationOptions = {
 
3448           transitionInClass: this._animationClass['close'],
 
3449           transitionOutClass: this._animationClass['open']
 
3454   reverseAnimationOptions.duration = this._closeDuration;
 
3457       .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
 
3462  * Set the height and width to match the panel if not provided.
 
3463  * @param {!angular.JQLite} panelEl
 
3466 MdPanelAnimation.prototype._fixBounds = function(panelEl) {
 
3467   var panelWidth = panelEl[0].offsetWidth;
 
3468   var panelHeight = panelEl[0].offsetHeight;
 
3470   if (this._openFrom && this._openFrom.bounds.height == null) {
 
3471     this._openFrom.bounds.height = panelHeight;
 
3473   if (this._openFrom && this._openFrom.bounds.width == null) {
 
3474     this._openFrom.bounds.width = panelWidth;
 
3476   if (this._closeTo && this._closeTo.bounds.height == null) {
 
3477     this._closeTo.bounds.height = panelHeight;
 
3479   if (this._closeTo && this._closeTo.bounds.width == null) {
 
3480     this._closeTo.bounds.width = panelWidth;
 
3486  * Identify the bounding RECT for the target element.
 
3487  * @param {!angular.JQLite} element
 
3488  * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
 
3491 MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
 
3492   if (element instanceof angular.element) {
 
3495       bounds: element[0].getBoundingClientRect()
 
3501 /*****************************************************************************
 
3503  *****************************************************************************/
 
3507  * Returns the angular element associated with a css selector or element.
 
3508  * @param el {string|!angular.JQLite|!Element}
 
3509  * @returns {!angular.JQLite}
 
3511 function getElement(el) {
 
3512   var queryResult = angular.isString(el) ?
 
3513       document.querySelector(el) : el;
 
3514   return angular.element(queryResult);
 
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}}
 
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 };
 
3533   if (openIndex > -1 && closeIndex > -1) {
 
3534     var parsedValues = transform
 
3535       .substring(openIndex + 1, closeIndex)
 
3539     output.x = parseInt(parsedValues[0]);
 
3540     output.y = parseInt(parsedValues[1]);
 
3546 ngmaterial.components.panel = angular.module("material.components.panel");