2 * Angular Material Design
3 * https://github.com/angular/material
7 goog.provide('ngmaterial.components.panel');
8 goog.require('ngmaterial.components.backdrop');
9 goog.require('ngmaterial.core');
12 * @name material.components.panel
14 MdPanelService['$inject'] = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
16 .module('material.components.panel', [
18 'material.components.backdrop'
20 .provider('$mdPanel', MdPanelProvider);
23 /*****************************************************************************
24 * PUBLIC DOCUMENTATION *
25 *****************************************************************************/
30 * @name $mdPanelProvider
31 * @module material.components.panel
34 * `$mdPanelProvider` allows users to create configuration presets that will be
35 * stored within a cached presets object. When the configuration is needed, the
36 * user can request the preset by passing it as the first parameter in the
37 * `$mdPanel.create` or `$mdPanel.open` methods.
41 * (function(angular, undefined) {
45 * .module('demoApp', ['ngMaterial'])
47 * .controller('DemoCtrl', DemoCtrl)
48 * .controller('DemoMenuCtrl', DemoMenuCtrl);
50 * function DemoConfig($mdPanelProvider) {
51 * $mdPanelProvider.definePreset('demoPreset', {
52 * attachTo: angular.element(document.body),
53 * controller: DemoMenuCtrl,
54 * controllerAs: 'ctrl',
56 * '<div class="menu-panel" md-whiteframe="4">' +
57 * ' <div class="menu-content">' +
58 * ' <div class="menu-item" ng-repeat="item in ctrl.items">' +
59 * ' <button class="md-button">' +
60 * ' <span>{{item}}</span>' +
63 * ' <md-divider></md-divider>' +
64 * ' <div class="menu-item">' +
65 * ' <button class="md-button" ng-click="ctrl.closeMenu()">' +
66 * ' <span>Close Menu</span>' +
71 * panelClass: 'menu-panel-container',
74 * propagateContainerEvents: true,
79 * function PanelProviderCtrl($mdPanel) {
102 * $mdPanel.newPanelGroup('menus', {
106 * this.showMenu = function($event, menu) {
107 * $mdPanel.open('demoPreset', {
108 * id: 'menu_' + menu.name,
109 * position: $mdPanel.newPanelPosition()
110 * .relativeTo($event.srcElement)
112 * $mdPanel.xPosition.ALIGN_START,
113 * $mdPanel.yPosition.BELOW
123 * function PanelMenuCtrl(mdPanelRef) {
124 * this.closeMenu = function() {
125 * mdPanelRef && mdPanelRef.close();
134 * @name $mdPanelProvider#definePreset
136 * Takes the passed in preset name and preset configuration object and adds it
137 * to the `_presets` object of the provider. This `_presets` object is then
138 * passed along to the `$mdPanel` service.
140 * @param {string} name Preset name.
141 * @param {!Object} preset Specific configuration object that can contain any
142 * and all of the parameters avaialble within the `$mdPanel.create` method.
143 * However, parameters that pertain to id, position, animation, and user
144 * interaction are not allowed and will be removed from the preset
149 /*****************************************************************************
151 *****************************************************************************/
157 * @module material.components.panel
160 * `$mdPanel` is a robust, low-level service for creating floating panels on
161 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
165 * (function(angular, undefined) {
169 * .module('demoApp', ['ngMaterial'])
170 * .controller('DemoDialogController', DialogController);
174 * function showPanel($event) {
175 * var panelPosition = $mdPanel.newPanelPosition()
180 * var panelAnimation = $mdPanel.newPanelAnimation()
181 * .targetEvent($event)
182 * .defaultAnimation('md-panel-animate-fly')
183 * .closeTo('.show-button');
186 * attachTo: angular.element(document.body),
187 * controller: DialogController,
188 * controllerAs: 'ctrl',
189 * position: panelPosition,
190 * animation: panelAnimation,
191 * targetEvent: $event,
192 * templateUrl: 'dialog-template.html',
193 * clickOutsideToClose: true,
194 * escapeToClose: true,
198 * $mdPanel.open(config)
199 * .then(function(result) {
204 * function DialogController(MdPanelRef) {
205 * function closeDialog() {
206 * if (MdPanelRef) MdPanelRef.close();
215 * @name $mdPanel#create
217 * Creates a panel with the specified options.
219 * @param config {!Object=} Specific configuration object that may contain the
220 * following properties:
222 * - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
223 * the created panel is added to a tracked panels object. Any subsequent
224 * requests made to create a panel with that ID are ignored. This is useful
225 * in having the panel service not open multiple panels from the same user
226 * interaction when there is no backdrop and events are propagated. Defaults
227 * to an arbitrary string that is not tracked.
228 * - `template` - `{string=}`: HTML template to show in the panel. This
229 * **must** be trusted HTML with respect to Angular’s
230 * [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
231 * - `templateUrl` - `{string=}`: The URL that will be used as the content of
233 * - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled
234 * element to be used as the panel's content.
235 * - `controller` - `{(function|string)=}`: The controller to associate with
236 * the panel. The controller can inject a reference to the returned
237 * panelRef, which allows the panel to be closed, hidden, and shown. Any
238 * fields passed in through locals or resolve will be bound to the
240 * - `controllerAs` - `{string=}`: An alias to assign the controller to on
242 * - `bindToController` - `{boolean=}`: Binds locals to the controller
243 * instead of passing them in. Defaults to true, as this is a best
245 * - `locals` - `{Object=}`: An object containing key/value pairs. The keys
246 * will be used as names of values to inject into the controller. For
247 * example, `locals: {three: 3}` would inject `three` into the controller,
249 * - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
250 * values. The panel will not open until all of the promises resolve.
251 * - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
252 * attach the panel to. Defaults to appending to the root element of the
254 * - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
255 * events should be allowed to propagate 'go through' the container, aka the
256 * wrapper, of the panel. Defaults to false.
257 * - `panelClass` - `{string=}`: A css class to apply to the panel element.
258 * This class should define any borders, box-shadow, etc. for the panel.
259 * - `zIndex` - `{number=}`: The z-index to place the panel at.
261 * - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
262 * specifies the alignment of the panel. For more information, see
264 * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
265 * outside the panel to close it. Defaults to false.
266 * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
267 * close the panel. Defaults to false.
268 * - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
269 * called after the close successfully finishes. The first parameter passed
270 * into this function is the current panelRef and the 2nd is an optional
271 * string explaining the close reason. The currently supported closeReasons
272 * can be found in the MdPanelRef.closeReasons enum. These are by default
273 * passed along by the panel.
274 * - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
275 * panel. If `trapFocus` is true, the user will not be able to interact
276 * with the rest of the page until the panel is dismissed. Defaults to
278 * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
279 * open. Only disable if focusing some other way, as focus management is
280 * required for panels to be accessible. Defaults to true.
281 * - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
282 * Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
284 * - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
285 * specifies the animation of the panel. For more information, see
286 * `MdPanelAnimation`.
287 * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
288 * behind the panel. Defaults to false.
289 * - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
290 * page behind the panel. Defaults to false.
291 * - `onDomAdded` - `{function=}`: Callback function used to announce when
292 * the panel is added to the DOM.
293 * - `onOpenComplete` - `{function=}`: Callback function used to announce
294 * when the open() action is finished.
295 * - `onRemoving` - `{function=}`: Callback function used to announce the
296 * close/hide() action is starting.
297 * - `onDomRemoved` - `{function=}`: Callback function used to announce when
298 * the panel is removed from the DOM.
299 * - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
300 * on when the panel closes. This is commonly the element which triggered
301 * the opening of the panel. If you do not use `origin`, you need to control
302 * the focus manually.
303 * - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
304 * group names. The group name is used for creating a group of panels. The
305 * group is used for configuring the number of open panels and identifying
306 * specific behaviors for groups. For instance, all tooltips could be
307 * identified using the same groupName.
309 * @returns {!MdPanelRef} panelRef
314 * @name $mdPanel#open
316 * Calls the create method above, then opens the panel. This is a shortcut for
317 * creating and then calling open manually. If custom methods need to be
318 * called when the panel is added to the DOM or opened, do not use this method.
319 * Instead create the panel, chain promises on the domAdded and openComplete
320 * methods, and call open from the returned panelRef.
322 * @param {!Object=} config Specific configuration object that may contain
323 * the properties defined in `$mdPanel.create`.
324 * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
325 * to an instance of the panel.
330 * @name $mdPanel#newPanelPosition
332 * Returns a new instance of the MdPanelPosition object. Use this to create
333 * the position config object.
335 * @returns {!MdPanelPosition} panelPosition
340 * @name $mdPanel#newPanelAnimation
342 * Returns a new instance of the MdPanelAnimation object. Use this to create
343 * the animation config object.
345 * @returns {!MdPanelAnimation} panelAnimation
350 * @name $mdPanel#newPanelGroup
352 * Creates a panel group and adds it to a tracked list of panel groups.
354 * @param {string} groupName Name of the group to create.
355 * @param {!Object=} config Specific configuration object that may contain the
356 * following properties:
358 * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
359 * be open within a defined panel group.
361 * @returns {!Object<string,
362 * {panels: !Array<!MdPanelRef>,
363 * openPanels: !Array<!MdPanelRef>,
364 * maxOpen: number}>} panelGroup
369 * @name $mdPanel#setGroupMaxOpen
371 * Sets the maximum number of panels in a group that can be opened at a given
374 * @param {string} groupName The name of the group to configure.
375 * @param {number} maxOpen The maximum number of panels that can be
376 * opened. Infinity can be passed in to remove the maxOpen limit.
380 /*****************************************************************************
382 *****************************************************************************/
388 * @module material.components.panel
390 * A reference to a created panel. This reference contains a unique id for the
391 * panel, along with the following properties:
393 * - `id` - `{string}`: The unique id for the panel. This id is used to track
394 * when a panel was interacted with.
395 * - `config` - `{!Object=}`: The entire config object that was used in
397 * - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
398 * Visibility to the user does not factor into isAttached.
399 * - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
400 * panel. This property is added in order to have access to the `addClass`,
401 * `removeClass`, `toggleClass`, etc methods.
402 * - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
403 * in order to have access to the `addClass`, `removeClass`, `toggleClass`,
409 * @name MdPanelRef#open
411 * Attaches and shows the panel.
413 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
419 * @name MdPanelRef#close
421 * Hides and detaches the panel. Note that this will **not** destroy the panel.
422 * If you don't intend on using the panel again, call the {@link #destroy
423 * destroy} method afterwards.
425 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
431 * @name MdPanelRef#attach
433 * Create the panel elements and attach them to the DOM. The panel will be
436 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
442 * @name MdPanelRef#detach
444 * Removes the panel from the DOM. This will NOT hide the panel before removing
447 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
453 * @name MdPanelRef#show
457 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
458 * shown and animations are completed.
463 * @name MdPanelRef#hide
467 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
468 * hidden and animations are completed.
473 * @name MdPanelRef#destroy
475 * Destroys the panel. The panel cannot be opened again after this is called.
480 * @name MdPanelRef#addClass
482 * This method is in the process of being deprecated in favor of using the panel
483 * and container JQLite elements that are referenced in the MdPanelRef object.
484 * Full deprecation is scheduled for material 1.2.
486 * Adds a class to the panel. DO NOT use this hide/show the panel.
488 * @param {string} newClass class to be added.
489 * @param {boolean} toElement Whether or not to add the class to the panel
490 * element instead of the container.
495 * @name MdPanelRef#removeClass
497 * This method is in the process of being deprecated in favor of using the panel
498 * and container JQLite elements that are referenced in the MdPanelRef object.
499 * Full deprecation is scheduled for material 1.2.
501 * Removes a class from the panel. DO NOT use this to hide/show the panel.
503 * @param {string} oldClass Class to be removed.
504 * @param {boolean} fromElement Whether or not to remove the class from the
505 * panel element instead of the container.
510 * @name MdPanelRef#toggleClass
512 * This method is in the process of being deprecated in favor of using the panel
513 * and container JQLite elements that are referenced in the MdPanelRef object.
514 * Full deprecation is scheduled for material 1.2.
516 * Toggles a class on the panel. DO NOT use this to hide/show the panel.
518 * @param {string} toggleClass Class to be toggled.
519 * @param {boolean} onElement Whether or not to remove the class from the panel
520 * element instead of the container.
525 * @name MdPanelRef#updatePosition
527 * Updates the position configuration of a panel. Use this to update the
528 * position of a panel that is open, without having to close and re-open the
531 * @param {!MdPanelPosition} position
536 * @name MdPanelRef#addToGroup
538 * Adds a panel to a group if the panel does not exist within the group already.
539 * A panel can only exist within a single group.
541 * @param {string} groupName The name of the group to add the panel to.
546 * @name MdPanelRef#removeFromGroup
548 * Removes a panel from a group if the panel exists within that group. The group
549 * must be created ahead of time.
551 * @param {string} groupName The name of the group.
556 * @name MdPanelRef#registerInterceptor
558 * Registers an interceptor with the panel. The callback should return a promise,
559 * which will allow the action to continue when it gets resolved, or will
560 * prevent an action if it is rejected. The interceptors are called sequentially
561 * and it reverse order. `type` must be one of the following
562 * values available on `$mdPanel.interceptorTypes`:
563 * * `CLOSE` - Gets called before the panel begins closing.
565 * @param {string} type Type of interceptor.
566 * @param {!angular.$q.Promise<any>} callback Callback to be registered.
567 * @returns {!MdPanelRef}
572 * @name MdPanelRef#removeInterceptor
574 * Removes a registered interceptor.
576 * @param {string} type Type of interceptor to be removed.
577 * @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
578 * @returns {!MdPanelRef}
583 * @name MdPanelRef#removeAllInterceptors
585 * Removes all interceptors. If a type is supplied, only the
586 * interceptors of that type will be cleared.
588 * @param {string=} type Type of interceptors to be removed.
589 * @returns {!MdPanelRef}
594 * @name MdPanelRef#updateAnimation
596 * Updates the animation configuration for a panel. You can use this to change
597 * the panel's animation without having to re-create it.
599 * @param {!MdPanelAnimation} animation
603 /*****************************************************************************
605 *****************************************************************************/
610 * @name MdPanelPosition
611 * @module material.components.panel
614 * Object for configuring the position of the panel.
618 * #### Centering the panel
621 * new MdPanelPosition().absolute().center();
624 * #### Overlapping the panel with an element
627 * new MdPanelPosition()
628 * .relativeTo(someElement)
630 * $mdPanel.xPosition.ALIGN_START,
631 * $mdPanel.yPosition.ALIGN_TOPS
635 * #### Aligning the panel with the bottom of an element
638 * new MdPanelPosition()
639 * .relativeTo(someElement)
640 * .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
646 * @name MdPanelPosition#absolute
648 * Positions the panel absolutely relative to the parent element. If the parent
649 * is document.body, this is equivalent to positioning the panel absolutely
650 * within the viewport.
652 * @returns {!MdPanelPosition}
657 * @name MdPanelPosition#relativeTo
659 * Positions the panel relative to a specific element.
661 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
662 * or angular element to position the panel with respect to.
663 * @returns {!MdPanelPosition}
668 * @name MdPanelPosition#top
670 * Sets the value of `top` for the panel. Clears any previously set vertical
673 * @param {string=} top Value of `top`. Defaults to '0'.
674 * @returns {!MdPanelPosition}
679 * @name MdPanelPosition#bottom
681 * Sets the value of `bottom` for the panel. Clears any previously set vertical
684 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
685 * @returns {!MdPanelPosition}
690 * @name MdPanelPosition#start
692 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
693 * `rtl`. Clears any previously set horizontal position.
695 * @param {string=} start Value of position. Defaults to '0'.
696 * @returns {!MdPanelPosition}
701 * @name MdPanelPosition#end
703 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
704 * Clears any previously set horizontal position.
706 * @param {string=} end Value of position. Defaults to '0'.
707 * @returns {!MdPanelPosition}
712 * @name MdPanelPosition#left
714 * Sets the value of `left` for the panel. Clears any previously set
715 * horizontal position.
717 * @param {string=} left Value of `left`. Defaults to '0'.
718 * @returns {!MdPanelPosition}
723 * @name MdPanelPosition#right
725 * Sets the value of `right` for the panel. Clears any previously set
726 * horizontal position.
728 * @param {string=} right Value of `right`. Defaults to '0'.
729 * @returns {!MdPanelPosition}
734 * @name MdPanelPosition#centerHorizontally
736 * Centers the panel horizontally in the viewport. Clears any previously set
737 * horizontal position.
739 * @returns {!MdPanelPosition}
744 * @name MdPanelPosition#centerVertically
746 * Centers the panel vertically in the viewport. Clears any previously set
749 * @returns {!MdPanelPosition}
754 * @name MdPanelPosition#center
756 * Centers the panel horizontally and vertically in the viewport. This is
757 * equivalent to calling both `centerHorizontally` and `centerVertically`.
758 * Clears any previously set horizontal and vertical positions.
760 * @returns {!MdPanelPosition}
765 * @name MdPanelPosition#addPanelPosition
767 * Sets the x and y position for the panel relative to another element. Can be
768 * called multiple times to specify an ordered list of panel positions. The
769 * first position which allows the panel to be completely on-screen will be
770 * chosen; the last position will be chose whether it is on-screen or not.
772 * xPosition must be one of the following values available on
773 * $mdPanel.xPosition:
776 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
786 * A: OFFSET_START (for LTR displays)
787 * B: ALIGN_START (for LTR displays)
789 * D: ALIGN_END (for LTR displays)
790 * E: OFFSET_END (for LTR displays)
793 * yPosition must be one of the following values available on
794 * $mdPanel.yPosition:
796 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
814 * @param {string} xPosition
815 * @param {string} yPosition
816 * @returns {!MdPanelPosition}
821 * @name MdPanelPosition#withOffsetX
823 * Sets the value of the offset in the x-direction.
825 * @param {string} offsetX
826 * @returns {!MdPanelPosition}
831 * @name MdPanelPosition#withOffsetY
833 * Sets the value of the offset in the y-direction.
835 * @param {string} offsetY
836 * @returns {!MdPanelPosition}
840 /*****************************************************************************
842 *****************************************************************************/
847 * @name MdPanelAnimation
848 * @module material.components.panel
850 * Animation configuration object. To use, create an MdPanelAnimation with the
851 * desired properties, then pass the object as part of $mdPanel creation.
856 * var panelAnimation = new MdPanelAnimation()
857 * .openFrom(myButtonEl)
859 * .closeTo('.my-button')
860 * .withAnimation($mdPanel.animation.SCALE);
863 * animation: panelAnimation
870 * @name MdPanelAnimation#openFrom
872 * Specifies where to start the open animation. `openFrom` accepts a
873 * click event object, query selector, DOM element, or a Rect object that
874 * is used to determine the bounds. When passed a click event, the location
875 * of the click will be used as the position to start the animation.
877 * @param {string|!Element|!Event|{top: number, left: number}}
878 * @returns {!MdPanelAnimation}
883 * @name MdPanelAnimation#closeTo
885 * Specifies where to animate the panel close. `closeTo` accepts a
886 * query selector, DOM element, or a Rect object that is used to determine
889 * @param {string|!Element|{top: number, left: number}}
890 * @returns {!MdPanelAnimation}
895 * @name MdPanelAnimation#withAnimation
897 * Specifies the animation class.
899 * There are several default animations that can be used:
900 * ($mdPanel.animation)
901 * SLIDE: The panel slides in and out from the specified
902 * elements. It will not fade in or out.
903 * SCALE: The panel scales in and out. Slide and fade are
904 * included in this animation.
905 * FADE: The panel fades in and out.
907 * Custom classes will by default fade in and out unless
908 * "transition: opacity 1ms" is added to the to custom class.
910 * @param {string|{open: string, close: string}} cssClass
911 * @returns {!MdPanelAnimation}
916 * @name MdPanelAnimation#duration
918 * Specifies the duration of the animation in milliseconds. The `duration`
919 * method accepts either a number or an object with separate open and close
922 * @param {number|{open: number, close: number}} duration
923 * @returns {!MdPanelAnimation}
927 /*****************************************************************************
928 * PUBLIC DOCUMENTATION *
929 *****************************************************************************/
932 var MD_PANEL_Z_INDEX = 80;
933 var MD_PANEL_HIDDEN = '_md-panel-hidden';
934 var FOCUS_TRAP_TEMPLATE = angular.element(
935 '<div class="_md-panel-focus-trap" tabindex="0"></div>');
941 * A provider that is used for creating presets for the panel API.
942 * @final @constructor ngInject
944 function MdPanelProvider() {
946 'definePreset': definePreset,
947 'getAllPresets': getAllPresets,
948 'clearPresets': clearPresets,
949 '$get': $getProvider()
955 * Takes the passed in panel configuration object and adds it to the `_presets`
956 * object at the specified name.
957 * @param {string} name Name of the preset to set.
958 * @param {!Object} preset Specific configuration object that can contain any
959 * and all of the parameters avaialble within the `$mdPanel.create` method.
960 * However, parameters that pertain to id, position, animation, and user
961 * interaction are not allowed and will be removed from the preset
964 function definePreset(name, preset) {
965 if (!name || !preset) {
966 throw new Error('mdPanelProvider: The panel preset definition is ' +
967 'malformed. The name and preset object are required.');
968 } else if (_presets.hasOwnProperty(name)) {
969 throw new Error('mdPanelProvider: The panel preset you have requested ' +
970 'has already been defined.');
973 // Delete any property on the preset that is not allowed.
975 delete preset.position;
976 delete preset.animation;
978 _presets[name] = preset;
983 * Gets a clone of the `_presets`.
986 function getAllPresets() {
987 return angular.copy(_presets);
992 * Clears all of the stored presets.
994 function clearPresets() {
1000 * Represents the `$get` method of the Angular provider. From here, a new
1001 * reference to the MdPanelService is returned where the needed arguments are
1002 * passed in including the MdPanelProvider `_presets`.
1003 * @param {!Object} _presets
1004 * @param {!angular.JQLite} $rootElement
1005 * @param {!angular.Scope} $rootScope
1006 * @param {!angular.$injector} $injector
1007 * @param {!angular.$window} $window
1009 function $getProvider() {
1011 '$rootElement', '$rootScope', '$injector', '$window',
1012 function($rootElement, $rootScope, $injector, $window) {
1013 return new MdPanelService(_presets, $rootElement, $rootScope,
1014 $injector, $window);
1020 /*****************************************************************************
1022 *****************************************************************************/
1026 * A service that is used for controlling/displaying panels on the screen.
1027 * @param {!Object} presets
1028 * @param {!angular.JQLite} $rootElement
1029 * @param {!angular.Scope} $rootScope
1030 * @param {!angular.$injector} $injector
1031 * @param {!angular.$window} $window
1032 * @final @constructor ngInject
1034 function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
1036 * Default config options for the panel.
1037 * Anything angular related needs to be done later. Therefore
1038 * scope: $rootScope.$new(true),
1039 * attachTo: $rootElement,
1041 * @private {!Object}
1043 this._defaultConfigOptions = {
1044 bindToController: true,
1045 clickOutsideToClose: false,
1046 disableParentScroll: false,
1047 escapeToClose: false,
1051 propagateContainerEvents: false,
1052 transformTemplate: angular.bind(this, this._wrapTemplate),
1054 zIndex: MD_PANEL_Z_INDEX
1057 /** @private {!Object} */
1060 /** @private {!Object} */
1061 this._presets = presets;
1063 /** @private @const */
1064 this._$rootElement = $rootElement;
1066 /** @private @const */
1067 this._$rootScope = $rootScope;
1069 /** @private @const */
1070 this._$injector = $injector;
1072 /** @private @const */
1073 this._$window = $window;
1075 /** @private @const */
1076 this._$mdUtil = this._$injector.get('$mdUtil');
1078 /** @private {!Object<string, !MdPanelRef>} */
1079 this._trackedPanels = {};
1082 * @private {!Object<string,
1083 * {panels: !Array<!MdPanelRef>,
1084 * openPanels: !Array<!MdPanelRef>,
1085 * maxOpen: number}>}
1087 this._groups = Object.create(null);
1090 * Default animations that can be used within the panel.
1093 this.animation = MdPanelAnimation.animation;
1096 * Possible values of xPosition for positioning the panel relative to
1100 this.xPosition = MdPanelPosition.xPosition;
1103 * Possible values of yPosition for positioning the panel relative to
1107 this.yPosition = MdPanelPosition.yPosition;
1110 * Possible values for the interceptors that can be registered on a panel.
1113 this.interceptorTypes = MdPanelRef.interceptorTypes;
1116 * Possible values for closing of a panel.
1119 this.closeReasons = MdPanelRef.closeReasons;
1122 * Possible values of absolute position.
1125 this.absPosition = MdPanelPosition.absPosition;
1130 * Creates a panel with the specified options.
1131 * @param {string=} preset Name of a preset configuration that can be used to
1132 * extend the panel configuration.
1133 * @param {!Object=} config Configuration object for the panel.
1134 * @returns {!MdPanelRef}
1136 MdPanelService.prototype.create = function(preset, config) {
1137 if (typeof preset === 'string') {
1138 preset = this._getPresetByName(preset);
1139 } else if (typeof preset === 'object' &&
1140 (angular.isUndefined(config) || !config)) {
1145 preset = preset || {};
1146 config = config || {};
1148 // If the passed-in config contains an ID and the ID is within _trackedPanels,
1149 // return the tracked panel after updating its config with the passed-in
1151 if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
1152 var trackedPanel = this._trackedPanels[config.id];
1153 angular.extend(trackedPanel.config, config);
1154 return trackedPanel;
1157 // Combine the passed-in config, the _defaultConfigOptions, and the preset
1158 // configuration into the `_config`.
1159 this._config = angular.extend({
1160 // If no ID is set within the passed-in config, then create an arbitrary ID.
1161 id: config.id || 'panel_' + this._$mdUtil.nextUid(),
1162 scope: this._$rootScope.$new(true),
1163 attachTo: this._$rootElement
1164 }, this._defaultConfigOptions, config, preset);
1166 // Create the panelRef and add it to the `_trackedPanels` object.
1167 var panelRef = new MdPanelRef(this._config, this._$injector);
1168 this._trackedPanels[config.id] = panelRef;
1170 // Add the panel to each of its requested groups.
1171 if (this._config.groupName) {
1172 if (angular.isString(this._config.groupName)) {
1173 this._config.groupName = [this._config.groupName];
1175 angular.forEach(this._config.groupName, function(group) {
1176 panelRef.addToGroup(group);
1180 this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
1187 * Creates and opens a panel with the specified options.
1188 * @param {string=} preset Name of a preset configuration that can be used to
1189 * extend the panel configuration.
1190 * @param {!Object=} config Configuration object for the panel.
1191 * @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
1193 MdPanelService.prototype.open = function(preset, config) {
1194 var panelRef = this.create(preset, config);
1195 return panelRef.open().then(function() {
1202 * Gets a specific preset configuration object saved within `_presets`.
1203 * @param {string} preset Name of the preset to search for.
1204 * @returns {!Object} The preset configuration object.
1206 MdPanelService.prototype._getPresetByName = function(preset) {
1207 if (!this._presets[preset]) {
1208 throw new Error('mdPanel: The panel preset configuration that you ' +
1209 'requested does not exist. Use the $mdPanelProvider to create a ' +
1210 'preset before requesting one.');
1212 return this._presets[preset];
1217 * Returns a new instance of the MdPanelPosition. Use this to create the
1218 * positioning object.
1219 * @returns {!MdPanelPosition}
1221 MdPanelService.prototype.newPanelPosition = function() {
1222 return new MdPanelPosition(this._$injector);
1227 * Returns a new instance of the MdPanelAnimation. Use this to create the
1229 * @returns {!MdPanelAnimation}
1231 MdPanelService.prototype.newPanelAnimation = function() {
1232 return new MdPanelAnimation(this._$injector);
1237 * Creates a panel group and adds it to a tracked list of panel groups.
1238 * @param groupName {string} Name of the group to create.
1239 * @param config {!Object=} Specific configuration object that may contain the
1240 * following properties:
1242 * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
1243 * open within a defined panel group.
1245 * @returns {!Object<string,
1246 * {panels: !Array<!MdPanelRef>,
1247 * openPanels: !Array<!MdPanelRef>,
1248 * maxOpen: number}>} panelGroup
1250 MdPanelService.prototype.newPanelGroup = function(groupName, config) {
1251 if (!this._groups[groupName]) {
1252 config = config || {};
1256 maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
1258 this._groups[groupName] = group;
1260 return this._groups[groupName];
1265 * Sets the maximum number of panels in a group that can be opened at a given
1267 * @param {string} groupName The name of the group to configure.
1268 * @param {number} maxOpen The maximum number of panels that can be
1269 * opened. Infinity can be passed in to remove the maxOpen limit.
1271 MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
1272 if (this._groups[groupName]) {
1273 this._groups[groupName].maxOpen = maxOpen;
1275 throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
1281 * Determines if the current number of open panels within a group exceeds the
1282 * limit of allowed open panels.
1283 * @param {string} groupName The name of the group to check.
1284 * @returns {boolean} true if open count does exceed maxOpen and false if not.
1287 MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
1288 if (this._groups[groupName]) {
1289 var group = this._groups[groupName];
1290 return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
1297 * Closes the first open panel within a specific group.
1298 * @param {string} groupName The name of the group.
1301 MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
1302 this._groups[groupName].openPanels[0].close();
1307 * Wraps the users template in two elements, md-panel-outer-wrapper, which
1308 * covers the entire attachTo element, and md-panel, which contains only the
1309 * template. This allows the panel control over positioning, animations,
1310 * and similar properties.
1311 * @param {string} origTemplate The original template.
1312 * @returns {string} The wrapped template.
1315 MdPanelService.prototype._wrapTemplate = function(origTemplate) {
1316 var template = origTemplate || '';
1318 // The panel should be initially rendered offscreen so we can calculate
1319 // height and width for positioning.
1321 '<div class="md-panel-outer-wrapper">' +
1322 ' <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
1328 * Wraps a content element in a md-panel-outer wrapper and
1329 * positions it off-screen. Allows for proper control over positoning
1331 * @param {!angular.JQLite} contentElement Element to be wrapped.
1332 * @return {!angular.JQLite} Wrapper element.
1335 MdPanelService.prototype._wrapContentElement = function(contentElement) {
1336 var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
1338 contentElement.addClass('md-panel').css('left', '-9999px');
1339 wrapper.append(contentElement);
1345 /*****************************************************************************
1347 *****************************************************************************/
1351 * A reference to a created panel. This reference contains a unique id for the
1352 * panel, along with properties/functions used to control the panel.
1353 * @param {!Object} config
1354 * @param {!angular.$injector} $injector
1355 * @final @constructor
1357 function MdPanelRef(config, $injector) {
1358 // Injected variables.
1359 /** @private @const {!angular.$q} */
1360 this._$q = $injector.get('$q');
1362 /** @private @const {!angular.$mdCompiler} */
1363 this._$mdCompiler = $injector.get('$mdCompiler');
1365 /** @private @const {!angular.$mdConstant} */
1366 this._$mdConstant = $injector.get('$mdConstant');
1368 /** @private @const {!angular.$mdUtil} */
1369 this._$mdUtil = $injector.get('$mdUtil');
1371 /** @private @const {!angular.$mdTheming} */
1372 this._$mdTheming = $injector.get('$mdTheming');
1374 /** @private @const {!angular.Scope} */
1375 this._$rootScope = $injector.get('$rootScope');
1377 /** @private @const {!angular.$animate} */
1378 this._$animate = $injector.get('$animate');
1380 /** @private @const {!MdPanelRef} */
1381 this._$mdPanel = $injector.get('$mdPanel');
1383 /** @private @const {!angular.$log} */
1384 this._$log = $injector.get('$log');
1386 /** @private @const {!angular.$window} */
1387 this._$window = $injector.get('$window');
1389 /** @private @const {!Function} */
1390 this._$$rAF = $injector.get('$$rAF');
1392 // Public variables.
1394 * Unique id for the panelRef.
1397 this.id = config.id;
1399 /** @type {!Object} */
1400 this.config = config;
1402 /** @type {!angular.JQLite|undefined} */
1403 this.panelContainer;
1405 /** @type {!angular.JQLite|undefined} */
1409 * Whether the panel is attached. This is synchronous. When attach is called,
1410 * isAttached is set to true. When detach is called, isAttached is set to
1414 this.isAttached = false;
1416 // Private variables.
1417 /** @private {Array<function()>} */
1418 this._removeListeners = [];
1420 /** @private {!angular.JQLite|undefined} */
1423 /** @private {!angular.JQLite|undefined} */
1424 this._bottomFocusTrap;
1426 /** @private {!$mdPanel|undefined} */
1429 /** @private {Function?} */
1430 this._restoreScroll = null;
1433 * Keeps track of all the panel interceptors.
1434 * @private {!Object}
1436 this._interceptors = Object.create(null);
1439 * Cleanup function, provided by `$mdCompiler` and assigned after the element
1440 * has been compiled. When `contentElement` is used, the function is used to
1441 * restore the element to it's proper place in the DOM.
1442 * @private {!Function}
1444 this._compilerCleanup = null;
1447 * Cache for saving and restoring element inline styles, CSS classes etc.
1448 * @type {{styles: string, classes: string}}
1450 this._restoreCache = {
1457 MdPanelRef.interceptorTypes = {
1463 * Opens an already created and configured panel. If the panel is already
1464 * visible, does nothing.
1465 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1466 * the panel is opened and animations finish.
1468 MdPanelRef.prototype.open = function() {
1470 return this._$q(function(resolve, reject) {
1471 var done = self._done(resolve, self);
1472 var show = self._simpleBind(self.show, self);
1473 var checkGroupMaxOpen = function() {
1474 if (self.config.groupName) {
1475 angular.forEach(self.config.groupName, function(group) {
1476 if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
1477 self._$mdPanel._closeFirstOpenedPanel(group);
1485 .then(checkGroupMaxOpen)
1494 * @param {string} closeReason The event type that triggered the close.
1495 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1496 * the panel is closed and animations finish.
1498 MdPanelRef.prototype.close = function(closeReason) {
1501 return this._$q(function(resolve, reject) {
1502 self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
1503 var done = self._done(resolve, self);
1504 var detach = self._simpleBind(self.detach, self);
1505 var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
1506 onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
1511 .then(onCloseSuccess)
1519 * Attaches the panel. The panel will be hidden afterwards.
1520 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1521 * the panel is attached.
1523 MdPanelRef.prototype.attach = function() {
1524 if (this.isAttached && this.panelEl) {
1525 return this._$q.when(this);
1529 return this._$q(function(resolve, reject) {
1530 var done = self._done(resolve, self);
1531 var onDomAdded = self.config['onDomAdded'] || angular.noop;
1532 var addListeners = function(response) {
1533 self.isAttached = true;
1534 self._addEventListeners();
1539 self._createBackdrop(),
1551 * Only detaches the panel. Will NOT hide the panel first.
1552 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1553 * the panel is detached.
1555 MdPanelRef.prototype.detach = function() {
1556 if (!this.isAttached) {
1557 return this._$q.when(this);
1561 var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
1563 var detachFn = function() {
1564 self._removeEventListeners();
1566 // Remove the focus traps that we added earlier for keeping focus within
1568 if (self._topFocusTrap && self._topFocusTrap.parentNode) {
1569 self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
1572 if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
1573 self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
1576 if (self._restoreCache.classes) {
1577 self.panelEl[0].className = self._restoreCache.classes;
1580 // Either restore the saved styles or clear the ones set by mdPanel.
1581 self.panelEl[0].style.cssText = self._restoreCache.styles || '';
1583 self._compilerCleanup();
1584 self.panelContainer.remove();
1585 self.isAttached = false;
1586 return self._$q.when(self);
1589 if (this._restoreScroll) {
1590 this._restoreScroll();
1591 this._restoreScroll = null;
1594 return this._$q(function(resolve, reject) {
1595 var done = self._done(resolve, self);
1599 self._backdropRef ? self._backdropRef.detach() : true
1600 ]).then(onDomRemoved)
1608 * Destroys the panel. The Panel cannot be opened again after this.
1610 MdPanelRef.prototype.destroy = function() {
1612 if (this.config.groupName) {
1613 angular.forEach(this.config.groupName, function(group) {
1614 self.removeFromGroup(group);
1617 this.config.scope.$destroy();
1618 this.config.locals = null;
1619 this._interceptors = null;
1625 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1626 * the panel has shown and animations finish.
1628 MdPanelRef.prototype.show = function() {
1629 if (!this.panelContainer) {
1630 return this._$q(function(resolve, reject) {
1631 reject('mdPanel: Panel does not exist yet. Call open() or attach().');
1635 if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1636 return this._$q.when(this);
1640 var animatePromise = function() {
1641 self.panelContainer.removeClass(MD_PANEL_HIDDEN);
1642 return self._animateOpen();
1645 return this._$q(function(resolve, reject) {
1646 var done = self._done(resolve, self);
1647 var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
1648 var addToGroupOpen = function() {
1649 if (self.config.groupName) {
1650 angular.forEach(self.config.groupName, function(group) {
1651 self._$mdPanel._groups[group].openPanels.push(self);
1657 self._backdropRef ? self._backdropRef.show() : self,
1658 animatePromise().then(function() { self._focusOnOpen(); }, reject)
1659 ]).then(onOpenComplete)
1660 .then(addToGroupOpen)
1669 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
1670 * the panel has hidden and animations finish.
1672 MdPanelRef.prototype.hide = function() {
1673 if (!this.panelContainer) {
1674 return this._$q(function(resolve, reject) {
1675 reject('mdPanel: Panel does not exist yet. Call open() or attach().');
1679 if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
1680 return this._$q.when(this);
1685 return this._$q(function(resolve, reject) {
1686 var done = self._done(resolve, self);
1687 var onRemoving = self.config['onRemoving'] || angular.noop;
1688 var hidePanel = function() {
1689 self.panelContainer.addClass(MD_PANEL_HIDDEN);
1691 var removeFromGroupOpen = function() {
1692 if (self.config.groupName) {
1694 angular.forEach(self.config.groupName, function(group) {
1695 group = self._$mdPanel._groups[group];
1696 index = group.openPanels.indexOf(self);
1698 group.openPanels.splice(index, 1);
1703 var focusOnOrigin = function() {
1704 var origin = self.config['origin'];
1706 getElement(origin).focus();
1711 self._backdropRef ? self._backdropRef.hide() : self,
1712 self._animateClose()
1715 .then(removeFromGroupOpen)
1716 .then(focusOnOrigin)
1718 ]).then(done, reject);
1724 * Add a class to the panel. DO NOT use this to hide/show the panel.
1726 * This method is in the process of being deprecated in favor of using the panel
1727 * and container JQLite elements that are referenced in the MdPanelRef object.
1728 * Full deprecation is scheduled for material 1.2.
1730 * @param {string} newClass Class to be added.
1731 * @param {boolean} toElement Whether or not to add the class to the panel
1732 * element instead of the container.
1734 MdPanelRef.prototype.addClass = function(newClass, toElement) {
1736 'mdPanel: The addClass method is in the process of being deprecated. ' +
1737 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1738 'To achieve the same results, use the panelContainer or panelEl ' +
1739 'JQLite elements that are referenced in MdPanelRef.');
1741 if (!this.panelContainer) {
1743 'mdPanel: Panel does not exist yet. Call open() or attach().');
1746 if (!toElement && !this.panelContainer.hasClass(newClass)) {
1747 this.panelContainer.addClass(newClass);
1748 } else if (toElement && !this.panelEl.hasClass(newClass)) {
1749 this.panelEl.addClass(newClass);
1755 * Remove a class from the panel. DO NOT use this to hide/show the panel.
1757 * This method is in the process of being deprecated in favor of using the panel
1758 * and container JQLite elements that are referenced in the MdPanelRef object.
1759 * Full deprecation is scheduled for material 1.2.
1761 * @param {string} oldClass Class to be removed.
1762 * @param {boolean} fromElement Whether or not to remove the class from the
1763 * panel element instead of the container.
1765 MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
1767 'mdPanel: The removeClass method is in the process of being deprecated. ' +
1768 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1769 'To achieve the same results, use the panelContainer or panelEl ' +
1770 'JQLite elements that are referenced in MdPanelRef.');
1772 if (!this.panelContainer) {
1774 'mdPanel: Panel does not exist yet. Call open() or attach().');
1777 if (!fromElement && this.panelContainer.hasClass(oldClass)) {
1778 this.panelContainer.removeClass(oldClass);
1779 } else if (fromElement && this.panelEl.hasClass(oldClass)) {
1780 this.panelEl.removeClass(oldClass);
1786 * Toggle a class on the panel. DO NOT use this to hide/show the panel.
1788 * This method is in the process of being deprecated in favor of using the panel
1789 * and container JQLite elements that are referenced in the MdPanelRef object.
1790 * Full deprecation is scheduled for material 1.2.
1792 * @param {string} toggleClass The class to toggle.
1793 * @param {boolean} onElement Whether or not to toggle the class on the panel
1794 * element instead of the container.
1796 MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
1798 'mdPanel: The toggleClass method is in the process of being deprecated. ' +
1799 'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
1800 'To achieve the same results, use the panelContainer or panelEl ' +
1801 'JQLite elements that are referenced in MdPanelRef.');
1803 if (!this.panelContainer) {
1805 'mdPanel: Panel does not exist yet. Call open() or attach().');
1809 this.panelContainer.toggleClass(toggleClass);
1811 this.panelEl.toggleClass(toggleClass);
1817 * Compiles the panel, according to the passed in config and appends it to
1818 * the DOM. Helps normalize differences in the compilation process between
1819 * using a string template and a content element.
1820 * @returns {!angular.$q.Promise<!MdPanelRef>} Promise that is resolved when
1821 * the element has been compiled and added to the DOM.
1824 MdPanelRef.prototype._compile = function() {
1827 // Compile the element via $mdCompiler. Note that when using a
1828 // contentElement, the element isn't actually being compiled, rather the
1829 // compiler saves it's place in the DOM and provides a way of restoring it.
1830 return self._$mdCompiler.compile(self.config).then(function(compileData) {
1831 var config = self.config;
1833 if (config.contentElement) {
1834 var panelEl = compileData.element;
1836 // Since mdPanel modifies the inline styles and CSS classes, we need
1837 // to save them in order to be able to restore on close.
1838 self._restoreCache.styles = panelEl[0].style.cssText;
1839 self._restoreCache.classes = panelEl[0].className;
1841 self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
1842 self.panelEl = panelEl;
1844 self.panelContainer = compileData.link(config['scope']);
1845 self.panelEl = angular.element(
1846 self.panelContainer[0].querySelector('.md-panel')
1850 // Save a reference to the cleanup function from the compiler.
1851 self._compilerCleanup = compileData.cleanup;
1853 // Attach the panel to the proper place in the DOM.
1854 getElement(self.config['attachTo']).append(self.panelContainer);
1862 * Creates a panel and adds it to the dom.
1863 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
1867 MdPanelRef.prototype._createPanel = function() {
1870 return this._$q(function(resolve, reject) {
1871 if (!self.config.locals) {
1872 self.config.locals = {};
1875 self.config.locals.mdPanelRef = self;
1877 self._compile().then(function() {
1878 if (self.config['disableParentScroll']) {
1879 self._restoreScroll = self._$mdUtil.disableScrollAround(
1881 self.panelContainer,
1882 { disableScrollMask: true }
1886 // Add a custom CSS class to the panel element.
1887 if (self.config['panelClass']) {
1888 self.panelEl.addClass(self.config['panelClass']);
1891 // Handle click and touch events for the panel container.
1892 if (self.config['propagateContainerEvents']) {
1893 self.panelContainer.css('pointer-events', 'none');
1896 // Panel may be outside the $rootElement, tell ngAnimate to animate
1898 if (self._$animate.pin) {
1900 self.panelContainer,
1901 getElement(self.config['attachTo'])
1905 self._configureTrapFocus();
1906 self._addStyles().then(function() {
1916 * Adds the styles for the panel, such as positioning and z-index. Also,
1917 * themes the panel element and panel container using `$mdTheming`.
1918 * @returns {!angular.$q.Promise<!MdPanelRef>}
1921 MdPanelRef.prototype._addStyles = function() {
1923 return this._$q(function(resolve) {
1924 self.panelContainer.css('z-index', self.config['zIndex']);
1925 self.panelEl.css('z-index', self.config['zIndex'] + 1);
1927 var hideAndResolve = function() {
1928 // Theme the element and container.
1931 // Remove left: -9999px and add hidden class.
1932 self.panelEl.css('left', '');
1933 self.panelContainer.addClass(MD_PANEL_HIDDEN);
1938 if (self.config['fullscreen']) {
1939 self.panelEl.addClass('_md-panel-fullscreen');
1941 return; // Don't setup positioning.
1944 var positionConfig = self.config['position'];
1945 if (!positionConfig) {
1947 return; // Don't setup positioning.
1950 // Wait for angular to finish processing the template
1951 self._$rootScope['$$postDigest'](function() {
1952 // Position it correctly. This is necessary so that the panel will have a
1953 // defined height and width.
1954 self._updatePosition(true);
1956 // Theme the element and container.
1966 * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
1969 MdPanelRef.prototype._setTheming = function() {
1970 this._$mdTheming(this.panelEl);
1971 this._$mdTheming(this.panelContainer);
1976 * Updates the position configuration of a panel
1977 * @param {!MdPanelPosition} position
1979 MdPanelRef.prototype.updatePosition = function(position) {
1980 if (!this.panelContainer) {
1982 'mdPanel: Panel does not exist yet. Call open() or attach().');
1985 this.config['position'] = position;
1986 this._updatePosition();
1991 * Calculates and updates the position of the panel.
1992 * @param {boolean=} init
1995 MdPanelRef.prototype._updatePosition = function(init) {
1996 var positionConfig = this.config['position'];
1998 if (positionConfig) {
1999 positionConfig._setPanelPosition(this.panelEl);
2001 // Hide the panel now that position is known.
2003 this.panelContainer.addClass(MD_PANEL_HIDDEN);
2007 MdPanelPosition.absPosition.TOP,
2008 positionConfig.getTop()
2011 MdPanelPosition.absPosition.BOTTOM,
2012 positionConfig.getBottom()
2015 MdPanelPosition.absPosition.LEFT,
2016 positionConfig.getLeft()
2019 MdPanelPosition.absPosition.RIGHT,
2020 positionConfig.getRight()
2027 * Focuses on the panel or the first focus target.
2030 MdPanelRef.prototype._focusOnOpen = function() {
2031 if (this.config['focusOnOpen']) {
2032 // Wait for the template to finish rendering to guarantee md-autofocus has
2033 // finished adding the class md-autofocus, otherwise the focusable element
2034 // isn't available to focus.
2036 this._$rootScope['$$postDigest'](function() {
2037 var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
2046 * Shows the backdrop.
2047 * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
2048 * is created and attached.
2051 MdPanelRef.prototype._createBackdrop = function() {
2052 if (this.config.hasBackdrop) {
2053 if (!this._backdropRef) {
2054 var backdropAnimation = this._$mdPanel.newPanelAnimation()
2055 .openFrom(this.config.attachTo)
2057 open: '_md-opaque-enter',
2058 close: '_md-opaque-leave'
2061 if (this.config.animation) {
2062 backdropAnimation.duration(this.config.animation._rawDuration);
2065 var backdropConfig = {
2066 animation: backdropAnimation,
2067 attachTo: this.config.attachTo,
2069 panelClass: '_md-panel-backdrop',
2070 zIndex: this.config.zIndex - 1
2073 this._backdropRef = this._$mdPanel.create(backdropConfig);
2075 if (!this._backdropRef.isAttached) {
2076 return this._backdropRef.attach();
2083 * Listen for escape keys and outside clicks to auto close.
2086 MdPanelRef.prototype._addEventListeners = function() {
2087 this._configureEscapeToClose();
2088 this._configureClickOutsideToClose();
2089 this._configureScrollListener();
2094 * Remove event listeners added in _addEventListeners.
2097 MdPanelRef.prototype._removeEventListeners = function() {
2098 this._removeListeners && this._removeListeners.forEach(function(removeFn) {
2101 this._removeListeners = [];
2106 * Setup the escapeToClose event listeners.
2109 MdPanelRef.prototype._configureEscapeToClose = function() {
2110 if (this.config['escapeToClose']) {
2111 var parentTarget = getElement(this.config['attachTo']);
2114 var keyHandlerFn = function(ev) {
2115 if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
2116 ev.stopPropagation();
2117 ev.preventDefault();
2119 self.close(MdPanelRef.closeReasons.ESCAPE);
2123 // Add keydown listeners
2124 this.panelContainer.on('keydown', keyHandlerFn);
2125 parentTarget.on('keydown', keyHandlerFn);
2127 // Queue remove listeners function
2128 this._removeListeners.push(function() {
2129 self.panelContainer.off('keydown', keyHandlerFn);
2130 parentTarget.off('keydown', keyHandlerFn);
2137 * Setup the clickOutsideToClose event listeners.
2140 MdPanelRef.prototype._configureClickOutsideToClose = function() {
2141 if (this.config['clickOutsideToClose']) {
2142 var target = this.config['propagateContainerEvents'] ?
2143 angular.element(document.body) :
2144 this.panelContainer;
2147 // Keep track of the element on which the mouse originally went down
2148 // so that we can only close the backdrop when the 'click' started on it.
2149 // A simple 'click' handler does not work, it sets the target object as the
2150 // element the mouse went down on.
2151 var mousedownHandler = function(ev) {
2152 sourceEl = ev.target;
2155 // We check if our original element and the target is the backdrop
2156 // because if the original was the backdrop and the target was inside the
2157 // panel we don't want to panel to close.
2159 var mouseupHandler = function(ev) {
2160 if (self.config['propagateContainerEvents']) {
2162 // We check if the sourceEl of the event is the panel element or one
2163 // of it's children. If it is not, then close the panel.
2164 if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) {
2168 } else if (sourceEl === target[0] && ev.target === target[0]) {
2169 ev.stopPropagation();
2170 ev.preventDefault();
2172 self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
2177 target.on('mousedown', mousedownHandler);
2178 target.on('mouseup', mouseupHandler);
2180 // Queue remove listeners function
2181 this._removeListeners.push(function() {
2182 target.off('mousedown', mousedownHandler);
2183 target.off('mouseup', mouseupHandler);
2190 * Configures the listeners for updating the panel position on scroll.
2193 MdPanelRef.prototype._configureScrollListener = function() {
2194 // No need to bind the event if scrolling is disabled.
2195 if (!this.config['disableParentScroll']) {
2196 var updatePosition = angular.bind(this, this._updatePosition);
2197 var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
2200 var onScroll = function() {
2201 debouncedUpdatePosition();
2205 this._$window.addEventListener('scroll', onScroll, true);
2207 // Queue remove listeners function.
2208 this._removeListeners.push(function() {
2209 self._$window.removeEventListener('scroll', onScroll, true);
2216 * Setup the focus traps. These traps will wrap focus when tabbing past the
2217 * panel. When shift-tabbing, the focus will stick in place.
2220 MdPanelRef.prototype._configureTrapFocus = function() {
2221 // Focus doesn't remain inside of the panel without this.
2222 this.panelEl.attr('tabIndex', '-1');
2223 if (this.config['trapFocus']) {
2224 var element = this.panelEl;
2225 // Set up elements before and after the panel to capture focus and
2226 // redirect back into the panel.
2227 this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
2228 this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
2230 // When focus is about to move out of the panel, we want to intercept it
2231 // and redirect it back to the panel element.
2232 var focusHandler = function() {
2235 this._topFocusTrap.addEventListener('focus', focusHandler);
2236 this._bottomFocusTrap.addEventListener('focus', focusHandler);
2238 // Queue remove listeners function
2239 this._removeListeners.push(this._simpleBind(function() {
2240 this._topFocusTrap.removeEventListener('focus', focusHandler);
2241 this._bottomFocusTrap.removeEventListener('focus', focusHandler);
2244 // The top focus trap inserted immediately before the md-panel element (as
2245 // a sibling). The bottom focus trap inserted immediately after the
2246 // md-panel element (as a sibling).
2247 element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
2248 element.after(this._bottomFocusTrap);
2254 * Updates the animation of a panel.
2255 * @param {!MdPanelAnimation} animation
2257 MdPanelRef.prototype.updateAnimation = function(animation) {
2258 this.config['animation'] = animation;
2260 if (this._backdropRef) {
2261 this._backdropRef.config.animation.duration(animation._rawDuration);
2267 * Animate the panel opening.
2268 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
2272 MdPanelRef.prototype._animateOpen = function() {
2273 this.panelContainer.addClass('md-panel-is-showing');
2274 var animationConfig = this.config['animation'];
2275 if (!animationConfig) {
2276 // Promise is in progress, return it.
2277 this.panelContainer.addClass('_md-panel-shown');
2278 return this._$q.when(this);
2282 return this._$q(function(resolve) {
2283 var done = self._done(resolve, self);
2284 var warnAndOpen = function() {
2286 'mdPanel: MdPanel Animations failed. ' +
2287 'Showing panel without animating.');
2291 animationConfig.animateOpen(self.panelEl)
2292 .then(done, warnAndOpen);
2298 * Animate the panel closing.
2299 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
2303 MdPanelRef.prototype._animateClose = function() {
2304 var animationConfig = this.config['animation'];
2305 if (!animationConfig) {
2306 this.panelContainer.removeClass('md-panel-is-showing');
2307 this.panelContainer.removeClass('_md-panel-shown');
2308 return this._$q.when(this);
2312 return this._$q(function(resolve) {
2313 var done = function() {
2314 self.panelContainer.removeClass('md-panel-is-showing');
2317 var warnAndClose = function() {
2319 'mdPanel: MdPanel Animations failed. ' +
2320 'Hiding panel without animating.');
2324 animationConfig.animateClose(self.panelEl)
2325 .then(done, warnAndClose);
2331 * Registers a interceptor with the panel. The callback should return a promise,
2332 * which will allow the action to continue when it gets resolved, or will
2333 * prevent an action if it is rejected.
2334 * @param {string} type Type of interceptor.
2335 * @param {!angular.$q.Promise<!any>} callback Callback to be registered.
2336 * @returns {!MdPanelRef}
2338 MdPanelRef.prototype.registerInterceptor = function(type, callback) {
2341 if (!angular.isString(type)) {
2342 error = 'Interceptor type must be a string, instead got ' + typeof type;
2343 } else if (!angular.isFunction(callback)) {
2344 error = 'Interceptor callback must be a function, instead got ' + typeof callback;
2348 throw new Error('MdPanel: ' + error);
2351 var interceptors = this._interceptors[type] = this._interceptors[type] || [];
2353 if (interceptors.indexOf(callback) === -1) {
2354 interceptors.push(callback);
2362 * Removes a registered interceptor.
2363 * @param {string} type Type of interceptor to be removed.
2364 * @param {Function} callback Interceptor to be removed.
2365 * @returns {!MdPanelRef}
2367 MdPanelRef.prototype.removeInterceptor = function(type, callback) {
2368 var index = this._interceptors[type] ?
2369 this._interceptors[type].indexOf(callback) : -1;
2372 this._interceptors[type].splice(index, 1);
2380 * Removes all interceptors.
2381 * @param {string=} type Type of interceptors to be removed.
2382 * If ommited, all interceptors types will be removed.
2383 * @returns {!MdPanelRef}
2385 MdPanelRef.prototype.removeAllInterceptors = function(type) {
2387 this._interceptors[type] = [];
2389 this._interceptors = Object.create(null);
2397 * Invokes all the interceptors of a certain type sequantially in
2398 * reverse order. Works in a similar way to `$q.all`, except it
2399 * respects the order of the functions.
2400 * @param {string} type Type of interceptors to be invoked.
2401 * @returns {!angular.$q.Promise<!MdPanelRef>}
2404 MdPanelRef.prototype._callInterceptors = function(type) {
2407 var interceptors = self._interceptors && self._interceptors[type] || [];
2409 return interceptors.reduceRight(function(promise, interceptor) {
2410 var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
2411 var response = isPromiseLike ? interceptor : null;
2414 * For interceptors to reject/cancel subsequent portions of the chain, simply
2415 * return a `$q.reject(<value>)`
2417 return promise.then(function() {
2420 response = interceptor(self);
2422 response = $q.reject(e);
2428 }, $q.resolve(self));
2433 * Faster, more basic than angular.bind
2434 * http://jsperf.com/angular-bind-vs-custom-vs-native
2435 * @param {function} callback
2436 * @param {!Object} self
2437 * @return {function} Callback function with a bound self.
2439 MdPanelRef.prototype._simpleBind = function(callback, self) {
2440 return function(value) {
2441 return callback.apply(self, value);
2447 * @param {function} callback
2448 * @param {!Object} self
2449 * @return {function} Callback function with a self param.
2451 MdPanelRef.prototype._done = function(callback, self) {
2459 * Adds a panel to a group if the panel does not exist within the group already.
2460 * A panel can only exist within a single group.
2461 * @param {string} groupName The name of the group.
2463 MdPanelRef.prototype.addToGroup = function(groupName) {
2464 if (!this._$mdPanel._groups[groupName]) {
2465 this._$mdPanel.newPanelGroup(groupName);
2468 var group = this._$mdPanel._groups[groupName];
2469 var index = group.panels.indexOf(this);
2472 group.panels.push(this);
2478 * Removes a panel from a group if the panel exists within that group. The group
2479 * must be created ahead of time.
2480 * @param {string} groupName The name of the group.
2482 MdPanelRef.prototype.removeFromGroup = function(groupName) {
2483 if (!this._$mdPanel._groups[groupName]) {
2484 throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
2487 var group = this._$mdPanel._groups[groupName];
2488 var index = group.panels.indexOf(this);
2491 group.panels.splice(index, 1);
2497 * Possible default closeReasons for the close function.
2500 MdPanelRef.closeReasons = {
2501 CLICK_OUTSIDE: 'clickOutsideToClose',
2502 ESCAPE: 'escapeToClose',
2506 /*****************************************************************************
2508 *****************************************************************************/
2512 * Position configuration object. To use, create an MdPanelPosition with the
2513 * desired properties, then pass the object as part of $mdPanel creation.
2517 * var panelPosition = new MdPanelPosition()
2518 * .relativeTo(myButtonEl)
2519 * .addPanelPosition(
2520 * $mdPanel.xPosition.CENTER,
2521 * $mdPanel.yPosition.ALIGN_TOPS
2525 * position: panelPosition
2528 * @param {!angular.$injector} $injector
2529 * @final @constructor
2531 function MdPanelPosition($injector) {
2532 /** @private @const {!angular.$window} */
2533 this._$window = $injector.get('$window');
2535 /** @private {boolean} */
2536 this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
2538 /** @private @const {!angular.$mdConstant} */
2539 this._$mdConstant = $injector.get('$mdConstant');
2541 /** @private {boolean} */
2542 this._absolute = false;
2544 /** @private {!angular.JQLite} */
2547 /** @private {string} */
2550 /** @private {string} */
2553 /** @private {string} */
2556 /** @private {string} */
2559 /** @private {!Array<string>} */
2560 this._translateX = [];
2562 /** @private {!Array<string>} */
2563 this._translateY = [];
2565 /** @private {!Array<{x:string, y:string}>} */
2566 this._positions = [];
2568 /** @private {?{x:string, y:string}} */
2569 this._actualPosition;
2574 * Possible values of xPosition.
2577 MdPanelPosition.xPosition = {
2579 ALIGN_START: 'align-start',
2580 ALIGN_END: 'align-end',
2581 OFFSET_START: 'offset-start',
2582 OFFSET_END: 'offset-end'
2587 * Possible values of yPosition.
2590 MdPanelPosition.yPosition = {
2592 ALIGN_TOPS: 'align-tops',
2593 ALIGN_BOTTOMS: 'align-bottoms',
2600 * Possible values of absolute position.
2603 MdPanelPosition.absPosition = {
2611 * Margin between the edges of a panel and the viewport.
2614 MdPanelPosition.viewportMargin = 8;
2618 * Sets absolute positioning for the panel.
2619 * @return {!MdPanelPosition}
2621 MdPanelPosition.prototype.absolute = function() {
2622 this._absolute = true;
2628 * Sets the value of a position for the panel. Clears any previously set
2630 * @param {string} position Position to set
2631 * @param {string=} value Value of the position. Defaults to '0'.
2632 * @returns {!MdPanelPosition}
2635 MdPanelPosition.prototype._setPosition = function(position, value) {
2636 if (position === MdPanelPosition.absPosition.RIGHT ||
2637 position === MdPanelPosition.absPosition.LEFT) {
2638 this._left = this._right = '';
2640 position === MdPanelPosition.absPosition.BOTTOM ||
2641 position === MdPanelPosition.absPosition.TOP) {
2642 this._top = this._bottom = '';
2644 var positions = Object.keys(MdPanelPosition.absPosition).join()
2647 throw new Error('mdPanel: Position must be one of ' + positions + '.');
2650 this['_' + position] = angular.isString(value) ? value : '0';
2657 * Sets the value of `top` for the panel. Clears any previously set vertical
2659 * @param {string=} top Value of `top`. Defaults to '0'.
2660 * @returns {!MdPanelPosition}
2662 MdPanelPosition.prototype.top = function(top) {
2663 return this._setPosition(MdPanelPosition.absPosition.TOP, top);
2668 * Sets the value of `bottom` for the panel. Clears any previously set vertical
2670 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
2671 * @returns {!MdPanelPosition}
2673 MdPanelPosition.prototype.bottom = function(bottom) {
2674 return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
2679 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
2680 * `rtl`. Clears any previously set horizontal position.
2681 * @param {string=} start Value of position. Defaults to '0'.
2682 * @returns {!MdPanelPosition}
2684 MdPanelPosition.prototype.start = function(start) {
2685 var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
2686 return this._setPosition(position, start);
2691 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
2692 * Clears any previously set horizontal position.
2693 * @param {string=} end Value of position. Defaults to '0'.
2694 * @returns {!MdPanelPosition}
2696 MdPanelPosition.prototype.end = function(end) {
2697 var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
2698 return this._setPosition(position, end);
2703 * Sets the value of `left` for the panel. Clears any previously set
2704 * horizontal position.
2705 * @param {string=} left Value of `left`. Defaults to '0'.
2706 * @returns {!MdPanelPosition}
2708 MdPanelPosition.prototype.left = function(left) {
2709 return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
2714 * Sets the value of `right` for the panel. Clears any previously set
2715 * horizontal position.
2716 * @param {string=} right Value of `right`. Defaults to '0'.
2717 * @returns {!MdPanelPosition}
2719 MdPanelPosition.prototype.right = function(right) {
2720 return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
2725 * Centers the panel horizontally in the viewport. Clears any previously set
2726 * horizontal position.
2727 * @returns {!MdPanelPosition}
2729 MdPanelPosition.prototype.centerHorizontally = function() {
2732 this._translateX = ['-50%'];
2738 * Centers the panel vertically in the viewport. Clears any previously set
2739 * vertical position.
2740 * @returns {!MdPanelPosition}
2742 MdPanelPosition.prototype.centerVertically = function() {
2745 this._translateY = ['-50%'];
2751 * Centers the panel horizontally and vertically in the viewport. This is
2752 * equivalent to calling both `centerHorizontally` and `centerVertically`.
2753 * Clears any previously set horizontal and vertical positions.
2754 * @returns {!MdPanelPosition}
2756 MdPanelPosition.prototype.center = function() {
2757 return this.centerHorizontally().centerVertically();
2762 * Sets element for relative positioning.
2763 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
2764 * or angular element to set the panel relative to.
2765 * @returns {!MdPanelPosition}
2767 MdPanelPosition.prototype.relativeTo = function(element) {
2768 this._absolute = false;
2769 this._relativeToEl = getElement(element);
2775 * Sets the x and y positions for the panel relative to another element.
2776 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
2778 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
2780 * @returns {!MdPanelPosition}
2782 MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
2783 if (!this._relativeToEl) {
2784 throw new Error('mdPanel: addPanelPosition can only be used with ' +
2785 'relative positioning. Set relativeTo first.');
2788 this._validateXPosition(xPosition);
2789 this._validateYPosition(yPosition);
2791 this._positions.push({
2800 * Ensures that yPosition is a valid position name. Throw an exception if not.
2801 * @param {string} yPosition
2803 MdPanelPosition.prototype._validateYPosition = function(yPosition) {
2805 if (yPosition == null) {
2809 var positionKeys = Object.keys(MdPanelPosition.yPosition);
2810 var positionValues = [];
2811 for (var key, i = 0; key = positionKeys[i]; i++) {
2812 var position = MdPanelPosition.yPosition[key];
2813 positionValues.push(position);
2815 if (position === yPosition) {
2820 throw new Error('mdPanel: Panel y position only accepts the following ' +
2821 'values:\n' + positionValues.join(' | '));
2826 * Ensures that xPosition is a valid position name. Throw an exception if not.
2827 * @param {string} xPosition
2829 MdPanelPosition.prototype._validateXPosition = function(xPosition) {
2831 if (xPosition == null) {
2835 var positionKeys = Object.keys(MdPanelPosition.xPosition);
2836 var positionValues = [];
2837 for (var key, i = 0; key = positionKeys[i]; i++) {
2838 var position = MdPanelPosition.xPosition[key];
2839 positionValues.push(position);
2840 if (position === xPosition) {
2845 throw new Error('mdPanel: Panel x Position only accepts the following ' +
2846 'values:\n' + positionValues.join(' | '));
2851 * Sets the value of the offset in the x-direction. This will add to any
2852 * previously set offsets.
2853 * @param {string|function(MdPanelPosition): string} offsetX
2854 * @returns {!MdPanelPosition}
2856 MdPanelPosition.prototype.withOffsetX = function(offsetX) {
2857 this._translateX.push(offsetX);
2863 * Sets the value of the offset in the y-direction. This will add to any
2864 * previously set offsets.
2865 * @param {string|function(MdPanelPosition): string} offsetY
2866 * @returns {!MdPanelPosition}
2868 MdPanelPosition.prototype.withOffsetY = function(offsetY) {
2869 this._translateY.push(offsetY);
2875 * Gets the value of `top` for the panel.
2878 MdPanelPosition.prototype.getTop = function() {
2884 * Gets the value of `bottom` for the panel.
2887 MdPanelPosition.prototype.getBottom = function() {
2888 return this._bottom;
2893 * Gets the value of `left` for the panel.
2896 MdPanelPosition.prototype.getLeft = function() {
2902 * Gets the value of `right` for the panel.
2905 MdPanelPosition.prototype.getRight = function() {
2911 * Gets the value of `transform` for the panel.
2914 MdPanelPosition.prototype.getTransform = function() {
2915 var translateX = this._reduceTranslateValues('translateX', this._translateX);
2916 var translateY = this._reduceTranslateValues('translateY', this._translateY);
2918 // It's important to trim the result, because the browser will ignore the set
2919 // operation if the string contains only whitespace.
2920 return (translateX + ' ' + translateY).trim();
2925 * Sets the `transform` value for a panel element.
2926 * @param {!angular.JQLite} panelEl
2927 * @returns {!angular.JQLite}
2930 MdPanelPosition.prototype._setTransform = function(panelEl) {
2931 return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
2936 * True if the panel is completely on-screen with this positioning; false
2938 * @param {!angular.JQLite} panelEl
2942 MdPanelPosition.prototype._isOnscreen = function(panelEl) {
2943 // this works because we always use fixed positioning for the panel,
2944 // which is relative to the viewport.
2945 var left = parseInt(this.getLeft());
2946 var top = parseInt(this.getTop());
2948 if (this._translateX.length || this._translateY.length) {
2949 var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
2950 var offsets = getComputedTranslations(panelEl, prefixedTransform);
2955 var right = left + panelEl[0].offsetWidth;
2956 var bottom = top + panelEl[0].offsetHeight;
2958 return (left >= 0) &&
2960 (bottom <= this._$window.innerHeight) &&
2961 (right <= this._$window.innerWidth);
2966 * Gets the first x/y position that can fit on-screen.
2967 * @returns {{x: string, y: string}}
2969 MdPanelPosition.prototype.getActualPosition = function() {
2970 return this._actualPosition;
2975 * Reduces a list of translate values to a string that can be used within
2977 * @param {string} translateFn
2978 * @param {!Array<string>} values
2982 MdPanelPosition.prototype._reduceTranslateValues =
2983 function(translateFn, values) {
2984 return values.map(function(translation) {
2985 // TODO(crisbeto): this should add the units after #9609 is merged.
2986 var translationValue = angular.isFunction(translation) ?
2987 translation(this) : translation;
2988 return translateFn + '(' + translationValue + ')';
2994 * Sets the panel position based on the created panel element and best x/y
2996 * @param {!angular.JQLite} panelEl
2999 MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
3000 // Remove the "position adjusted" class in case it has been added before.
3001 panelEl.removeClass('_md-panel-position-adjusted');
3003 // Only calculate the position if necessary.
3004 if (this._absolute) {
3005 this._setTransform(panelEl);
3009 if (this._actualPosition) {
3010 this._calculatePanelPosition(panelEl, this._actualPosition);
3011 this._setTransform(panelEl);
3012 this._constrainToViewport(panelEl);
3016 for (var i = 0; i < this._positions.length; i++) {
3017 this._actualPosition = this._positions[i];
3018 this._calculatePanelPosition(panelEl, this._actualPosition);
3019 this._setTransform(panelEl);
3021 if (this._isOnscreen(panelEl)) {
3026 this._constrainToViewport(panelEl);
3031 * Constrains a panel's position to the viewport.
3032 * @param {!angular.JQLite} panelEl
3035 MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
3036 var margin = MdPanelPosition.viewportMargin;
3037 var initialTop = this._top;
3038 var initialLeft = this._left;
3040 if (this.getTop()) {
3041 var top = parseInt(this.getTop());
3042 var bottom = panelEl[0].offsetHeight + top;
3043 var viewportHeight = this._$window.innerHeight;
3046 this._top = margin + 'px';
3047 } else if (bottom > viewportHeight) {
3048 this._top = top - (bottom - viewportHeight + margin) + 'px';
3052 if (this.getLeft()) {
3053 var left = parseInt(this.getLeft());
3054 var right = panelEl[0].offsetWidth + left;
3055 var viewportWidth = this._$window.innerWidth;
3057 if (left < margin) {
3058 this._left = margin + 'px';
3059 } else if (right > viewportWidth) {
3060 this._left = left - (right - viewportWidth + margin) + 'px';
3064 // Class that can be used to re-style the panel if it was repositioned.
3065 panelEl.toggleClass(
3066 '_md-panel-position-adjusted',
3067 this._top !== initialTop || this._left !== initialLeft
3073 * Switches between 'start' and 'end'.
3074 * @param {string} position Horizontal position of the panel
3075 * @returns {string} Reversed position
3078 MdPanelPosition.prototype._reverseXPosition = function(position) {
3079 if (position === MdPanelPosition.xPosition.CENTER) {
3083 var start = 'start';
3086 return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
3091 * Handles horizontal positioning in rtl or ltr environments.
3092 * @param {string} position Horizontal position of the panel
3093 * @returns {string} The correct position according the page direction
3096 MdPanelPosition.prototype._bidi = function(position) {
3097 return this._isRTL ? this._reverseXPosition(position) : position;
3102 * Calculates the panel position based on the created panel element and the
3103 * provided positioning.
3104 * @param {!angular.JQLite} panelEl
3105 * @param {!{x:string, y:string}} position
3108 MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
3110 var panelBounds = panelEl[0].getBoundingClientRect();
3111 var panelWidth = panelBounds.width;
3112 var panelHeight = panelBounds.height;
3114 var targetBounds = this._relativeToEl[0].getBoundingClientRect();
3116 var targetLeft = targetBounds.left;
3117 var targetRight = targetBounds.right;
3118 var targetWidth = targetBounds.width;
3120 switch (this._bidi(position.x)) {
3121 case MdPanelPosition.xPosition.OFFSET_START:
3122 this._left = targetLeft - panelWidth + 'px';
3124 case MdPanelPosition.xPosition.ALIGN_END:
3125 this._left = targetRight - panelWidth + 'px';
3127 case MdPanelPosition.xPosition.CENTER:
3128 var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
3129 this._left = left + 'px';
3131 case MdPanelPosition.xPosition.ALIGN_START:
3132 this._left = targetLeft + 'px';
3134 case MdPanelPosition.xPosition.OFFSET_END:
3135 this._left = targetRight + 'px';
3139 var targetTop = targetBounds.top;
3140 var targetBottom = targetBounds.bottom;
3141 var targetHeight = targetBounds.height;
3143 switch (position.y) {
3144 case MdPanelPosition.yPosition.ABOVE:
3145 this._top = targetTop - panelHeight + 'px';
3147 case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
3148 this._top = targetBottom - panelHeight + 'px';
3150 case MdPanelPosition.yPosition.CENTER:
3151 var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
3152 this._top = top + 'px';
3154 case MdPanelPosition.yPosition.ALIGN_TOPS:
3155 this._top = targetTop + 'px';
3157 case MdPanelPosition.yPosition.BELOW:
3158 this._top = targetBottom + 'px';
3164 /*****************************************************************************
3165 * MdPanelAnimation *
3166 *****************************************************************************/
3170 * Animation configuration object. To use, create an MdPanelAnimation with the
3171 * desired properties, then pass the object as part of $mdPanel creation.
3175 * var panelAnimation = new MdPanelAnimation()
3176 * .openFrom(myButtonEl)
3177 * .closeTo('.my-button')
3178 * .withAnimation($mdPanel.animation.SCALE);
3181 * animation: panelAnimation
3184 * @param {!angular.$injector} $injector
3185 * @final @constructor
3187 function MdPanelAnimation($injector) {
3188 /** @private @const {!angular.$mdUtil} */
3189 this._$mdUtil = $injector.get('$mdUtil');
3192 * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
3198 * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
3203 /** @private {string|{open: string, close: string}} */
3204 this._animationClass = '';
3206 /** @private {number} */
3209 /** @private {number} */
3210 this._closeDuration;
3212 /** @private {number|{open: number, close: number}} */
3218 * Possible default animations.
3221 MdPanelAnimation.animation = {
3222 SLIDE: 'md-panel-animate-slide',
3223 SCALE: 'md-panel-animate-scale',
3224 FADE: 'md-panel-animate-fade'
3229 * Specifies where to start the open animation. `openFrom` accepts a
3230 * click event object, query selector, DOM element, or a Rect object that
3231 * is used to determine the bounds. When passed a click event, the location
3232 * of the click will be used as the position to start the animation.
3233 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
3234 * @returns {!MdPanelAnimation}
3236 MdPanelAnimation.prototype.openFrom = function(openFrom) {
3237 // Check if 'openFrom' is an Event.
3238 openFrom = openFrom.target ? openFrom.target : openFrom;
3240 this._openFrom = this._getPanelAnimationTarget(openFrom);
3242 if (!this._closeTo) {
3243 this._closeTo = this._openFrom;
3250 * Specifies where to animate the panel close. `closeTo` accepts a
3251 * query selector, DOM element, or a Rect object that is used to determine
3253 * @param {string|!Element|{top: number, left: number}} closeTo
3254 * @returns {!MdPanelAnimation}
3256 MdPanelAnimation.prototype.closeTo = function(closeTo) {
3257 this._closeTo = this._getPanelAnimationTarget(closeTo);
3263 * Specifies the duration of the animation in milliseconds.
3264 * @param {number|{open: number, close: number}} duration
3265 * @returns {!MdPanelAnimation}
3267 MdPanelAnimation.prototype.duration = function(duration) {
3269 if (angular.isNumber(duration)) {
3270 this._openDuration = this._closeDuration = toSeconds(duration);
3271 } else if (angular.isObject(duration)) {
3272 this._openDuration = toSeconds(duration.open);
3273 this._closeDuration = toSeconds(duration.close);
3277 // Save the original value so it can be passed to the backdrop.
3278 this._rawDuration = duration;
3282 function toSeconds(value) {
3283 if (angular.isNumber(value)) return value / 1000;
3289 * Returns the element and bounds for the animation target.
3290 * @param {string|!Element|{top: number, left: number}} location
3291 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
3294 MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
3295 if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
3299 top: location.top || 0,
3300 left: location.left || 0
3304 return this._getBoundingClientRect(getElement(location));
3310 * Specifies the animation class.
3312 * There are several default animations that can be used:
3313 * (MdPanelAnimation.animation)
3314 * SLIDE: The panel slides in and out from the specified
3316 * SCALE: The panel scales in and out.
3317 * FADE: The panel fades in and out.
3319 * @param {string|{open: string, close: string}} cssClass
3320 * @returns {!MdPanelAnimation}
3322 MdPanelAnimation.prototype.withAnimation = function(cssClass) {
3323 this._animationClass = cssClass;
3329 * Animate the panel open.
3330 * @param {!angular.JQLite} panelEl
3331 * @returns {!angular.$q.Promise} A promise that is resolved when the open
3332 * animation is complete.
3334 MdPanelAnimation.prototype.animateOpen = function(panelEl) {
3335 var animator = this._$mdUtil.dom.animator;
3337 this._fixBounds(panelEl);
3338 var animationOptions = {};
3340 // Include the panel transformations when calculating the animations.
3341 var panelTransform = panelEl[0].style.transform || '';
3343 var openFrom = animator.toTransformCss(panelTransform);
3344 var openTo = animator.toTransformCss(panelTransform);
3346 switch (this._animationClass) {
3347 case MdPanelAnimation.animation.SLIDE:
3348 // Slide should start with opacity: 1.
3349 panelEl.css('opacity', '1');
3351 animationOptions = {
3352 transitionInClass: '_md-panel-animate-enter'
3355 var openSlide = animator.calculateSlideToOrigin(
3356 panelEl, this._openFrom) || '';
3357 openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
3360 case MdPanelAnimation.animation.SCALE:
3361 animationOptions = {
3362 transitionInClass: '_md-panel-animate-enter'
3365 var openScale = animator.calculateZoomToOrigin(
3366 panelEl, this._openFrom) || '';
3367 openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
3370 case MdPanelAnimation.animation.FADE:
3371 animationOptions = {
3372 transitionInClass: '_md-panel-animate-enter'
3377 if (angular.isString(this._animationClass)) {
3378 animationOptions = {
3379 transitionInClass: this._animationClass
3382 animationOptions = {
3383 transitionInClass: this._animationClass['open'],
3384 transitionOutClass: this._animationClass['close'],
3389 animationOptions.duration = this._openDuration;
3392 .translate3d(panelEl, openFrom, openTo, animationOptions);
3397 * Animate the panel close.
3398 * @param {!angular.JQLite} panelEl
3399 * @returns {!angular.$q.Promise} A promise that resolves when the close
3400 * animation is complete.
3402 MdPanelAnimation.prototype.animateClose = function(panelEl) {
3403 var animator = this._$mdUtil.dom.animator;
3404 var reverseAnimationOptions = {};
3406 // Include the panel transformations when calculating the animations.
3407 var panelTransform = panelEl[0].style.transform || '';
3409 var closeFrom = animator.toTransformCss(panelTransform);
3410 var closeTo = animator.toTransformCss(panelTransform);
3412 switch (this._animationClass) {
3413 case MdPanelAnimation.animation.SLIDE:
3414 // Slide should start with opacity: 1.
3415 panelEl.css('opacity', '1');
3416 reverseAnimationOptions = {
3417 transitionInClass: '_md-panel-animate-leave'
3420 var closeSlide = animator.calculateSlideToOrigin(
3421 panelEl, this._closeTo) || '';
3422 closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
3425 case MdPanelAnimation.animation.SCALE:
3426 reverseAnimationOptions = {
3427 transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
3430 var closeScale = animator.calculateZoomToOrigin(
3431 panelEl, this._closeTo) || '';
3432 closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
3435 case MdPanelAnimation.animation.FADE:
3436 reverseAnimationOptions = {
3437 transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
3442 if (angular.isString(this._animationClass)) {
3443 reverseAnimationOptions = {
3444 transitionOutClass: this._animationClass
3447 reverseAnimationOptions = {
3448 transitionInClass: this._animationClass['close'],
3449 transitionOutClass: this._animationClass['open']
3454 reverseAnimationOptions.duration = this._closeDuration;
3457 .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
3462 * Set the height and width to match the panel if not provided.
3463 * @param {!angular.JQLite} panelEl
3466 MdPanelAnimation.prototype._fixBounds = function(panelEl) {
3467 var panelWidth = panelEl[0].offsetWidth;
3468 var panelHeight = panelEl[0].offsetHeight;
3470 if (this._openFrom && this._openFrom.bounds.height == null) {
3471 this._openFrom.bounds.height = panelHeight;
3473 if (this._openFrom && this._openFrom.bounds.width == null) {
3474 this._openFrom.bounds.width = panelWidth;
3476 if (this._closeTo && this._closeTo.bounds.height == null) {
3477 this._closeTo.bounds.height = panelHeight;
3479 if (this._closeTo && this._closeTo.bounds.width == null) {
3480 this._closeTo.bounds.width = panelWidth;
3486 * Identify the bounding RECT for the target element.
3487 * @param {!angular.JQLite} element
3488 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
3491 MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
3492 if (element instanceof angular.element) {
3495 bounds: element[0].getBoundingClientRect()
3501 /*****************************************************************************
3503 *****************************************************************************/
3507 * Returns the angular element associated with a css selector or element.
3508 * @param el {string|!angular.JQLite|!Element}
3509 * @returns {!angular.JQLite}
3511 function getElement(el) {
3512 var queryResult = angular.isString(el) ?
3513 document.querySelector(el) : el;
3514 return angular.element(queryResult);
3519 * Gets the computed values for an element's translateX and translateY in px.
3520 * @param {!angular.JQLite|!Element} el
3521 * @param {string} property
3522 * @return {{x: number, y: number}}
3524 function getComputedTranslations(el, property) {
3525 // The transform being returned by `getComputedStyle` is in the format:
3526 // `matrix(a, b, c, d, translateX, translateY)` if defined and `none`
3527 // if the element doesn't have a transform.
3528 var transform = getComputedStyle(el[0] || el)[property];
3529 var openIndex = transform.indexOf('(');
3530 var closeIndex = transform.lastIndexOf(')');
3531 var output = { x: 0, y: 0 };
3533 if (openIndex > -1 && closeIndex > -1) {
3534 var parsedValues = transform
3535 .substring(openIndex + 1, closeIndex)
3539 output.x = parseInt(parsedValues[0]);
3540 output.y = parseInt(parsedValues[1]);
3546 ngmaterial.components.panel = angular.module("material.components.panel");