panel.js 102 KB

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