aboutsummaryrefslogtreecommitdiffstats
path: root/vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js')
-rw-r--r--vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js3546
1 files changed, 3546 insertions, 0 deletions
diff --git a/vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js b/vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js
new file mode 100644
index 00000000..b100c735
--- /dev/null
+++ b/vnfmarket/src/main/webapp/common/thirdparty/angular-material/modules/js/panel/panel.js
@@ -0,0 +1,3546 @@
+/*!
+ * Angular Material Design
+ * https://github.com/angular/material
+ * @license MIT
+ * v1.1.3
+ */
+(function( window, angular, undefined ){
+"use strict";
+
+/**
+ * @ngdoc module
+ * @name material.components.panel
+ */
+MdPanelService['$inject'] = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
+angular
+ .module('material.components.panel', [
+ 'material.core',
+ 'material.components.backdrop'
+ ])
+ .provider('$mdPanel', MdPanelProvider);
+
+
+/*****************************************************************************
+ * PUBLIC DOCUMENTATION *
+ *****************************************************************************/
+
+
+/**
+ * @ngdoc service
+ * @name $mdPanelProvider
+ * @module material.components.panel
+ *
+ * @description
+ * `$mdPanelProvider` allows users to create configuration presets that will be
+ * stored within a cached presets object. When the configuration is needed, the
+ * user can request the preset by passing it as the first parameter in the
+ * `$mdPanel.create` or `$mdPanel.open` methods.
+ *
+ * @usage
+ * <hljs lang="js">
+ * (function(angular, undefined) {
+ * 'use strict';
+ *
+ * angular
+ * .module('demoApp', ['ngMaterial'])
+ * .config(DemoConfig)
+ * .controller('DemoCtrl', DemoCtrl)
+ * .controller('DemoMenuCtrl', DemoMenuCtrl);
+ *
+ * function DemoConfig($mdPanelProvider) {
+ * $mdPanelProvider.definePreset('demoPreset', {
+ * attachTo: angular.element(document.body),
+ * controller: DemoMenuCtrl,
+ * controllerAs: 'ctrl',
+ * template: '' +
+ * '<div class="menu-panel" md-whiteframe="4">' +
+ * ' <div class="menu-content">' +
+ * ' <div class="menu-item" ng-repeat="item in ctrl.items">' +
+ * ' <button class="md-button">' +
+ * ' <span>{{item}}</span>' +
+ * ' </button>' +
+ * ' </div>' +
+ * ' <md-divider></md-divider>' +
+ * ' <div class="menu-item">' +
+ * ' <button class="md-button" ng-click="ctrl.closeMenu()">' +
+ * ' <span>Close Menu</span>' +
+ * ' </button>' +
+ * ' </div>' +
+ * ' </div>' +
+ * '</div>',
+ * panelClass: 'menu-panel-container',
+ * focusOnOpen: false,
+ * zIndex: 100,
+ * propagateContainerEvents: true,
+ * groupName: 'menus'
+ * });
+ * }
+ *
+ * function PanelProviderCtrl($mdPanel) {
+ * this.navigation = {
+ * name: 'navigation',
+ * items: [
+ * 'Home',
+ * 'About',
+ * 'Contact'
+ * ]
+ * };
+ * this.favorites = {
+ * name: 'favorites',
+ * items: [
+ * 'Add to Favorites'
+ * ]
+ * };
+ * this.more = {
+ * name: 'more',
+ * items: [
+ * 'Account',
+ * 'Sign Out'
+ * ]
+ * };
+ *
+ * $mdPanel.newPanelGroup('menus', {
+ * maxOpen: 2
+ * });
+ *
+ * this.showMenu = function($event, menu) {
+ * $mdPanel.open('demoPreset', {
+ * id: 'menu_' + menu.name,
+ * position: $mdPanel.newPanelPosition()
+ * .relativeTo($event.srcElement)
+ * .addPanelPosition(
+ * $mdPanel.xPosition.ALIGN_START,
+ * $mdPanel.yPosition.BELOW
+ * ),
+ * locals: {
+ * items: menu.items
+ * },
+ * openFrom: $event
+ * });
+ * };
+ * }
+ *
+ * function PanelMenuCtrl(mdPanelRef) {
+ * this.closeMenu = function() {
+ * mdPanelRef && mdPanelRef.close();
+ * };
+ * }
+ * })(angular);
+ * </hljs>
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanelProvider#definePreset
+ * @description
+ * Takes the passed in preset name and preset configuration object and adds it
+ * to the `_presets` object of the provider. This `_presets` object is then
+ * passed along to the `$mdPanel` service.
+ *
+ * @param {string} name Preset name.
+ * @param {!Object} preset Specific configuration object that can contain any
+ * and all of the parameters avaialble within the `$mdPanel.create` method.
+ * However, parameters that pertain to id, position, animation, and user
+ * interaction are not allowed and will be removed from the preset
+ * configuration.
+ */
+
+
+/*****************************************************************************
+ * MdPanel Service *
+ *****************************************************************************/
+
+
+/**
+ * @ngdoc service
+ * @name $mdPanel
+ * @module material.components.panel
+ *
+ * @description
+ * `$mdPanel` is a robust, low-level service for creating floating panels on
+ * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
+ *
+ * @usage
+ * <hljs lang="js">
+ * (function(angular, undefined) {
+ * 'use strict';
+ *
+ * angular
+ * .module('demoApp', ['ngMaterial'])
+ * .controller('DemoDialogController', DialogController);
+ *
+ * var panelRef;
+ *
+ * function showPanel($event) {
+ * var panelPosition = $mdPanel.newPanelPosition()
+ * .absolute()
+ * .top('50%')
+ * .left('50%');
+ *
+ * var panelAnimation = $mdPanel.newPanelAnimation()
+ * .targetEvent($event)
+ * .defaultAnimation('md-panel-animate-fly')
+ * .closeTo('.show-button');
+ *
+ * var config = {
+ * attachTo: angular.element(document.body),
+ * controller: DialogController,
+ * controllerAs: 'ctrl',
+ * position: panelPosition,
+ * animation: panelAnimation,
+ * targetEvent: $event,
+ * templateUrl: 'dialog-template.html',
+ * clickOutsideToClose: true,
+ * escapeToClose: true,
+ * focusOnOpen: true
+ * }
+ *
+ * $mdPanel.open(config)
+ * .then(function(result) {
+ * panelRef = result;
+ * });
+ * }
+ *
+ * function DialogController(MdPanelRef) {
+ * function closeDialog() {
+ * if (MdPanelRef) MdPanelRef.close();
+ * }
+ * }
+ * })(angular);
+ * </hljs>
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#create
+ * @description
+ * Creates a panel with the specified options.
+ *
+ * @param config {!Object=} Specific configuration object that may contain the
+ * following properties:
+ *
+ * - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
+ * the created panel is added to a tracked panels object. Any subsequent
+ * requests made to create a panel with that ID are ignored. This is useful
+ * in having the panel service not open multiple panels from the same user
+ * interaction when there is no backdrop and events are propagated. Defaults
+ * to an arbitrary string that is not tracked.
+ * - `template` - `{string=}`: HTML template to show in the panel. This
+ * **must** be trusted HTML with respect to Angular’s
+ * [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
+ * - `templateUrl` - `{string=}`: The URL that will be used as the content of
+ * the panel.
+ * - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled
+ * element to be used as the panel's content.
+ * - `controller` - `{(function|string)=}`: The controller to associate with
+ * the panel. The controller can inject a reference to the returned
+ * panelRef, which allows the panel to be closed, hidden, and shown. Any
+ * fields passed in through locals or resolve will be bound to the
+ * controller.
+ * - `controllerAs` - `{string=}`: An alias to assign the controller to on
+ * the scope.
+ * - `bindToController` - `{boolean=}`: Binds locals to the controller
+ * instead of passing them in. Defaults to true, as this is a best
+ * practice.
+ * - `locals` - `{Object=}`: An object containing key/value pairs. The keys
+ * will be used as names of values to inject into the controller. For
+ * example, `locals: {three: 3}` would inject `three` into the controller,
+ * with the value 3.
+ * - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
+ * values. The panel will not open until all of the promises resolve.
+ * - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
+ * attach the panel to. Defaults to appending to the root element of the
+ * application.
+ * - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
+ * events should be allowed to propagate 'go through' the container, aka the
+ * wrapper, of the panel. Defaults to false.
+ * - `panelClass` - `{string=}`: A css class to apply to the panel element.
+ * This class should define any borders, box-shadow, etc. for the panel.
+ * - `zIndex` - `{number=}`: The z-index to place the panel at.
+ * Defaults to 80.
+ * - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
+ * specifies the alignment of the panel. For more information, see
+ * `MdPanelPosition`.
+ * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
+ * outside the panel to close it. Defaults to false.
+ * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
+ * close the panel. Defaults to false.
+ * - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
+ * called after the close successfully finishes. The first parameter passed
+ * into this function is the current panelRef and the 2nd is an optional
+ * string explaining the close reason. The currently supported closeReasons
+ * can be found in the MdPanelRef.closeReasons enum. These are by default
+ * passed along by the panel.
+ * - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
+ * panel. If `trapFocus` is true, the user will not be able to interact
+ * with the rest of the page until the panel is dismissed. Defaults to
+ * false.
+ * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
+ * open. Only disable if focusing some other way, as focus management is
+ * required for panels to be accessible. Defaults to true.
+ * - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
+ * Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
+ * to false.
+ * - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
+ * specifies the animation of the panel. For more information, see
+ * `MdPanelAnimation`.
+ * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
+ * behind the panel. Defaults to false.
+ * - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
+ * page behind the panel. Defaults to false.
+ * - `onDomAdded` - `{function=}`: Callback function used to announce when
+ * the panel is added to the DOM.
+ * - `onOpenComplete` - `{function=}`: Callback function used to announce
+ * when the open() action is finished.
+ * - `onRemoving` - `{function=}`: Callback function used to announce the
+ * close/hide() action is starting.
+ * - `onDomRemoved` - `{function=}`: Callback function used to announce when
+ * the panel is removed from the DOM.
+ * - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
+ * on when the panel closes. This is commonly the element which triggered
+ * the opening of the panel. If you do not use `origin`, you need to control
+ * the focus manually.
+ * - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
+ * group names. The group name is used for creating a group of panels. The
+ * group is used for configuring the number of open panels and identifying
+ * specific behaviors for groups. For instance, all tooltips could be
+ * identified using the same groupName.
+ *
+ * @returns {!MdPanelRef} panelRef
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#open
+ * @description
+ * Calls the create method above, then opens the panel. This is a shortcut for
+ * creating and then calling open manually. If custom methods need to be
+ * called when the panel is added to the DOM or opened, do not use this method.
+ * Instead create the panel, chain promises on the domAdded and openComplete
+ * methods, and call open from the returned panelRef.
+ *
+ * @param {!Object=} config Specific configuration object that may contain
+ * the properties defined in `$mdPanel.create`.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
+ * to an instance of the panel.
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#newPanelPosition
+ * @description
+ * Returns a new instance of the MdPanelPosition object. Use this to create
+ * the position config object.
+ *
+ * @returns {!MdPanelPosition} panelPosition
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#newPanelAnimation
+ * @description
+ * Returns a new instance of the MdPanelAnimation object. Use this to create
+ * the animation config object.
+ *
+ * @returns {!MdPanelAnimation} panelAnimation
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#newPanelGroup
+ * @description
+ * Creates a panel group and adds it to a tracked list of panel groups.
+ *
+ * @param {string} groupName Name of the group to create.
+ * @param {!Object=} config Specific configuration object that may contain the
+ * following properties:
+ *
+ * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
+ * be open within a defined panel group.
+ *
+ * @returns {!Object<string,
+ * {panels: !Array<!MdPanelRef>,
+ * openPanels: !Array<!MdPanelRef>,
+ * maxOpen: number}>} panelGroup
+ */
+
+/**
+ * @ngdoc method
+ * @name $mdPanel#setGroupMaxOpen
+ * @description
+ * Sets the maximum number of panels in a group that can be opened at a given
+ * time.
+ *
+ * @param {string} groupName The name of the group to configure.
+ * @param {number} maxOpen The maximum number of panels that can be
+ * opened. Infinity can be passed in to remove the maxOpen limit.
+ */
+
+
+/*****************************************************************************
+ * MdPanelRef *
+ *****************************************************************************/
+
+
+/**
+ * @ngdoc type
+ * @name MdPanelRef
+ * @module material.components.panel
+ * @description
+ * A reference to a created panel. This reference contains a unique id for the
+ * panel, along with the following properties:
+ *
+ * - `id` - `{string}`: The unique id for the panel. This id is used to track
+ * when a panel was interacted with.
+ * - `config` - `{!Object=}`: The entire config object that was used in
+ * create.
+ * - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
+ * Visibility to the user does not factor into isAttached.
+ * - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
+ * panel. This property is added in order to have access to the `addClass`,
+ * `removeClass`, `toggleClass`, etc methods.
+ * - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
+ * in order to have access to the `addClass`, `removeClass`, `toggleClass`,
+ * etc methods.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#open
+ * @description
+ * Attaches and shows the panel.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
+ * opened.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#close
+ * @description
+ * Hides and detaches the panel. Note that this will **not** destroy the panel.
+ * If you don't intend on using the panel again, call the {@link #destroy
+ * destroy} method afterwards.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
+ * closed.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#attach
+ * @description
+ * Create the panel elements and attach them to the DOM. The panel will be
+ * hidden by default.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
+ * attached.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#detach
+ * @description
+ * Removes the panel from the DOM. This will NOT hide the panel before removing
+ * it.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
+ * detached.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#show
+ * @description
+ * Shows the panel.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
+ * shown and animations are completed.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#hide
+ * @description
+ * Hides the panel.
+ *
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
+ * hidden and animations are completed.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#destroy
+ * @description
+ * Destroys the panel. The panel cannot be opened again after this is called.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#addClass
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ * @description
+ * Adds a class to the panel. DO NOT use this hide/show the panel.
+ *
+ * @param {string} newClass class to be added.
+ * @param {boolean} toElement Whether or not to add the class to the panel
+ * element instead of the container.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#removeClass
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ * @description
+ * Removes a class from the panel. DO NOT use this to hide/show the panel.
+ *
+ * @param {string} oldClass Class to be removed.
+ * @param {boolean} fromElement Whether or not to remove the class from the
+ * panel element instead of the container.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#toggleClass
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ * @description
+ * Toggles a class on the panel. DO NOT use this to hide/show the panel.
+ *
+ * @param {string} toggleClass Class to be toggled.
+ * @param {boolean} onElement Whether or not to remove the class from the panel
+ * element instead of the container.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#updatePosition
+ * @description
+ * Updates the position configuration of a panel. Use this to update the
+ * position of a panel that is open, without having to close and re-open the
+ * panel.
+ *
+ * @param {!MdPanelPosition} position
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#addToGroup
+ * @description
+ * Adds a panel to a group if the panel does not exist within the group already.
+ * A panel can only exist within a single group.
+ *
+ * @param {string} groupName The name of the group to add the panel to.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#removeFromGroup
+ * @description
+ * Removes a panel from a group if the panel exists within that group. The group
+ * must be created ahead of time.
+ *
+ * @param {string} groupName The name of the group.
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#registerInterceptor
+ * @description
+ * Registers an interceptor with the panel. The callback should return a promise,
+ * which will allow the action to continue when it gets resolved, or will
+ * prevent an action if it is rejected. The interceptors are called sequentially
+ * and it reverse order. `type` must be one of the following
+ * values available on `$mdPanel.interceptorTypes`:
+ * * `CLOSE` - Gets called before the panel begins closing.
+ *
+ * @param {string} type Type of interceptor.
+ * @param {!angular.$q.Promise<any>} callback Callback to be registered.
+ * @returns {!MdPanelRef}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#removeInterceptor
+ * @description
+ * Removes a registered interceptor.
+ *
+ * @param {string} type Type of interceptor to be removed.
+ * @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
+ * @returns {!MdPanelRef}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#removeAllInterceptors
+ * @description
+ * Removes all interceptors. If a type is supplied, only the
+ * interceptors of that type will be cleared.
+ *
+ * @param {string=} type Type of interceptors to be removed.
+ * @returns {!MdPanelRef}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelRef#updateAnimation
+ * @description
+ * Updates the animation configuration for a panel. You can use this to change
+ * the panel's animation without having to re-create it.
+ *
+ * @param {!MdPanelAnimation} animation
+ */
+
+
+/*****************************************************************************
+ * MdPanelPosition *
+ *****************************************************************************/
+
+
+/**
+ * @ngdoc type
+ * @name MdPanelPosition
+ * @module material.components.panel
+ * @description
+ *
+ * Object for configuring the position of the panel.
+ *
+ * @usage
+ *
+ * #### Centering the panel
+ *
+ * <hljs lang="js">
+ * new MdPanelPosition().absolute().center();
+ * </hljs>
+ *
+ * #### Overlapping the panel with an element
+ *
+ * <hljs lang="js">
+ * new MdPanelPosition()
+ * .relativeTo(someElement)
+ * .addPanelPosition(
+ * $mdPanel.xPosition.ALIGN_START,
+ * $mdPanel.yPosition.ALIGN_TOPS
+ * );
+ * </hljs>
+ *
+ * #### Aligning the panel with the bottom of an element
+ *
+ * <hljs lang="js">
+ * new MdPanelPosition()
+ * .relativeTo(someElement)
+ * .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
+ * </hljs>
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#absolute
+ * @description
+ * Positions the panel absolutely relative to the parent element. If the parent
+ * is document.body, this is equivalent to positioning the panel absolutely
+ * within the viewport.
+ *
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#relativeTo
+ * @description
+ * Positions the panel relative to a specific element.
+ *
+ * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
+ * or angular element to position the panel with respect to.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#top
+ * @description
+ * Sets the value of `top` for the panel. Clears any previously set vertical
+ * position.
+ *
+ * @param {string=} top Value of `top`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#bottom
+ * @description
+ * Sets the value of `bottom` for the panel. Clears any previously set vertical
+ * position.
+ *
+ * @param {string=} bottom Value of `bottom`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#start
+ * @description
+ * Sets the panel to the start of the page - `left` if `ltr` or `right` for
+ * `rtl`. Clears any previously set horizontal position.
+ *
+ * @param {string=} start Value of position. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#end
+ * @description
+ * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
+ * Clears any previously set horizontal position.
+ *
+ * @param {string=} end Value of position. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#left
+ * @description
+ * Sets the value of `left` for the panel. Clears any previously set
+ * horizontal position.
+ *
+ * @param {string=} left Value of `left`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#right
+ * @description
+ * Sets the value of `right` for the panel. Clears any previously set
+ * horizontal position.
+ *
+ * @param {string=} right Value of `right`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#centerHorizontally
+ * @description
+ * Centers the panel horizontally in the viewport. Clears any previously set
+ * horizontal position.
+ *
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#centerVertically
+ * @description
+ * Centers the panel vertically in the viewport. Clears any previously set
+ * vertical position.
+ *
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#center
+ * @description
+ * Centers the panel horizontally and vertically in the viewport. This is
+ * equivalent to calling both `centerHorizontally` and `centerVertically`.
+ * Clears any previously set horizontal and vertical positions.
+ *
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#addPanelPosition
+ * @description
+ * Sets the x and y position for the panel relative to another element. Can be
+ * called multiple times to specify an ordered list of panel positions. The
+ * first position which allows the panel to be completely on-screen will be
+ * chosen; the last position will be chose whether it is on-screen or not.
+ *
+ * xPosition must be one of the following values available on
+ * $mdPanel.xPosition:
+ *
+ *
+ * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
+ *
+ * <pre>
+ * *************
+ * * *
+ * * PANEL *
+ * * *
+ * *************
+ * A B C D E
+ *
+ * A: OFFSET_START (for LTR displays)
+ * B: ALIGN_START (for LTR displays)
+ * C: CENTER
+ * D: ALIGN_END (for LTR displays)
+ * E: OFFSET_END (for LTR displays)
+ * </pre>
+ *
+ * yPosition must be one of the following values available on
+ * $mdPanel.yPosition:
+ *
+ * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
+ *
+ * <pre>
+ * F
+ * G *************
+ * * *
+ * H * PANEL *
+ * * *
+ * I *************
+ * J
+ *
+ * F: BELOW
+ * G: ALIGN_TOPS
+ * H: CENTER
+ * I: ALIGN_BOTTOMS
+ * J: ABOVE
+ * </pre>
+ *
+ * @param {string} xPosition
+ * @param {string} yPosition
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#withOffsetX
+ * @description
+ * Sets the value of the offset in the x-direction.
+ *
+ * @param {string} offsetX
+ * @returns {!MdPanelPosition}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelPosition#withOffsetY
+ * @description
+ * Sets the value of the offset in the y-direction.
+ *
+ * @param {string} offsetY
+ * @returns {!MdPanelPosition}
+ */
+
+
+/*****************************************************************************
+ * MdPanelAnimation *
+ *****************************************************************************/
+
+
+/**
+ * @ngdoc type
+ * @name MdPanelAnimation
+ * @module material.components.panel
+ * @description
+ * Animation configuration object. To use, create an MdPanelAnimation with the
+ * desired properties, then pass the object as part of $mdPanel creation.
+ *
+ * @usage
+ *
+ * <hljs lang="js">
+ * var panelAnimation = new MdPanelAnimation()
+ * .openFrom(myButtonEl)
+ * .duration(1337)
+ * .closeTo('.my-button')
+ * .withAnimation($mdPanel.animation.SCALE);
+ *
+ * $mdPanel.create({
+ * animation: panelAnimation
+ * });
+ * </hljs>
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelAnimation#openFrom
+ * @description
+ * Specifies where to start the open animation. `openFrom` accepts a
+ * click event object, query selector, DOM element, or a Rect object that
+ * is used to determine the bounds. When passed a click event, the location
+ * of the click will be used as the position to start the animation.
+ *
+ * @param {string|!Element|!Event|{top: number, left: number}}
+ * @returns {!MdPanelAnimation}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelAnimation#closeTo
+ * @description
+ * Specifies where to animate the panel close. `closeTo` accepts a
+ * query selector, DOM element, or a Rect object that is used to determine
+ * the bounds.
+ *
+ * @param {string|!Element|{top: number, left: number}}
+ * @returns {!MdPanelAnimation}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelAnimation#withAnimation
+ * @description
+ * Specifies the animation class.
+ *
+ * There are several default animations that can be used:
+ * ($mdPanel.animation)
+ * SLIDE: The panel slides in and out from the specified
+ * elements. It will not fade in or out.
+ * SCALE: The panel scales in and out. Slide and fade are
+ * included in this animation.
+ * FADE: The panel fades in and out.
+ *
+ * Custom classes will by default fade in and out unless
+ * "transition: opacity 1ms" is added to the to custom class.
+ *
+ * @param {string|{open: string, close: string}} cssClass
+ * @returns {!MdPanelAnimation}
+ */
+
+/**
+ * @ngdoc method
+ * @name MdPanelAnimation#duration
+ * @description
+ * Specifies the duration of the animation in milliseconds. The `duration`
+ * method accepts either a number or an object with separate open and close
+ * durations.
+ *
+ * @param {number|{open: number, close: number}} duration
+ * @returns {!MdPanelAnimation}
+ */
+
+
+/*****************************************************************************
+ * PUBLIC DOCUMENTATION *
+ *****************************************************************************/
+
+
+var MD_PANEL_Z_INDEX = 80;
+var MD_PANEL_HIDDEN = '_md-panel-hidden';
+var FOCUS_TRAP_TEMPLATE = angular.element(
+ '<div class="_md-panel-focus-trap" tabindex="0"></div>');
+
+var _presets = {};
+
+
+/**
+ * A provider that is used for creating presets for the panel API.
+ * @final @constructor ngInject
+ */
+function MdPanelProvider() {
+ return {
+ 'definePreset': definePreset,
+ 'getAllPresets': getAllPresets,
+ 'clearPresets': clearPresets,
+ '$get': $getProvider()
+ };
+}
+
+
+/**
+ * Takes the passed in panel configuration object and adds it to the `_presets`
+ * object at the specified name.
+ * @param {string} name Name of the preset to set.
+ * @param {!Object} preset Specific configuration object that can contain any
+ * and all of the parameters avaialble within the `$mdPanel.create` method.
+ * However, parameters that pertain to id, position, animation, and user
+ * interaction are not allowed and will be removed from the preset
+ * configuration.
+ */
+function definePreset(name, preset) {
+ if (!name || !preset) {
+ throw new Error('mdPanelProvider: The panel preset definition is ' +
+ 'malformed. The name and preset object are required.');
+ } else if (_presets.hasOwnProperty(name)) {
+ throw new Error('mdPanelProvider: The panel preset you have requested ' +
+ 'has already been defined.');
+ }
+
+ // Delete any property on the preset that is not allowed.
+ delete preset.id;
+ delete preset.position;
+ delete preset.animation;
+
+ _presets[name] = preset;
+}
+
+
+/**
+ * Gets a clone of the `_presets`.
+ * @return {!Object}
+ */
+function getAllPresets() {
+ return angular.copy(_presets);
+}
+
+
+/**
+ * Clears all of the stored presets.
+ */
+function clearPresets() {
+ _presets = {};
+}
+
+
+/**
+ * Represents the `$get` method of the Angular provider. From here, a new
+ * reference to the MdPanelService is returned where the needed arguments are
+ * passed in including the MdPanelProvider `_presets`.
+ * @param {!Object} _presets
+ * @param {!angular.JQLite} $rootElement
+ * @param {!angular.Scope} $rootScope
+ * @param {!angular.$injector} $injector
+ * @param {!angular.$window} $window
+ */
+function $getProvider() {
+ return [
+ '$rootElement', '$rootScope', '$injector', '$window',
+ function($rootElement, $rootScope, $injector, $window) {
+ return new MdPanelService(_presets, $rootElement, $rootScope,
+ $injector, $window);
+ }
+ ];
+}
+
+
+/*****************************************************************************
+ * MdPanel Service *
+ *****************************************************************************/
+
+
+/**
+ * A service that is used for controlling/displaying panels on the screen.
+ * @param {!Object} presets
+ * @param {!angular.JQLite} $rootElement
+ * @param {!angular.Scope} $rootScope
+ * @param {!angular.$injector} $injector
+ * @param {!angular.$window} $window
+ * @final @constructor ngInject
+ */
+function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
+ /**
+ * Default config options for the panel.
+ * Anything angular related needs to be done later. Therefore
+ * scope: $rootScope.$new(true),
+ * attachTo: $rootElement,
+ * are added later.
+ * @private {!Object}
+ */
+ this._defaultConfigOptions = {
+ bindToController: true,
+ clickOutsideToClose: false,
+ disableParentScroll: false,
+ escapeToClose: false,
+ focusOnOpen: true,
+ fullscreen: false,
+ hasBackdrop: false,
+ propagateContainerEvents: false,
+ transformTemplate: angular.bind(this, this._wrapTemplate),
+ trapFocus: false,
+ zIndex: MD_PANEL_Z_INDEX
+ };
+
+ /** @private {!Object} */
+ this._config = {};
+
+ /** @private {!Object} */
+ this._presets = presets;
+
+ /** @private @const */
+ this._$rootElement = $rootElement;
+
+ /** @private @const */
+ this._$rootScope = $rootScope;
+
+ /** @private @const */
+ this._$injector = $injector;
+
+ /** @private @const */
+ this._$window = $window;
+
+ /** @private @const */
+ this._$mdUtil = this._$injector.get('$mdUtil');
+
+ /** @private {!Object<string, !MdPanelRef>} */
+ this._trackedPanels = {};
+
+ /**
+ * @private {!Object<string,
+ * {panels: !Array<!MdPanelRef>,
+ * openPanels: !Array<!MdPanelRef>,
+ * maxOpen: number}>}
+ */
+ this._groups = Object.create(null);
+
+ /**
+ * Default animations that can be used within the panel.
+ * @type {enum}
+ */
+ this.animation = MdPanelAnimation.animation;
+
+ /**
+ * Possible values of xPosition for positioning the panel relative to
+ * another element.
+ * @type {enum}
+ */
+ this.xPosition = MdPanelPosition.xPosition;
+
+ /**
+ * Possible values of yPosition for positioning the panel relative to
+ * another element.
+ * @type {enum}
+ */
+ this.yPosition = MdPanelPosition.yPosition;
+
+ /**
+ * Possible values for the interceptors that can be registered on a panel.
+ * @type {enum}
+ */
+ this.interceptorTypes = MdPanelRef.interceptorTypes;
+
+ /**
+ * Possible values for closing of a panel.
+ * @type {enum}
+ */
+ this.closeReasons = MdPanelRef.closeReasons;
+
+ /**
+ * Possible values of absolute position.
+ * @type {enum}
+ */
+ this.absPosition = MdPanelPosition.absPosition;
+}
+
+
+/**
+ * Creates a panel with the specified options.
+ * @param {string=} preset Name of a preset configuration that can be used to
+ * extend the panel configuration.
+ * @param {!Object=} config Configuration object for the panel.
+ * @returns {!MdPanelRef}
+ */
+MdPanelService.prototype.create = function(preset, config) {
+ if (typeof preset === 'string') {
+ preset = this._getPresetByName(preset);
+ } else if (typeof preset === 'object' &&
+ (angular.isUndefined(config) || !config)) {
+ config = preset;
+ preset = {};
+ }
+
+ preset = preset || {};
+ config = config || {};
+
+ // If the passed-in config contains an ID and the ID is within _trackedPanels,
+ // return the tracked panel after updating its config with the passed-in
+ // config.
+ if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
+ var trackedPanel = this._trackedPanels[config.id];
+ angular.extend(trackedPanel.config, config);
+ return trackedPanel;
+ }
+
+ // Combine the passed-in config, the _defaultConfigOptions, and the preset
+ // configuration into the `_config`.
+ this._config = angular.extend({
+ // If no ID is set within the passed-in config, then create an arbitrary ID.
+ id: config.id || 'panel_' + this._$mdUtil.nextUid(),
+ scope: this._$rootScope.$new(true),
+ attachTo: this._$rootElement
+ }, this._defaultConfigOptions, config, preset);
+
+ // Create the panelRef and add it to the `_trackedPanels` object.
+ var panelRef = new MdPanelRef(this._config, this._$injector);
+ this._trackedPanels[config.id] = panelRef;
+
+ // Add the panel to each of its requested groups.
+ if (this._config.groupName) {
+ if (angular.isString(this._config.groupName)) {
+ this._config.groupName = [this._config.groupName];
+ }
+ angular.forEach(this._config.groupName, function(group) {
+ panelRef.addToGroup(group);
+ });
+ }
+
+ this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
+
+ return panelRef;
+};
+
+
+/**
+ * Creates and opens a panel with the specified options.
+ * @param {string=} preset Name of a preset configuration that can be used to
+ * extend the panel configuration.
+ * @param {!Object=} config Configuration object for the panel.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
+ */
+MdPanelService.prototype.open = function(preset, config) {
+ var panelRef = this.create(preset, config);
+ return panelRef.open().then(function() {
+ return panelRef;
+ });
+};
+
+
+/**
+ * Gets a specific preset configuration object saved within `_presets`.
+ * @param {string} preset Name of the preset to search for.
+ * @returns {!Object} The preset configuration object.
+ */
+MdPanelService.prototype._getPresetByName = function(preset) {
+ if (!this._presets[preset]) {
+ throw new Error('mdPanel: The panel preset configuration that you ' +
+ 'requested does not exist. Use the $mdPanelProvider to create a ' +
+ 'preset before requesting one.');
+ }
+ return this._presets[preset];
+};
+
+
+/**
+ * Returns a new instance of the MdPanelPosition. Use this to create the
+ * positioning object.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelService.prototype.newPanelPosition = function() {
+ return new MdPanelPosition(this._$injector);
+};
+
+
+/**
+ * Returns a new instance of the MdPanelAnimation. Use this to create the
+ * animation object.
+ * @returns {!MdPanelAnimation}
+ */
+MdPanelService.prototype.newPanelAnimation = function() {
+ return new MdPanelAnimation(this._$injector);
+};
+
+
+/**
+ * Creates a panel group and adds it to a tracked list of panel groups.
+ * @param groupName {string} Name of the group to create.
+ * @param config {!Object=} Specific configuration object that may contain the
+ * following properties:
+ *
+ * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
+ * open within a defined panel group.
+ *
+ * @returns {!Object<string,
+ * {panels: !Array<!MdPanelRef>,
+ * openPanels: !Array<!MdPanelRef>,
+ * maxOpen: number}>} panelGroup
+ */
+MdPanelService.prototype.newPanelGroup = function(groupName, config) {
+ if (!this._groups[groupName]) {
+ config = config || {};
+ var group = {
+ panels: [],
+ openPanels: [],
+ maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
+ };
+ this._groups[groupName] = group;
+ }
+ return this._groups[groupName];
+};
+
+
+/**
+ * Sets the maximum number of panels in a group that can be opened at a given
+ * time.
+ * @param {string} groupName The name of the group to configure.
+ * @param {number} maxOpen The maximum number of panels that can be
+ * opened. Infinity can be passed in to remove the maxOpen limit.
+ */
+MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
+ if (this._groups[groupName]) {
+ this._groups[groupName].maxOpen = maxOpen;
+ } else {
+ throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
+ }
+};
+
+
+/**
+ * Determines if the current number of open panels within a group exceeds the
+ * limit of allowed open panels.
+ * @param {string} groupName The name of the group to check.
+ * @returns {boolean} true if open count does exceed maxOpen and false if not.
+ * @private
+ */
+MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
+ if (this._groups[groupName]) {
+ var group = this._groups[groupName];
+ return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
+ }
+ return false;
+};
+
+
+/**
+ * Closes the first open panel within a specific group.
+ * @param {string} groupName The name of the group.
+ * @private
+ */
+MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
+ this._groups[groupName].openPanels[0].close();
+};
+
+
+/**
+ * Wraps the users template in two elements, md-panel-outer-wrapper, which
+ * covers the entire attachTo element, and md-panel, which contains only the
+ * template. This allows the panel control over positioning, animations,
+ * and similar properties.
+ * @param {string} origTemplate The original template.
+ * @returns {string} The wrapped template.
+ * @private
+ */
+MdPanelService.prototype._wrapTemplate = function(origTemplate) {
+ var template = origTemplate || '';
+
+ // The panel should be initially rendered offscreen so we can calculate
+ // height and width for positioning.
+ return '' +
+ '<div class="md-panel-outer-wrapper">' +
+ ' <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
+ '</div>';
+};
+
+
+/**
+ * Wraps a content element in a md-panel-outer wrapper and
+ * positions it off-screen. Allows for proper control over positoning
+ * and animations.
+ * @param {!angular.JQLite} contentElement Element to be wrapped.
+ * @return {!angular.JQLite} Wrapper element.
+ * @private
+ */
+MdPanelService.prototype._wrapContentElement = function(contentElement) {
+ var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
+
+ contentElement.addClass('md-panel').css('left', '-9999px');
+ wrapper.append(contentElement);
+
+ return wrapper;
+};
+
+
+/*****************************************************************************
+ * MdPanelRef *
+ *****************************************************************************/
+
+
+/**
+ * A reference to a created panel. This reference contains a unique id for the
+ * panel, along with properties/functions used to control the panel.
+ * @param {!Object} config
+ * @param {!angular.$injector} $injector
+ * @final @constructor
+ */
+function MdPanelRef(config, $injector) {
+ // Injected variables.
+ /** @private @const {!angular.$q} */
+ this._$q = $injector.get('$q');
+
+ /** @private @const {!angular.$mdCompiler} */
+ this._$mdCompiler = $injector.get('$mdCompiler');
+
+ /** @private @const {!angular.$mdConstant} */
+ this._$mdConstant = $injector.get('$mdConstant');
+
+ /** @private @const {!angular.$mdUtil} */
+ this._$mdUtil = $injector.get('$mdUtil');
+
+ /** @private @const {!angular.$mdTheming} */
+ this._$mdTheming = $injector.get('$mdTheming');
+
+ /** @private @const {!angular.Scope} */
+ this._$rootScope = $injector.get('$rootScope');
+
+ /** @private @const {!angular.$animate} */
+ this._$animate = $injector.get('$animate');
+
+ /** @private @const {!MdPanelRef} */
+ this._$mdPanel = $injector.get('$mdPanel');
+
+ /** @private @const {!angular.$log} */
+ this._$log = $injector.get('$log');
+
+ /** @private @const {!angular.$window} */
+ this._$window = $injector.get('$window');
+
+ /** @private @const {!Function} */
+ this._$$rAF = $injector.get('$$rAF');
+
+ // Public variables.
+ /**
+ * Unique id for the panelRef.
+ * @type {string}
+ */
+ this.id = config.id;
+
+ /** @type {!Object} */
+ this.config = config;
+
+ /** @type {!angular.JQLite|undefined} */
+ this.panelContainer;
+
+ /** @type {!angular.JQLite|undefined} */
+ this.panelEl;
+
+ /**
+ * Whether the panel is attached. This is synchronous. When attach is called,
+ * isAttached is set to true. When detach is called, isAttached is set to
+ * false.
+ * @type {boolean}
+ */
+ this.isAttached = false;
+
+ // Private variables.
+ /** @private {Array<function()>} */
+ this._removeListeners = [];
+
+ /** @private {!angular.JQLite|undefined} */
+ this._topFocusTrap;
+
+ /** @private {!angular.JQLite|undefined} */
+ this._bottomFocusTrap;
+
+ /** @private {!$mdPanel|undefined} */
+ this._backdropRef;
+
+ /** @private {Function?} */
+ this._restoreScroll = null;
+
+ /**
+ * Keeps track of all the panel interceptors.
+ * @private {!Object}
+ */
+ this._interceptors = Object.create(null);
+
+ /**
+ * Cleanup function, provided by `$mdCompiler` and assigned after the element
+ * has been compiled. When `contentElement` is used, the function is used to
+ * restore the element to it's proper place in the DOM.
+ * @private {!Function}
+ */
+ this._compilerCleanup = null;
+
+ /**
+ * Cache for saving and restoring element inline styles, CSS classes etc.
+ * @type {{styles: string, classes: string}}
+ */
+ this._restoreCache = {
+ styles: '',
+ classes: ''
+ };
+}
+
+
+MdPanelRef.interceptorTypes = {
+ CLOSE: 'onClose'
+};
+
+
+/**
+ * Opens an already created and configured panel. If the panel is already
+ * visible, does nothing.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel is opened and animations finish.
+ */
+MdPanelRef.prototype.open = function() {
+ var self = this;
+ return this._$q(function(resolve, reject) {
+ var done = self._done(resolve, self);
+ var show = self._simpleBind(self.show, self);
+ var checkGroupMaxOpen = function() {
+ if (self.config.groupName) {
+ angular.forEach(self.config.groupName, function(group) {
+ if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
+ self._$mdPanel._closeFirstOpenedPanel(group);
+ }
+ });
+ }
+ };
+
+ self.attach()
+ .then(show)
+ .then(checkGroupMaxOpen)
+ .then(done)
+ .catch(reject);
+ });
+};
+
+
+/**
+ * Closes the panel.
+ * @param {string} closeReason The event type that triggered the close.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel is closed and animations finish.
+ */
+MdPanelRef.prototype.close = function(closeReason) {
+ var self = this;
+
+ return this._$q(function(resolve, reject) {
+ self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
+ var done = self._done(resolve, self);
+ var detach = self._simpleBind(self.detach, self);
+ var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
+ onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
+
+ self.hide()
+ .then(detach)
+ .then(done)
+ .then(onCloseSuccess)
+ .catch(reject);
+ }, reject);
+ });
+};
+
+
+/**
+ * Attaches the panel. The panel will be hidden afterwards.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel is attached.
+ */
+MdPanelRef.prototype.attach = function() {
+ if (this.isAttached && this.panelEl) {
+ return this._$q.when(this);
+ }
+
+ var self = this;
+ return this._$q(function(resolve, reject) {
+ var done = self._done(resolve, self);
+ var onDomAdded = self.config['onDomAdded'] || angular.noop;
+ var addListeners = function(response) {
+ self.isAttached = true;
+ self._addEventListeners();
+ return response;
+ };
+
+ self._$q.all([
+ self._createBackdrop(),
+ self._createPanel()
+ .then(addListeners)
+ .catch(reject)
+ ]).then(onDomAdded)
+ .then(done)
+ .catch(reject);
+ });
+};
+
+
+/**
+ * Only detaches the panel. Will NOT hide the panel first.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel is detached.
+ */
+MdPanelRef.prototype.detach = function() {
+ if (!this.isAttached) {
+ return this._$q.when(this);
+ }
+
+ var self = this;
+ var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
+
+ var detachFn = function() {
+ self._removeEventListeners();
+
+ // Remove the focus traps that we added earlier for keeping focus within
+ // the panel.
+ if (self._topFocusTrap && self._topFocusTrap.parentNode) {
+ self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
+ }
+
+ if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
+ self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
+ }
+
+ if (self._restoreCache.classes) {
+ self.panelEl[0].className = self._restoreCache.classes;
+ }
+
+ // Either restore the saved styles or clear the ones set by mdPanel.
+ self.panelEl[0].style.cssText = self._restoreCache.styles || '';
+
+ self._compilerCleanup();
+ self.panelContainer.remove();
+ self.isAttached = false;
+ return self._$q.when(self);
+ };
+
+ if (this._restoreScroll) {
+ this._restoreScroll();
+ this._restoreScroll = null;
+ }
+
+ return this._$q(function(resolve, reject) {
+ var done = self._done(resolve, self);
+
+ self._$q.all([
+ detachFn(),
+ self._backdropRef ? self._backdropRef.detach() : true
+ ]).then(onDomRemoved)
+ .then(done)
+ .catch(reject);
+ });
+};
+
+
+/**
+ * Destroys the panel. The Panel cannot be opened again after this.
+ */
+MdPanelRef.prototype.destroy = function() {
+ var self = this;
+ if (this.config.groupName) {
+ angular.forEach(this.config.groupName, function(group) {
+ self.removeFromGroup(group);
+ });
+ }
+ this.config.scope.$destroy();
+ this.config.locals = null;
+ this._interceptors = null;
+};
+
+
+/**
+ * Shows the panel.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel has shown and animations finish.
+ */
+MdPanelRef.prototype.show = function() {
+ if (!this.panelContainer) {
+ return this._$q(function(resolve, reject) {
+ reject('mdPanel: Panel does not exist yet. Call open() or attach().');
+ });
+ }
+
+ if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
+ return this._$q.when(this);
+ }
+
+ var self = this;
+ var animatePromise = function() {
+ self.panelContainer.removeClass(MD_PANEL_HIDDEN);
+ return self._animateOpen();
+ };
+
+ return this._$q(function(resolve, reject) {
+ var done = self._done(resolve, self);
+ var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
+ var addToGroupOpen = function() {
+ if (self.config.groupName) {
+ angular.forEach(self.config.groupName, function(group) {
+ self._$mdPanel._groups[group].openPanels.push(self);
+ });
+ }
+ };
+
+ self._$q.all([
+ self._backdropRef ? self._backdropRef.show() : self,
+ animatePromise().then(function() { self._focusOnOpen(); }, reject)
+ ]).then(onOpenComplete)
+ .then(addToGroupOpen)
+ .then(done)
+ .catch(reject);
+ });
+};
+
+
+/**
+ * Hides the panel.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
+ * the panel has hidden and animations finish.
+ */
+MdPanelRef.prototype.hide = function() {
+ if (!this.panelContainer) {
+ return this._$q(function(resolve, reject) {
+ reject('mdPanel: Panel does not exist yet. Call open() or attach().');
+ });
+ }
+
+ if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
+ return this._$q.when(this);
+ }
+
+ var self = this;
+
+ return this._$q(function(resolve, reject) {
+ var done = self._done(resolve, self);
+ var onRemoving = self.config['onRemoving'] || angular.noop;
+ var hidePanel = function() {
+ self.panelContainer.addClass(MD_PANEL_HIDDEN);
+ };
+ var removeFromGroupOpen = function() {
+ if (self.config.groupName) {
+ var group, index;
+ angular.forEach(self.config.groupName, function(group) {
+ group = self._$mdPanel._groups[group];
+ index = group.openPanels.indexOf(self);
+ if (index > -1) {
+ group.openPanels.splice(index, 1);
+ }
+ });
+ }
+ };
+ var focusOnOrigin = function() {
+ var origin = self.config['origin'];
+ if (origin) {
+ getElement(origin).focus();
+ }
+ };
+
+ self._$q.all([
+ self._backdropRef ? self._backdropRef.hide() : self,
+ self._animateClose()
+ .then(onRemoving)
+ .then(hidePanel)
+ .then(removeFromGroupOpen)
+ .then(focusOnOrigin)
+ .catch(reject)
+ ]).then(done, reject);
+ });
+};
+
+
+/**
+ * Add a class to the panel. DO NOT use this to hide/show the panel.
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ *
+ * @param {string} newClass Class to be added.
+ * @param {boolean} toElement Whether or not to add the class to the panel
+ * element instead of the container.
+ */
+MdPanelRef.prototype.addClass = function(newClass, toElement) {
+ this._$log.warn(
+ 'mdPanel: The addClass method is in the process of being deprecated. ' +
+ 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
+ 'To achieve the same results, use the panelContainer or panelEl ' +
+ 'JQLite elements that are referenced in MdPanelRef.');
+
+ if (!this.panelContainer) {
+ throw new Error(
+ 'mdPanel: Panel does not exist yet. Call open() or attach().');
+ }
+
+ if (!toElement && !this.panelContainer.hasClass(newClass)) {
+ this.panelContainer.addClass(newClass);
+ } else if (toElement && !this.panelEl.hasClass(newClass)) {
+ this.panelEl.addClass(newClass);
+ }
+};
+
+
+/**
+ * Remove a class from the panel. DO NOT use this to hide/show the panel.
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ *
+ * @param {string} oldClass Class to be removed.
+ * @param {boolean} fromElement Whether or not to remove the class from the
+ * panel element instead of the container.
+ */
+MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
+ this._$log.warn(
+ 'mdPanel: The removeClass method is in the process of being deprecated. ' +
+ 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
+ 'To achieve the same results, use the panelContainer or panelEl ' +
+ 'JQLite elements that are referenced in MdPanelRef.');
+
+ if (!this.panelContainer) {
+ throw new Error(
+ 'mdPanel: Panel does not exist yet. Call open() or attach().');
+ }
+
+ if (!fromElement && this.panelContainer.hasClass(oldClass)) {
+ this.panelContainer.removeClass(oldClass);
+ } else if (fromElement && this.panelEl.hasClass(oldClass)) {
+ this.panelEl.removeClass(oldClass);
+ }
+};
+
+
+/**
+ * Toggle a class on the panel. DO NOT use this to hide/show the panel.
+ * @deprecated
+ * This method is in the process of being deprecated in favor of using the panel
+ * and container JQLite elements that are referenced in the MdPanelRef object.
+ * Full deprecation is scheduled for material 1.2.
+ *
+ * @param {string} toggleClass The class to toggle.
+ * @param {boolean} onElement Whether or not to toggle the class on the panel
+ * element instead of the container.
+ */
+MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
+ this._$log.warn(
+ 'mdPanel: The toggleClass method is in the process of being deprecated. ' +
+ 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
+ 'To achieve the same results, use the panelContainer or panelEl ' +
+ 'JQLite elements that are referenced in MdPanelRef.');
+
+ if (!this.panelContainer) {
+ throw new Error(
+ 'mdPanel: Panel does not exist yet. Call open() or attach().');
+ }
+
+ if (!onElement) {
+ this.panelContainer.toggleClass(toggleClass);
+ } else {
+ this.panelEl.toggleClass(toggleClass);
+ }
+};
+
+
+/**
+ * Compiles the panel, according to the passed in config and appends it to
+ * the DOM. Helps normalize differences in the compilation process between
+ * using a string template and a content element.
+ * @returns {!angular.$q.Promise<!MdPanelRef>} Promise that is resolved when
+ * the element has been compiled and added to the DOM.
+ * @private
+ */
+MdPanelRef.prototype._compile = function() {
+ var self = this;
+
+ // Compile the element via $mdCompiler. Note that when using a
+ // contentElement, the element isn't actually being compiled, rather the
+ // compiler saves it's place in the DOM and provides a way of restoring it.
+ return self._$mdCompiler.compile(self.config).then(function(compileData) {
+ var config = self.config;
+
+ if (config.contentElement) {
+ var panelEl = compileData.element;
+
+ // Since mdPanel modifies the inline styles and CSS classes, we need
+ // to save them in order to be able to restore on close.
+ self._restoreCache.styles = panelEl[0].style.cssText;
+ self._restoreCache.classes = panelEl[0].className;
+
+ self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
+ self.panelEl = panelEl;
+ } else {
+ self.panelContainer = compileData.link(config['scope']);
+ self.panelEl = angular.element(
+ self.panelContainer[0].querySelector('.md-panel')
+ );
+ }
+
+ // Save a reference to the cleanup function from the compiler.
+ self._compilerCleanup = compileData.cleanup;
+
+ // Attach the panel to the proper place in the DOM.
+ getElement(self.config['attachTo']).append(self.panelContainer);
+
+ return self;
+ });
+};
+
+
+/**
+ * Creates a panel and adds it to the dom.
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
+ * created.
+ * @private
+ */
+MdPanelRef.prototype._createPanel = function() {
+ var self = this;
+
+ return this._$q(function(resolve, reject) {
+ if (!self.config.locals) {
+ self.config.locals = {};
+ }
+
+ self.config.locals.mdPanelRef = self;
+
+ self._compile().then(function() {
+ if (self.config['disableParentScroll']) {
+ self._restoreScroll = self._$mdUtil.disableScrollAround(
+ null,
+ self.panelContainer,
+ { disableScrollMask: true }
+ );
+ }
+
+ // Add a custom CSS class to the panel element.
+ if (self.config['panelClass']) {
+ self.panelEl.addClass(self.config['panelClass']);
+ }
+
+ // Handle click and touch events for the panel container.
+ if (self.config['propagateContainerEvents']) {
+ self.panelContainer.css('pointer-events', 'none');
+ }
+
+ // Panel may be outside the $rootElement, tell ngAnimate to animate
+ // regardless.
+ if (self._$animate.pin) {
+ self._$animate.pin(
+ self.panelContainer,
+ getElement(self.config['attachTo'])
+ );
+ }
+
+ self._configureTrapFocus();
+ self._addStyles().then(function() {
+ resolve(self);
+ }, reject);
+ }, reject);
+
+ });
+};
+
+
+/**
+ * Adds the styles for the panel, such as positioning and z-index. Also,
+ * themes the panel element and panel container using `$mdTheming`.
+ * @returns {!angular.$q.Promise<!MdPanelRef>}
+ * @private
+ */
+MdPanelRef.prototype._addStyles = function() {
+ var self = this;
+ return this._$q(function(resolve) {
+ self.panelContainer.css('z-index', self.config['zIndex']);
+ self.panelEl.css('z-index', self.config['zIndex'] + 1);
+
+ var hideAndResolve = function() {
+ // Theme the element and container.
+ self._setTheming();
+
+ // Remove left: -9999px and add hidden class.
+ self.panelEl.css('left', '');
+ self.panelContainer.addClass(MD_PANEL_HIDDEN);
+
+ resolve(self);
+ };
+
+ if (self.config['fullscreen']) {
+ self.panelEl.addClass('_md-panel-fullscreen');
+ hideAndResolve();
+ return; // Don't setup positioning.
+ }
+
+ var positionConfig = self.config['position'];
+ if (!positionConfig) {
+ hideAndResolve();
+ return; // Don't setup positioning.
+ }
+
+ // Wait for angular to finish processing the template
+ self._$rootScope['$$postDigest'](function() {
+ // Position it correctly. This is necessary so that the panel will have a
+ // defined height and width.
+ self._updatePosition(true);
+
+ // Theme the element and container.
+ self._setTheming();
+
+ resolve(self);
+ });
+ });
+};
+
+
+/**
+ * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
+ * @private
+ */
+MdPanelRef.prototype._setTheming = function() {
+ this._$mdTheming(this.panelEl);
+ this._$mdTheming(this.panelContainer);
+};
+
+
+/**
+ * Updates the position configuration of a panel
+ * @param {!MdPanelPosition} position
+ */
+MdPanelRef.prototype.updatePosition = function(position) {
+ if (!this.panelContainer) {
+ throw new Error(
+ 'mdPanel: Panel does not exist yet. Call open() or attach().');
+ }
+
+ this.config['position'] = position;
+ this._updatePosition();
+};
+
+
+/**
+ * Calculates and updates the position of the panel.
+ * @param {boolean=} init
+ * @private
+ */
+MdPanelRef.prototype._updatePosition = function(init) {
+ var positionConfig = this.config['position'];
+
+ if (positionConfig) {
+ positionConfig._setPanelPosition(this.panelEl);
+
+ // Hide the panel now that position is known.
+ if (init) {
+ this.panelContainer.addClass(MD_PANEL_HIDDEN);
+ }
+
+ this.panelEl.css(
+ MdPanelPosition.absPosition.TOP,
+ positionConfig.getTop()
+ );
+ this.panelEl.css(
+ MdPanelPosition.absPosition.BOTTOM,
+ positionConfig.getBottom()
+ );
+ this.panelEl.css(
+ MdPanelPosition.absPosition.LEFT,
+ positionConfig.getLeft()
+ );
+ this.panelEl.css(
+ MdPanelPosition.absPosition.RIGHT,
+ positionConfig.getRight()
+ );
+ }
+};
+
+
+/**
+ * Focuses on the panel or the first focus target.
+ * @private
+ */
+MdPanelRef.prototype._focusOnOpen = function() {
+ if (this.config['focusOnOpen']) {
+ // Wait for the template to finish rendering to guarantee md-autofocus has
+ // finished adding the class md-autofocus, otherwise the focusable element
+ // isn't available to focus.
+ var self = this;
+ this._$rootScope['$$postDigest'](function() {
+ var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
+ self.panelEl;
+ target.focus();
+ });
+ }
+};
+
+
+/**
+ * Shows the backdrop.
+ * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
+ * is created and attached.
+ * @private
+ */
+MdPanelRef.prototype._createBackdrop = function() {
+ if (this.config.hasBackdrop) {
+ if (!this._backdropRef) {
+ var backdropAnimation = this._$mdPanel.newPanelAnimation()
+ .openFrom(this.config.attachTo)
+ .withAnimation({
+ open: '_md-opaque-enter',
+ close: '_md-opaque-leave'
+ });
+
+ if (this.config.animation) {
+ backdropAnimation.duration(this.config.animation._rawDuration);
+ }
+
+ var backdropConfig = {
+ animation: backdropAnimation,
+ attachTo: this.config.attachTo,
+ focusOnOpen: false,
+ panelClass: '_md-panel-backdrop',
+ zIndex: this.config.zIndex - 1
+ };
+
+ this._backdropRef = this._$mdPanel.create(backdropConfig);
+ }
+ if (!this._backdropRef.isAttached) {
+ return this._backdropRef.attach();
+ }
+ }
+};
+
+
+/**
+ * Listen for escape keys and outside clicks to auto close.
+ * @private
+ */
+MdPanelRef.prototype._addEventListeners = function() {
+ this._configureEscapeToClose();
+ this._configureClickOutsideToClose();
+ this._configureScrollListener();
+};
+
+
+/**
+ * Remove event listeners added in _addEventListeners.
+ * @private
+ */
+MdPanelRef.prototype._removeEventListeners = function() {
+ this._removeListeners && this._removeListeners.forEach(function(removeFn) {
+ removeFn();
+ });
+ this._removeListeners = [];
+};
+
+
+/**
+ * Setup the escapeToClose event listeners.
+ * @private
+ */
+MdPanelRef.prototype._configureEscapeToClose = function() {
+ if (this.config['escapeToClose']) {
+ var parentTarget = getElement(this.config['attachTo']);
+ var self = this;
+
+ var keyHandlerFn = function(ev) {
+ if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ self.close(MdPanelRef.closeReasons.ESCAPE);
+ }
+ };
+
+ // Add keydown listeners
+ this.panelContainer.on('keydown', keyHandlerFn);
+ parentTarget.on('keydown', keyHandlerFn);
+
+ // Queue remove listeners function
+ this._removeListeners.push(function() {
+ self.panelContainer.off('keydown', keyHandlerFn);
+ parentTarget.off('keydown', keyHandlerFn);
+ });
+ }
+};
+
+
+/**
+ * Setup the clickOutsideToClose event listeners.
+ * @private
+ */
+MdPanelRef.prototype._configureClickOutsideToClose = function() {
+ if (this.config['clickOutsideToClose']) {
+ var target = this.config['propagateContainerEvents'] ?
+ angular.element(document.body) :
+ this.panelContainer;
+ var sourceEl;
+
+ // Keep track of the element on which the mouse originally went down
+ // so that we can only close the backdrop when the 'click' started on it.
+ // A simple 'click' handler does not work, it sets the target object as the
+ // element the mouse went down on.
+ var mousedownHandler = function(ev) {
+ sourceEl = ev.target;
+ };
+
+ // We check if our original element and the target is the backdrop
+ // because if the original was the backdrop and the target was inside the
+ // panel we don't want to panel to close.
+ var self = this;
+ var mouseupHandler = function(ev) {
+ if (self.config['propagateContainerEvents']) {
+
+ // We check if the sourceEl of the event is the panel element or one
+ // of it's children. If it is not, then close the panel.
+ if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) {
+ self.close();
+ }
+
+ } else if (sourceEl === target[0] && ev.target === target[0]) {
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
+ }
+ };
+
+ // Add listeners
+ target.on('mousedown', mousedownHandler);
+ target.on('mouseup', mouseupHandler);
+
+ // Queue remove listeners function
+ this._removeListeners.push(function() {
+ target.off('mousedown', mousedownHandler);
+ target.off('mouseup', mouseupHandler);
+ });
+ }
+};
+
+
+/**
+ * Configures the listeners for updating the panel position on scroll.
+ * @private
+*/
+MdPanelRef.prototype._configureScrollListener = function() {
+ // No need to bind the event if scrolling is disabled.
+ if (!this.config['disableParentScroll']) {
+ var updatePosition = angular.bind(this, this._updatePosition);
+ var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
+ var self = this;
+
+ var onScroll = function() {
+ debouncedUpdatePosition();
+ };
+
+ // Add listeners.
+ this._$window.addEventListener('scroll', onScroll, true);
+
+ // Queue remove listeners function.
+ this._removeListeners.push(function() {
+ self._$window.removeEventListener('scroll', onScroll, true);
+ });
+ }
+};
+
+
+/**
+ * Setup the focus traps. These traps will wrap focus when tabbing past the
+ * panel. When shift-tabbing, the focus will stick in place.
+ * @private
+ */
+MdPanelRef.prototype._configureTrapFocus = function() {
+ // Focus doesn't remain inside of the panel without this.
+ this.panelEl.attr('tabIndex', '-1');
+ if (this.config['trapFocus']) {
+ var element = this.panelEl;
+ // Set up elements before and after the panel to capture focus and
+ // redirect back into the panel.
+ this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
+ this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
+
+ // When focus is about to move out of the panel, we want to intercept it
+ // and redirect it back to the panel element.
+ var focusHandler = function() {
+ element.focus();
+ };
+ this._topFocusTrap.addEventListener('focus', focusHandler);
+ this._bottomFocusTrap.addEventListener('focus', focusHandler);
+
+ // Queue remove listeners function
+ this._removeListeners.push(this._simpleBind(function() {
+ this._topFocusTrap.removeEventListener('focus', focusHandler);
+ this._bottomFocusTrap.removeEventListener('focus', focusHandler);
+ }, this));
+
+ // The top focus trap inserted immediately before the md-panel element (as
+ // a sibling). The bottom focus trap inserted immediately after the
+ // md-panel element (as a sibling).
+ element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
+ element.after(this._bottomFocusTrap);
+ }
+};
+
+
+/**
+ * Updates the animation of a panel.
+ * @param {!MdPanelAnimation} animation
+ */
+MdPanelRef.prototype.updateAnimation = function(animation) {
+ this.config['animation'] = animation;
+
+ if (this._backdropRef) {
+ this._backdropRef.config.animation.duration(animation._rawDuration);
+ }
+};
+
+
+/**
+ * Animate the panel opening.
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
+ * animated open.
+ * @private
+ */
+MdPanelRef.prototype._animateOpen = function() {
+ this.panelContainer.addClass('md-panel-is-showing');
+ var animationConfig = this.config['animation'];
+ if (!animationConfig) {
+ // Promise is in progress, return it.
+ this.panelContainer.addClass('_md-panel-shown');
+ return this._$q.when(this);
+ }
+
+ var self = this;
+ return this._$q(function(resolve) {
+ var done = self._done(resolve, self);
+ var warnAndOpen = function() {
+ self._$log.warn(
+ 'mdPanel: MdPanel Animations failed. ' +
+ 'Showing panel without animating.');
+ done();
+ };
+
+ animationConfig.animateOpen(self.panelEl)
+ .then(done, warnAndOpen);
+ });
+};
+
+
+/**
+ * Animate the panel closing.
+ * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
+ * animated closed.
+ * @private
+ */
+MdPanelRef.prototype._animateClose = function() {
+ var animationConfig = this.config['animation'];
+ if (!animationConfig) {
+ this.panelContainer.removeClass('md-panel-is-showing');
+ this.panelContainer.removeClass('_md-panel-shown');
+ return this._$q.when(this);
+ }
+
+ var self = this;
+ return this._$q(function(resolve) {
+ var done = function() {
+ self.panelContainer.removeClass('md-panel-is-showing');
+ resolve(self);
+ };
+ var warnAndClose = function() {
+ self._$log.warn(
+ 'mdPanel: MdPanel Animations failed. ' +
+ 'Hiding panel without animating.');
+ done();
+ };
+
+ animationConfig.animateClose(self.panelEl)
+ .then(done, warnAndClose);
+ });
+};
+
+
+/**
+ * Registers a interceptor with the panel. The callback should return a promise,
+ * which will allow the action to continue when it gets resolved, or will
+ * prevent an action if it is rejected.
+ * @param {string} type Type of interceptor.
+ * @param {!angular.$q.Promise<!any>} callback Callback to be registered.
+ * @returns {!MdPanelRef}
+ */
+MdPanelRef.prototype.registerInterceptor = function(type, callback) {
+ var error = null;
+
+ if (!angular.isString(type)) {
+ error = 'Interceptor type must be a string, instead got ' + typeof type;
+ } else if (!angular.isFunction(callback)) {
+ error = 'Interceptor callback must be a function, instead got ' + typeof callback;
+ }
+
+ if (error) {
+ throw new Error('MdPanel: ' + error);
+ }
+
+ var interceptors = this._interceptors[type] = this._interceptors[type] || [];
+
+ if (interceptors.indexOf(callback) === -1) {
+ interceptors.push(callback);
+ }
+
+ return this;
+};
+
+
+/**
+ * Removes a registered interceptor.
+ * @param {string} type Type of interceptor to be removed.
+ * @param {Function} callback Interceptor to be removed.
+ * @returns {!MdPanelRef}
+ */
+MdPanelRef.prototype.removeInterceptor = function(type, callback) {
+ var index = this._interceptors[type] ?
+ this._interceptors[type].indexOf(callback) : -1;
+
+ if (index > -1) {
+ this._interceptors[type].splice(index, 1);
+ }
+
+ return this;
+};
+
+
+/**
+ * Removes all interceptors.
+ * @param {string=} type Type of interceptors to be removed.
+ * If ommited, all interceptors types will be removed.
+ * @returns {!MdPanelRef}
+ */
+MdPanelRef.prototype.removeAllInterceptors = function(type) {
+ if (type) {
+ this._interceptors[type] = [];
+ } else {
+ this._interceptors = Object.create(null);
+ }
+
+ return this;
+};
+
+
+/**
+ * Invokes all the interceptors of a certain type sequantially in
+ * reverse order. Works in a similar way to `$q.all`, except it
+ * respects the order of the functions.
+ * @param {string} type Type of interceptors to be invoked.
+ * @returns {!angular.$q.Promise<!MdPanelRef>}
+ * @private
+ */
+MdPanelRef.prototype._callInterceptors = function(type) {
+ var self = this;
+ var $q = self._$q;
+ var interceptors = self._interceptors && self._interceptors[type] || [];
+
+ return interceptors.reduceRight(function(promise, interceptor) {
+ var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
+ var response = isPromiseLike ? interceptor : null;
+
+ /**
+ * For interceptors to reject/cancel subsequent portions of the chain, simply
+ * return a `$q.reject(<value>)`
+ */
+ return promise.then(function() {
+ if (!response) {
+ try {
+ response = interceptor(self);
+ } catch(e) {
+ response = $q.reject(e);
+ }
+ }
+
+ return response;
+ });
+ }, $q.resolve(self));
+};
+
+
+/**
+ * Faster, more basic than angular.bind
+ * http://jsperf.com/angular-bind-vs-custom-vs-native
+ * @param {function} callback
+ * @param {!Object} self
+ * @return {function} Callback function with a bound self.
+ */
+MdPanelRef.prototype._simpleBind = function(callback, self) {
+ return function(value) {
+ return callback.apply(self, value);
+ };
+};
+
+
+/**
+ * @param {function} callback
+ * @param {!Object} self
+ * @return {function} Callback function with a self param.
+ */
+MdPanelRef.prototype._done = function(callback, self) {
+ return function() {
+ callback(self);
+ };
+};
+
+
+/**
+ * Adds a panel to a group if the panel does not exist within the group already.
+ * A panel can only exist within a single group.
+ * @param {string} groupName The name of the group.
+ */
+MdPanelRef.prototype.addToGroup = function(groupName) {
+ if (!this._$mdPanel._groups[groupName]) {
+ this._$mdPanel.newPanelGroup(groupName);
+ }
+
+ var group = this._$mdPanel._groups[groupName];
+ var index = group.panels.indexOf(this);
+
+ if (index < 0) {
+ group.panels.push(this);
+ }
+};
+
+
+/**
+ * Removes a panel from a group if the panel exists within that group. The group
+ * must be created ahead of time.
+ * @param {string} groupName The name of the group.
+ */
+MdPanelRef.prototype.removeFromGroup = function(groupName) {
+ if (!this._$mdPanel._groups[groupName]) {
+ throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
+ }
+
+ var group = this._$mdPanel._groups[groupName];
+ var index = group.panels.indexOf(this);
+
+ if (index > -1) {
+ group.panels.splice(index, 1);
+ }
+};
+
+
+/**
+ * Possible default closeReasons for the close function.
+ * @enum {string}
+ */
+MdPanelRef.closeReasons = {
+ CLICK_OUTSIDE: 'clickOutsideToClose',
+ ESCAPE: 'escapeToClose',
+};
+
+
+/*****************************************************************************
+ * MdPanelPosition *
+ *****************************************************************************/
+
+
+/**
+ * Position configuration object. To use, create an MdPanelPosition with the
+ * desired properties, then pass the object as part of $mdPanel creation.
+ *
+ * Example:
+ *
+ * var panelPosition = new MdPanelPosition()
+ * .relativeTo(myButtonEl)
+ * .addPanelPosition(
+ * $mdPanel.xPosition.CENTER,
+ * $mdPanel.yPosition.ALIGN_TOPS
+ * );
+ *
+ * $mdPanel.create({
+ * position: panelPosition
+ * });
+ *
+ * @param {!angular.$injector} $injector
+ * @final @constructor
+ */
+function MdPanelPosition($injector) {
+ /** @private @const {!angular.$window} */
+ this._$window = $injector.get('$window');
+
+ /** @private {boolean} */
+ this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
+
+ /** @private @const {!angular.$mdConstant} */
+ this._$mdConstant = $injector.get('$mdConstant');
+
+ /** @private {boolean} */
+ this._absolute = false;
+
+ /** @private {!angular.JQLite} */
+ this._relativeToEl;
+
+ /** @private {string} */
+ this._top = '';
+
+ /** @private {string} */
+ this._bottom = '';
+
+ /** @private {string} */
+ this._left = '';
+
+ /** @private {string} */
+ this._right = '';
+
+ /** @private {!Array<string>} */
+ this._translateX = [];
+
+ /** @private {!Array<string>} */
+ this._translateY = [];
+
+ /** @private {!Array<{x:string, y:string}>} */
+ this._positions = [];
+
+ /** @private {?{x:string, y:string}} */
+ this._actualPosition;
+}
+
+
+/**
+ * Possible values of xPosition.
+ * @enum {string}
+ */
+MdPanelPosition.xPosition = {
+ CENTER: 'center',
+ ALIGN_START: 'align-start',
+ ALIGN_END: 'align-end',
+ OFFSET_START: 'offset-start',
+ OFFSET_END: 'offset-end'
+};
+
+
+/**
+ * Possible values of yPosition.
+ * @enum {string}
+ */
+MdPanelPosition.yPosition = {
+ CENTER: 'center',
+ ALIGN_TOPS: 'align-tops',
+ ALIGN_BOTTOMS: 'align-bottoms',
+ ABOVE: 'above',
+ BELOW: 'below'
+};
+
+
+/**
+ * Possible values of absolute position.
+ * @enum {string}
+ */
+MdPanelPosition.absPosition = {
+ TOP: 'top',
+ RIGHT: 'right',
+ BOTTOM: 'bottom',
+ LEFT: 'left'
+};
+
+/**
+ * Margin between the edges of a panel and the viewport.
+ * @const {number}
+ */
+MdPanelPosition.viewportMargin = 8;
+
+
+/**
+ * Sets absolute positioning for the panel.
+ * @return {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.absolute = function() {
+ this._absolute = true;
+ return this;
+};
+
+
+/**
+ * Sets the value of a position for the panel. Clears any previously set
+ * position.
+ * @param {string} position Position to set
+ * @param {string=} value Value of the position. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ * @private
+ */
+MdPanelPosition.prototype._setPosition = function(position, value) {
+ if (position === MdPanelPosition.absPosition.RIGHT ||
+ position === MdPanelPosition.absPosition.LEFT) {
+ this._left = this._right = '';
+ } else if (
+ position === MdPanelPosition.absPosition.BOTTOM ||
+ position === MdPanelPosition.absPosition.TOP) {
+ this._top = this._bottom = '';
+ } else {
+ var positions = Object.keys(MdPanelPosition.absPosition).join()
+ .toLowerCase();
+
+ throw new Error('mdPanel: Position must be one of ' + positions + '.');
+ }
+
+ this['_' + position] = angular.isString(value) ? value : '0';
+
+ return this;
+};
+
+
+/**
+ * Sets the value of `top` for the panel. Clears any previously set vertical
+ * position.
+ * @param {string=} top Value of `top`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.top = function(top) {
+ return this._setPosition(MdPanelPosition.absPosition.TOP, top);
+};
+
+
+/**
+ * Sets the value of `bottom` for the panel. Clears any previously set vertical
+ * position.
+ * @param {string=} bottom Value of `bottom`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.bottom = function(bottom) {
+ return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
+};
+
+
+/**
+ * Sets the panel to the start of the page - `left` if `ltr` or `right` for
+ * `rtl`. Clears any previously set horizontal position.
+ * @param {string=} start Value of position. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.start = function(start) {
+ var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
+ return this._setPosition(position, start);
+};
+
+
+/**
+ * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
+ * Clears any previously set horizontal position.
+ * @param {string=} end Value of position. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.end = function(end) {
+ var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
+ return this._setPosition(position, end);
+};
+
+
+/**
+ * Sets the value of `left` for the panel. Clears any previously set
+ * horizontal position.
+ * @param {string=} left Value of `left`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.left = function(left) {
+ return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
+};
+
+
+/**
+ * Sets the value of `right` for the panel. Clears any previously set
+ * horizontal position.
+ * @param {string=} right Value of `right`. Defaults to '0'.
+ * @returns {!MdPanelPosition}
+*/
+MdPanelPosition.prototype.right = function(right) {
+ return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
+};
+
+
+/**
+ * Centers the panel horizontally in the viewport. Clears any previously set
+ * horizontal position.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.centerHorizontally = function() {
+ this._left = '50%';
+ this._right = '';
+ this._translateX = ['-50%'];
+ return this;
+};
+
+
+/**
+ * Centers the panel vertically in the viewport. Clears any previously set
+ * vertical position.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.centerVertically = function() {
+ this._top = '50%';
+ this._bottom = '';
+ this._translateY = ['-50%'];
+ return this;
+};
+
+
+/**
+ * Centers the panel horizontally and vertically in the viewport. This is
+ * equivalent to calling both `centerHorizontally` and `centerVertically`.
+ * Clears any previously set horizontal and vertical positions.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.center = function() {
+ return this.centerHorizontally().centerVertically();
+};
+
+
+/**
+ * Sets element for relative positioning.
+ * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
+ * or angular element to set the panel relative to.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.relativeTo = function(element) {
+ this._absolute = false;
+ this._relativeToEl = getElement(element);
+ return this;
+};
+
+
+/**
+ * Sets the x and y positions for the panel relative to another element.
+ * @param {string} xPosition must be one of the MdPanelPosition.xPosition
+ * values.
+ * @param {string} yPosition must be one of the MdPanelPosition.yPosition
+ * values.
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
+ if (!this._relativeToEl) {
+ throw new Error('mdPanel: addPanelPosition can only be used with ' +
+ 'relative positioning. Set relativeTo first.');
+ }
+
+ this._validateXPosition(xPosition);
+ this._validateYPosition(yPosition);
+
+ this._positions.push({
+ x: xPosition,
+ y: yPosition,
+ });
+ return this;
+};
+
+
+/**
+ * Ensures that yPosition is a valid position name. Throw an exception if not.
+ * @param {string} yPosition
+ */
+MdPanelPosition.prototype._validateYPosition = function(yPosition) {
+ // empty is ok
+ if (yPosition == null) {
+ return;
+ }
+
+ var positionKeys = Object.keys(MdPanelPosition.yPosition);
+ var positionValues = [];
+ for (var key, i = 0; key = positionKeys[i]; i++) {
+ var position = MdPanelPosition.yPosition[key];
+ positionValues.push(position);
+
+ if (position === yPosition) {
+ return;
+ }
+ }
+
+ throw new Error('mdPanel: Panel y position only accepts the following ' +
+ 'values:\n' + positionValues.join(' | '));
+};
+
+
+/**
+ * Ensures that xPosition is a valid position name. Throw an exception if not.
+ * @param {string} xPosition
+ */
+MdPanelPosition.prototype._validateXPosition = function(xPosition) {
+ // empty is ok
+ if (xPosition == null) {
+ return;
+ }
+
+ var positionKeys = Object.keys(MdPanelPosition.xPosition);
+ var positionValues = [];
+ for (var key, i = 0; key = positionKeys[i]; i++) {
+ var position = MdPanelPosition.xPosition[key];
+ positionValues.push(position);
+ if (position === xPosition) {
+ return;
+ }
+ }
+
+ throw new Error('mdPanel: Panel x Position only accepts the following ' +
+ 'values:\n' + positionValues.join(' | '));
+};
+
+
+/**
+ * Sets the value of the offset in the x-direction. This will add to any
+ * previously set offsets.
+ * @param {string|function(MdPanelPosition): string} offsetX
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.withOffsetX = function(offsetX) {
+ this._translateX.push(offsetX);
+ return this;
+};
+
+
+/**
+ * Sets the value of the offset in the y-direction. This will add to any
+ * previously set offsets.
+ * @param {string|function(MdPanelPosition): string} offsetY
+ * @returns {!MdPanelPosition}
+ */
+MdPanelPosition.prototype.withOffsetY = function(offsetY) {
+ this._translateY.push(offsetY);
+ return this;
+};
+
+
+/**
+ * Gets the value of `top` for the panel.
+ * @returns {string}
+ */
+MdPanelPosition.prototype.getTop = function() {
+ return this._top;
+};
+
+
+/**
+ * Gets the value of `bottom` for the panel.
+ * @returns {string}
+ */
+MdPanelPosition.prototype.getBottom = function() {
+ return this._bottom;
+};
+
+
+/**
+ * Gets the value of `left` for the panel.
+ * @returns {string}
+ */
+MdPanelPosition.prototype.getLeft = function() {
+ return this._left;
+};
+
+
+/**
+ * Gets the value of `right` for the panel.
+ * @returns {string}
+ */
+MdPanelPosition.prototype.getRight = function() {
+ return this._right;
+};
+
+
+/**
+ * Gets the value of `transform` for the panel.
+ * @returns {string}
+ */
+MdPanelPosition.prototype.getTransform = function() {
+ var translateX = this._reduceTranslateValues('translateX', this._translateX);
+ var translateY = this._reduceTranslateValues('translateY', this._translateY);
+
+ // It's important to trim the result, because the browser will ignore the set
+ // operation if the string contains only whitespace.
+ return (translateX + ' ' + translateY).trim();
+};
+
+
+/**
+ * Sets the `transform` value for a panel element.
+ * @param {!angular.JQLite} panelEl
+ * @returns {!angular.JQLite}
+ * @private
+ */
+MdPanelPosition.prototype._setTransform = function(panelEl) {
+ return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
+};
+
+
+/**
+ * True if the panel is completely on-screen with this positioning; false
+ * otherwise.
+ * @param {!angular.JQLite} panelEl
+ * @return {boolean}
+ * @private
+ */
+MdPanelPosition.prototype._isOnscreen = function(panelEl) {
+ // this works because we always use fixed positioning for the panel,
+ // which is relative to the viewport.
+ var left = parseInt(this.getLeft());
+ var top = parseInt(this.getTop());
+
+ if (this._translateX.length || this._translateY.length) {
+ var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
+ var offsets = getComputedTranslations(panelEl, prefixedTransform);
+ left += offsets.x;
+ top += offsets.y;
+ }
+
+ var right = left + panelEl[0].offsetWidth;
+ var bottom = top + panelEl[0].offsetHeight;
+
+ return (left >= 0) &&
+ (top >= 0) &&
+ (bottom <= this._$window.innerHeight) &&
+ (right <= this._$window.innerWidth);
+};
+
+
+/**
+ * Gets the first x/y position that can fit on-screen.
+ * @returns {{x: string, y: string}}
+ */
+MdPanelPosition.prototype.getActualPosition = function() {
+ return this._actualPosition;
+};
+
+
+/**
+ * Reduces a list of translate values to a string that can be used within
+ * transform.
+ * @param {string} translateFn
+ * @param {!Array<string>} values
+ * @returns {string}
+ * @private
+ */
+MdPanelPosition.prototype._reduceTranslateValues =
+ function(translateFn, values) {
+ return values.map(function(translation) {
+ // TODO(crisbeto): this should add the units after #9609 is merged.
+ var translationValue = angular.isFunction(translation) ?
+ translation(this) : translation;
+ return translateFn + '(' + translationValue + ')';
+ }, this).join(' ');
+ };
+
+
+/**
+ * Sets the panel position based on the created panel element and best x/y
+ * positioning.
+ * @param {!angular.JQLite} panelEl
+ * @private
+ */
+MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
+ // Remove the "position adjusted" class in case it has been added before.
+ panelEl.removeClass('_md-panel-position-adjusted');
+
+ // Only calculate the position if necessary.
+ if (this._absolute) {
+ this._setTransform(panelEl);
+ return;
+ }
+
+ if (this._actualPosition) {
+ this._calculatePanelPosition(panelEl, this._actualPosition);
+ this._setTransform(panelEl);
+ this._constrainToViewport(panelEl);
+ return;
+ }
+
+ for (var i = 0; i < this._positions.length; i++) {
+ this._actualPosition = this._positions[i];
+ this._calculatePanelPosition(panelEl, this._actualPosition);
+ this._setTransform(panelEl);
+
+ if (this._isOnscreen(panelEl)) {
+ return;
+ }
+ }
+
+ this._constrainToViewport(panelEl);
+};
+
+
+/**
+ * Constrains a panel's position to the viewport.
+ * @param {!angular.JQLite} panelEl
+ * @private
+ */
+MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
+ var margin = MdPanelPosition.viewportMargin;
+ var initialTop = this._top;
+ var initialLeft = this._left;
+
+ if (this.getTop()) {
+ var top = parseInt(this.getTop());
+ var bottom = panelEl[0].offsetHeight + top;
+ var viewportHeight = this._$window.innerHeight;
+
+ if (top < margin) {
+ this._top = margin + 'px';
+ } else if (bottom > viewportHeight) {
+ this._top = top - (bottom - viewportHeight + margin) + 'px';
+ }
+ }
+
+ if (this.getLeft()) {
+ var left = parseInt(this.getLeft());
+ var right = panelEl[0].offsetWidth + left;
+ var viewportWidth = this._$window.innerWidth;
+
+ if (left < margin) {
+ this._left = margin + 'px';
+ } else if (right > viewportWidth) {
+ this._left = left - (right - viewportWidth + margin) + 'px';
+ }
+ }
+
+ // Class that can be used to re-style the panel if it was repositioned.
+ panelEl.toggleClass(
+ '_md-panel-position-adjusted',
+ this._top !== initialTop || this._left !== initialLeft
+ );
+};
+
+
+/**
+ * Switches between 'start' and 'end'.
+ * @param {string} position Horizontal position of the panel
+ * @returns {string} Reversed position
+ * @private
+ */
+MdPanelPosition.prototype._reverseXPosition = function(position) {
+ if (position === MdPanelPosition.xPosition.CENTER) {
+ return;
+ }
+
+ var start = 'start';
+ var end = 'end';
+
+ return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
+};
+
+
+/**
+ * Handles horizontal positioning in rtl or ltr environments.
+ * @param {string} position Horizontal position of the panel
+ * @returns {string} The correct position according the page direction
+ * @private
+ */
+MdPanelPosition.prototype._bidi = function(position) {
+ return this._isRTL ? this._reverseXPosition(position) : position;
+};
+
+
+/**
+ * Calculates the panel position based on the created panel element and the
+ * provided positioning.
+ * @param {!angular.JQLite} panelEl
+ * @param {!{x:string, y:string}} position
+ * @private
+ */
+MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
+
+ var panelBounds = panelEl[0].getBoundingClientRect();
+ var panelWidth = panelBounds.width;
+ var panelHeight = panelBounds.height;
+
+ var targetBounds = this._relativeToEl[0].getBoundingClientRect();
+
+ var targetLeft = targetBounds.left;
+ var targetRight = targetBounds.right;
+ var targetWidth = targetBounds.width;
+
+ switch (this._bidi(position.x)) {
+ case MdPanelPosition.xPosition.OFFSET_START:
+ this._left = targetLeft - panelWidth + 'px';
+ break;
+ case MdPanelPosition.xPosition.ALIGN_END:
+ this._left = targetRight - panelWidth + 'px';
+ break;
+ case MdPanelPosition.xPosition.CENTER:
+ var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
+ this._left = left + 'px';
+ break;
+ case MdPanelPosition.xPosition.ALIGN_START:
+ this._left = targetLeft + 'px';
+ break;
+ case MdPanelPosition.xPosition.OFFSET_END:
+ this._left = targetRight + 'px';
+ break;
+ }
+
+ var targetTop = targetBounds.top;
+ var targetBottom = targetBounds.bottom;
+ var targetHeight = targetBounds.height;
+
+ switch (position.y) {
+ case MdPanelPosition.yPosition.ABOVE:
+ this._top = targetTop - panelHeight + 'px';
+ break;
+ case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
+ this._top = targetBottom - panelHeight + 'px';
+ break;
+ case MdPanelPosition.yPosition.CENTER:
+ var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
+ this._top = top + 'px';
+ break;
+ case MdPanelPosition.yPosition.ALIGN_TOPS:
+ this._top = targetTop + 'px';
+ break;
+ case MdPanelPosition.yPosition.BELOW:
+ this._top = targetBottom + 'px';
+ break;
+ }
+};
+
+
+/*****************************************************************************
+ * MdPanelAnimation *
+ *****************************************************************************/
+
+
+/**
+ * Animation configuration object. To use, create an MdPanelAnimation with the
+ * desired properties, then pass the object as part of $mdPanel creation.
+ *
+ * Example:
+ *
+ * var panelAnimation = new MdPanelAnimation()
+ * .openFrom(myButtonEl)
+ * .closeTo('.my-button')
+ * .withAnimation($mdPanel.animation.SCALE);
+ *
+ * $mdPanel.create({
+ * animation: panelAnimation
+ * });
+ *
+ * @param {!angular.$injector} $injector
+ * @final @constructor
+ */
+function MdPanelAnimation($injector) {
+ /** @private @const {!angular.$mdUtil} */
+ this._$mdUtil = $injector.get('$mdUtil');
+
+ /**
+ * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
+ * undefined}
+ */
+ this._openFrom;
+
+ /**
+ * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
+ * undefined}
+ */
+ this._closeTo;
+
+ /** @private {string|{open: string, close: string}} */
+ this._animationClass = '';
+
+ /** @private {number} */
+ this._openDuration;
+
+ /** @private {number} */
+ this._closeDuration;
+
+ /** @private {number|{open: number, close: number}} */
+ this._rawDuration;
+}
+
+
+/**
+ * Possible default animations.
+ * @enum {string}
+ */
+MdPanelAnimation.animation = {
+ SLIDE: 'md-panel-animate-slide',
+ SCALE: 'md-panel-animate-scale',
+ FADE: 'md-panel-animate-fade'
+};
+
+
+/**
+ * Specifies where to start the open animation. `openFrom` accepts a
+ * click event object, query selector, DOM element, or a Rect object that
+ * is used to determine the bounds. When passed a click event, the location
+ * of the click will be used as the position to start the animation.
+ * @param {string|!Element|!Event|{top: number, left: number}} openFrom
+ * @returns {!MdPanelAnimation}
+ */
+MdPanelAnimation.prototype.openFrom = function(openFrom) {
+ // Check if 'openFrom' is an Event.
+ openFrom = openFrom.target ? openFrom.target : openFrom;
+
+ this._openFrom = this._getPanelAnimationTarget(openFrom);
+
+ if (!this._closeTo) {
+ this._closeTo = this._openFrom;
+ }
+ return this;
+};
+
+
+/**
+ * Specifies where to animate the panel close. `closeTo` accepts a
+ * query selector, DOM element, or a Rect object that is used to determine
+ * the bounds.
+ * @param {string|!Element|{top: number, left: number}} closeTo
+ * @returns {!MdPanelAnimation}
+ */
+MdPanelAnimation.prototype.closeTo = function(closeTo) {
+ this._closeTo = this._getPanelAnimationTarget(closeTo);
+ return this;
+};
+
+
+/**
+ * Specifies the duration of the animation in milliseconds.
+ * @param {number|{open: number, close: number}} duration
+ * @returns {!MdPanelAnimation}
+ */
+MdPanelAnimation.prototype.duration = function(duration) {
+ if (duration) {
+ if (angular.isNumber(duration)) {
+ this._openDuration = this._closeDuration = toSeconds(duration);
+ } else if (angular.isObject(duration)) {
+ this._openDuration = toSeconds(duration.open);
+ this._closeDuration = toSeconds(duration.close);
+ }
+ }
+
+ // Save the original value so it can be passed to the backdrop.
+ this._rawDuration = duration;
+
+ return this;
+
+ function toSeconds(value) {
+ if (angular.isNumber(value)) return value / 1000;
+ }
+};
+
+
+/**
+ * Returns the element and bounds for the animation target.
+ * @param {string|!Element|{top: number, left: number}} location
+ * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
+ * @private
+ */
+MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
+ if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
+ return {
+ element: undefined,
+ bounds: {
+ top: location.top || 0,
+ left: location.left || 0
+ }
+ };
+ } else {
+ return this._getBoundingClientRect(getElement(location));
+ }
+};
+
+
+/**
+ * Specifies the animation class.
+ *
+ * There are several default animations that can be used:
+ * (MdPanelAnimation.animation)
+ * SLIDE: The panel slides in and out from the specified
+ * elements.
+ * SCALE: The panel scales in and out.
+ * FADE: The panel fades in and out.
+ *
+ * @param {string|{open: string, close: string}} cssClass
+ * @returns {!MdPanelAnimation}
+ */
+MdPanelAnimation.prototype.withAnimation = function(cssClass) {
+ this._animationClass = cssClass;
+ return this;
+};
+
+
+/**
+ * Animate the panel open.
+ * @param {!angular.JQLite} panelEl
+ * @returns {!angular.$q.Promise} A promise that is resolved when the open
+ * animation is complete.
+ */
+MdPanelAnimation.prototype.animateOpen = function(panelEl) {
+ var animator = this._$mdUtil.dom.animator;
+
+ this._fixBounds(panelEl);
+ var animationOptions = {};
+
+ // Include the panel transformations when calculating the animations.
+ var panelTransform = panelEl[0].style.transform || '';
+
+ var openFrom = animator.toTransformCss(panelTransform);
+ var openTo = animator.toTransformCss(panelTransform);
+
+ switch (this._animationClass) {
+ case MdPanelAnimation.animation.SLIDE:
+ // Slide should start with opacity: 1.
+ panelEl.css('opacity', '1');
+
+ animationOptions = {
+ transitionInClass: '_md-panel-animate-enter'
+ };
+
+ var openSlide = animator.calculateSlideToOrigin(
+ panelEl, this._openFrom) || '';
+ openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
+ break;
+
+ case MdPanelAnimation.animation.SCALE:
+ animationOptions = {
+ transitionInClass: '_md-panel-animate-enter'
+ };
+
+ var openScale = animator.calculateZoomToOrigin(
+ panelEl, this._openFrom) || '';
+ openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
+ break;
+
+ case MdPanelAnimation.animation.FADE:
+ animationOptions = {
+ transitionInClass: '_md-panel-animate-enter'
+ };
+ break;
+
+ default:
+ if (angular.isString(this._animationClass)) {
+ animationOptions = {
+ transitionInClass: this._animationClass
+ };
+ } else {
+ animationOptions = {
+ transitionInClass: this._animationClass['open'],
+ transitionOutClass: this._animationClass['close'],
+ };
+ }
+ }
+
+ animationOptions.duration = this._openDuration;
+
+ return animator
+ .translate3d(panelEl, openFrom, openTo, animationOptions);
+};
+
+
+/**
+ * Animate the panel close.
+ * @param {!angular.JQLite} panelEl
+ * @returns {!angular.$q.Promise} A promise that resolves when the close
+ * animation is complete.
+ */
+MdPanelAnimation.prototype.animateClose = function(panelEl) {
+ var animator = this._$mdUtil.dom.animator;
+ var reverseAnimationOptions = {};
+
+ // Include the panel transformations when calculating the animations.
+ var panelTransform = panelEl[0].style.transform || '';
+
+ var closeFrom = animator.toTransformCss(panelTransform);
+ var closeTo = animator.toTransformCss(panelTransform);
+
+ switch (this._animationClass) {
+ case MdPanelAnimation.animation.SLIDE:
+ // Slide should start with opacity: 1.
+ panelEl.css('opacity', '1');
+ reverseAnimationOptions = {
+ transitionInClass: '_md-panel-animate-leave'
+ };
+
+ var closeSlide = animator.calculateSlideToOrigin(
+ panelEl, this._closeTo) || '';
+ closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
+ break;
+
+ case MdPanelAnimation.animation.SCALE:
+ reverseAnimationOptions = {
+ transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
+ };
+
+ var closeScale = animator.calculateZoomToOrigin(
+ panelEl, this._closeTo) || '';
+ closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
+ break;
+
+ case MdPanelAnimation.animation.FADE:
+ reverseAnimationOptions = {
+ transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
+ };
+ break;
+
+ default:
+ if (angular.isString(this._animationClass)) {
+ reverseAnimationOptions = {
+ transitionOutClass: this._animationClass
+ };
+ } else {
+ reverseAnimationOptions = {
+ transitionInClass: this._animationClass['close'],
+ transitionOutClass: this._animationClass['open']
+ };
+ }
+ }
+
+ reverseAnimationOptions.duration = this._closeDuration;
+
+ return animator
+ .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
+};
+
+
+/**
+ * Set the height and width to match the panel if not provided.
+ * @param {!angular.JQLite} panelEl
+ * @private
+ */
+MdPanelAnimation.prototype._fixBounds = function(panelEl) {
+ var panelWidth = panelEl[0].offsetWidth;
+ var panelHeight = panelEl[0].offsetHeight;
+
+ if (this._openFrom && this._openFrom.bounds.height == null) {
+ this._openFrom.bounds.height = panelHeight;
+ }
+ if (this._openFrom && this._openFrom.bounds.width == null) {
+ this._openFrom.bounds.width = panelWidth;
+ }
+ if (this._closeTo && this._closeTo.bounds.height == null) {
+ this._closeTo.bounds.height = panelHeight;
+ }
+ if (this._closeTo && this._closeTo.bounds.width == null) {
+ this._closeTo.bounds.width = panelWidth;
+ }
+};
+
+
+/**
+ * Identify the bounding RECT for the target element.
+ * @param {!angular.JQLite} element
+ * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
+ * @private
+ */
+MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
+ if (element instanceof angular.element) {
+ return {
+ element: element,
+ bounds: element[0].getBoundingClientRect()
+ };
+ }
+};
+
+
+/*****************************************************************************
+ * Util Methods *
+ *****************************************************************************/
+
+
+/**
+ * Returns the angular element associated with a css selector or element.
+ * @param el {string|!angular.JQLite|!Element}
+ * @returns {!angular.JQLite}
+ */
+function getElement(el) {
+ var queryResult = angular.isString(el) ?
+ document.querySelector(el) : el;
+ return angular.element(queryResult);
+}
+
+
+/**
+ * Gets the computed values for an element's translateX and translateY in px.
+ * @param {!angular.JQLite|!Element} el
+ * @param {string} property
+ * @return {{x: number, y: number}}
+ */
+function getComputedTranslations(el, property) {
+ // The transform being returned by `getComputedStyle` is in the format:
+ // `matrix(a, b, c, d, translateX, translateY)` if defined and `none`
+ // if the element doesn't have a transform.
+ var transform = getComputedStyle(el[0] || el)[property];
+ var openIndex = transform.indexOf('(');
+ var closeIndex = transform.lastIndexOf(')');
+ var output = { x: 0, y: 0 };
+
+ if (openIndex > -1 && closeIndex > -1) {
+ var parsedValues = transform
+ .substring(openIndex + 1, closeIndex)
+ .split(', ')
+ .slice(-2);
+
+ output.x = parseInt(parsedValues[0]);
+ output.y = parseInt(parsedValues[1]);
+ }
+
+ return output;
+}
+
+})(window, window.angular); \ No newline at end of file