You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2563 lines
72 KiB

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