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