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