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