arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/closure/panel/panel.js
www/lib/angular-material/modules/closure/panel/panel.jsRaw
   1/*!
   2 * Angular Material Design
   3 * https://github.com/angular/material
   4 * @license MIT
   5 * v1.1.1
   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 = ["$rootElement", "$rootScope", "$injector", "$window"];
  15angular
  16    .module('material.components.panel', [
  17      'material.core',
  18      'material.components.backdrop'
  19    ])
  20    .service('$mdPanel', MdPanelService);
  21
  22
  23/*****************************************************************************
  24 *                            PUBLIC DOCUMENTATION                           *
  25 *****************************************************************************/
  26
  27/**
  28 * @ngdoc service
  29 * @name $mdPanel
  30 * @module material.components.panel
  31 *
  32 * @description
  33 * `$mdPanel` is a robust, low-level service for creating floating panels on
  34 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
  35 *
  36 * @usage
  37 * <hljs lang="js">
  38 * (function(angular, undefined) {
  39 *   ‘use strict’;
  40 *
  41 *   angular
  42 *       .module('demoApp', ['ngMaterial'])
  43 *       .controller('DemoDialogController', DialogController);
  44 *
  45 *   var panelRef;
  46 *
  47 *   function showPanel($event) {
  48 *     var panelPosition = $mdPanel.newPanelPosition()
  49 *         .absolute()
  50 *         .top('50%')
  51 *         .left('50%');
  52 *
  53 *     var panelAnimation = $mdPanel.newPanelAnimation()
  54 *         .targetEvent($event)
  55 *         .defaultAnimation('md-panel-animate-fly')
  56 *         .closeTo('.show-button');
  57 *
  58 *     var config = {
  59 *       attachTo: angular.element(document.body),
  60 *       controller: DialogController,
  61 *       controllerAs: 'ctrl',
  62 *       position: panelPosition,
  63 *       animation: panelAnimation,
  64 *       targetEvent: $event,
  65 *       templateUrl: 'dialog-template.html',
  66 *       clickOutsideToClose: true,
  67 *       escapeToClose: true,
  68 *       focusOnOpen: true
  69 *     }
  70 *
  71 *     $mdPanel.open(config)
  72 *         .then(function(result) {
  73 *           panelRef = result;
  74 *         });
  75 *   }
  76 *
  77 *   function DialogController(MdPanelRef, toppings) {
  78 *     var toppings;
  79 *
  80 *     function closeDialog() {
  81 *       MdPanelRef && MdPanelRef.close();
  82 *     }
  83 *   }
  84 * })(angular);
  85 * </hljs>
  86 */
  87
  88/**
  89 * @ngdoc method
  90 * @name $mdPanel#create
  91 * @description
  92 * Creates a panel with the specified options.
  93 *
  94 * @param config {!Object=} Specific configuration object that may contain the
  95 *     following properties:
  96 *
  97 *   - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
  98 *     the created panel is added to a tracked panels object. Any subsequent
  99 *     requests made to create a panel with that ID are ignored. This is useful
 100 *     in having the panel service not open multiple panels from the same user
 101 *     interaction when there is no backdrop and events are propagated. Defaults
 102 *     to an arbitrary string that is not tracked.
 103 *   - `template` - `{string=}`: HTML template to show in the panel. This
 104 *     **must** be trusted HTML with respect to Angular’s
 105 *     [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
 106 *   - `templateUrl` - `{string=}`: The URL that will be used as the content of
 107 *     the panel.
 108 *   - `controller` - `{(function|string)=}`: The controller to associate with
 109 *     the panel. The controller can inject a reference to the returned
 110 *     panelRef, which allows the panel to be closed, hidden, and shown. Any
 111 *     fields passed in through locals or resolve will be bound to the
 112 *     controller.
 113 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on
 114 *     the scope.
 115 *   - `bindToController` - `{boolean=}`: Binds locals to the controller
 116 *     instead of passing them in. Defaults to true, as this is a best
 117 *     practice.
 118 *   - `locals` - `{Object=}`: An object containing key/value pairs. The keys
 119 *     will be used as names of values to inject into the controller. For
 120 *     example, `locals: {three: 3}` would inject `three` into the controller,
 121 *     with the value 3.
 122 *   - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
 123 *     values. The panel will not open until all of the promises resolve.
 124 *   - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
 125 *     attach the panel to. Defaults to appending to the root element of the
 126 *     application.
 127 *   - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
 128 *     events should be allowed to propagate 'go through' the container, aka the
 129 *     wrapper, of the panel. Defaults to false.
 130 *   - `panelClass` - `{string=}`: A css class to apply to the panel element.
 131 *     This class should define any borders, box-shadow, etc. for the panel.
 132 *   - `zIndex` - `{number=}`: The z-index to place the panel at.
 133 *     Defaults to 80.
 134 *   - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
 135 *     specifies the alignment of the panel. For more information, see
 136 *     `MdPanelPosition`.
 137 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
 138 *     outside the panel to close it. Defaults to false.
 139 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
 140 *     close the panel. Defaults to false.
 141 *   - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
 142 *     panel. If `trapFocus` is true, the user will not be able to interact
 143 *     with the rest of the page until the panel is dismissed. Defaults to
 144 *     false.
 145 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
 146 *     open. Only disable if focusing some other way, as focus management is
 147 *     required for panels to be accessible. Defaults to true.
 148 *   - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
 149 *     Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
 150 *     to false.
 151 *   - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
 152 *     specifies the animation of the panel. For more information, see
 153 *     `MdPanelAnimation`.
 154 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
 155 *     behind the panel. Defaults to false.
 156 *   - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
 157 *     page behind the panel. Defaults to false.
 158 *   - `onDomAdded` - `{function=}`: Callback function used to announce when
 159 *     the panel is added to the DOM.
 160 *   - `onOpenComplete` - `{function=}`: Callback function used to announce
 161 *     when the open() action is finished.
 162 *   - `onRemoving` - `{function=}`: Callback function used to announce the
 163 *     close/hide() action is starting.
 164 *   - `onDomRemoved` - `{function=}`: Callback function used to announce when
 165 *     the panel is removed from the DOM.
 166 *   - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
 167 *     on when the panel closes. This is commonly the element which triggered
 168 *     the opening of the panel. If you do not use `origin`, you need to control
 169 *     the focus manually.
 170 *
 171 * @returns {!MdPanelRef} panelRef
 172 */
 173
 174
 175/**
 176 * @ngdoc method
 177 * @name $mdPanel#open
 178 * @description
 179 * Calls the create method above, then opens the panel. This is a shortcut for
 180 * creating and then calling open manually. If custom methods need to be
 181 * called when the panel is added to the DOM or opened, do not use this method.
 182 * Instead create the panel, chain promises on the domAdded and openComplete
 183 * methods, and call open from the returned panelRef.
 184 *
 185 * @param {!Object=} config Specific configuration object that may contain
 186 *     the properties defined in `$mdPanel.create`.
 187 * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
 188 *     to an instance of the panel.
 189 */
 190
 191
 192/**
 193 * @ngdoc method
 194 * @name $mdPanel#newPanelPosition
 195 * @description
 196 * Returns a new instance of the MdPanelPosition object. Use this to create
 197 * the position config object.
 198 *
 199 * @returns {!MdPanelPosition} panelPosition
 200 */
 201
 202
 203/**
 204 * @ngdoc method
 205 * @name $mdPanel#newPanelAnimation
 206 * @description
 207 * Returns a new instance of the MdPanelAnimation object. Use this to create
 208 * the animation config object.
 209 *
 210 * @returns {!MdPanelAnimation} panelAnimation
 211 */
 212
 213
 214/*****************************************************************************
 215 *                                 MdPanelRef                                *
 216 *****************************************************************************/
 217
 218
 219/**
 220 * @ngdoc type
 221 * @name MdPanelRef
 222 * @module material.components.panel
 223 * @description
 224 * A reference to a created panel. This reference contains a unique id for the
 225 * panel, along with the following properties:
 226 *
 227 *   - `id` - `{string}`: The unique id for the panel. This id is used to track
 228 *     when a panel was interacted with.
 229 *   - `config` - `{!Object=}`: The entire config object that was used in
 230 *     create.
 231 *   - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
 232 *     Visibility to the user does not factor into isAttached.
 233 *   - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
 234 *     panel. This property is added in order to have access to the `addClass`,
 235 *     `removeClass`, `toggleClass`, etc methods.
 236 *   - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
 237 *     in order to have access to the `addClass`, `removeClass`, `toggleClass`,
 238 *     etc methods.
 239 */
 240
 241/**
 242 * @ngdoc method
 243 * @name MdPanelRef#open
 244 * @description
 245 * Attaches and shows the panel.
 246 *
 247 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 248 *     opened.
 249 */
 250
 251/**
 252 * @ngdoc method
 253 * @name MdPanelRef#close
 254 * @description
 255 * Hides and detaches the panel. Note that this will **not** destroy the panel.
 256 * If you don't intend on using the panel again, call the {@link #destroy
 257 * destroy} method afterwards.
 258 *
 259 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 260 *     closed.
 261 */
 262
 263/**
 264 * @ngdoc method
 265 * @name MdPanelRef#attach
 266 * @description
 267 * Create the panel elements and attach them to the DOM. The panel will be
 268 * hidden by default.
 269 *
 270 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 271 *     attached.
 272 */
 273
 274/**
 275 * @ngdoc method
 276 * @name MdPanelRef#detach
 277 * @description
 278 * Removes the panel from the DOM. This will NOT hide the panel before removing
 279 * it.
 280 *
 281 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
 282 *     detached.
 283 */
 284
 285/**
 286 * @ngdoc method
 287 * @name MdPanelRef#show
 288 * @description
 289 * Shows the panel.
 290 *
 291 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 292 *     shown and animations are completed.
 293 */
 294
 295/**
 296 * @ngdoc method
 297 * @name MdPanelRef#hide
 298 * @description
 299 * Hides the panel.
 300 *
 301 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
 302 *     hidden and animations are completed.
 303 */
 304
 305/**
 306 * @ngdoc method
 307 * @name MdPanelRef#destroy
 308 * @description
 309 * Destroys the panel. The panel cannot be opened again after this is called.
 310 */
 311
 312/**
 313 * @ngdoc method
 314 * @name MdPanelRef#addClass
 315 * @deprecated
 316 * This method is in the process of being deprecated in favor of using the panel
 317 * and container JQLite elements that are referenced in the MdPanelRef object.
 318 * Full deprecation is scheduled for material 1.2.
 319 * @description
 320 * Adds a class to the panel. DO NOT use this hide/show the panel.
 321 *
 322 * @param {string} newClass class to be added.
 323 * @param {boolean} toElement Whether or not to add the class to the panel
 324 *     element instead of the container.
 325 */
 326
 327/**
 328 * @ngdoc method
 329 * @name MdPanelRef#removeClass
 330 * @deprecated
 331 * This method is in the process of being deprecated in favor of using the panel
 332 * and container JQLite elements that are referenced in the MdPanelRef object.
 333 * Full deprecation is scheduled for material 1.2.
 334 * @description
 335 * Removes a class from the panel. DO NOT use this to hide/show the panel.
 336 *
 337 * @param {string} oldClass Class to be removed.
 338 * @param {boolean} fromElement Whether or not to remove the class from the
 339 *     panel element instead of the container.
 340 */
 341
 342/**
 343 * @ngdoc method
 344 * @name MdPanelRef#toggleClass
 345 * @deprecated
 346 * This method is in the process of being deprecated in favor of using the panel
 347 * and container JQLite elements that are referenced in the MdPanelRef object.
 348 * Full deprecation is scheduled for material 1.2.
 349 * @description
 350 * Toggles a class on the panel. DO NOT use this to hide/show the panel.
 351 *
 352 * @param {string} toggleClass Class to be toggled.
 353 * @param {boolean} onElement Whether or not to remove the class from the panel
 354 *     element instead of the container.
 355 */
 356
 357/**
 358 * @ngdoc method
 359 * @name MdPanelRef#updatePosition
 360 * @description
 361 * Updates the position configuration of a panel. Use this to update the
 362 * position of a panel that is open, without having to close and re-open the
 363 * panel.
 364 *
 365 * @param {!MdPanelPosition} position
 366 */
 367
 368
 369/*****************************************************************************
 370 *                               MdPanelPosition                            *
 371 *****************************************************************************/
 372
 373
 374/**
 375 * @ngdoc type
 376 * @name MdPanelPosition
 377 * @module material.components.panel
 378 * @description
 379 *
 380 * Object for configuring the position of the panel.
 381 *
 382 * @usage
 383 *
 384 * #### Centering the panel
 385 *
 386 * <hljs lang="js">
 387 * new MdPanelPosition().absolute().center();
 388 * </hljs>
 389 *
 390 * #### Overlapping the panel with an element
 391 *
 392 * <hljs lang="js">
 393 * new MdPanelPosition()
 394 *     .relativeTo(someElement)
 395 *     .addPanelPosition(
 396 *       $mdPanel.xPosition.ALIGN_START,
 397 *       $mdPanel.yPosition.ALIGN_TOPS
 398 *     );
 399 * </hljs>
 400 *
 401 * #### Aligning the panel with the bottom of an element
 402 *
 403 * <hljs lang="js">
 404 * new MdPanelPosition()
 405 *     .relativeTo(someElement)
 406 *     .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
 407 * </hljs>
 408 */
 409
 410/**
 411 * @ngdoc method
 412 * @name MdPanelPosition#absolute
 413 * @description
 414 * Positions the panel absolutely relative to the parent element. If the parent
 415 * is document.body, this is equivalent to positioning the panel absolutely
 416 * within the viewport.
 417 *
 418 * @returns {!MdPanelPosition}
 419 */
 420
 421/**
 422 * @ngdoc method
 423 * @name MdPanelPosition#relativeTo
 424 * @description
 425 * Positions the panel relative to a specific element.
 426 *
 427 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
 428 *     or angular element to position the panel with respect to.
 429 * @returns {!MdPanelPosition}
 430 */
 431
 432/**
 433 * @ngdoc method
 434 * @name MdPanelPosition#top
 435 * @description
 436 * Sets the value of `top` for the panel. Clears any previously set vertical
 437 * position.
 438 *
 439 * @param {string=} top Value of `top`. Defaults to '0'.
 440 * @returns {!MdPanelPosition}
 441 */
 442
 443/**
 444 * @ngdoc method
 445 * @name MdPanelPosition#bottom
 446 * @description
 447 * Sets the value of `bottom` for the panel. Clears any previously set vertical
 448 * position.
 449 *
 450 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
 451 * @returns {!MdPanelPosition}
 452 */
 453
 454/**
 455 * @ngdoc method
 456 * @name MdPanelPosition#start
 457 * @description
 458 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
 459 * `rtl`. Clears any previously set horizontal position.
 460 *
 461 * @param {string=} start Value of position. Defaults to '0'.
 462 * @returns {!MdPanelPosition}
 463 */
 464
 465/**
 466 * @ngdoc method
 467 * @name MdPanelPosition#end
 468 * @description
 469 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
 470 * Clears any previously set horizontal position.
 471 *
 472 * @param {string=} end Value of position. Defaults to '0'.
 473 * @returns {!MdPanelPosition}
 474 */
 475
 476/**
 477 * @ngdoc method
 478 * @name MdPanelPosition#left
 479 * @description
 480 * Sets the value of `left` for the panel. Clears any previously set
 481 * horizontal position.
 482 *
 483 * @param {string=} left Value of `left`. Defaults to '0'.
 484 * @returns {!MdPanelPosition}
 485 */
 486
 487/**
 488 * @ngdoc method
 489 * @name MdPanelPosition#right
 490 * @description
 491 * Sets the value of `right` for the panel. Clears any previously set
 492 * horizontal position.
 493 *
 494 * @param {string=} right Value of `right`. Defaults to '0'.
 495 * @returns {!MdPanelPosition}
 496 */
 497
 498/**
 499 * @ngdoc method
 500 * @name MdPanelPosition#centerHorizontally
 501 * @description
 502 * Centers the panel horizontally in the viewport. Clears any previously set
 503 * horizontal position.
 504 *
 505 * @returns {!MdPanelPosition}
 506 */
 507
 508/**
 509 * @ngdoc method
 510 * @name MdPanelPosition#centerVertically
 511 * @description
 512 * Centers the panel vertically in the viewport. Clears any previously set
 513 * vertical position.
 514 *
 515 * @returns {!MdPanelPosition}
 516 */
 517
 518/**
 519 * @ngdoc method
 520 * @name MdPanelPosition#center
 521 * @description
 522 * Centers the panel horizontally and vertically in the viewport. This is
 523 * equivalent to calling both `centerHorizontally` and `centerVertically`.
 524 * Clears any previously set horizontal and vertical positions.
 525 *
 526 * @returns {!MdPanelPosition}
 527 */
 528
 529/**
 530 * @ngdoc method
 531 * @name MdPanelPosition#addPanelPosition
 532 * @description
 533 * Sets the x and y position for the panel relative to another element. Can be
 534 * called multiple times to specify an ordered list of panel positions. The
 535 * first position which allows the panel to be completely on-screen will be
 536 * chosen; the last position will be chose whether it is on-screen or not.
 537 *
 538 * xPosition must be one of the following values available on
 539 * $mdPanel.xPosition:
 540 *
 541 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
 542 *
 543 *    *************
 544 *    *           *
 545 *    *   PANEL   *
 546 *    *           *
 547 *    *************
 548 *   A B    C    D E
 549 *
 550 * A: OFFSET_START (for LTR displays)
 551 * B: ALIGN_START (for LTR displays)
 552 * C: CENTER
 553 * D: ALIGN_END (for LTR displays)
 554 * E: OFFSET_END (for LTR displays)
 555 *
 556 * yPosition must be one of the following values available on
 557 * $mdPanel.yPosition:
 558 *
 559 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
 560 *
 561 *   F
 562 *   G *************
 563 *     *           *
 564 *   H *   PANEL   *
 565 *     *           *
 566 *   I *************
 567 *   J
 568 *
 569 * F: BELOW
 570 * G: ALIGN_TOPS
 571 * H: CENTER
 572 * I: ALIGN_BOTTOMS
 573 * J: ABOVE
 574 *
 575 * @param {string} xPosition
 576 * @param {string} yPosition
 577 * @returns {!MdPanelPosition}
 578 */
 579
 580/**
 581 * @ngdoc method
 582 * @name MdPanelPosition#withOffsetX
 583 * @description
 584 * Sets the value of the offset in the x-direction.
 585 *
 586 * @param {string} offsetX
 587 * @returns {!MdPanelPosition}
 588 */
 589
 590/**
 591 * @ngdoc method
 592 * @name MdPanelPosition#withOffsetY
 593 * @description
 594 * Sets the value of the offset in the y-direction.
 595 *
 596 * @param {string} offsetY
 597 * @returns {!MdPanelPosition}
 598 */
 599
 600
 601/*****************************************************************************
 602 *                               MdPanelAnimation                            *
 603 *****************************************************************************/
 604
 605
 606/**
 607 * @ngdoc object
 608 * @name MdPanelAnimation
 609 * @description
 610 * Animation configuration object. To use, create an MdPanelAnimation with the
 611 * desired properties, then pass the object as part of $mdPanel creation.
 612 *
 613 * Example:
 614 *
 615 * var panelAnimation = new MdPanelAnimation()
 616 *     .openFrom(myButtonEl)
 617 *     .closeTo('.my-button')
 618 *     .withAnimation($mdPanel.animation.SCALE);
 619 *
 620 * $mdPanel.create({
 621 *   animation: panelAnimation
 622 * });
 623 */
 624
 625/**
 626 * @ngdoc method
 627 * @name MdPanelAnimation#openFrom
 628 * @description
 629 * Specifies where to start the open animation. `openFrom` accepts a
 630 * click event object, query selector, DOM element, or a Rect object that
 631 * is used to determine the bounds. When passed a click event, the location
 632 * of the click will be used as the position to start the animation.
 633 *
 634 * @param {string|!Element|!Event|{top: number, left: number}}
 635 * @returns {!MdPanelAnimation}
 636 */
 637
 638/**
 639 * @ngdoc method
 640 * @name MdPanelAnimation#closeTo
 641 * @description
 642 * Specifies where to animate the panel close. `closeTo` accepts a
 643 * query selector, DOM element, or a Rect object that is used to determine
 644 * the bounds.
 645 *
 646 * @param {string|!Element|{top: number, left: number}}
 647 * @returns {!MdPanelAnimation}
 648 */
 649
 650/**
 651 * @ngdoc method
 652 * @name MdPanelAnimation#withAnimation
 653 * @description
 654 * Specifies the animation class.
 655 *
 656 * There are several default animations that can be used:
 657 * ($mdPanel.animation)
 658 *   SLIDE: The panel slides in and out from the specified
 659 *       elements. It will not fade in or out.
 660 *   SCALE: The panel scales in and out. Slide and fade are
 661 *       included in this animation.
 662 *   FADE: The panel fades in and out.
 663 *
 664 * Custom classes will by default fade in and out unless
 665 * "transition: opacity 1ms" is added to the to custom class.
 666 *
 667 * @param {string|{open: string, close: string}} cssClass
 668 * @returns {!MdPanelAnimation}
 669 */
 670
 671
 672/*****************************************************************************
 673 *                                IMPLEMENTATION                             *
 674 *****************************************************************************/
 675
 676
 677// Default z-index for the panel.
 678var defaultZIndex = 80;
 679var MD_PANEL_HIDDEN = '_md-panel-hidden';
 680
 681var FOCUS_TRAP_TEMPLATE = angular.element(
 682    '<div class="_md-panel-focus-trap" tabindex="0"></div>');
 683
 684
 685/**
 686 * A service that is used for controlling/displaying panels on the screen.
 687 * @param {!angular.JQLite} $rootElement
 688 * @param {!angular.Scope} $rootScope
 689 * @param {!angular.$injector} $injector
 690 * @param {!angular.$window} $window
 691 * @final @constructor ngInject
 692 */
 693function MdPanelService($rootElement, $rootScope, $injector, $window) {
 694  /**
 695   * Default config options for the panel.
 696   * Anything angular related needs to be done later. Therefore
 697   *     scope: $rootScope.$new(true),
 698   *     attachTo: $rootElement,
 699   * are added later.
 700   * @private {!Object}
 701   */
 702  this._defaultConfigOptions = {
 703    bindToController: true,
 704    clickOutsideToClose: false,
 705    disableParentScroll: false,
 706    escapeToClose: false,
 707    focusOnOpen: true,
 708    fullscreen: false,
 709    hasBackdrop: false,
 710    propagateContainerEvents: false,
 711    transformTemplate: angular.bind(this, this._wrapTemplate),
 712    trapFocus: false,
 713    zIndex: defaultZIndex
 714  };
 715
 716  /** @private {!Object} */
 717  this._config = {};
 718
 719  /** @private @const */
 720  this._$rootElement = $rootElement;
 721
 722  /** @private @const */
 723  this._$rootScope = $rootScope;
 724
 725  /** @private @const */
 726  this._$injector = $injector;
 727
 728  /** @private @const */
 729  this._$window = $window;
 730
 731  /** @private {!Object<string, !MdPanelRef>} */
 732  this._trackedPanels = {};
 733
 734  /**
 735   * Default animations that can be used within the panel.
 736   * @type {enum}
 737   */
 738  this.animation = MdPanelAnimation.animation;
 739
 740  /**
 741   * Possible values of xPosition for positioning the panel relative to
 742   * another element.
 743   * @type {enum}
 744   */
 745  this.xPosition = MdPanelPosition.xPosition;
 746
 747  /**
 748   * Possible values of yPosition for positioning the panel relative to
 749   * another element.
 750   * @type {enum}
 751   */
 752  this.yPosition = MdPanelPosition.yPosition;
 753}
 754
 755
 756/**
 757 * Creates a panel with the specified options.
 758 * @param {!Object=} config Configuration object for the panel.
 759 * @returns {!MdPanelRef}
 760 */
 761MdPanelService.prototype.create = function(config) {
 762  config = config || {};
 763
 764  // If the passed-in config contains an ID and the ID is within _trackedPanels,
 765  // return the tracked panel.
 766  if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
 767    return this._trackedPanels[config.id];
 768  }
 769
 770  // If no ID is set within the passed-in config, then create an arbitrary ID.
 771  this._config = {
 772    id: config.id || 'panel_' + this._$injector.get('$mdUtil').nextUid(),
 773    scope: this._$rootScope.$new(true),
 774    attachTo: this._$rootElement
 775  };
 776  angular.extend(this._config, this._defaultConfigOptions, config);
 777
 778  var panelRef = new MdPanelRef(this._config, this._$injector);
 779  this._trackedPanels[config.id] = panelRef;
 780
 781  return panelRef;
 782};
 783
 784
 785/**
 786 * Creates and opens a panel with the specified options.
 787 * @param {!Object=} config Configuration object for the panel.
 788 * @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
 789 */
 790MdPanelService.prototype.open = function(config) {
 791  var panelRef = this.create(config);
 792  return panelRef.open().then(function() {
 793    return panelRef;
 794  });
 795};
 796
 797
 798/**
 799 * Returns a new instance of the MdPanelPosition. Use this to create the
 800 * positioning object.
 801 * @returns {!MdPanelPosition}
 802 */
 803MdPanelService.prototype.newPanelPosition = function() {
 804  return new MdPanelPosition(this._$injector);
 805};
 806
 807
 808/**
 809 * Returns a new instance of the MdPanelAnimation. Use this to create the
 810 * animation object.
 811 * @returns {!MdPanelAnimation}
 812 */
 813MdPanelService.prototype.newPanelAnimation = function() {
 814  return new MdPanelAnimation(this._$injector);
 815};
 816
 817
 818/**
 819 * Wraps the users template in two elements, md-panel-outer-wrapper, which
 820 * covers the entire attachTo element, and md-panel, which contains only the
 821 * template. This allows the panel control over positioning, animations,
 822 * and similar properties.
 823 * @param {string} origTemplate The original template.
 824 * @returns {string} The wrapped template.
 825 * @private
 826 */
 827MdPanelService.prototype._wrapTemplate = function(origTemplate) {
 828  var template = origTemplate || '';
 829
 830  // The panel should be initially rendered offscreen so we can calculate
 831  // height and width for positioning.
 832  return '' +
 833      '<div class="md-panel-outer-wrapper">' +
 834      '  <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
 835      '</div>';
 836};
 837
 838
 839/*****************************************************************************
 840 *                                 MdPanelRef                                *
 841 *****************************************************************************/
 842
 843
 844/**
 845 * A reference to a created panel. This reference contains a unique id for the
 846 * panel, along with properties/functions used to control the panel.
 847 * @param {!Object} config
 848 * @param {!angular.$injector} $injector
 849 * @final @constructor
 850 */
 851function MdPanelRef(config, $injector) {
 852  // Injected variables.
 853  /** @private @const {!angular.$q} */
 854  this._$q = $injector.get('$q');
 855
 856  /** @private @const {!angular.$mdCompiler} */
 857  this._$mdCompiler = $injector.get('$mdCompiler');
 858
 859  /** @private @const {!angular.$mdConstant} */
 860  this._$mdConstant = $injector.get('$mdConstant');
 861
 862  /** @private @const {!angular.$mdUtil} */
 863  this._$mdUtil = $injector.get('$mdUtil');
 864
 865  /** @private @const {!angular.Scope} */
 866  this._$rootScope = $injector.get('$rootScope');
 867
 868  /** @private @const {!angular.$animate} */
 869  this._$animate = $injector.get('$animate');
 870
 871  /** @private @const {!MdPanelRef} */
 872  this._$mdPanel = $injector.get('$mdPanel');
 873
 874  /** @private @const {!angular.$log} */
 875  this._$log = $injector.get('$log');
 876
 877  /** @private @const {!angular.$window} */
 878  this._$window = $injector.get('$window');
 879
 880  /** @private @const {!Function} */
 881  this._$$rAF = $injector.get('$$rAF');
 882
 883  // Public variables.
 884  /**
 885   * Unique id for the panelRef.
 886   * @type {string}
 887   */
 888  this.id = config.id;
 889
 890  /** @type {!Object} */
 891  this.config = config;
 892
 893  /** @type {!angular.JQLite|undefined} */
 894  this.panelContainer;
 895
 896  /** @type {!angular.JQLite|undefined} */
 897  this.panelEl;
 898
 899  /**
 900   * Whether the panel is attached. This is synchronous. When attach is called,
 901   * isAttached is set to true. When detach is called, isAttached is set to
 902   * false.
 903   * @type {boolean}
 904   */
 905  this.isAttached = false;
 906
 907  // Private variables.
 908  /** @private {Array<function()>} */
 909  this._removeListeners = [];
 910
 911  /** @private {!angular.JQLite|undefined} */
 912  this._topFocusTrap;
 913
 914  /** @private {!angular.JQLite|undefined} */
 915  this._bottomFocusTrap;
 916
 917  /** @private {!$mdPanel|undefined} */
 918  this._backdropRef;
 919
 920  /** @private {Function?} */
 921  this._restoreScroll = null;
 922}
 923
 924
 925/**
 926 * Opens an already created and configured panel. If the panel is already
 927 * visible, does nothing.
 928 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
 929 *     the panel is opened and animations finish.
 930 */
 931MdPanelRef.prototype.open = function() {
 932  var self = this;
 933  return this._$q(function(resolve, reject) {
 934    var done = self._done(resolve, self);
 935    var show = self._simpleBind(self.show, self);
 936
 937    self.attach()
 938        .then(show)
 939        .then(done)
 940        .catch(reject);
 941  });
 942};
 943
 944
 945/**
 946 * Closes the panel.
 947 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
 948 *     the panel is closed and animations finish.
 949 */
 950MdPanelRef.prototype.close = function() {
 951  var self = this;
 952
 953  return this._$q(function(resolve, reject) {
 954    var done = self._done(resolve, self);
 955    var detach = self._simpleBind(self.detach, self);
 956
 957    self.hide()
 958        .then(detach)
 959        .then(done)
 960        .catch(reject);
 961  });
 962};
 963
 964
 965/**
 966 * Attaches the panel. The panel will be hidden afterwards.
 967 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
 968 *     the panel is attached.
 969 */
 970MdPanelRef.prototype.attach = function() {
 971  if (this.isAttached && this.panelEl) {
 972    return this._$q.when(this);
 973  }
 974
 975  var self = this;
 976  return this._$q(function(resolve, reject) {
 977    var done = self._done(resolve, self);
 978    var onDomAdded = self.config['onDomAdded'] || angular.noop;
 979    var addListeners = function(response) {
 980        self.isAttached = true;
 981        self._addEventListeners();
 982        return response;
 983    };
 984
 985    self._$q.all([
 986        self._createBackdrop(),
 987        self._createPanel()
 988            .then(addListeners)
 989            .catch(reject)
 990    ]).then(onDomAdded)
 991      .then(done)
 992      .catch(reject);
 993  });
 994};
 995
 996
 997/**
 998 * Only detaches the panel. Will NOT hide the panel first.
 999 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1000 *     the panel is detached.
1001 */
1002MdPanelRef.prototype.detach = function() {
1003  if (!this.isAttached) {
1004    return this._$q.when(this);
1005  }
1006
1007  var self = this;
1008  var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
1009
1010  var detachFn = function() {
1011    self._removeEventListeners();
1012
1013    // Remove the focus traps that we added earlier for keeping focus within
1014    // the panel.
1015    if (self._topFocusTrap && self._topFocusTrap.parentNode) {
1016      self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
1017    }
1018
1019    if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
1020      self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
1021    }
1022
1023    self.panelContainer.remove();
1024    self.isAttached = false;
1025    return self._$q.when(self);
1026  };
1027
1028  if (this._restoreScroll) {
1029    this._restoreScroll();
1030    this._restoreScroll = null;
1031  }
1032
1033  return this._$q(function(resolve, reject) {
1034    var done = self._done(resolve, self);
1035
1036    self._$q.all([
1037      detachFn(),
1038      self._backdropRef ? self._backdropRef.detach() : true
1039    ]).then(onDomRemoved)
1040      .then(done)
1041      .catch(reject);
1042  });
1043};
1044
1045
1046/**
1047 * Destroys the panel. The Panel cannot be opened again after this.
1048 */
1049MdPanelRef.prototype.destroy = function() {
1050  this.config.scope.$destroy();
1051  this.config.locals = null;
1052};
1053
1054
1055/**
1056 * Shows the panel.
1057 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1058 *     the panel has shown and animations finish.
1059 */
1060MdPanelRef.prototype.show = function() {
1061  if (!this.panelContainer) {
1062    return this._$q(function(resolve, reject) {
1063      reject('Panel does not exist yet. Call open() or attach().');
1064    });
1065  }
1066
1067  if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1068    return this._$q.when(this);
1069  }
1070
1071  var self = this;
1072  var animatePromise = function() {
1073    self.panelContainer.removeClass(MD_PANEL_HIDDEN);
1074    return self._animateOpen();
1075  };
1076
1077  return this._$q(function(resolve, reject) {
1078    var done = self._done(resolve, self);
1079    var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
1080
1081    self._$q.all([
1082      self._backdropRef ? self._backdropRef.show() : self,
1083      animatePromise().then(function() { self._focusOnOpen(); }, reject)
1084    ]).then(onOpenComplete)
1085      .then(done)
1086      .catch(reject);
1087  });
1088};
1089
1090
1091/**
1092 * Hides the panel.
1093 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1094 *     the panel has hidden and animations finish.
1095 */
1096MdPanelRef.prototype.hide = function() {
1097  if (!this.panelContainer) {
1098    return this._$q(function(resolve, reject) {
1099      reject('Panel does not exist yet. Call open() or attach().');
1100    });
1101  }
1102
1103  if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1104    return this._$q.when(this);
1105  }
1106
1107  var self = this;
1108
1109  return this._$q(function(resolve, reject) {
1110    var done = self._done(resolve, self);
1111    var onRemoving = self.config['onRemoving'] || angular.noop;
1112
1113    var focusOnOrigin = function() {
1114      var origin = self.config['origin'];
1115      if (origin) {
1116        getElement(origin).focus();
1117      }
1118    };
1119
1120    var hidePanel = function() {
1121      self.panelContainer.addClass(MD_PANEL_HIDDEN);
1122    };
1123
1124    self._$q.all([
1125      self._backdropRef ? self._backdropRef.hide() : self,
1126      self._animateClose()
1127          .then(onRemoving)
1128          .then(hidePanel)
1129          .then(focusOnOrigin)
1130          .catch(reject)
1131    ]).then(done, reject);
1132  });
1133};
1134
1135
1136/**
1137 * Add a class to the panel. DO NOT use this to hide/show the panel.
1138 * @deprecated
1139 * This method is in the process of being deprecated in favor of using the panel
1140 * and container JQLite elements that are referenced in the MdPanelRef object.
1141 * Full deprecation is scheduled for material 1.2.
1142 *
1143 * @param {string} newClass Class to be added.
1144 * @param {boolean} toElement Whether or not to add the class to the panel
1145 *     element instead of the container.
1146 */
1147MdPanelRef.prototype.addClass = function(newClass, toElement) {
1148  this._$log.warn(
1149      'The addClass method is in the process of being deprecated. ' +
1150      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1151      'To achieve the same results, use the panelContainer or panelEl ' +
1152      'JQLite elements that are referenced in MdPanelRef.');
1153
1154  if (!this.panelContainer) {
1155    throw new Error('Panel does not exist yet. Call open() or attach().');
1156  }
1157
1158  if (!toElement && !this.panelContainer.hasClass(newClass)) {
1159    this.panelContainer.addClass(newClass);
1160  } else if (toElement && !this.panelEl.hasClass(newClass)) {
1161    this.panelEl.addClass(newClass);
1162  }
1163};
1164
1165
1166/**
1167 * Remove a class from the panel. DO NOT use this to hide/show the panel.
1168 * @deprecated
1169 * This method is in the process of being deprecated in favor of using the panel
1170 * and container JQLite elements that are referenced in the MdPanelRef object.
1171 * Full deprecation is scheduled for material 1.2.
1172 *
1173 * @param {string} oldClass Class to be removed.
1174 * @param {boolean} fromElement Whether or not to remove the class from the
1175 *     panel element instead of the container.
1176 */
1177MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
1178  this._$log.warn(
1179      'The removeClass method is in the process of being deprecated. ' +
1180      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1181      'To achieve the same results, use the panelContainer or panelEl ' +
1182      'JQLite elements that are referenced in MdPanelRef.');
1183
1184  if (!this.panelContainer) {
1185    throw new Error('Panel does not exist yet. Call open() or attach().');
1186  }
1187
1188  if (!fromElement && this.panelContainer.hasClass(oldClass)) {
1189    this.panelContainer.removeClass(oldClass);
1190  } else if (fromElement && this.panelEl.hasClass(oldClass)) {
1191    this.panelEl.removeClass(oldClass);
1192  }
1193};
1194
1195
1196/**
1197 * Toggle a class on the panel. DO NOT use this to hide/show the panel.
1198 * @deprecated
1199 * This method is in the process of being deprecated in favor of using the panel
1200 * and container JQLite elements that are referenced in the MdPanelRef object.
1201 * Full deprecation is scheduled for material 1.2.
1202 *
1203 * @param {string} toggleClass The class to toggle.
1204 * @param {boolean} onElement Whether or not to toggle the class on the panel
1205 *     element instead of the container.
1206 */
1207MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
1208  this._$log.warn(
1209      'The toggleClass method is in the process of being deprecated. ' +
1210      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1211      'To achieve the same results, use the panelContainer or panelEl ' +
1212      'JQLite elements that are referenced in MdPanelRef.');
1213
1214  if (!this.panelContainer) {
1215    throw new Error('Panel does not exist yet. Call open() or attach().');
1216  }
1217
1218  if (!onElement) {
1219    this.panelContainer.toggleClass(toggleClass);
1220  } else {
1221    this.panelEl.toggleClass(toggleClass);
1222  }
1223};
1224
1225
1226/**
1227 * Creates a panel and adds it to the dom.
1228 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
1229 *     created.
1230 * @private
1231 */
1232MdPanelRef.prototype._createPanel = function() {
1233  var self = this;
1234
1235  return this._$q(function(resolve, reject) {
1236    if (!self.config.locals) {
1237      self.config.locals = {};
1238    }
1239
1240    self.config.locals.mdPanelRef = self;
1241    self._$mdCompiler.compile(self.config)
1242        .then(function(compileData) {
1243          self.panelContainer = compileData.link(self.config['scope']);
1244          getElement(self.config['attachTo']).append(self.panelContainer);
1245
1246          if (self.config['disableParentScroll']) {
1247            self._restoreScroll = self._$mdUtil.disableScrollAround(
1248              null,
1249              self.panelContainer,
1250              { disableScrollMask: true }
1251            );
1252          }
1253
1254          self.panelEl = angular.element(
1255              self.panelContainer[0].querySelector('.md-panel'));
1256
1257          // Add a custom CSS class to the panel element.
1258          if (self.config['panelClass']) {
1259            self.panelEl.addClass(self.config['panelClass']);
1260          }
1261
1262          // Handle click and touch events for the panel container.
1263          if (self.config['propagateContainerEvents']) {
1264            self.panelContainer.css('pointer-events', 'none');
1265          }
1266
1267          // Panel may be outside the $rootElement, tell ngAnimate to animate
1268          // regardless.
1269          if (self._$animate.pin) {
1270            self._$animate.pin(self.panelContainer,
1271                getElement(self.config['attachTo']));
1272          }
1273
1274          self._configureTrapFocus();
1275          self._addStyles().then(function() {
1276            resolve(self);
1277          }, reject);
1278        }, reject);
1279  });
1280};
1281
1282
1283/**
1284 * Adds the styles for the panel, such as positioning and z-index.
1285 * @returns {!angular.$q.Promise<!MdPanelRef>}
1286 * @private
1287 */
1288MdPanelRef.prototype._addStyles = function() {
1289  var self = this;
1290  return this._$q(function(resolve) {
1291    self.panelContainer.css('z-index', self.config['zIndex']);
1292    self.panelEl.css('z-index', self.config['zIndex'] + 1);
1293
1294    var hideAndResolve = function() {
1295      // Remove left: -9999px and add hidden class.
1296      self.panelEl.css('left', '');
1297      self.panelContainer.addClass(MD_PANEL_HIDDEN);
1298      resolve(self);
1299    };
1300
1301    if (self.config['fullscreen']) {
1302      self.panelEl.addClass('_md-panel-fullscreen');
1303      hideAndResolve();
1304      return; // Don't setup positioning.
1305    }
1306
1307    var positionConfig = self.config['position'];
1308    if (!positionConfig) {
1309      hideAndResolve();
1310      return; // Don't setup positioning.
1311    }
1312
1313    // Wait for angular to finish processing the template, then position it
1314    // correctly. This is necessary so that the panel will have a defined height
1315    // and width.
1316    self._$rootScope['$$postDigest'](function() {
1317      self._updatePosition(true);
1318      resolve(self);
1319    });
1320  });
1321};
1322
1323
1324/**
1325 * Updates the position configuration of a panel
1326 * @param {!MdPanelPosition} position
1327 */
1328MdPanelRef.prototype.updatePosition = function(position) {
1329  if (!this.panelContainer) {
1330    throw new Error('Panel does not exist yet. Call open() or attach().');
1331  }
1332
1333  this.config['position'] = position;
1334  this._updatePosition();
1335};
1336
1337
1338/**
1339 * Calculates and updates the position of the panel.
1340 * @param {boolean=} init
1341 * @private
1342 */
1343MdPanelRef.prototype._updatePosition = function(init) {
1344  var positionConfig = this.config['position'];
1345
1346  if (positionConfig) {
1347    positionConfig._setPanelPosition(this.panelEl);
1348
1349    // Hide the panel now that position is known.
1350    if (init) {
1351      this.panelContainer.addClass(MD_PANEL_HIDDEN);
1352    }
1353
1354    this.panelEl.css(
1355      MdPanelPosition.absPosition.TOP,
1356      positionConfig.getTop()
1357    );
1358    this.panelEl.css(
1359      MdPanelPosition.absPosition.BOTTOM,
1360      positionConfig.getBottom()
1361    );
1362    this.panelEl.css(
1363      MdPanelPosition.absPosition.LEFT,
1364      positionConfig.getLeft()
1365    );
1366    this.panelEl.css(
1367      MdPanelPosition.absPosition.RIGHT,
1368      positionConfig.getRight()
1369    );
1370
1371    // Use the vendor prefixed version of transform.
1372    var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
1373    this.panelEl.css(prefixedTransform, positionConfig.getTransform());
1374  }
1375};
1376
1377
1378/**
1379 * Focuses on the panel or the first focus target.
1380 * @private
1381 */
1382MdPanelRef.prototype._focusOnOpen = function() {
1383  if (this.config['focusOnOpen']) {
1384    // Wait for the template to finish rendering to guarantee md-autofocus has
1385    // finished adding the class md-autofocus, otherwise the focusable element
1386    // isn't available to focus.
1387    var self = this;
1388    this._$rootScope['$$postDigest'](function() {
1389      var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
1390          self.panelEl;
1391      target.focus();
1392    });
1393  }
1394};
1395
1396
1397/**
1398 * Shows the backdrop.
1399 * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
1400 *     is created and attached.
1401 * @private
1402 */
1403MdPanelRef.prototype._createBackdrop = function() {
1404  if (this.config.hasBackdrop) {
1405    if (!this._backdropRef) {
1406      var backdropAnimation = this._$mdPanel.newPanelAnimation()
1407          .openFrom(this.config.attachTo)
1408          .withAnimation({
1409            open: '_md-opaque-enter',
1410            close: '_md-opaque-leave'
1411          });
1412      var backdropConfig = {
1413        animation: backdropAnimation,
1414        attachTo: this.config.attachTo,
1415        focusOnOpen: false,
1416        panelClass: '_md-panel-backdrop',
1417        zIndex: this.config.zIndex - 1
1418      };
1419      this._backdropRef = this._$mdPanel.create(backdropConfig);
1420    }
1421    if (!this._backdropRef.isAttached) {
1422      return this._backdropRef.attach();
1423    }
1424  }
1425};
1426
1427
1428/**
1429 * Listen for escape keys and outside clicks to auto close.
1430 * @private
1431 */
1432MdPanelRef.prototype._addEventListeners = function() {
1433  this._configureEscapeToClose();
1434  this._configureClickOutsideToClose();
1435  this._configureScrollListener();
1436};
1437
1438
1439/**
1440 * Remove event listeners added in _addEventListeners.
1441 * @private
1442 */
1443MdPanelRef.prototype._removeEventListeners = function() {
1444  this._removeListeners && this._removeListeners.forEach(function(removeFn) {
1445    removeFn();
1446  });
1447  this._removeListeners = [];
1448};
1449
1450
1451/**
1452 * Setup the escapeToClose event listeners.
1453 * @private
1454 */
1455MdPanelRef.prototype._configureEscapeToClose = function() {
1456  if (this.config['escapeToClose']) {
1457    var parentTarget = getElement(this.config['attachTo']);
1458    var self = this;
1459
1460    var keyHandlerFn = function(ev) {
1461      if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
1462        ev.stopPropagation();
1463        ev.preventDefault();
1464
1465        self.close();
1466      }
1467    };
1468
1469    // Add keydown listeners
1470    this.panelContainer.on('keydown', keyHandlerFn);
1471    parentTarget.on('keydown', keyHandlerFn);
1472
1473    // Queue remove listeners function
1474    this._removeListeners.push(function() {
1475      self.panelContainer.off('keydown', keyHandlerFn);
1476      parentTarget.off('keydown', keyHandlerFn);
1477    });
1478  }
1479};
1480
1481
1482/**
1483 * Setup the clickOutsideToClose event listeners.
1484 * @private
1485 */
1486MdPanelRef.prototype._configureClickOutsideToClose = function() {
1487  if (this.config['clickOutsideToClose']) {
1488    var target = this.panelContainer;
1489    var sourceElem;
1490
1491    // Keep track of the element on which the mouse originally went down
1492    // so that we can only close the backdrop when the 'click' started on it.
1493    // A simple 'click' handler does not work,
1494    // it sets the target object as the element the mouse went down on.
1495    var mousedownHandler = function(ev) {
1496      sourceElem = ev.target;
1497    };
1498
1499    // We check if our original element and the target is the backdrop
1500    // because if the original was the backdrop and the target was inside the
1501    // panel we don't want to panel to close.
1502    var self = this;
1503    var mouseupHandler = function(ev) {
1504      if (sourceElem === target[0] && ev.target === target[0]) {
1505        ev.stopPropagation();
1506        ev.preventDefault();
1507
1508        self.close();
1509      }
1510    };
1511
1512    // Add listeners
1513    target.on('mousedown', mousedownHandler);
1514    target.on('mouseup', mouseupHandler);
1515
1516    // Queue remove listeners function
1517    this._removeListeners.push(function() {
1518      target.off('mousedown', mousedownHandler);
1519      target.off('mouseup', mouseupHandler);
1520    });
1521  }
1522};
1523
1524
1525/**
1526 * Configures the listeners for updating the panel position on scroll.
1527 * @private
1528*/
1529MdPanelRef.prototype._configureScrollListener = function() {
1530  var updatePosition = angular.bind(this, this._updatePosition);
1531  var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
1532  var self = this;
1533
1534  var onScroll = function() {
1535    if (!self.config['disableParentScroll']) {
1536      debouncedUpdatePosition();
1537    }
1538  };
1539
1540  // Add listeners.
1541  this._$window.addEventListener('scroll', onScroll, true);
1542
1543  // Queue remove listeners function.
1544  this._removeListeners.push(function() {
1545    self._$window.removeEventListener('scroll', onScroll, true);
1546  });
1547};
1548
1549
1550/**
1551 * Setup the focus traps. These traps will wrap focus when tabbing past the
1552 * panel. When shift-tabbing, the focus will stick in place.
1553 * @private
1554 */
1555MdPanelRef.prototype._configureTrapFocus = function() {
1556  // Focus doesn't remain instead of the panel without this.
1557  this.panelEl.attr('tabIndex', '-1');
1558  if (this.config['trapFocus']) {
1559    var element = this.panelEl;
1560    // Set up elements before and after the panel to capture focus and
1561    // redirect back into the panel.
1562    this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
1563    this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
1564
1565    // When focus is about to move out of the panel, we want to intercept it
1566    // and redirect it back to the panel element.
1567    var focusHandler = function() {
1568      element.focus();
1569    };
1570    this._topFocusTrap.addEventListener('focus', focusHandler);
1571    this._bottomFocusTrap.addEventListener('focus', focusHandler);
1572
1573    // Queue remove listeners function
1574    this._removeListeners.push(this._simpleBind(function() {
1575      this._topFocusTrap.removeEventListener('focus', focusHandler);
1576      this._bottomFocusTrap.removeEventListener('focus', focusHandler);
1577    }, this));
1578
1579    // The top focus trap inserted immediately before the md-panel element (as
1580    // a sibling). The bottom focus trap inserted immediately after the
1581    // md-panel element (as a sibling).
1582    element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
1583    element.after(this._bottomFocusTrap);
1584  }
1585};
1586
1587
1588/**
1589 * Animate the panel opening.
1590 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
1591 *     animated open.
1592 * @private
1593 */
1594MdPanelRef.prototype._animateOpen = function() {
1595  this.panelContainer.addClass('md-panel-is-showing');
1596  var animationConfig = this.config['animation'];
1597  if (!animationConfig) {
1598    // Promise is in progress, return it.
1599    this.panelContainer.addClass('_md-panel-shown');
1600    return this._$q.when(this);
1601  }
1602
1603  var self = this;
1604  return this._$q(function(resolve) {
1605    var done = self._done(resolve, self);
1606    var warnAndOpen = function() {
1607      self._$log.warn(
1608          'MdPanel Animations failed. Showing panel without animating.');
1609      done();
1610    };
1611
1612    animationConfig.animateOpen(self.panelEl)
1613        .then(done, warnAndOpen);
1614  });
1615};
1616
1617
1618/**
1619 * Animate the panel closing.
1620 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
1621 *     animated closed.
1622 * @private
1623 */
1624MdPanelRef.prototype._animateClose = function() {
1625  var animationConfig = this.config['animation'];
1626  if (!animationConfig) {
1627    this.panelContainer.removeClass('md-panel-is-showing');
1628    this.panelContainer.removeClass('_md-panel-shown');
1629    return this._$q.when(this);
1630  }
1631
1632  var self = this;
1633  return this._$q(function(resolve) {
1634    var done = function() {
1635      self.panelContainer.removeClass('md-panel-is-showing');
1636      resolve(self);
1637    };
1638    var warnAndClose = function() {
1639      self._$log.warn(
1640          'MdPanel Animations failed. Hiding panel without animating.');
1641      done();
1642    };
1643
1644    animationConfig.animateClose(self.panelEl)
1645        .then(done, warnAndClose);
1646  });
1647};
1648
1649
1650/**
1651 * Faster, more basic than angular.bind
1652 * http://jsperf.com/angular-bind-vs-custom-vs-native
1653 * @param {function} callback
1654 * @param {!Object} self
1655 * @return {function} Callback function with a bound self.
1656 */
1657MdPanelRef.prototype._simpleBind = function(callback, self) {
1658  return function(value) {
1659    return callback.apply(self, value);
1660  };
1661};
1662
1663
1664/**
1665 * @param {function} callback
1666 * @param {!Object} self
1667 * @return {function} Callback function with a self param.
1668 */
1669MdPanelRef.prototype._done = function(callback, self) {
1670  return function() {
1671    callback(self);
1672  };
1673};
1674
1675
1676/*****************************************************************************
1677 *                               MdPanelPosition                             *
1678 *****************************************************************************/
1679
1680
1681/**
1682 * Position configuration object. To use, create an MdPanelPosition with the
1683 * desired properties, then pass the object as part of $mdPanel creation.
1684 *
1685 * Example:
1686 *
1687 * var panelPosition = new MdPanelPosition()
1688 *     .relativeTo(myButtonEl)
1689 *     .addPanelPosition(
1690 *       $mdPanel.xPosition.CENTER,
1691 *       $mdPanel.yPosition.ALIGN_TOPS
1692 *     );
1693 *
1694 * $mdPanel.create({
1695 *   position: panelPosition
1696 * });
1697 *
1698 * @param {!angular.$injector} $injector
1699 * @final @constructor
1700 */
1701function MdPanelPosition($injector) {
1702  /** @private @const {!angular.$window} */
1703  this._$window = $injector.get('$window');
1704
1705  /** @private {boolean} */
1706  this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
1707
1708  /** @private {boolean} */
1709  this._absolute = false;
1710
1711  /** @private {!angular.JQLite} */
1712  this._relativeToEl;
1713
1714  /** @private {string} */
1715  this._top = '';
1716
1717  /** @private {string} */
1718  this._bottom = '';
1719
1720  /** @private {string} */
1721  this._left = '';
1722
1723  /** @private {string} */
1724  this._right = '';
1725
1726  /** @private {!Array<string>} */
1727  this._translateX = [];
1728
1729  /** @private {!Array<string>} */
1730  this._translateY = [];
1731
1732  /** @private {!Array<{x:string, y:string}>} */
1733  this._positions = [];
1734
1735  /** @private {?{x:string, y:string}} */
1736  this._actualPosition;
1737}
1738
1739
1740/**
1741 * Possible values of xPosition.
1742 * @enum {string}
1743 */
1744MdPanelPosition.xPosition = {
1745  CENTER: 'center',
1746  ALIGN_START: 'align-start',
1747  ALIGN_END: 'align-end',
1748  OFFSET_START: 'offset-start',
1749  OFFSET_END: 'offset-end'
1750};
1751
1752
1753/**
1754 * Possible values of yPosition.
1755 * @enum {string}
1756 */
1757MdPanelPosition.yPosition = {
1758  CENTER: 'center',
1759  ALIGN_TOPS: 'align-tops',
1760  ALIGN_BOTTOMS: 'align-bottoms',
1761  ABOVE: 'above',
1762  BELOW: 'below'
1763};
1764
1765
1766/**
1767 * Possible values of absolute position.
1768 * @enum {string}
1769 */
1770MdPanelPosition.absPosition = {
1771  TOP: 'top',
1772  RIGHT: 'right',
1773  BOTTOM: 'bottom',
1774  LEFT: 'left'
1775};
1776
1777
1778/**
1779 * Sets absolute positioning for the panel.
1780 * @return {!MdPanelPosition}
1781 */
1782MdPanelPosition.prototype.absolute = function() {
1783  this._absolute = true;
1784  return this;
1785};
1786
1787/**
1788 * Sets the value of a position for the panel. Clears any previously set
1789 * position.
1790 * @param {string} position Position to set
1791 * @param {string=} value Value of the position. Defaults to '0'.
1792 * @returns {!MdPanelPosition}
1793 * @private
1794 */
1795MdPanelPosition.prototype._setPosition = function(position, value) {
1796  if (position === MdPanelPosition.absPosition.RIGHT ||
1797      position === MdPanelPosition.absPosition.LEFT) {
1798    this._left = this._right = '';
1799  } else if (
1800      position === MdPanelPosition.absPosition.BOTTOM ||
1801      position === MdPanelPosition.absPosition.TOP) {
1802    this._top = this._bottom = '';
1803  } else {
1804    var positions = Object.keys(MdPanelPosition.absPosition).join()
1805        .toLowerCase();
1806
1807    throw new Error('Position must be one of ' + positions + '.');
1808  }
1809
1810  this['_' +  position] = angular.isString(value) ? value : '0';
1811
1812  return this;
1813};
1814
1815
1816/**
1817 * Sets the value of `top` for the panel. Clears any previously set vertical
1818 * position.
1819 * @param {string=} top Value of `top`. Defaults to '0'.
1820 * @returns {!MdPanelPosition}
1821 */
1822MdPanelPosition.prototype.top = function(top) {
1823  return this._setPosition(MdPanelPosition.absPosition.TOP, top);
1824};
1825
1826
1827/**
1828 * Sets the value of `bottom` for the panel. Clears any previously set vertical
1829 * position.
1830 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
1831 * @returns {!MdPanelPosition}
1832 */
1833MdPanelPosition.prototype.bottom = function(bottom) {
1834  return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
1835};
1836
1837
1838/**
1839 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
1840 * `rtl`. Clears any previously set horizontal position.
1841 * @param {string=} start Value of position. Defaults to '0'.
1842 * @returns {!MdPanelPosition}
1843 */
1844MdPanelPosition.prototype.start = function(start) {
1845  var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
1846  return this._setPosition(position, start);
1847};
1848
1849
1850/**
1851 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
1852 * Clears any previously set horizontal position.
1853 * @param {string=} end Value of position. Defaults to '0'.
1854 * @returns {!MdPanelPosition}
1855 */
1856MdPanelPosition.prototype.end = function(end) {
1857  var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
1858  return this._setPosition(position, end);
1859};
1860
1861
1862/**
1863 * Sets the value of `left` for the panel. Clears any previously set
1864 * horizontal position.
1865 * @param {string=} left Value of `left`. Defaults to '0'.
1866 * @returns {!MdPanelPosition}
1867 */
1868MdPanelPosition.prototype.left = function(left) {
1869  return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
1870};
1871
1872
1873/**
1874 * Sets the value of `right` for the panel. Clears any previously set
1875 * horizontal position.
1876 * @param {string=} right Value of `right`. Defaults to '0'.
1877 * @returns {!MdPanelPosition}
1878*/
1879MdPanelPosition.prototype.right = function(right) {
1880  return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
1881};
1882
1883
1884/**
1885 * Centers the panel horizontally in the viewport. Clears any previously set
1886 * horizontal position.
1887 * @returns {!MdPanelPosition}
1888 */
1889MdPanelPosition.prototype.centerHorizontally = function() {
1890  this._left = '50%';
1891  this._right = '';
1892  this._translateX = ['-50%'];
1893  return this;
1894};
1895
1896
1897/**
1898 * Centers the panel vertically in the viewport. Clears any previously set
1899 * vertical position.
1900 * @returns {!MdPanelPosition}
1901 */
1902MdPanelPosition.prototype.centerVertically = function() {
1903  this._top = '50%';
1904  this._bottom = '';
1905  this._translateY = ['-50%'];
1906  return this;
1907};
1908
1909
1910/**
1911 * Centers the panel horizontally and vertically in the viewport. This is
1912 * equivalent to calling both `centerHorizontally` and `centerVertically`.
1913 * Clears any previously set horizontal and vertical positions.
1914 * @returns {!MdPanelPosition}
1915 */
1916MdPanelPosition.prototype.center = function() {
1917  return this.centerHorizontally().centerVertically();
1918};
1919
1920
1921/**
1922 * Sets element for relative positioning.
1923 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
1924 *     or angular element to set the panel relative to.
1925 * @returns {!MdPanelPosition}
1926 */
1927MdPanelPosition.prototype.relativeTo = function(element) {
1928  this._absolute = false;
1929  this._relativeToEl = getElement(element);
1930  return this;
1931};
1932
1933
1934/**
1935 * Sets the x and y positions for the panel relative to another element.
1936 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
1937 *     values.
1938 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
1939 *     values.
1940 * @returns {!MdPanelPosition}
1941 */
1942MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
1943  if (!this._relativeToEl) {
1944    throw new Error('addPanelPosition can only be used with relative ' +
1945        'positioning. Set relativeTo first.');
1946  }
1947
1948  this._validateXPosition(xPosition);
1949  this._validateYPosition(yPosition);
1950
1951  this._positions.push({
1952      x: xPosition,
1953      y: yPosition,
1954  });
1955  return this;
1956};
1957
1958
1959/**
1960 * Ensures that yPosition is a valid position name. Throw an exception if not.
1961 * @param {string} yPosition
1962 */
1963MdPanelPosition.prototype._validateYPosition = function(yPosition) {
1964  // empty is ok
1965  if (yPosition == null) {
1966      return;
1967  }
1968
1969  var positionKeys = Object.keys(MdPanelPosition.yPosition);
1970  var positionValues = [];
1971  for (var key, i = 0; key = positionKeys[i]; i++) {
1972    var position = MdPanelPosition.yPosition[key];
1973    positionValues.push(position);
1974
1975    if (position === yPosition) {
1976      return;
1977    }
1978  }
1979
1980  throw new Error('Panel y position only accepts the following values:\n' +
1981    positionValues.join(' | '));
1982};
1983
1984
1985/**
1986 * Ensures that xPosition is a valid position name. Throw an exception if not.
1987 * @param {string} xPosition
1988 */
1989MdPanelPosition.prototype._validateXPosition = function(xPosition) {
1990  // empty is ok
1991  if (xPosition == null) {
1992      return;
1993  }
1994
1995  var positionKeys = Object.keys(MdPanelPosition.xPosition);
1996  var positionValues = [];
1997  for (var key, i = 0; key = positionKeys[i]; i++) {
1998    var position = MdPanelPosition.xPosition[key];
1999    positionValues.push(position);
2000    if (position === xPosition) {
2001      return;
2002    }
2003  }
2004
2005  throw new Error('Panel x Position only accepts the following values:\n' +
2006      positionValues.join(' | '));
2007};
2008
2009
2010/**
2011 * Sets the value of the offset in the x-direction. This will add to any
2012 * previously set offsets.
2013 * @param {string} offsetX
2014 * @returns {!MdPanelPosition}
2015 */
2016MdPanelPosition.prototype.withOffsetX = function(offsetX) {
2017  this._translateX.push(offsetX);
2018  return this;
2019};
2020
2021
2022/**
2023 * Sets the value of the offset in the y-direction. This will add to any
2024 * previously set offsets.
2025 * @param {string} offsetY
2026 * @returns {!MdPanelPosition}
2027 */
2028MdPanelPosition.prototype.withOffsetY = function(offsetY) {
2029  this._translateY.push(offsetY);
2030  return this;
2031};
2032
2033
2034/**
2035 * Gets the value of `top` for the panel.
2036 * @returns {string}
2037 */
2038MdPanelPosition.prototype.getTop = function() {
2039  return this._top;
2040};
2041
2042
2043/**
2044 * Gets the value of `bottom` for the panel.
2045 * @returns {string}
2046 */
2047MdPanelPosition.prototype.getBottom = function() {
2048  return this._bottom;
2049};
2050
2051
2052/**
2053 * Gets the value of `left` for the panel.
2054 * @returns {string}
2055 */
2056MdPanelPosition.prototype.getLeft = function() {
2057  return this._left;
2058};
2059
2060
2061/**
2062 * Gets the value of `right` for the panel.
2063 * @returns {string}
2064 */
2065MdPanelPosition.prototype.getRight = function() {
2066  return this._right;
2067};
2068
2069
2070/**
2071 * Gets the value of `transform` for the panel.
2072 * @returns {string}
2073 */
2074MdPanelPosition.prototype.getTransform = function() {
2075  var translateX = this._reduceTranslateValues('translateX', this._translateX);
2076  var translateY = this._reduceTranslateValues('translateY', this._translateY);
2077
2078  // It's important to trim the result, because the browser will ignore the set
2079  // operation if the string contains only whitespace.
2080  return (translateX + ' ' + translateY).trim();
2081};
2082
2083/**
2084 * True if the panel is completely on-screen with this positioning; false
2085 * otherwise.
2086 * @param {!angular.JQLite} panelEl
2087 * @return {boolean}
2088 */
2089MdPanelPosition.prototype._isOnscreen = function(panelEl) {
2090  // this works because we always use fixed positioning for the panel,
2091  // which is relative to the viewport.
2092  // TODO(gmoothart): take into account _translateX and _translateY to the
2093  // extent feasible.
2094
2095  var left = parseInt(this.getLeft());
2096  var top = parseInt(this.getTop());
2097  var right = left + panelEl[0].offsetWidth;
2098  var bottom = top + panelEl[0].offsetHeight;
2099
2100  return (left >= 0) &&
2101    (top >= 0) &&
2102    (bottom <= this._$window.innerHeight) &&
2103    (right <= this._$window.innerWidth);
2104};
2105
2106
2107/**
2108 * Gets the first x/y position that can fit on-screen.
2109 * @returns {{x: string, y: string}}
2110 */
2111MdPanelPosition.prototype.getActualPosition = function() {
2112  return this._actualPosition;
2113};
2114
2115
2116/**
2117 * Reduces a list of translate values to a string that can be used within
2118 * transform.
2119 * @param {string} translateFn
2120 * @param {!Array<string>} values
2121 * @returns {string}
2122 * @private
2123 */
2124MdPanelPosition.prototype._reduceTranslateValues =
2125    function(translateFn, values) {
2126      return values.map(function(translation) {
2127        return translateFn + '(' + translation + ')';
2128      }).join(' ');
2129    };
2130
2131
2132/**
2133 * Sets the panel position based on the created panel element and best x/y
2134 * positioning.
2135 * @param {!angular.JQLite} panelEl
2136 * @private
2137 */
2138MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
2139  // Only calculate the position if necessary.
2140  if (this._absolute) {
2141    return;
2142  }
2143
2144  if (this._actualPosition) {
2145    this._calculatePanelPosition(panelEl, this._actualPosition);
2146    return;
2147  }
2148
2149  for (var i = 0; i < this._positions.length; i++) {
2150    this._actualPosition = this._positions[i];
2151    this._calculatePanelPosition(panelEl, this._actualPosition);
2152    if (this._isOnscreen(panelEl)) {
2153      break;
2154    }
2155  }
2156};
2157
2158
2159/**
2160 * Switches between 'start' and 'end'.
2161 * @param {string} position Horizontal position of the panel
2162 * @returns {string} Reversed position
2163 * @private
2164 */
2165MdPanelPosition.prototype._reverseXPosition = function(position) {
2166  if (position === MdPanelPosition.xPosition.CENTER) {
2167    return;
2168  }
2169
2170  var start = 'start';
2171  var end = 'end';
2172
2173  return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
2174};
2175
2176
2177/**
2178 * Handles horizontal positioning in rtl or ltr environments.
2179 * @param {string} position Horizontal position of the panel
2180 * @returns {string} The correct position according the page direction
2181 * @private
2182 */
2183MdPanelPosition.prototype._bidi = function(position) {
2184  return this._isRTL ? this._reverseXPosition(position) : position;
2185};
2186
2187
2188/**
2189 * Calculates the panel position based on the created panel element and the
2190 * provided positioning.
2191 * @param {!angular.JQLite} panelEl
2192 * @param {!{x:string, y:string}} position
2193 * @private
2194 */
2195MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
2196
2197  var panelBounds = panelEl[0].getBoundingClientRect();
2198  var panelWidth = panelBounds.width;
2199  var panelHeight = panelBounds.height;
2200
2201  var targetBounds = this._relativeToEl[0].getBoundingClientRect();
2202
2203  var targetLeft = targetBounds.left;
2204  var targetRight = targetBounds.right;
2205  var targetWidth = targetBounds.width;
2206
2207  switch (this._bidi(position.x)) {
2208    case MdPanelPosition.xPosition.OFFSET_START:
2209      this._left = targetLeft - panelWidth + 'px';
2210      break;
2211    case MdPanelPosition.xPosition.ALIGN_END:
2212      this._left = targetRight - panelWidth + 'px';
2213      break;
2214    case MdPanelPosition.xPosition.CENTER:
2215      var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
2216      this._left = left + 'px';
2217      break;
2218    case MdPanelPosition.xPosition.ALIGN_START:
2219      this._left = targetLeft + 'px';
2220      break;
2221    case MdPanelPosition.xPosition.OFFSET_END:
2222      this._left = targetRight + 'px';
2223      break;
2224  }
2225
2226  var targetTop = targetBounds.top;
2227  var targetBottom = targetBounds.bottom;
2228  var targetHeight = targetBounds.height;
2229
2230  switch (position.y) {
2231    case MdPanelPosition.yPosition.ABOVE:
2232      this._top = targetTop - panelHeight + 'px';
2233      break;
2234    case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
2235      this._top = targetBottom - panelHeight + 'px';
2236      break;
2237    case MdPanelPosition.yPosition.CENTER:
2238      var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
2239      this._top = top + 'px';
2240      break;
2241    case MdPanelPosition.yPosition.ALIGN_TOPS:
2242      this._top = targetTop + 'px';
2243      break;
2244    case MdPanelPosition.yPosition.BELOW:
2245      this._top = targetBottom + 'px';
2246      break;
2247  }
2248};
2249
2250
2251/*****************************************************************************
2252 *                               MdPanelAnimation                            *
2253 *****************************************************************************/
2254
2255
2256/**
2257 * Animation configuration object. To use, create an MdPanelAnimation with the
2258 * desired properties, then pass the object as part of $mdPanel creation.
2259 *
2260 * Example:
2261 *
2262 * var panelAnimation = new MdPanelAnimation()
2263 *     .openFrom(myButtonEl)
2264 *     .closeTo('.my-button')
2265 *     .withAnimation($mdPanel.animation.SCALE);
2266 *
2267 * $mdPanel.create({
2268 *   animation: panelAnimation
2269 * });
2270 *
2271 * @param {!angular.$injector} $injector
2272 * @final @constructor
2273 */
2274function MdPanelAnimation($injector) {
2275  /** @private @const {!angular.$mdUtil} */
2276  this._$mdUtil = $injector.get('$mdUtil');
2277
2278  /**
2279   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
2280   *     undefined}
2281   */
2282  this._openFrom;
2283
2284  /**
2285   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
2286   *     undefined}
2287   */
2288  this._closeTo;
2289
2290  /** @private {string|{open: string, close: string}} */
2291  this._animationClass = '';
2292}
2293
2294
2295/**
2296 * Possible default animations.
2297 * @enum {string}
2298 */
2299MdPanelAnimation.animation = {
2300  SLIDE: 'md-panel-animate-slide',
2301  SCALE: 'md-panel-animate-scale',
2302  FADE: 'md-panel-animate-fade'
2303};
2304
2305
2306/**
2307 * Specifies where to start the open animation. `openFrom` accepts a
2308 * click event object, query selector, DOM element, or a Rect object that
2309 * is used to determine the bounds. When passed a click event, the location
2310 * of the click will be used as the position to start the animation.
2311 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
2312 * @returns {!MdPanelAnimation}
2313 */
2314MdPanelAnimation.prototype.openFrom = function(openFrom) {
2315  // Check if 'openFrom' is an Event.
2316  openFrom = openFrom.target ? openFrom.target : openFrom;
2317
2318  this._openFrom = this._getPanelAnimationTarget(openFrom);
2319
2320  if (!this._closeTo) {
2321    this._closeTo = this._openFrom;
2322  }
2323  return this;
2324};
2325
2326
2327/**
2328 * Specifies where to animate the panel close. `closeTo` accepts a
2329 * query selector, DOM element, or a Rect object that is used to determine
2330 * the bounds.
2331 * @param {string|!Element|{top: number, left: number}} closeTo
2332 * @returns {!MdPanelAnimation}
2333 */
2334MdPanelAnimation.prototype.closeTo = function(closeTo) {
2335  this._closeTo = this._getPanelAnimationTarget(closeTo);
2336  return this;
2337};
2338
2339
2340/**
2341 * Returns the element and bounds for the animation target.
2342 * @param {string|!Element|{top: number, left: number}} location
2343 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
2344 * @private
2345 */
2346MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
2347  if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
2348    return {
2349      element: undefined,
2350      bounds: {
2351        top: location.top || 0,
2352        left: location.left || 0
2353      }
2354    };
2355  } else {
2356    return this._getBoundingClientRect(getElement(location));
2357  }
2358};
2359
2360
2361/**
2362 * Specifies the animation class.
2363 *
2364 * There are several default animations that can be used:
2365 * (MdPanelAnimation.animation)
2366 *   SLIDE: The panel slides in and out from the specified
2367 *        elements.
2368 *   SCALE: The panel scales in and out.
2369 *   FADE: The panel fades in and out.
2370 *
2371 * @param {string|{open: string, close: string}} cssClass
2372 * @returns {!MdPanelAnimation}
2373 */
2374MdPanelAnimation.prototype.withAnimation = function(cssClass) {
2375  this._animationClass = cssClass;
2376  return this;
2377};
2378
2379
2380/**
2381 * Animate the panel open.
2382 * @param {!angular.JQLite} panelEl
2383 * @returns {!angular.$q.Promise} A promise that is resolved when the open
2384 *     animation is complete.
2385 */
2386MdPanelAnimation.prototype.animateOpen = function(panelEl) {
2387  var animator = this._$mdUtil.dom.animator;
2388
2389  this._fixBounds(panelEl);
2390  var animationOptions = {};
2391
2392  // Include the panel transformations when calculating the animations.
2393  var panelTransform = panelEl[0].style.transform || '';
2394
2395  var openFrom = animator.toTransformCss(panelTransform);
2396  var openTo = animator.toTransformCss(panelTransform);
2397
2398  switch (this._animationClass) {
2399    case MdPanelAnimation.animation.SLIDE:
2400      // Slide should start with opacity: 1.
2401      panelEl.css('opacity', '1');
2402
2403      animationOptions = {
2404        transitionInClass: '_md-panel-animate-enter'
2405      };
2406
2407      var openSlide = animator.calculateSlideToOrigin(
2408              panelEl, this._openFrom) || '';
2409      openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
2410      break;
2411
2412    case MdPanelAnimation.animation.SCALE:
2413      animationOptions = {
2414        transitionInClass: '_md-panel-animate-enter'
2415      };
2416
2417      var openScale = animator.calculateZoomToOrigin(
2418              panelEl, this._openFrom) || '';
2419      openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
2420      break;
2421
2422    case MdPanelAnimation.animation.FADE:
2423      animationOptions = {
2424        transitionInClass: '_md-panel-animate-enter'
2425      };
2426      break;
2427
2428    default:
2429      if (angular.isString(this._animationClass)) {
2430        animationOptions = {
2431          transitionInClass: this._animationClass
2432        };
2433      } else {
2434        animationOptions = {
2435          transitionInClass: this._animationClass['open'],
2436          transitionOutClass: this._animationClass['close'],
2437        };
2438      }
2439  }
2440
2441  return animator
2442      .translate3d(panelEl, openFrom, openTo, animationOptions);
2443};
2444
2445
2446/**
2447 * Animate the panel close.
2448 * @param {!angular.JQLite} panelEl
2449 * @returns {!angular.$q.Promise} A promise that resolves when the close
2450 *     animation is complete.
2451 */
2452MdPanelAnimation.prototype.animateClose = function(panelEl) {
2453  var animator = this._$mdUtil.dom.animator;
2454  var reverseAnimationOptions = {};
2455
2456  // Include the panel transformations when calculating the animations.
2457  var panelTransform = panelEl[0].style.transform || '';
2458
2459  var closeFrom = animator.toTransformCss(panelTransform);
2460  var closeTo = animator.toTransformCss(panelTransform);
2461
2462  switch (this._animationClass) {
2463    case MdPanelAnimation.animation.SLIDE:
2464      // Slide should start with opacity: 1.
2465      panelEl.css('opacity', '1');
2466      reverseAnimationOptions = {
2467        transitionInClass: '_md-panel-animate-leave'
2468      };
2469
2470      var closeSlide = animator.calculateSlideToOrigin(
2471              panelEl, this._closeTo) || '';
2472      closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
2473      break;
2474
2475    case MdPanelAnimation.animation.SCALE:
2476      reverseAnimationOptions = {
2477        transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
2478      };
2479
2480      var closeScale = animator.calculateZoomToOrigin(
2481              panelEl, this._closeTo) || '';
2482      closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
2483      break;
2484
2485    case MdPanelAnimation.animation.FADE:
2486      reverseAnimationOptions = {
2487        transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
2488      };
2489      break;
2490
2491    default:
2492      if (angular.isString(this._animationClass)) {
2493        reverseAnimationOptions = {
2494          transitionOutClass: this._animationClass
2495        };
2496      } else {
2497        reverseAnimationOptions = {
2498          transitionInClass: this._animationClass['close'],
2499          transitionOutClass: this._animationClass['open']
2500        };
2501      }
2502  }
2503
2504  return animator
2505      .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
2506};
2507
2508
2509/**
2510 * Set the height and width to match the panel if not provided.
2511 * @param {!angular.JQLite} panelEl
2512 * @private
2513 */
2514MdPanelAnimation.prototype._fixBounds = function(panelEl) {
2515  var panelWidth = panelEl[0].offsetWidth;
2516  var panelHeight = panelEl[0].offsetHeight;
2517
2518  if (this._openFrom && this._openFrom.bounds.height == null) {
2519    this._openFrom.bounds.height = panelHeight;
2520  }
2521  if (this._openFrom && this._openFrom.bounds.width == null) {
2522    this._openFrom.bounds.width = panelWidth;
2523  }
2524  if (this._closeTo && this._closeTo.bounds.height == null) {
2525    this._closeTo.bounds.height = panelHeight;
2526  }
2527  if (this._closeTo && this._closeTo.bounds.width == null) {
2528    this._closeTo.bounds.width = panelWidth;
2529  }
2530};
2531
2532
2533/**
2534 * Identify the bounding RECT for the target element.
2535 * @param {!angular.JQLite} element
2536 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
2537 * @private
2538 */
2539MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
2540  if (element instanceof angular.element) {
2541    return {
2542      element: element,
2543      bounds: element[0].getBoundingClientRect()
2544    };
2545  }
2546};
2547
2548
2549/*****************************************************************************
2550 *                                Util Methods                               *
2551 *****************************************************************************/
2552
2553/**
2554 * Returns the angular element associated with a css selector or element.
2555 * @param el {string|!angular.JQLite|!Element}
2556 * @returns {!angular.JQLite}
2557 */
2558function getElement(el) {
2559  var queryResult = angular.isString(el) ?
2560      document.querySelector(el) : el;
2561  return angular.element(queryResult);
2562}
2563
2564ngmaterial.components.panel = angular.module("material.components.panel");