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.

3545 lines
101 KiB

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