diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..889c261b9f93de3260709607cc42c535724a7b1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db +src/assets/config/config.dev.json +src/assets/config/config.prod.json +src/assets/config/config.theming.json +/src/assets/config/theming.scss +/.angular diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2d0ba6bad44fcc3fccf95bfed7add474f65f9cc5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx +FROM nginx:1.19.4 +#Copy ci-dashboard-dist +COPY ./src /usr/share/nginx/html/nfvportal +#Copy default nginx configuration +COPY ./nginx-custom.conf /etc/nginx/conf.d/default.conf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/nginx-custom.conf b/nginx-custom.conf new file mode 100644 index 0000000000000000000000000000000000000000..9a3b80c937b6cf269ac99734212caf090aa8a8fd --- /dev/null +++ b/nginx-custom.conf @@ -0,0 +1,33 @@ + + + # Expires map + map $sent_http_content_type $expires { + default off; + text/html epoch; + text/css max; + application/json max; + application/javascript max; + ~image/ max; + } + + server { + listen 80; + include /etc/nginx/mime.types; + + server_name localhost; + + location / { + root /usr/share/nginx/html/nfvportal/openslicehome; + index index.html; + } + + location ^~/nfvportal/ { + + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /nfvportal/index.html =404; + } + + expires $expires; + gzip on; + } diff --git a/src/Categories.html b/src/Categories.html new file mode 100644 index 0000000000000000000000000000000000000000..866809646d67788c018df9e6e32c030269b9d5a2 --- /dev/null +++ b/src/Categories.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/src/CategoryAdd.html b/src/CategoryAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..f919cd0c661a1d93688b5e1999f9d71026fbbcfd --- /dev/null +++ b/src/CategoryAdd.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/src/CategoryEdit.html b/src/CategoryEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..a174f63c8b8a9f947490a6e8589d3acf1c1d4a7e --- /dev/null +++ b/src/CategoryEdit.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/src/DeploymentAdd.html b/src/DeploymentAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..c60513ad1b3d39f10c4bc12814439b4e68a0d844 --- /dev/null +++ b/src/DeploymentAdd.html @@ -0,0 +1,121 @@ + \ No newline at end of file diff --git a/src/DeploymentEdit.html b/src/DeploymentEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..e83d7c629231b86b96fa9382c3c5199f4b052332 --- /dev/null +++ b/src/DeploymentEdit.html @@ -0,0 +1,145 @@ + \ No newline at end of file diff --git a/src/Deployments.html b/src/Deployments.html new file mode 100644 index 0000000000000000000000000000000000000000..1c759fdb6223ea08ec60971f6bcb73dedebcd224 --- /dev/null +++ b/src/Deployments.html @@ -0,0 +1,85 @@ + + diff --git a/src/DeploymentsAdmin.html b/src/DeploymentsAdmin.html new file mode 100644 index 0000000000000000000000000000000000000000..40abe9ffbae832fd66b3a6eaab5b204ca2a90cc9 --- /dev/null +++ b/src/DeploymentsAdmin.html @@ -0,0 +1,86 @@ + \ No newline at end of file diff --git a/src/ExperimentAdd.html b/src/ExperimentAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..e9321ece5767f3e23a38dbbacea0666e0dd6b755 --- /dev/null +++ b/src/ExperimentAdd.html @@ -0,0 +1,109 @@ + \ No newline at end of file diff --git a/src/ExperimentEdit.html b/src/ExperimentEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..d9e24cefa674481fe0030fdbf42c907c3991b21d --- /dev/null +++ b/src/ExperimentEdit.html @@ -0,0 +1,178 @@ + \ No newline at end of file diff --git a/src/ExperimentUpload.html b/src/ExperimentUpload.html new file mode 100644 index 0000000000000000000000000000000000000000..f0a456544e67a81587cec4a70aa8f42b45d165ca --- /dev/null +++ b/src/ExperimentUpload.html @@ -0,0 +1,46 @@ + \ No newline at end of file diff --git a/src/ExperimentView.html b/src/ExperimentView.html new file mode 100644 index 0000000000000000000000000000000000000000..1989615b1c59bf837fac46e19df6a80fd3c7ca0e --- /dev/null +++ b/src/ExperimentView.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/src/Experiments.html b/src/Experiments.html new file mode 100644 index 0000000000000000000000000000000000000000..baed7175769dc5040f8240acaebb5a1232c925d9 --- /dev/null +++ b/src/Experiments.html @@ -0,0 +1,76 @@ + \ No newline at end of file diff --git a/src/ExperimentsMarketplace.html b/src/ExperimentsMarketplace.html new file mode 100644 index 0000000000000000000000000000000000000000..0624803409d051c55345e1dcb75849629e20eb76 --- /dev/null +++ b/src/ExperimentsMarketplace.html @@ -0,0 +1,66 @@ + \ No newline at end of file diff --git a/src/FeaturedAppsHome.html b/src/FeaturedAppsHome.html new file mode 100644 index 0000000000000000000000000000000000000000..f3463a85b02d3bfc9fbaf5f0d6953aeabc87827d --- /dev/null +++ b/src/FeaturedAppsHome.html @@ -0,0 +1,46 @@ + +
+
+ +
+
+

 

+

Featured {{portalName}} Network Services

+

 

+
+
+ +
+ +
+
+
+ + +

{{app.name}}

+

by {{app.owner.organization}}

+

 

+
+ +

{{app.shortDescription}}

+ + View +
+
+ + + + + +
+ + + +
+
\ No newline at end of file diff --git a/src/FiwareInstances.html b/src/FiwareInstances.html new file mode 100644 index 0000000000000000000000000000000000000000..458fc8aca67b424593d0a13fe2e387ed610b35c1 --- /dev/null +++ b/src/FiwareInstances.html @@ -0,0 +1,25 @@ + diff --git a/src/Footer.html b/src/Footer.html new file mode 100644 index 0000000000000000000000000000000000000000..673f24695e3882dcb7b15e1bb3e159eb9e689604 --- /dev/null +++ b/src/Footer.html @@ -0,0 +1,64 @@ +
+
+
+
+ +
+

Who we are

+
openslice.io project | openslice.io
openslice.io an opensource OSS +
+
{{portalName}} | {{weburl}}
A portal that allows 5G + experimenters to deploy experiment descriptors towards the infrastructure.
+
{{portalName}} wiki | {{portalwiki}}
A wiki containing project + documentation. +
+
{{portalName}} Report Issues | {{bugzilla}}
Our + issue tracking system.
+
+ +
+

Connect with us

+
+
+ +
+
+ +
+
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/src/InfrastructureAdd.html b/src/InfrastructureAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..9c0877bdc2a724e2266c91e92e4fed48c726fcde --- /dev/null +++ b/src/InfrastructureAdd.html @@ -0,0 +1,52 @@ + \ No newline at end of file diff --git a/src/InfrastructureAddImage.html b/src/InfrastructureAddImage.html new file mode 100644 index 0000000000000000000000000000000000000000..52ef7f90cec20a020d5c3f091318adce197cc09c --- /dev/null +++ b/src/InfrastructureAddImage.html @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/src/InfrastructureEdit.html b/src/InfrastructureEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..3b1a5e3a501ad14fc53f093aac80785bdc444fcb --- /dev/null +++ b/src/InfrastructureEdit.html @@ -0,0 +1,64 @@ + \ No newline at end of file diff --git a/src/InfrastructureView.html b/src/InfrastructureView.html new file mode 100644 index 0000000000000000000000000000000000000000..f540c4b6e683bcb6705448bb289ef08978474495 --- /dev/null +++ b/src/InfrastructureView.html @@ -0,0 +1,7 @@ + +

Details of Infrastructure

{{portalinfrastructure.name}}

+

Name: {{portalinfrastructure.name}}

+

Organization: {{portalinfrastructure.organization}}

+

e-mail: {{portalinfrastructure.email}}

+ + \ No newline at end of file diff --git a/src/Infrastructures.html b/src/Infrastructures.html new file mode 100644 index 0000000000000000000000000000000000000000..02dcf2a677a0a339b3f225df632369bf1033c1b2 --- /dev/null +++ b/src/Infrastructures.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/src/MANOplatformAdd.html b/src/MANOplatformAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..0a88f1d31bd3528e8499d7285088562c627ec60f --- /dev/null +++ b/src/MANOplatformAdd.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/src/MANOplatformEdit.html b/src/MANOplatformEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..93474be2f3628405a1c24e75c8ed2bb47b117b4b --- /dev/null +++ b/src/MANOplatformEdit.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/src/MANOplatforms.html b/src/MANOplatforms.html new file mode 100644 index 0000000000000000000000000000000000000000..eba84be7b95f2fcb79a73a11bb39f185ef7aaf49 --- /dev/null +++ b/src/MANOplatforms.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/src/MANOproviderAdd.html b/src/MANOproviderAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..aa98280f2aa412aa3a45ae80eb479181cec6255b --- /dev/null +++ b/src/MANOproviderAdd.html @@ -0,0 +1,111 @@ + \ No newline at end of file diff --git a/src/MANOproviderEdit.html b/src/MANOproviderEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..6e4393deab8505936232a299487c7065d5374525 --- /dev/null +++ b/src/MANOproviderEdit.html @@ -0,0 +1,108 @@ + \ No newline at end of file diff --git a/src/MANOproviders.html b/src/MANOproviders.html new file mode 100644 index 0000000000000000000000000000000000000000..6317eb30ae6d54d0cc9a4fb88eaaff6d5561891b --- /dev/null +++ b/src/MANOproviders.html @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/src/RegisterConfig.html b/src/RegisterConfig.html new file mode 100644 index 0000000000000000000000000000000000000000..df28bb3fbd12c3d81738ce9dd51d914ba57d2ece --- /dev/null +++ b/src/RegisterConfig.html @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/src/ServiceSpecAdd.html b/src/ServiceSpecAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..6e77ad816f4981c02411b28a40e72c6192469fe7 --- /dev/null +++ b/src/ServiceSpecAdd.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/src/ServiceSpecEdit.html b/src/ServiceSpecEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..a2b63e7e82a0e24b2da70670c8d532ecdde60e31 --- /dev/null +++ b/src/ServiceSpecEdit.html @@ -0,0 +1,105 @@ + \ No newline at end of file diff --git a/src/ServicesCatalog.html b/src/ServicesCatalog.html new file mode 100644 index 0000000000000000000000000000000000000000..f4efc24ea6b3a78c78bb265d73048acefaae526a --- /dev/null +++ b/src/ServicesCatalog.html @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/src/ServicesCatalogAdd.html b/src/ServicesCatalogAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..6e1cb285a41c13b8c1517c39b3fcd4b6bfd98221 --- /dev/null +++ b/src/ServicesCatalogAdd.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/src/ServicesCatalogEdit.html b/src/ServicesCatalogEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..43484716a1ed270f2221a1a48fc3d17dc361eff8 --- /dev/null +++ b/src/ServicesCatalogEdit.html @@ -0,0 +1,134 @@ + \ No newline at end of file diff --git a/src/ServicesCategory.html b/src/ServicesCategory.html new file mode 100644 index 0000000000000000000000000000000000000000..9d668d46343028046bf8b148e98d4b50c0e3aee3 --- /dev/null +++ b/src/ServicesCategory.html @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/src/ServicesCategoryAdd.html b/src/ServicesCategoryAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..a96fa4a926fb9f01b2096932aa52b56254396499 --- /dev/null +++ b/src/ServicesCategoryAdd.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/src/ServicesCategoryEdit.html b/src/ServicesCategoryEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..70c248113c867aeab7dd73e31f6a767fce1655d6 --- /dev/null +++ b/src/ServicesCategoryEdit.html @@ -0,0 +1,108 @@ + \ No newline at end of file diff --git a/src/ServicesCategoryServiceCandidatesEdit.html b/src/ServicesCategoryServiceCandidatesEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..e1e1874d543d6d996d07f59703ce7ab1459917b6 --- /dev/null +++ b/src/ServicesCategoryServiceCandidatesEdit.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/src/ServicesMarketplace.html b/src/ServicesMarketplace.html new file mode 100644 index 0000000000000000000000000000000000000000..c69959a5b4976180722594149d70f470e2291968 --- /dev/null +++ b/src/ServicesMarketplace.html @@ -0,0 +1,70 @@ + \ No newline at end of file diff --git a/src/ServicesSpecs.html b/src/ServicesSpecs.html new file mode 100644 index 0000000000000000000000000000000000000000..a793ad2c8d01c708e95755820cc3aa8df16dd0a1 --- /dev/null +++ b/src/ServicesSpecs.html @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/src/SignInHomeSection.html b/src/SignInHomeSection.html new file mode 100644 index 0000000000000000000000000000000000000000..c18384233fcf314a6c3be40dceba8a78748d588f --- /dev/null +++ b/src/SignInHomeSection.html @@ -0,0 +1,54 @@ +
+
+
+
+

 

+

+ Welcome to the openslice {{portalName}}! +

+

Use the fields on the right to login + to {{portalName}} and easily deploy an experiment over + the {{portalName}} infrastructure!

+

Check our wiki for further documenation

+

Report platform issues at our Bugzilla

+

Check the Health Status of our infrastructure

+ + +
+
+

Sign In

+ + + + + + +
+
+
+
+ + + +
+
+
+ +
+

 

+

Deploy {{portalName}} Network Services!

+

Access, create + and share Network Services over the {{portalName}} infrastructure!

+

 

+
+ + +
+ +
+
\ No newline at end of file diff --git a/src/SubscribedResourceAdd.html b/src/SubscribedResourceAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..c35dbfc9da1491d109d1ca3c41b4fe01aaa992dc --- /dev/null +++ b/src/SubscribedResourceAdd.html @@ -0,0 +1,42 @@ + \ No newline at end of file diff --git a/src/SubscribedResourceEdit.html b/src/SubscribedResourceEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..0986424b3022ca9368e44cd6841d43af664282a8 --- /dev/null +++ b/src/SubscribedResourceEdit.html @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/src/SubscribedResourceView.html b/src/SubscribedResourceView.html new file mode 100644 index 0000000000000000000000000000000000000000..0686a5905bd995368c94d147a10261619b75802a --- /dev/null +++ b/src/SubscribedResourceView.html @@ -0,0 +1,5 @@ + +

Details of Subscribed Resource

{{subscribedresource.url}}

+

URL: {{subscribedresource.url}}

+

UUID: {{subscribedresource.uuid}}

+

Active: {{subscribedresource.active}}

diff --git a/src/SubscribedResources.html b/src/SubscribedResources.html new file mode 100644 index 0000000000000000000000000000000000000000..b06c0785591c8042f00e476c78cf504584d6db96 --- /dev/null +++ b/src/SubscribedResources.html @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/src/SystemInfo.html b/src/SystemInfo.html new file mode 100644 index 0000000000000000000000000000000000000000..6402185634975fce3dce982bb87fc9dfd5740048 --- /dev/null +++ b/src/SystemInfo.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/src/SystemInfoEdit.html b/src/SystemInfoEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..53e08799bc5b32fd849fa97664de73cbbec3f41c --- /dev/null +++ b/src/SystemInfoEdit.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/src/UserAdd.html b/src/UserAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..2c48af62240e01f823fe3a32c8a047405d8145c8 --- /dev/null +++ b/src/UserAdd.html @@ -0,0 +1,79 @@ + \ No newline at end of file diff --git a/src/UserEdit.html b/src/UserEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..0ca27bda1b4da9ec6574d2407f421d45ff69928e --- /dev/null +++ b/src/UserEdit.html @@ -0,0 +1,106 @@ + \ No newline at end of file diff --git a/src/UserView.html b/src/UserView.html new file mode 100644 index 0000000000000000000000000000000000000000..5f6aae4c261b03bb648f154eba90f83123cef039 --- /dev/null +++ b/src/UserView.html @@ -0,0 +1,9 @@ + +

Details of User

{{portaluser.username}}

+

First Name: {{portaluser.firstname}}

+

Last Name: {{portaluser.lastname}}

+

Organization: {{portaluser.organization}}

+

e-mail: {{portaluser.email}}

+

role: {{portaluser.role}}

+ + \ No newline at end of file diff --git a/src/Users.html b/src/Users.html new file mode 100644 index 0000000000000000000000000000000000000000..e52d9855807f53756003907e829ce3357e497b07 --- /dev/null +++ b/src/Users.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/src/VFImageEdit.html b/src/VFImageEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..c16a902eeb76875bab3c4fae0c3b59e3db2093fd --- /dev/null +++ b/src/VFImageEdit.html @@ -0,0 +1,79 @@ + \ No newline at end of file diff --git a/src/VFImageUpload.html b/src/VFImageUpload.html new file mode 100644 index 0000000000000000000000000000000000000000..2c4d59b69b6258207b7f2f1c939b14cb3cfcd41f --- /dev/null +++ b/src/VFImageUpload.html @@ -0,0 +1,80 @@ + \ No newline at end of file diff --git a/src/VFImageView.html b/src/VFImageView.html new file mode 100644 index 0000000000000000000000000000000000000000..37fb94f363eaeab1f4faf9e7160ca00fe53b6baa --- /dev/null +++ b/src/VFImageView.html @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/src/VFImages.html b/src/VFImages.html new file mode 100644 index 0000000000000000000000000000000000000000..e409c559b5843b38af4a87d4eacdcee9bd8e6fad --- /dev/null +++ b/src/VFImages.html @@ -0,0 +1,87 @@ + \ No newline at end of file diff --git a/src/VxFAdd.html b/src/VxFAdd.html new file mode 100644 index 0000000000000000000000000000000000000000..da0e7d9884b71ccf55db44624f0c2ee3a097ad0d --- /dev/null +++ b/src/VxFAdd.html @@ -0,0 +1,131 @@ + \ No newline at end of file diff --git a/src/VxFEdit.html b/src/VxFEdit.html new file mode 100644 index 0000000000000000000000000000000000000000..67965f08b843e39fece9e6ff13d3c944204feee2 --- /dev/null +++ b/src/VxFEdit.html @@ -0,0 +1,207 @@ + \ No newline at end of file diff --git a/src/VxFUpload.html b/src/VxFUpload.html new file mode 100644 index 0000000000000000000000000000000000000000..1a5e24650caeb5395017422fe32b8549d558f69d --- /dev/null +++ b/src/VxFUpload.html @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/src/VxFView.html b/src/VxFView.html new file mode 100644 index 0000000000000000000000000000000000000000..2d9dda74000f04061d560b825ef57c9430b811f6 --- /dev/null +++ b/src/VxFView.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/src/VxFs.html b/src/VxFs.html new file mode 100644 index 0000000000000000000000000000000000000000..1cbb9ee251da296d35b4a9491fba0b50fdeff9cc --- /dev/null +++ b/src/VxFs.html @@ -0,0 +1,92 @@ + \ No newline at end of file diff --git a/src/VxFsMarketplace.html b/src/VxFsMarketplace.html new file mode 100644 index 0000000000000000000000000000000000000000..e7cfe06d8772677988fd41b9ebaca56eca907612 --- /dev/null +++ b/src/VxFsMarketplace.html @@ -0,0 +1,65 @@ + \ No newline at end of file diff --git a/src/css/fonts/fontawesome-webfont.ttf b/src/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d46172476a3c7caf4f44946e3c40218559f3edfa Binary files /dev/null and b/src/css/fonts/fontawesome-webfont.ttf differ diff --git a/src/css/fonts/fontawesome-webfont.woff b/src/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..628b6a52a87e62c6f22426e17c01f6a303aa194e Binary files /dev/null and b/src/css/fonts/fontawesome-webfont.woff differ diff --git a/src/css/libs/angular-material.css b/src/css/libs/angular-material.css new file mode 100644 index 0000000000000000000000000000000000000000..de4887f594d7cdb17e5c4b6780c68a34e2f059cd --- /dev/null +++ b/src/css/libs/angular-material.css @@ -0,0 +1,18140 @@ +/*! + * AngularJS Material Design + * https://github.com/angular/material + * @license MIT + * v1.1.5 + */ +html, body { + height: 100%; + position: relative; } + +body { + margin: 0; + padding: 0; } + +[tabindex='-1']:focus { + outline: none; } + +.inset { + padding: 10px; } + +a.md-no-style, +button.md-no-style { + font-weight: normal; + background-color: inherit; + text-align: left; + border: none; + padding: 0; + margin: 0; } + +select, +button, +textarea, +input { + vertical-align: baseline; } + +input[type="reset"], +input[type="submit"], +html input[type="button"], +button { + cursor: pointer; + -webkit-appearance: button; } + input[type="reset"][disabled], + input[type="submit"][disabled], + html input[type="button"][disabled], + button[disabled] { + cursor: default; } + +textarea { + vertical-align: top; + overflow: auto; } + +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; + -webkit-box-sizing: content-box; } + input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; } + +input:-webkit-autofill { + text-shadow: none; } + +.md-visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + text-transform: none; + width: 1px; } + +.md-shadow { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: inherit; + pointer-events: none; } + +.md-shadow-bottom-z-1 { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); } + +.md-shadow-bottom-z-2 { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); } + +.md-shadow-animated.md-shadow { + -webkit-transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); + transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); } + +/* + * A container inside of a rippling element (eg a button), + * which contains all of the individual ripples + */ +.md-ripple-container { + pointer-events: none; + position: absolute; + overflow: hidden; + left: 0; + top: 0; + width: 100%; + height: 100%; + -webkit-transition: all 0.55s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.55s cubic-bezier(0.25, 0.8, 0.25, 1); } + +.md-ripple { + position: absolute; + -webkit-transform: translate(-50%, -50%) scale(0); + transform: translate(-50%, -50%) scale(0); + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + opacity: 0; + border-radius: 50%; } + .md-ripple.md-ripple-placed { + -webkit-transition: margin 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), border 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), width 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), height 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: margin 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), border 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), width 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), height 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: margin 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), border 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), width 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), height 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: margin 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), border 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), width 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), height 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); } + .md-ripple.md-ripple-scaled { + -webkit-transform: translate(-50%, -50%) scale(1); + transform: translate(-50%, -50%) scale(1); } + .md-ripple.md-ripple-active, .md-ripple.md-ripple-full, .md-ripple.md-ripple-visible { + opacity: 0.20; } + .md-ripple.md-ripple-remove { + -webkit-animation: md-remove-ripple 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); + animation: md-remove-ripple 0.9s cubic-bezier(0.25, 0.8, 0.25, 1); } + +@-webkit-keyframes md-remove-ripple { + 0% { + opacity: .15; } + 100% { + opacity: 0; } } + +@keyframes md-remove-ripple { + 0% { + opacity: .15; } + 100% { + opacity: 0; } } + +.md-padding { + padding: 8px; } + +.md-margin { + margin: 8px; } + +.md-scroll-mask { + position: absolute; + background-color: transparent; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 50; } + .md-scroll-mask > .md-scroll-mask-bar { + display: block; + position: absolute; + background-color: #fafafa; + right: 0; + top: 0; + bottom: 0; + z-index: 65; + box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.3); } + +.md-no-momentum { + -webkit-overflow-scrolling: auto; } + +.md-no-flicker { + -webkit-filter: blur(0px); } + +@media (min-width: 960px) { + .md-padding { + padding: 16px; } } + +html[dir=rtl], html[dir=ltr], body[dir=rtl], body[dir=ltr] { + unicode-bidi: embed; } + +bdo[dir=rtl] { + direction: rtl; + unicode-bidi: bidi-override; } + +bdo[dir=ltr] { + direction: ltr; + unicode-bidi: bidi-override; } + +html, body { + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + min-height: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + +/************ + * Headings + ************/ +.md-display-4 { + font-size: 112px; + font-weight: 300; + letter-spacing: -0.010em; + line-height: 112px; } + +.md-display-3 { + font-size: 56px; + font-weight: 400; + letter-spacing: -0.005em; + line-height: 56px; } + +.md-display-2 { + font-size: 45px; + font-weight: 400; + line-height: 64px; } + +.md-display-1 { + font-size: 34px; + font-weight: 400; + line-height: 40px; } + +.md-headline { + font-size: 24px; + font-weight: 400; + line-height: 32px; } + +.md-title { + font-size: 20px; + font-weight: 500; + letter-spacing: 0.005em; } + +.md-subhead { + font-size: 16px; + font-weight: 400; + letter-spacing: 0.010em; + line-height: 24px; } + +/************ + * Body Copy + ************/ +.md-body-1 { + font-size: 14px; + font-weight: 400; + letter-spacing: 0.010em; + line-height: 20px; } + +.md-body-2 { + font-size: 14px; + font-weight: 500; + letter-spacing: 0.010em; + line-height: 24px; } + +.md-caption { + font-size: 12px; + letter-spacing: 0.020em; } + +.md-button { + letter-spacing: 0.010em; } + +/************ + * Defaults + ************/ +button, +select, +html, +textarea, +input { + font-family: Roboto, "Helvetica Neue", sans-serif; } + +select, +button, +textarea, +input { + font-size: 100%; } + +/* +* +* Responsive attributes +* +* References: +* 1) https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties#flex +* 2) https://css-tricks.com/almanac/properties/f/flex/ +* 3) https://css-tricks.com/snippets/css/a-guide-to-flexbox/ +* 4) https://github.com/philipwalton/flexbugs#3-min-height-on-a-flex-container-wont-apply-to-its-flex-items +* 5) http://godban.com.ua/projects/flexgrid +* +* +*/ +.md-panel-outer-wrapper { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; } + +._md-panel-hidden { + display: none; } + +._md-panel-offscreen { + left: -9999px; } + +._md-panel-fullscreen { + border-radius: 0; + left: 0; + min-height: 100%; + min-width: 100%; + position: fixed; + top: 0; } + +._md-panel-shown .md-panel { + opacity: 1; + -webkit-transition: none; + transition: none; } + +.md-panel { + opacity: 0; + position: fixed; } + .md-panel._md-panel-shown { + opacity: 1; + -webkit-transition: none; + transition: none; } + .md-panel._md-panel-animate-enter { + opacity: 1; + -webkit-transition: all 0.3s cubic-bezier(0, 0, 0.2, 1); + transition: all 0.3s cubic-bezier(0, 0, 0.2, 1); } + .md-panel._md-panel-animate-leave { + opacity: 1; + -webkit-transition: all 0.3s cubic-bezier(0.4, 0, 1, 1); + transition: all 0.3s cubic-bezier(0.4, 0, 1, 1); } + .md-panel._md-panel-animate-scale-out, .md-panel._md-panel-animate-fade-out { + opacity: 0; } + .md-panel._md-panel-backdrop { + height: 100%; + position: absolute; + width: 100%; } + .md-panel._md-opaque-enter { + opacity: .48; + -webkit-transition: opacity 0.3s cubic-bezier(0, 0, 0.2, 1); + transition: opacity 0.3s cubic-bezier(0, 0, 0.2, 1); } + .md-panel._md-opaque-leave { + -webkit-transition: opacity 0.3s cubic-bezier(0.4, 0, 1, 1); + transition: opacity 0.3s cubic-bezier(0.4, 0, 1, 1); } + +md-autocomplete { + border-radius: 2px; + display: block; + height: 40px; + position: relative; + overflow: visible; + min-width: 190px; } + md-autocomplete[disabled] input { + cursor: default; } + md-autocomplete[md-floating-label] { + border-radius: 0; + background: transparent; + height: auto; } + md-autocomplete[md-floating-label] md-input-container { + padding-bottom: 0; } + md-autocomplete[md-floating-label] md-autocomplete-wrap { + height: auto; } + md-autocomplete[md-floating-label] .md-show-clear-button button { + display: block; + position: absolute; + right: 0; + top: 20px; + width: 30px; + height: 30px; } + md-autocomplete[md-floating-label] .md-show-clear-button input { + padding-right: 30px; } + [dir=rtl] md-autocomplete[md-floating-label] .md-show-clear-button input { + padding-right: 0; + padding-left: 30px; } + md-autocomplete md-autocomplete-wrap { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + box-sizing: border-box; + position: relative; + overflow: visible; + height: 40px; } + md-autocomplete md-autocomplete-wrap.md-menu-showing { + z-index: 51; } + md-autocomplete md-autocomplete-wrap md-input-container, md-autocomplete md-autocomplete-wrap input { + -webkit-box-flex: 1; + -webkit-flex: 1 1 0%; + flex: 1 1 0%; + box-sizing: border-box; + min-width: 0; } + md-autocomplete md-autocomplete-wrap md-progress-linear { + position: absolute; + bottom: -2px; + left: 0; } + md-autocomplete md-autocomplete-wrap md-progress-linear.md-inline { + bottom: 40px; + right: 2px; + left: 2px; + width: auto; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 3px; + -webkit-transition: none; + transition: none; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate .md-container { + -webkit-transition: none; + transition: none; + height: 3px; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-enter { + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-enter.ng-enter-active { + opacity: 1; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-leave { + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; } + md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-leave.ng-leave-active { + opacity: 0; } + md-autocomplete input:not(.md-input) { + font-size: 14px; + box-sizing: border-box; + border: none; + box-shadow: none; + outline: none; + background: transparent; + width: 100%; + padding: 0 15px; + line-height: 40px; + height: 40px; } + md-autocomplete input:not(.md-input)::-ms-clear { + display: none; } + md-autocomplete .md-show-clear-button button { + position: relative; + line-height: 20px; + text-align: center; + width: 30px; + height: 30px; + cursor: pointer; + border: none; + border-radius: 50%; + padding: 0; + font-size: 12px; + background: transparent; + margin: auto 5px; } + md-autocomplete .md-show-clear-button button:after { + content: ''; + position: absolute; + top: -6px; + right: -6px; + bottom: -6px; + left: -6px; + border-radius: 50%; + -webkit-transform: scale(0); + transform: scale(0); + opacity: 0; + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } + md-autocomplete .md-show-clear-button button:focus { + outline: none; } + md-autocomplete .md-show-clear-button button:focus:after { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } + md-autocomplete .md-show-clear-button button md-icon { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate3d(-50%, -50%, 0) scale(0.9); + transform: translate3d(-50%, -50%, 0) scale(0.9); } + md-autocomplete .md-show-clear-button button md-icon path { + stroke-width: 0; } + md-autocomplete .md-show-clear-button button.ng-enter { + -webkit-transform: scale(0); + transform: scale(0); + -webkit-transition: -webkit-transform 0.15s ease-out; + transition: -webkit-transform 0.15s ease-out; + transition: transform 0.15s ease-out; + transition: transform 0.15s ease-out, -webkit-transform 0.15s ease-out; } + md-autocomplete .md-show-clear-button button.ng-enter.ng-enter-active { + -webkit-transform: scale(1); + transform: scale(1); } + md-autocomplete .md-show-clear-button button.ng-leave { + -webkit-transition: -webkit-transform 0.15s ease-out; + transition: -webkit-transform 0.15s ease-out; + transition: transform 0.15s ease-out; + transition: transform 0.15s ease-out, -webkit-transform 0.15s ease-out; } + md-autocomplete .md-show-clear-button button.ng-leave.ng-leave-active { + -webkit-transform: scale(0); + transform: scale(0); } + @media screen and (-ms-high-contrast: active) { + md-autocomplete input { + border: 1px solid #fff; } + md-autocomplete li:focus { + color: #fff; } } + +.md-virtual-repeat-container.md-autocomplete-suggestions-container { + position: absolute; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25); + z-index: 100; + height: 100%; } + +.md-virtual-repeat-container.md-not-found { + height: 48px; } + +.md-autocomplete-suggestions { + margin: 0; + list-style: none; + padding: 0; } + .md-autocomplete-suggestions li { + font-size: 14px; + overflow: hidden; + padding: 0 15px; + line-height: 48px; + height: 48px; + -webkit-transition: background 0.15s linear; + transition: background 0.15s linear; + margin: 0; + white-space: nowrap; + text-overflow: ellipsis; } + .md-autocomplete-suggestions li:focus { + outline: none; } + .md-autocomplete-suggestions li:not(.md-not-found-wrapper) { + cursor: pointer; } + +@media screen and (-ms-high-contrast: active) { + md-autocomplete, + .md-autocomplete-suggestions { + border: 1px solid #fff; } } + +md-backdrop { + -webkit-transition: opacity 450ms; + transition: opacity 450ms; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 50; } + md-backdrop.md-menu-backdrop { + position: fixed !important; + z-index: 99; } + md-backdrop.md-select-backdrop { + z-index: 81; + -webkit-transition-duration: 0; + transition-duration: 0; } + md-backdrop.md-dialog-backdrop { + z-index: 79; } + md-backdrop.md-bottom-sheet-backdrop { + z-index: 69; } + md-backdrop.md-sidenav-backdrop { + z-index: 59; } + md-backdrop.md-click-catcher { + position: absolute; } + md-backdrop.md-opaque { + opacity: .48; } + md-backdrop.md-opaque.ng-enter { + opacity: 0; } + md-backdrop.md-opaque.ng-enter.md-opaque.ng-enter-active { + opacity: .48; } + md-backdrop.md-opaque.ng-leave { + opacity: .48; + -webkit-transition: opacity 400ms; + transition: opacity 400ms; } + md-backdrop.md-opaque.ng-leave.md-opaque.ng-leave-active { + opacity: 0; } + +md-bottom-sheet { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 8px 16px 88px 16px; + z-index: 70; + border-top-width: 1px; + border-top-style: solid; + -webkit-transform: translate3d(0, 80px, 0); + transform: translate3d(0, 80px, 0); + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transition-property: -webkit-transform; + transition-property: -webkit-transform; + transition-property: transform; + transition-property: transform, -webkit-transform; } + md-bottom-sheet.md-has-header { + padding-top: 0; } + md-bottom-sheet.ng-enter { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); } + md-bottom-sheet.ng-enter-active { + opacity: 1; + display: block; + -webkit-transform: translate3d(0, 80px, 0) !important; + transform: translate3d(0, 80px, 0) !important; } + md-bottom-sheet.ng-leave-active { + -webkit-transform: translate3d(0, 100%, 0) !important; + transform: translate3d(0, 100%, 0) !important; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); } + md-bottom-sheet .md-subheader { + background-color: transparent; + font-family: Roboto, "Helvetica Neue", sans-serif; + line-height: 56px; + padding: 0; + white-space: nowrap; } + md-bottom-sheet md-inline-icon { + display: inline-block; + height: 24px; + width: 24px; + fill: #444; } + md-bottom-sheet md-list-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + outline: none; } + md-bottom-sheet md-list-item:hover { + cursor: pointer; } + md-bottom-sheet.md-list md-list-item { + padding: 0; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + height: 48px; } + md-bottom-sheet.md-grid { + padding-left: 24px; + padding-right: 24px; + padding-top: 0; } + md-bottom-sheet.md-grid md-list { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-transition: all 0.5s; + transition: all 0.5s; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; } + md-bottom-sheet.md-grid md-list-item { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-transition: all 0.5s; + transition: all 0.5s; + height: 96px; + margin-top: 8px; + margin-bottom: 8px; + /* Mixin for how many grid items to show per row */ } + @media (max-width: 960px) { + md-bottom-sheet.md-grid md-list-item { + -webkit-box-flex: 1; + -webkit-flex: 1 1 33.33333%; + flex: 1 1 33.33333%; + max-width: 33.33333%; } + md-bottom-sheet.md-grid md-list-item:nth-of-type(3n + 1) { + -webkit-box-align: start; + -webkit-align-items: flex-start; + align-items: flex-start; } + md-bottom-sheet.md-grid md-list-item:nth-of-type(3n) { + -webkit-box-align: end; + -webkit-align-items: flex-end; + align-items: flex-end; } } + @media (min-width: 960px) and (max-width: 1279px) { + md-bottom-sheet.md-grid md-list-item { + -webkit-box-flex: 1; + -webkit-flex: 1 1 25%; + flex: 1 1 25%; + max-width: 25%; } } + @media (min-width: 1280px) and (max-width: 1919px) { + md-bottom-sheet.md-grid md-list-item { + -webkit-box-flex: 1; + -webkit-flex: 1 1 16.66667%; + flex: 1 1 16.66667%; + max-width: 16.66667%; } } + @media (min-width: 1920px) { + md-bottom-sheet.md-grid md-list-item { + -webkit-box-flex: 1; + -webkit-flex: 1 1 14.28571%; + flex: 1 1 14.28571%; + max-width: 14.28571%; } } + md-bottom-sheet.md-grid md-list-item::before { + display: none; } + md-bottom-sheet.md-grid md-list-item .md-list-item-content { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + width: 48px; + padding-bottom: 16px; } + md-bottom-sheet.md-grid md-list-item .md-grid-item-content { + border: 1px solid transparent; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + width: 80px; } + md-bottom-sheet.md-grid md-list-item .md-grid-text { + font-weight: 400; + line-height: 16px; + font-size: 13px; + margin: 0; + white-space: nowrap; + width: 64px; + text-align: center; + text-transform: none; + padding-top: 8px; } + +@media screen and (-ms-high-contrast: active) { + md-bottom-sheet { + border: 1px solid #fff; } } + +button.md-button::-moz-focus-inner { + border: 0; } + +.md-button { + display: inline-block; + position: relative; + cursor: pointer; + /** Alignment adjustments */ + min-height: 36px; + min-width: 88px; + line-height: 36px; + vertical-align: middle; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + text-align: center; + border-radius: 2px; + box-sizing: border-box; + /* Reset default button appearance */ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + outline: none; + border: 0; + /** Custom styling for button */ + padding: 0 6px; + margin: 6px 8px; + background: transparent; + color: currentColor; + white-space: nowrap; + /* Uppercase text content */ + text-transform: uppercase; + font-weight: 500; + font-size: 14px; + font-style: inherit; + font-variant: inherit; + font-family: inherit; + text-decoration: none; + overflow: hidden; + -webkit-transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } + .md-dense > .md-button:not(.md-dense-disabled), + .md-dense :not(.md-dense-disabled) .md-button:not(.md-dense-disabled) { + min-height: 32px; } + .md-dense > .md-button:not(.md-dense-disabled), + .md-dense :not(.md-dense-disabled) .md-button:not(.md-dense-disabled) { + line-height: 32px; } + .md-dense > .md-button:not(.md-dense-disabled), + .md-dense :not(.md-dense-disabled) .md-button:not(.md-dense-disabled) { + font-size: 13px; } + .md-button:focus { + outline: none; } + .md-button:hover, .md-button:focus { + text-decoration: none; } + .md-button.ng-hide, .md-button.ng-leave { + -webkit-transition: none; + transition: none; } + .md-button.md-cornered { + border-radius: 0; } + .md-button.md-icon { + padding: 0; + background: none; } + .md-button.md-raised:not([disabled]) { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); } + .md-button.md-icon-button { + margin: 0 6px; + height: 40px; + min-width: 0; + line-height: 24px; + padding: 8px; + width: 40px; + border-radius: 50%; } + .md-button.md-icon-button .md-ripple-container { + border-radius: 50%; + background-clip: padding-box; + overflow: hidden; + -webkit-mask-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC"); } + .md-button.md-fab { + z-index: 20; + line-height: 56px; + min-width: 0; + width: 56px; + height: 56px; + vertical-align: middle; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + border-radius: 50%; + background-clip: padding-box; + overflow: hidden; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + -webkit-transition-property: background-color, box-shadow, -webkit-transform; + transition-property: background-color, box-shadow, -webkit-transform; + transition-property: background-color, box-shadow, transform; + transition-property: background-color, box-shadow, transform, -webkit-transform; } + .md-button.md-fab.md-fab-bottom-right { + top: auto; + right: 20px; + bottom: 20px; + left: auto; + position: absolute; } + .md-button.md-fab.md-fab-bottom-left { + top: auto; + right: auto; + bottom: 20px; + left: 20px; + position: absolute; } + .md-button.md-fab.md-fab-top-right { + top: 20px; + right: 20px; + bottom: auto; + left: auto; + position: absolute; } + .md-button.md-fab.md-fab-top-left { + top: 20px; + right: auto; + bottom: auto; + left: 20px; + position: absolute; } + .md-button.md-fab .md-ripple-container { + border-radius: 50%; + background-clip: padding-box; + overflow: hidden; + -webkit-mask-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC"); } + .md-button.md-fab.md-mini { + line-height: 40px; + width: 40px; + height: 40px; } + .md-button.md-fab.ng-hide, .md-button.md-fab.ng-leave { + -webkit-transition: none; + transition: none; } + .md-button:not([disabled]).md-raised.md-focused, .md-button:not([disabled]).md-fab.md-focused { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); } + .md-button:not([disabled]).md-raised:active, .md-button:not([disabled]).md-fab:active { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); } + .md-button .md-ripple-container { + border-radius: 2px; + background-clip: padding-box; + overflow: hidden; + -webkit-mask-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC"); } + +.md-button.md-icon-button md-icon, +button.md-button.md-fab md-icon { + display: block; } + +.md-toast-open-top .md-button.md-fab-top-left, +.md-toast-open-top .md-button.md-fab-top-right { + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transform: translate3d(0, 42px, 0); + transform: translate3d(0, 42px, 0); } + .md-toast-open-top .md-button.md-fab-top-left:not([disabled]).md-focused, .md-toast-open-top .md-button.md-fab-top-left:not([disabled]):hover, + .md-toast-open-top .md-button.md-fab-top-right:not([disabled]).md-focused, + .md-toast-open-top .md-button.md-fab-top-right:not([disabled]):hover { + -webkit-transform: translate3d(0, 41px, 0); + transform: translate3d(0, 41px, 0); } + +.md-toast-open-bottom .md-button.md-fab-bottom-left, +.md-toast-open-bottom .md-button.md-fab-bottom-right { + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transform: translate3d(0, -42px, 0); + transform: translate3d(0, -42px, 0); } + .md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]).md-focused, .md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]):hover, + .md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]).md-focused, + .md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]):hover { + -webkit-transform: translate3d(0, -43px, 0); + transform: translate3d(0, -43px, 0); } + +.md-button-group { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + width: 100%; } + .md-button-group > .md-button { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + display: block; + overflow: hidden; + width: 0; + border-width: 1px 0px 1px 1px; + border-radius: 0; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; } + .md-button-group > .md-button:first-child { + border-radius: 2px 0px 0px 2px; } + .md-button-group > .md-button:last-child { + border-right-width: 1px; + border-radius: 0px 2px 2px 0px; } + +@media screen and (-ms-high-contrast: active) { + .md-button.md-raised, + .md-button.md-fab { + border: 1px solid #fff; } } + +md-card { + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + margin: 8px; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12); } + md-card md-card-header { + padding: 16px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-card md-card-header:first-child md-card-avatar { + margin-right: 12px; } + [dir=rtl] md-card md-card-header:first-child md-card-avatar { + margin-right: auto; + margin-left: 12px; } + md-card md-card-header:last-child md-card-avatar { + margin-left: 12px; } + [dir=rtl] md-card md-card-header:last-child md-card-avatar { + margin-left: auto; + margin-right: 12px; } + md-card md-card-header md-card-avatar { + width: 40px; + height: 40px; } + md-card md-card-header md-card-avatar .md-user-avatar, + md-card md-card-header md-card-avatar md-icon { + border-radius: 50%; } + md-card md-card-header md-card-avatar md-icon { + padding: 8px; } + md-card md-card-header md-card-avatar md-icon > svg { + height: inherit; + width: inherit; } + md-card md-card-header md-card-avatar + md-card-header-text { + max-height: 40px; } + md-card md-card-header md-card-avatar + md-card-header-text .md-title { + font-size: 14px; } + md-card md-card-header md-card-header-text { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; } + md-card md-card-header md-card-header-text .md-subhead { + font-size: 14px; } + md-card > img, + md-card > md-card-header img, + md-card md-card-title-media img { + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; + height: auto; } + md-card md-card-title { + padding: 24px 16px 16px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-card md-card-title + md-card-content { + padding-top: 0; } + md-card md-card-title md-card-title-text { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + display: -webkit-box; + display: -webkit-flex; + display: flex; } + md-card md-card-title md-card-title-text .md-subhead { + padding-top: 0; + font-size: 14px; } + md-card md-card-title md-card-title-text:only-child .md-subhead { + padding-top: 12px; } + md-card md-card-title md-card-title-media { + margin-top: -8px; } + md-card md-card-title md-card-title-media .md-media-sm { + height: 80px; + width: 80px; } + md-card md-card-title md-card-title-media .md-media-md { + height: 112px; + width: 112px; } + md-card md-card-title md-card-title-media .md-media-lg { + height: 152px; + width: 152px; } + md-card md-card-content { + display: block; + padding: 16px; } + md-card md-card-content > p:first-child { + margin-top: 0; } + md-card md-card-content > p:last-child { + margin-bottom: 0; } + md-card md-card-content .md-media-xl { + height: 240px; + width: 240px; } + md-card .md-actions, md-card md-card-actions { + margin: 8px; } + md-card .md-actions.layout-column .md-button:not(.md-icon-button), md-card md-card-actions.layout-column .md-button:not(.md-icon-button) { + margin: 2px 0; } + md-card .md-actions.layout-column .md-button:not(.md-icon-button):first-of-type, md-card md-card-actions.layout-column .md-button:not(.md-icon-button):first-of-type { + margin-top: 0; } + md-card .md-actions.layout-column .md-button:not(.md-icon-button):last-of-type, md-card md-card-actions.layout-column .md-button:not(.md-icon-button):last-of-type { + margin-bottom: 0; } + md-card .md-actions.layout-column .md-button.md-icon-button, md-card md-card-actions.layout-column .md-button.md-icon-button { + margin-top: 6px; + margin-bottom: 6px; } + md-card .md-actions md-card-icon-actions, md-card md-card-actions md-card-icon-actions { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button), md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button) { + margin: 0 4px; } + md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type, md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type { + margin-left: 0; } + [dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type, [dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type { + margin-left: auto; + margin-right: 0; } + md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type, md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type { + margin-right: 0; } + [dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type, [dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type { + margin-right: auto; + margin-left: 0; } + md-card .md-actions:not(.layout-column) .md-button.md-icon-button, md-card md-card-actions:not(.layout-column) .md-button.md-icon-button { + margin-left: 6px; + margin-right: 6px; } + md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type, md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type { + margin-left: 12px; } + [dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type, [dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type { + margin-left: auto; + margin-right: 12px; } + md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type, md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type { + margin-right: 12px; } + [dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type, [dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type { + margin-right: auto; + margin-left: 12px; } + md-card .md-actions:not(.layout-column) .md-button + md-card-icon-actions, md-card md-card-actions:not(.layout-column) .md-button + md-card-icon-actions { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-card md-card-footer { + margin-top: auto; + padding: 16px; } + +@media screen and (-ms-high-contrast: active) { + md-card { + border: 1px solid #fff; } } + +.md-image-no-fill > img { + width: auto; + height: auto; } + +.md-contact-chips .md-chips md-chip { + padding: 0 25px 0 0; } + [dir=rtl] .md-contact-chips .md-chips md-chip { + padding: 0 0 0 25px; } + .md-contact-chips .md-chips md-chip .md-contact-avatar { + float: left; } + [dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-avatar { + float: right; } + .md-contact-chips .md-chips md-chip .md-contact-avatar img { + height: 32px; + border-radius: 16px; } + .md-contact-chips .md-chips md-chip .md-contact-name { + display: inline-block; + height: 32px; + margin-left: 8px; } + [dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-name { + margin-left: auto; + margin-right: 8px; } + +.md-contact-suggestion { + height: 56px; } + .md-contact-suggestion img { + height: 40px; + border-radius: 20px; + margin-top: 8px; } + .md-contact-suggestion .md-contact-name { + margin-left: 8px; + width: 120px; } + [dir=rtl] .md-contact-suggestion .md-contact-name { + margin-left: auto; + margin-right: 8px; } + .md-contact-suggestion .md-contact-name, .md-contact-suggestion .md-contact-email { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; } + +.md-contact-chips-suggestions li { + height: 100%; } + +.md-chips { + display: block; + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 16px; + padding: 0 0 8px 3px; + vertical-align: middle; } + .md-chips:after { + content: ''; + display: table; + clear: both; } + [dir=rtl] .md-chips { + padding: 0 3px 8px 0; } + .md-chips.md-readonly .md-chip-input-container { + min-height: 32px; } + .md-chips:not(.md-readonly) { + cursor: text; } + .md-chips.md-removable md-chip { + padding-right: 22px; } + [dir=rtl] .md-chips.md-removable md-chip { + padding-right: 0; + padding-left: 22px; } + .md-chips.md-removable md-chip .md-chip-content { + padding-right: 4px; } + [dir=rtl] .md-chips.md-removable md-chip .md-chip-content { + padding-right: 0; + padding-left: 4px; } + .md-chips md-chip { + cursor: default; + border-radius: 16px; + display: block; + height: 32px; + line-height: 32px; + margin: 8px 8px 0 0; + padding: 0 12px 0 12px; + float: left; + box-sizing: border-box; + max-width: 100%; + position: relative; } + [dir=rtl] .md-chips md-chip { + margin: 8px 0 0 8px; } + [dir=rtl] .md-chips md-chip { + float: right; } + .md-chips md-chip .md-chip-content { + display: block; + float: left; + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; } + [dir=rtl] .md-chips md-chip .md-chip-content { + float: right; } + .md-chips md-chip .md-chip-content:focus { + outline: none; } + .md-chips md-chip._md-chip-content-edit-is-enabled { + -webkit-user-select: none; + /* webkit (safari, chrome) browsers */ + -moz-user-select: none; + /* mozilla browsers */ + -khtml-user-select: none; + /* webkit (konqueror) browsers */ + -ms-user-select: none; + /* IE10+ */ } + .md-chips md-chip .md-chip-remove-container { + position: absolute; + right: 0; + line-height: 22px; } + [dir=rtl] .md-chips md-chip .md-chip-remove-container { + right: auto; + left: 0; } + .md-chips md-chip .md-chip-remove { + text-align: center; + width: 32px; + height: 32px; + min-width: 0; + padding: 0; + background: transparent; + border: none; + box-shadow: none; + margin: 0; + position: relative; } + .md-chips md-chip .md-chip-remove md-icon { + height: 18px; + width: 18px; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate3d(-50%, -50%, 0); + transform: translate3d(-50%, -50%, 0); } + .md-chips .md-chip-input-container { + display: block; + line-height: 32px; + margin: 8px 8px 0 0; + padding: 0; + float: left; } + [dir=rtl] .md-chips .md-chip-input-container { + margin: 8px 0 0 8px; } + [dir=rtl] .md-chips .md-chip-input-container { + float: right; } + .md-chips .md-chip-input-container input:not([type]), .md-chips .md-chip-input-container input[type="email"], .md-chips .md-chip-input-container input[type="number"], .md-chips .md-chip-input-container input[type="tel"], .md-chips .md-chip-input-container input[type="url"], .md-chips .md-chip-input-container input[type="text"] { + border: 0; + height: 32px; + line-height: 32px; + padding: 0; } + .md-chips .md-chip-input-container input:not([type]):focus, .md-chips .md-chip-input-container input[type="email"]:focus, .md-chips .md-chip-input-container input[type="number"]:focus, .md-chips .md-chip-input-container input[type="tel"]:focus, .md-chips .md-chip-input-container input[type="url"]:focus, .md-chips .md-chip-input-container input[type="text"]:focus { + outline: none; } + .md-chips .md-chip-input-container md-autocomplete, .md-chips .md-chip-input-container md-autocomplete-wrap { + background: transparent; + height: 32px; } + .md-chips .md-chip-input-container md-autocomplete md-autocomplete-wrap { + box-shadow: none; } + .md-chips .md-chip-input-container md-autocomplete input { + position: relative; } + .md-chips .md-chip-input-container input { + border: 0; + height: 32px; + line-height: 32px; + padding: 0; } + .md-chips .md-chip-input-container input:focus { + outline: none; } + .md-chips .md-chip-input-container md-autocomplete, .md-chips .md-chip-input-container md-autocomplete-wrap { + height: 32px; } + .md-chips .md-chip-input-container md-autocomplete { + box-shadow: none; } + .md-chips .md-chip-input-container md-autocomplete input { + position: relative; } + .md-chips .md-chip-input-container:not(:first-child) { + margin: 8px 8px 0 0; } + [dir=rtl] .md-chips .md-chip-input-container:not(:first-child) { + margin: 8px 0 0 8px; } + .md-chips .md-chip-input-container input { + background: transparent; + border-width: 0; } + .md-chips md-autocomplete button { + display: none; } + +@media screen and (-ms-high-contrast: active) { + .md-chip-input-container, + md-chip { + border: 1px solid #fff; } + .md-chip-input-container md-autocomplete { + border: none; } } + +.md-inline-form md-checkbox { + margin: 19px 0 18px; } + +md-checkbox { + box-sizing: border-box; + display: inline-block; + margin-bottom: 16px; + white-space: nowrap; + cursor: pointer; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + min-width: 20px; + min-height: 20px; + margin-left: 0; + margin-right: 16px; } + [dir=rtl] md-checkbox { + margin-left: 16px; } + [dir=rtl] md-checkbox { + margin-right: 0; } + md-checkbox:last-of-type { + margin-left: 0; + margin-right: 0; } + md-checkbox.md-focused:not([disabled]) .md-container:before { + left: -8px; + top: -8px; + right: -8px; + bottom: -8px; } + md-checkbox.md-focused:not([disabled]):not(.md-checked) .md-container:before { + background-color: rgba(0, 0, 0, 0.12); } + md-checkbox.md-align-top-left > div.md-container { + top: 12px; } + md-checkbox .md-container { + position: absolute; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + box-sizing: border-box; + display: inline-block; + width: 20px; + height: 20px; + left: 0; + right: auto; } + [dir=rtl] md-checkbox .md-container { + left: auto; } + [dir=rtl] md-checkbox .md-container { + right: 0; } + md-checkbox .md-container:before { + box-sizing: border-box; + background-color: transparent; + border-radius: 50%; + content: ''; + position: absolute; + display: block; + height: auto; + left: 0; + top: 0; + right: 0; + bottom: 0; + -webkit-transition: all 0.5s; + transition: all 0.5s; + width: auto; } + md-checkbox .md-container:after { + box-sizing: border-box; + content: ''; + position: absolute; + top: -10px; + right: -10px; + bottom: -10px; + left: -10px; } + md-checkbox .md-container .md-ripple-container { + position: absolute; + display: block; + width: auto; + height: auto; + left: -15px; + top: -15px; + right: -15px; + bottom: -15px; } + md-checkbox .md-icon { + box-sizing: border-box; + -webkit-transition: 240ms; + transition: 240ms; + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 20px; + border-width: 2px; + border-style: solid; + border-radius: 2px; } + md-checkbox.md-checked .md-icon { + border-color: transparent; } + md-checkbox.md-checked .md-icon:after { + box-sizing: border-box; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + position: absolute; + left: 4.66667px; + top: 0.22222px; + display: table; + width: 6.66667px; + height: 13.33333px; + border-width: 2px; + border-style: solid; + border-top: 0; + border-left: 0; + content: ''; } + md-checkbox[disabled] { + cursor: default; } + md-checkbox.md-indeterminate .md-icon:after { + box-sizing: border-box; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + display: table; + width: 12px; + height: 2px; + border-width: 2px; + border-style: solid; + border-top: 0; + border-left: 0; + content: ''; } + md-checkbox .md-label { + box-sizing: border-box; + position: relative; + display: inline-block; + vertical-align: middle; + white-space: normal; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + margin-left: 30px; + margin-right: 0; } + [dir=rtl] md-checkbox .md-label { + margin-left: 0; } + [dir=rtl] md-checkbox .md-label { + margin-right: 30px; } + +md-content { + display: block; + position: relative; + overflow: auto; + -webkit-overflow-scrolling: touch; } + md-content[md-scroll-y] { + overflow-y: auto; + overflow-x: hidden; } + md-content[md-scroll-x] { + overflow-x: auto; + overflow-y: hidden; } + @media print { + md-content { + overflow: visible !important; } } + +/** Styles for mdCalendar. */ +md-calendar { + font-size: 13px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +.md-calendar-scroll-mask { + display: inline-block; + overflow: hidden; + height: 308px; } + .md-calendar-scroll-mask .md-virtual-repeat-scroller { + overflow-y: scroll; + -webkit-overflow-scrolling: touch; } + .md-calendar-scroll-mask .md-virtual-repeat-scroller::-webkit-scrollbar { + display: none; } + .md-calendar-scroll-mask .md-virtual-repeat-offsetter { + width: 100%; } + +.md-calendar-scroll-container { + box-shadow: inset -3px 3px 6px rgba(0, 0, 0, 0.2); + display: inline-block; + height: 308px; + width: 346px; } + +.md-calendar-date { + height: 44px; + width: 44px; + text-align: center; + padding: 0; + border: none; + box-sizing: content-box; } + .md-calendar-date:first-child { + padding-left: 16px; } + [dir=rtl] .md-calendar-date:first-child { + padding-left: 0; + padding-right: 16px; } + .md-calendar-date:last-child { + padding-right: 16px; } + [dir=rtl] .md-calendar-date:last-child { + padding-right: 0; + padding-left: 16px; } + .md-calendar-date.md-calendar-date-disabled { + cursor: default; } + +.md-calendar-date-selection-indicator { + -webkit-transition: background-color, color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: background-color, color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + border-radius: 50%; + display: inline-block; + width: 40px; + height: 40px; + line-height: 40px; } + .md-calendar-date:not(.md-disabled) .md-calendar-date-selection-indicator { + cursor: pointer; } + +.md-calendar-month-label { + height: 44px; + font-size: 14px; + font-weight: 500; + padding: 0 0 0 24px; } + [dir=rtl] .md-calendar-month-label { + padding: 0 24px 0 0; } + md-calendar-month .md-calendar-month-label:not(.md-calendar-month-label-disabled) { + cursor: pointer; } + .md-calendar-month-label md-icon { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + [dir=rtl] .md-calendar-month-label md-icon { + -webkit-transform: none; + transform: none; } + .md-calendar-month-label span { + vertical-align: middle; } + +.md-calendar-day-header { + table-layout: fixed; + border-spacing: 0; + border-collapse: collapse; } + .md-calendar-day-header th { + height: 40px; + width: 44px; + text-align: center; + padding: 0; + border: none; + box-sizing: content-box; + font-weight: normal; } + .md-calendar-day-header th:first-child { + padding-left: 16px; } + [dir=rtl] .md-calendar-day-header th:first-child { + padding-left: 0; + padding-right: 16px; } + .md-calendar-day-header th:last-child { + padding-right: 16px; } + [dir=rtl] .md-calendar-day-header th:last-child { + padding-right: 0; + padding-left: 16px; } + +.md-calendar { + table-layout: fixed; + border-spacing: 0; + border-collapse: collapse; } + .md-calendar tr:last-child td { + border-bottom-width: 1px; + border-bottom-style: solid; } + .md-calendar:first-child { + border-top: 1px solid transparent; } + .md-calendar tbody, .md-calendar td, .md-calendar tr { + vertical-align: middle; + box-sizing: content-box; } + +/** Styles for mdDatepicker. */ +md-datepicker { + white-space: nowrap; + overflow: hidden; + vertical-align: middle; } + +.md-inline-form md-datepicker { + margin-top: 12px; } + +.md-datepicker-button { + display: inline-block; + box-sizing: border-box; + background: none; + vertical-align: middle; + position: relative; } + .md-datepicker-button:before { + top: 0; + left: 0; + bottom: 0; + right: 0; + position: absolute; + content: ''; + speak: none; } + +.md-datepicker-input { + font-size: 14px; + box-sizing: border-box; + border: none; + box-shadow: none; + outline: none; + background: transparent; + min-width: 120px; + max-width: 328px; + padding: 0 0 5px; } + .md-datepicker-input::-ms-clear { + display: none; } + +._md-datepicker-floating-label > md-datepicker { + overflow: visible; } + ._md-datepicker-floating-label > md-datepicker .md-datepicker-input-container { + border: none; } + ._md-datepicker-floating-label > md-datepicker .md-datepicker-button { + float: left; + margin-top: -12px; + top: 9.5px; } + [dir=rtl] ._md-datepicker-floating-label > md-datepicker .md-datepicker-button { + float: right; } + +._md-datepicker-floating-label .md-input { + float: none; } + +._md-datepicker-floating-label._md-datepicker-has-calendar-icon > label:not(.md-no-float):not(.md-container-ignore) { + right: 18px; + left: auto; + width: calc(100% - 84px); } + [dir=rtl] ._md-datepicker-floating-label._md-datepicker-has-calendar-icon > label:not(.md-no-float):not(.md-container-ignore) { + right: auto; } + [dir=rtl] ._md-datepicker-floating-label._md-datepicker-has-calendar-icon > label:not(.md-no-float):not(.md-container-ignore) { + left: 18px; } + +._md-datepicker-floating-label._md-datepicker-has-calendar-icon .md-input-message-animation { + margin-left: 64px; } + [dir=rtl] ._md-datepicker-floating-label._md-datepicker-has-calendar-icon .md-input-message-animation { + margin-left: auto; + margin-right: 64px; } + +._md-datepicker-has-triangle-icon { + padding-right: 18px; + margin-right: -18px; } + [dir=rtl] ._md-datepicker-has-triangle-icon { + padding-right: 0; + padding-left: 18px; } + [dir=rtl] ._md-datepicker-has-triangle-icon { + margin-right: auto; + margin-left: -18px; } + +.md-datepicker-input-container { + position: relative; + border-bottom-width: 1px; + border-bottom-style: solid; + display: inline-block; + width: auto; } + .md-icon-button + .md-datepicker-input-container { + margin-left: 12px; } + [dir=rtl] .md-icon-button + .md-datepicker-input-container { + margin-left: auto; + margin-right: 12px; } + .md-datepicker-input-container.md-datepicker-focused { + border-bottom-width: 2px; } + +.md-datepicker-is-showing .md-scroll-mask { + z-index: 99; } + +.md-datepicker-calendar-pane { + position: absolute; + top: 0; + left: -100%; + z-index: 100; + border-width: 1px; + border-style: solid; + background: transparent; + -webkit-transform: scale(0); + transform: scale(0); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: -webkit-transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: -webkit-transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); } + .md-datepicker-calendar-pane.md-pane-open { + -webkit-transform: scale(1); + transform: scale(1); } + +.md-datepicker-input-mask { + height: 40px; + width: 340px; + position: relative; + overflow: hidden; + background: transparent; + pointer-events: none; + cursor: text; } + +.md-datepicker-calendar { + opacity: 0; + -webkit-transition: opacity 0.2s cubic-bezier(0.5, 0, 0.25, 1); + transition: opacity 0.2s cubic-bezier(0.5, 0, 0.25, 1); } + .md-pane-open .md-datepicker-calendar { + opacity: 1; } + .md-datepicker-calendar md-calendar:focus { + outline: none; } + +.md-datepicker-expand-triangle { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid; } + +.md-datepicker-triangle-button { + position: absolute; + right: 0; + bottom: -2.5px; + -webkit-transform: translateX(45%); + transform: translateX(45%); } + [dir=rtl] .md-datepicker-triangle-button { + right: auto; + left: 0; } + [dir=rtl] .md-datepicker-triangle-button { + -webkit-transform: translateX(-45%); + transform: translateX(-45%); } + +.md-datepicker-triangle-button.md-button.md-icon-button { + height: 36px; + width: 36px; + position: absolute; + padding: 8px; } + +md-datepicker[disabled] .md-datepicker-input-container { + border-bottom-color: transparent; } + +md-datepicker[disabled] .md-datepicker-triangle-button { + display: none; } + +.md-datepicker-open { + overflow: hidden; } + .md-datepicker-open .md-datepicker-input-container, + .md-datepicker-open input.md-input { + border-bottom-color: transparent; } + .md-datepicker-open .md-datepicker-triangle-button, + .md-datepicker-open.md-input-has-value > label, + .md-datepicker-open.md-input-has-placeholder > label { + display: none; } + +.md-datepicker-pos-adjusted .md-datepicker-input-mask { + display: none; } + +.md-datepicker-calendar-pane .md-calendar { + -webkit-transform: translateY(-85px); + transform: translateY(-85px); + -webkit-transition: -webkit-transform 0.65s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: -webkit-transform 0.65s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.65s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.65s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.65s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transition-delay: 0.125s; + transition-delay: 0.125s; } + +.md-datepicker-calendar-pane.md-pane-open .md-calendar { + -webkit-transform: translateY(0); + transform: translateY(0); } + +.md-dialog-is-showing { + max-height: 100%; } + +.md-dialog-container { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 80; + overflow: hidden; } + +md-dialog { + opacity: 0; + min-width: 240px; + max-width: 80%; + max-height: 80%; + position: relative; + overflow: auto; + box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 13px 19px 2px rgba(0, 0, 0, 0.14), 0px 5px 24px 4px rgba(0, 0, 0, 0.12); + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; } + md-dialog.md-transition-in { + opacity: 1; + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transform: translate(0, 0) scale(1); + transform: translate(0, 0) scale(1); } + md-dialog.md-transition-out { + opacity: 0; + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transform: translate(0, 100%) scale(0.2); + transform: translate(0, 100%) scale(0.2); } + md-dialog > form { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + overflow: auto; } + md-dialog .md-dialog-content { + padding: 24px; } + md-dialog md-dialog-content { + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + overflow: auto; + -webkit-overflow-scrolling: touch; } + md-dialog md-dialog-content:not([layout=row]) > *:first-child:not(.md-subheader) { + margin-top: 0; } + md-dialog md-dialog-content:focus { + outline: none; } + md-dialog md-dialog-content .md-subheader { + margin: 0; } + md-dialog md-dialog-content .md-dialog-content-body { + width: 100%; } + md-dialog md-dialog-content .md-prompt-input-container { + width: 100%; + box-sizing: border-box; } + md-dialog .md-actions, md-dialog md-dialog-actions { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; + box-sizing: border-box; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + margin-bottom: 0; + padding-right: 8px; + padding-left: 16px; + min-height: 52px; + overflow: hidden; } + [dir=rtl] md-dialog .md-actions, [dir=rtl] md-dialog md-dialog-actions { + padding-right: 16px; } + [dir=rtl] md-dialog .md-actions, [dir=rtl] md-dialog md-dialog-actions { + padding-left: 8px; } + md-dialog .md-actions .md-button, md-dialog md-dialog-actions .md-button { + margin-bottom: 8px; + margin-left: 8px; + margin-right: 0; + margin-top: 8px; } + [dir=rtl] md-dialog .md-actions .md-button, [dir=rtl] md-dialog md-dialog-actions .md-button { + margin-left: 0; } + [dir=rtl] md-dialog .md-actions .md-button, [dir=rtl] md-dialog md-dialog-actions .md-button { + margin-right: 8px; } + md-dialog.md-content-overflow .md-actions, md-dialog.md-content-overflow md-dialog-actions { + border-top-width: 1px; + border-top-style: solid; } + +@media screen and (-ms-high-contrast: active) { + md-dialog { + border: 1px solid #fff; } } + +@media (max-width: 959px) { + md-dialog.md-dialog-fullscreen { + min-height: 100%; + min-width: 100%; + border-radius: 0; } } + +md-divider { + display: block; + border-top-width: 1px; + border-top-style: solid; + margin: 0; } + md-divider[md-inset] { + margin-left: 80px; } + [dir=rtl] md-divider[md-inset] { + margin-left: auto; + margin-right: 80px; } + +.layout-row > md-divider, +.layout-xs-row > md-divider, .layout-gt-xs-row > md-divider, +.layout-sm-row > md-divider, .layout-gt-sm-row > md-divider, +.layout-md-row > md-divider, .layout-gt-md-row > md-divider, +.layout-lg-row > md-divider, .layout-gt-lg-row > md-divider, +.layout-xl-row > md-divider { + border-top-width: 0; + border-right-width: 1px; + border-right-style: solid; } + +md-fab-speed-dial { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + z-index: 20; + /* + * Hide some graphics glitches if switching animation types + */ + /* + * Handle the animations + */ } + md-fab-speed-dial.md-fab-bottom-right { + top: auto; + right: 20px; + bottom: 20px; + left: auto; + position: absolute; } + md-fab-speed-dial.md-fab-bottom-left { + top: auto; + right: auto; + bottom: 20px; + left: 20px; + position: absolute; } + md-fab-speed-dial.md-fab-top-right { + top: 20px; + right: 20px; + bottom: auto; + left: auto; + position: absolute; } + md-fab-speed-dial.md-fab-top-left { + top: 20px; + right: auto; + bottom: auto; + left: 20px; + position: absolute; } + md-fab-speed-dial:not(.md-hover-full) { + pointer-events: none; } + md-fab-speed-dial:not(.md-hover-full) md-fab-trigger, md-fab-speed-dial:not(.md-hover-full) .md-fab-action-item { + pointer-events: auto; } + md-fab-speed-dial:not(.md-hover-full).md-is-open { + pointer-events: auto; } + md-fab-speed-dial ._md-css-variables { + z-index: 20; } + md-fab-speed-dial.md-is-open .md-fab-action-item { + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; } + md-fab-speed-dial md-fab-actions { + display: -webkit-box; + display: -webkit-flex; + display: flex; + height: auto; } + md-fab-speed-dial md-fab-actions .md-fab-action-item { + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); } + md-fab-speed-dial.md-down { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; } + md-fab-speed-dial.md-down md-fab-trigger { + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; } + md-fab-speed-dial.md-down md-fab-actions { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; } + md-fab-speed-dial.md-up { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; } + md-fab-speed-dial.md-up md-fab-trigger { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; } + md-fab-speed-dial.md-up md-fab-actions { + -webkit-box-orient: vertical; + -webkit-box-direction: reverse; + -webkit-flex-direction: column-reverse; + flex-direction: column-reverse; + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; } + md-fab-speed-dial.md-left { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-fab-speed-dial.md-left md-fab-trigger { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; } + md-fab-speed-dial.md-left md-fab-actions { + -webkit-box-orient: horizontal; + -webkit-box-direction: reverse; + -webkit-flex-direction: row-reverse; + flex-direction: row-reverse; + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; } + md-fab-speed-dial.md-left md-fab-actions .md-fab-action-item { + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); } + md-fab-speed-dial.md-right { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-fab-speed-dial.md-right md-fab-trigger { + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; } + md-fab-speed-dial.md-right md-fab-actions { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; } + md-fab-speed-dial.md-right md-fab-actions .md-fab-action-item { + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); } + md-fab-speed-dial.md-fling-remove .md-fab-action-item > *, md-fab-speed-dial.md-scale-remove .md-fab-action-item > * { + visibility: hidden; } + md-fab-speed-dial.md-fling .md-fab-action-item { + opacity: 1; } + md-fab-speed-dial.md-fling.md-animations-waiting .md-fab-action-item { + opacity: 0; + -webkit-transition-duration: 0s; + transition-duration: 0s; } + md-fab-speed-dial.md-scale .md-fab-action-item { + -webkit-transform: scale(0); + transform: scale(0); + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + -webkit-transition-duration: 0.14286s; + transition-duration: 0.14286s; } + +md-fab-toolbar { + display: block; + /* + * Closed styling + */ + /* + * Hover styling + */ } + md-fab-toolbar.md-fab-bottom-right { + top: auto; + right: 20px; + bottom: 20px; + left: auto; + position: absolute; } + md-fab-toolbar.md-fab-bottom-left { + top: auto; + right: auto; + bottom: 20px; + left: 20px; + position: absolute; } + md-fab-toolbar.md-fab-top-right { + top: 20px; + right: 20px; + bottom: auto; + left: auto; + position: absolute; } + md-fab-toolbar.md-fab-top-left { + top: 20px; + right: auto; + bottom: auto; + left: 20px; + position: absolute; } + md-fab-toolbar .md-fab-toolbar-wrapper { + display: block; + position: relative; + overflow: hidden; + height: 68px; } + md-fab-toolbar md-fab-trigger { + position: absolute; + z-index: 20; } + md-fab-toolbar md-fab-trigger button { + overflow: visible !important; } + md-fab-toolbar md-fab-trigger .md-fab-toolbar-background { + display: block; + position: absolute; + z-index: 21; + opacity: 1; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); } + md-fab-toolbar md-fab-trigger md-icon { + position: relative; + z-index: 22; + opacity: 1; + -webkit-transition: all 200ms ease-in; + transition: all 200ms ease-in; } + md-fab-toolbar.md-left md-fab-trigger { + right: 0; } + [dir=rtl] md-fab-toolbar.md-left md-fab-trigger { + right: auto; + left: 0; } + md-fab-toolbar.md-left .md-toolbar-tools { + -webkit-box-orient: horizontal; + -webkit-box-direction: reverse; + -webkit-flex-direction: row-reverse; + flex-direction: row-reverse; } + md-fab-toolbar.md-left .md-toolbar-tools > .md-button:first-child { + margin-right: 0.6rem; } + [dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools > .md-button:first-child { + margin-right: auto; + margin-left: 0.6rem; } + md-fab-toolbar.md-left .md-toolbar-tools > .md-button:first-child { + margin-left: -0.8rem; } + [dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools > .md-button:first-child { + margin-left: auto; + margin-right: -0.8rem; } + md-fab-toolbar.md-left .md-toolbar-tools > .md-button:last-child { + margin-right: 8px; } + [dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools > .md-button:last-child { + margin-right: auto; + margin-left: 8px; } + md-fab-toolbar.md-right md-fab-trigger { + left: 0; } + [dir=rtl] md-fab-toolbar.md-right md-fab-trigger { + left: auto; + right: 0; } + md-fab-toolbar.md-right .md-toolbar-tools { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; } + md-fab-toolbar md-toolbar { + background-color: transparent !important; + pointer-events: none; + z-index: 23; } + md-fab-toolbar md-toolbar .md-toolbar-tools { + padding: 0 20px; + margin-top: 3px; } + md-fab-toolbar md-toolbar .md-fab-action-item { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + -webkit-transition-duration: 0.15s; + transition-duration: 0.15s; } + md-fab-toolbar.md-is-open md-fab-trigger > button { + box-shadow: none; } + md-fab-toolbar.md-is-open md-fab-trigger > button md-icon { + opacity: 0; } + md-fab-toolbar.md-is-open .md-fab-action-item { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); } + +md-icon { + margin: auto; + background-repeat: no-repeat no-repeat; + display: inline-block; + vertical-align: middle; + fill: currentColor; + height: 24px; + width: 24px; + min-height: 24px; + min-width: 24px; } + md-icon svg { + pointer-events: none; + display: block; } + md-icon[md-font-icon] { + line-height: 24px; + width: auto; } + +md-grid-list { + box-sizing: border-box; + display: block; + position: relative; } + md-grid-list md-grid-tile, + md-grid-list md-grid-tile > figure, + md-grid-list md-grid-tile-header, + md-grid-list md-grid-tile-footer { + box-sizing: border-box; } + md-grid-list md-grid-tile { + display: block; + position: absolute; } + md-grid-list md-grid-tile figure { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + height: 100%; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 0; + margin: 0; } + md-grid-list md-grid-tile md-grid-tile-header, + md-grid-list md-grid-tile md-grid-tile-footer { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, 0.18); + overflow: hidden; + position: absolute; + left: 0; + right: 0; } + md-grid-list md-grid-tile md-grid-tile-header h3, + md-grid-list md-grid-tile md-grid-tile-header h4, + md-grid-list md-grid-tile md-grid-tile-footer h3, + md-grid-list md-grid-tile md-grid-tile-footer h4 { + font-weight: 400; + margin: 0 0 0 16px; } + md-grid-list md-grid-tile md-grid-tile-header h3, + md-grid-list md-grid-tile md-grid-tile-footer h3 { + font-size: 14px; } + md-grid-list md-grid-tile md-grid-tile-header h4, + md-grid-list md-grid-tile md-grid-tile-footer h4 { + font-size: 12px; } + md-grid-list md-grid-tile md-grid-tile-header { + top: 0; } + md-grid-list md-grid-tile md-grid-tile-footer { + bottom: 0; } + +@media screen and (-ms-high-contrast: active) { + md-grid-tile { + border: 1px solid #fff; } + md-grid-tile-footer { + border-top: 1px solid #fff; } } + +md-input-container { + display: inline-block; + position: relative; + padding: 2px; + margin: 18px 0; + vertical-align: middle; + /* + * The .md-input class is added to the input/textarea + */ } + md-input-container:after { + content: ''; + display: table; + clear: both; } + md-input-container.md-block { + display: block; } + md-input-container .md-errors-spacer { + float: right; + min-height: 24px; + min-width: 1px; } + [dir=rtl] md-input-container .md-errors-spacer { + float: left; } + md-input-container > md-icon { + position: absolute; + top: 8px; + left: 2px; + right: auto; } + [dir=rtl] md-input-container > md-icon { + left: auto; } + [dir=rtl] md-input-container > md-icon { + right: 2px; } + md-input-container textarea, + md-input-container input[type="text"], + md-input-container input[type="password"], + md-input-container input[type="datetime"], + md-input-container input[type="datetime-local"], + md-input-container input[type="date"], + md-input-container input[type="month"], + md-input-container input[type="time"], + md-input-container input[type="week"], + md-input-container input[type="number"], + md-input-container input[type="email"], + md-input-container input[type="url"], + md-input-container input[type="search"], + md-input-container input[type="tel"], + md-input-container input[type="color"] { + /* remove default appearance from all input/textarea */ + -moz-appearance: none; + -webkit-appearance: none; } + md-input-container input[type="date"], + md-input-container input[type="datetime-local"], + md-input-container input[type="month"], + md-input-container input[type="time"], + md-input-container input[type="week"] { + min-height: 26px; } + md-input-container textarea { + resize: none; + overflow: hidden; } + md-input-container textarea.md-input { + min-height: 26px; + -ms-flex-preferred-size: auto; } + md-input-container textarea[md-no-autogrow] { + height: auto; + overflow: auto; } + md-input-container label:not(.md-container-ignore) { + position: absolute; + bottom: 100%; + left: 0; + right: auto; } + [dir=rtl] md-input-container label:not(.md-container-ignore) { + left: auto; } + [dir=rtl] md-input-container label:not(.md-container-ignore) { + right: 0; } + md-input-container label:not(.md-container-ignore).md-required:after { + content: ' *'; + font-size: 13px; + vertical-align: top; } + md-input-container label:not(.md-no-float):not(.md-container-ignore), + md-input-container .md-placeholder { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + order: 1; + pointer-events: none; + -webkit-font-smoothing: antialiased; + padding-left: 3px; + padding-right: 0; + z-index: 1; + -webkit-transform: translate3d(0, 28px, 0) scale(1); + transform: translate3d(0, 28px, 0) scale(1); + -webkit-transition: -webkit-transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: -webkit-transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + max-width: 100%; + -webkit-transform-origin: left top; + transform-origin: left top; } + [dir=rtl] md-input-container label:not(.md-no-float):not(.md-container-ignore), [dir=rtl] + md-input-container .md-placeholder { + padding-left: 0; } + [dir=rtl] md-input-container label:not(.md-no-float):not(.md-container-ignore), [dir=rtl] + md-input-container .md-placeholder { + padding-right: 3px; } + [dir=rtl] md-input-container label:not(.md-no-float):not(.md-container-ignore), [dir=rtl] + md-input-container .md-placeholder { + -webkit-transform-origin: right top; + transform-origin: right top; } + md-input-container .md-placeholder { + position: absolute; + top: 0; + opacity: 0; + -webkit-transition-property: opacity, -webkit-transform; + transition-property: opacity, -webkit-transform; + transition-property: opacity, transform; + transition-property: opacity, transform, -webkit-transform; + -webkit-transform: translate3d(0, 30px, 0); + transform: translate3d(0, 30px, 0); } + md-input-container.md-input-focused .md-placeholder { + opacity: 1; + -webkit-transform: translate3d(0, 24px, 0); + transform: translate3d(0, 24px, 0); } + md-input-container.md-input-has-value .md-placeholder { + -webkit-transition: none; + transition: none; + opacity: 0; } + md-input-container:not(.md-input-has-value) input:not(:focus), + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-ampm-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-day-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-hour-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-millisecond-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-minute-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-month-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-second-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-week-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-year-field, + md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-text { + color: transparent; } + md-input-container .md-input { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; + display: block; + margin-top: 0; + background: none; + padding-top: 2px; + padding-bottom: 1px; + padding-left: 2px; + padding-right: 2px; + border-width: 0 0 1px 0; + line-height: 26px; + height: 30px; + -ms-flex-preferred-size: 26px; + border-radius: 0; + border-style: solid; + width: 100%; + box-sizing: border-box; + float: left; } + [dir=rtl] md-input-container .md-input { + float: right; } + md-input-container .md-input:focus { + outline: none; } + md-input-container .md-input:invalid { + outline: none; + box-shadow: none; } + md-input-container .md-input.md-no-flex { + -webkit-box-flex: 0 !important; + -webkit-flex: none !important; + flex: none !important; } + md-input-container .md-char-counter { + text-align: right; + padding-right: 2px; + padding-left: 0; } + [dir=rtl] md-input-container .md-char-counter { + text-align: left; } + [dir=rtl] md-input-container .md-char-counter { + padding-right: 0; } + [dir=rtl] md-input-container .md-char-counter { + padding-left: 2px; } + md-input-container .md-input-messages-animation { + position: relative; + -webkit-box-ordinal-group: 5; + -webkit-order: 4; + order: 4; + overflow: hidden; + clear: left; } + [dir=rtl] md-input-container .md-input-messages-animation { + clear: right; } + md-input-container .md-input-message-animation, md-input-container .md-char-counter { + font-size: 12px; + line-height: 14px; + overflow: hidden; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + opacity: 1; + margin-top: 0; + padding-top: 5px; } + md-input-container .md-input-message-animation:not(.md-char-counter), md-input-container .md-char-counter:not(.md-char-counter) { + padding-right: 5px; + padding-left: 0; } + [dir=rtl] md-input-container .md-input-message-animation:not(.md-char-counter), [dir=rtl] md-input-container .md-char-counter:not(.md-char-counter) { + padding-right: 0; } + [dir=rtl] md-input-container .md-input-message-animation:not(.md-char-counter), [dir=rtl] md-input-container .md-char-counter:not(.md-char-counter) { + padding-left: 5px; } + md-input-container:not(.md-input-invalid) .md-auto-hide .md-input-message-animation { + opacity: 0; + margin-top: -100px; } + md-input-container .md-input-message-animation.ng-enter-prepare { + opacity: 0; + margin-top: -100px; } + md-input-container .md-input-message-animation.ng-enter:not(.ng-enter-active) { + opacity: 0; + margin-top: -100px; } + md-input-container.md-input-focused label:not(.md-no-float), md-input-container.md-input-has-placeholder label:not(.md-no-float), md-input-container.md-input-has-value label:not(.md-no-float) { + -webkit-transform: translate3d(0, 6px, 0) scale(0.75); + transform: translate3d(0, 6px, 0) scale(0.75); + -webkit-transition: width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, -webkit-transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s; + transition: width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, -webkit-transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s; + transition: transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s; + transition: transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, -webkit-transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s; } + md-input-container.md-input-has-value label { + -webkit-transition: none; + transition: none; } + md-input-container.md-input-focused .md-input, + md-input-container .md-input.ng-invalid.ng-dirty, + md-input-container.md-input-resized .md-input { + padding-bottom: 0; + border-width: 0 0 2px 0; } + md-input-container .md-input[disabled], + [disabled] md-input-container .md-input { + background-position: bottom -1px left 0; + background-size: 4px 1px; + background-repeat: repeat-x; } + md-input-container.md-icon-float { + -webkit-transition: margin-top 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: margin-top 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } + md-input-container.md-icon-float > label { + pointer-events: none; + position: absolute; } + md-input-container.md-icon-float > md-icon { + top: 8px; + left: 2px; + right: auto; } + [dir=rtl] md-input-container.md-icon-float > md-icon { + left: auto; } + [dir=rtl] md-input-container.md-icon-float > md-icon { + right: 2px; } + md-input-container.md-icon-left > label:not(.md-no-float):not(.md-container-ignore), + md-input-container.md-icon-left > label .md-placeholder, md-input-container.md-icon-right > label:not(.md-no-float):not(.md-container-ignore), + md-input-container.md-icon-right > label .md-placeholder { + width: calc(100% - 36px - 18px); } + md-input-container.md-icon-left { + padding-left: 36px; + padding-right: 0; } + [dir=rtl] md-input-container.md-icon-left { + padding-left: 0; } + [dir=rtl] md-input-container.md-icon-left { + padding-right: 36px; } + md-input-container.md-icon-left > label { + left: 36px; + right: auto; } + [dir=rtl] md-input-container.md-icon-left > label { + left: auto; } + [dir=rtl] md-input-container.md-icon-left > label { + right: 36px; } + md-input-container.md-icon-right { + padding-left: 0; + padding-right: 36px; } + [dir=rtl] md-input-container.md-icon-right { + padding-left: 36px; } + [dir=rtl] md-input-container.md-icon-right { + padding-right: 0; } + md-input-container.md-icon-right > md-icon:last-of-type { + margin: 0; + right: 2px; + left: auto; } + [dir=rtl] md-input-container.md-icon-right > md-icon:last-of-type { + right: auto; } + [dir=rtl] md-input-container.md-icon-right > md-icon:last-of-type { + left: 2px; } + md-input-container.md-icon-left.md-icon-right { + padding-left: 36px; + padding-right: 36px; } + md-input-container.md-icon-left.md-icon-right > label:not(.md-no-float):not(.md-container-ignore), + md-input-container.md-icon-left.md-icon-right > label .md-placeholder { + width: calc(100% - (36px * 2)); } + +.md-resize-wrapper { + position: relative; } + .md-resize-wrapper:after { + content: ''; + display: table; + clear: both; } + +.md-resize-handle { + position: absolute; + bottom: -5px; + left: 0; + height: 10px; + background: transparent; + width: 100%; + cursor: ns-resize; } + +@media screen and (-ms-high-contrast: active) { + md-input-container.md-default-theme > md-icon { + fill: #fff; } } + +md-list { + display: block; + padding: 8px 0px 8px 0px; } + md-list .md-subheader { + font-size: 14px; + font-weight: 500; + letter-spacing: 0.010em; + line-height: 1.2em; } + md-list.md-dense md-list-item, + md-list.md-dense md-list-item .md-list-item-inner { + min-height: 48px; } + md-list.md-dense md-list-item::before, + md-list.md-dense md-list-item .md-list-item-inner::before { + content: ''; + min-height: 48px; + visibility: hidden; + display: inline-block; } + md-list.md-dense md-list-item md-icon:first-child, + md-list.md-dense md-list-item .md-list-item-inner md-icon:first-child { + width: 20px; + height: 20px; } + md-list.md-dense md-list-item > md-icon:first-child:not(.md-avatar-icon), + md-list.md-dense md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon) { + margin-right: 36px; } + [dir=rtl] md-list.md-dense md-list-item > md-icon:first-child:not(.md-avatar-icon), [dir=rtl] + md-list.md-dense md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon) { + margin-right: auto; + margin-left: 36px; } + md-list.md-dense md-list-item .md-avatar, md-list.md-dense md-list-item .md-avatar-icon, + md-list.md-dense md-list-item .md-list-item-inner .md-avatar, + md-list.md-dense md-list-item .md-list-item-inner .md-avatar-icon { + margin-right: 20px; } + [dir=rtl] md-list.md-dense md-list-item .md-avatar, [dir=rtl] md-list.md-dense md-list-item .md-avatar-icon, [dir=rtl] + md-list.md-dense md-list-item .md-list-item-inner .md-avatar, [dir=rtl] + md-list.md-dense md-list-item .md-list-item-inner .md-avatar-icon { + margin-right: auto; + margin-left: 20px; } + md-list.md-dense md-list-item .md-avatar, + md-list.md-dense md-list-item .md-list-item-inner .md-avatar { + -webkit-box-flex: 0; + -webkit-flex: none; + flex: none; + width: 36px; + height: 36px; } + md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset, md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text.md-offset, md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset, md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text.md-offset { + margin-left: 56px; } + [dir=rtl] md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset, [dir=rtl] md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text.md-offset, [dir=rtl] md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset, [dir=rtl] md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text.md-offset { + margin-left: auto; + margin-right: 56px; } + md-list.md-dense md-list-item.md-2-line .md-list-item-text h3, + md-list.md-dense md-list-item.md-2-line .md-list-item-text h4, + md-list.md-dense md-list-item.md-2-line .md-list-item-text p, md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text h3, + md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text h4, + md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text p, md-list.md-dense md-list-item.md-3-line .md-list-item-text h3, + md-list.md-dense md-list-item.md-3-line .md-list-item-text h4, + md-list.md-dense md-list-item.md-3-line .md-list-item-text p, md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text h3, + md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text h4, + md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text p { + line-height: 1.05; + font-size: 12px; } + md-list.md-dense md-list-item.md-2-line .md-list-item-text h3, md-list.md-dense md-list-item.md-2-line > .md-no-style .md-list-item-text h3, md-list.md-dense md-list-item.md-3-line .md-list-item-text h3, md-list.md-dense md-list-item.md-3-line > .md-no-style .md-list-item-text h3 { + font-size: 13px; } + md-list.md-dense md-list-item.md-2-line, md-list.md-dense md-list-item.md-2-line > .md-no-style { + min-height: 60px; } + md-list.md-dense md-list-item.md-2-line::before, md-list.md-dense md-list-item.md-2-line > .md-no-style::before { + content: ''; + min-height: 60px; + visibility: hidden; + display: inline-block; } + md-list.md-dense md-list-item.md-2-line > .md-avatar, md-list.md-dense md-list-item.md-2-line .md-avatar-icon, md-list.md-dense md-list-item.md-2-line > .md-no-style > .md-avatar, md-list.md-dense md-list-item.md-2-line > .md-no-style .md-avatar-icon { + margin-top: 12px; } + md-list.md-dense md-list-item.md-3-line, md-list.md-dense md-list-item.md-3-line > .md-no-style { + min-height: 76px; } + md-list.md-dense md-list-item.md-3-line::before, md-list.md-dense md-list-item.md-3-line > .md-no-style::before { + content: ''; + min-height: 76px; + visibility: hidden; + display: inline-block; } + md-list.md-dense md-list-item.md-3-line > md-icon:first-child, + md-list.md-dense md-list-item.md-3-line > .md-avatar, md-list.md-dense md-list-item.md-3-line > .md-no-style > md-icon:first-child, + md-list.md-dense md-list-item.md-3-line > .md-no-style > .md-avatar { + margin-top: 16px; } + +md-list-item { + position: relative; } + md-list-item.md-proxy-focus.md-focused .md-no-style { + -webkit-transition: background-color 0.15s linear; + transition: background-color 0.15s linear; } + md-list-item._md-button-wrap { + position: relative; } + md-list-item._md-button-wrap > div.md-button:first-child { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + padding: 0 16px; + margin: 0; + font-weight: 400; + text-align: left; + border: medium none; } + [dir=rtl] md-list-item._md-button-wrap > div.md-button:first-child { + text-align: right; } + md-list-item._md-button-wrap > div.md-button:first-child > .md-button:first-child { + position: absolute; + top: 0; + left: 0; + height: 100%; + margin: 0; + padding: 0; } + md-list-item._md-button-wrap > div.md-button:first-child .md-list-item-inner { + width: 100%; + min-height: inherit; } + md-list-item.md-no-proxy, + md-list-item .md-no-style { + position: relative; + padding: 0px 16px; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; } + md-list-item.md-no-proxy.md-button, + md-list-item .md-no-style.md-button { + font-size: inherit; + height: inherit; + text-align: left; + text-transform: none; + width: 100%; + white-space: normal; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: inherit; + flex-direction: inherit; + -webkit-box-align: inherit; + -webkit-align-items: inherit; + align-items: inherit; + border-radius: 0; + margin: 0; } + [dir=rtl] md-list-item.md-no-proxy.md-button, [dir=rtl] + md-list-item .md-no-style.md-button { + text-align: right; } + md-list-item.md-no-proxy.md-button > .md-ripple-container, + md-list-item .md-no-style.md-button > .md-ripple-container { + border-radius: 0; } + md-list-item.md-no-proxy:focus, + md-list-item .md-no-style:focus { + outline: none; } + md-list-item.md-clickable:hover { + cursor: pointer; } + md-list-item md-divider { + position: absolute; + bottom: 0; + left: 0; + width: 100%; } + [dir=rtl] md-list-item md-divider { + left: auto; + right: 0; } + md-list-item md-divider[md-inset] { + left: 72px; + width: calc(100% - 72px); + margin: 0 !important; } + [dir=rtl] md-list-item md-divider[md-inset] { + left: auto; + right: 72px; } + md-list-item, + md-list-item .md-list-item-inner { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + min-height: 48px; + height: auto; } + md-list-item::before, + md-list-item .md-list-item-inner::before { + content: ''; + min-height: 48px; + visibility: hidden; + display: inline-block; } + md-list-item > div.md-primary > md-icon:not(.md-avatar-icon), + md-list-item > div.md-secondary > md-icon:not(.md-avatar-icon), + md-list-item > md-icon:first-child:not(.md-avatar-icon), + md-list-item > md-icon.md-secondary:not(.md-avatar-icon), + md-list-item .md-list-item-inner > div.md-primary > md-icon:not(.md-avatar-icon), + md-list-item .md-list-item-inner > div.md-secondary > md-icon:not(.md-avatar-icon), + md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon), + md-list-item .md-list-item-inner > md-icon.md-secondary:not(.md-avatar-icon) { + width: 24px; + margin-top: 16px; + margin-bottom: 12px; + box-sizing: content-box; } + md-list-item > div.md-primary > md-checkbox, + md-list-item > div.md-secondary > md-checkbox, + md-list-item > md-checkbox, + md-list-item md-checkbox.md-secondary, + md-list-item .md-list-item-inner > div.md-primary > md-checkbox, + md-list-item .md-list-item-inner > div.md-secondary > md-checkbox, + md-list-item .md-list-item-inner > md-checkbox, + md-list-item .md-list-item-inner md-checkbox.md-secondary { + -webkit-align-self: center; + -ms-grid-row-align: center; + align-self: center; } + md-list-item > div.md-primary > md-checkbox .md-label, + md-list-item > div.md-secondary > md-checkbox .md-label, + md-list-item > md-checkbox .md-label, + md-list-item md-checkbox.md-secondary .md-label, + md-list-item .md-list-item-inner > div.md-primary > md-checkbox .md-label, + md-list-item .md-list-item-inner > div.md-secondary > md-checkbox .md-label, + md-list-item .md-list-item-inner > md-checkbox .md-label, + md-list-item .md-list-item-inner md-checkbox.md-secondary .md-label { + display: none; } + md-list-item > md-icon:first-child:not(.md-avatar-icon), + md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon) { + margin-right: 32px; } + [dir=rtl] md-list-item > md-icon:first-child:not(.md-avatar-icon), [dir=rtl] + md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon) { + margin-right: auto; + margin-left: 32px; } + md-list-item .md-avatar, md-list-item .md-avatar-icon, + md-list-item .md-list-item-inner .md-avatar, + md-list-item .md-list-item-inner .md-avatar-icon { + margin-top: 8px; + margin-bottom: 8px; + margin-right: 16px; + border-radius: 50%; + box-sizing: content-box; } + [dir=rtl] md-list-item .md-avatar, [dir=rtl] md-list-item .md-avatar-icon, [dir=rtl] + md-list-item .md-list-item-inner .md-avatar, [dir=rtl] + md-list-item .md-list-item-inner .md-avatar-icon { + margin-right: auto; + margin-left: 16px; } + md-list-item .md-avatar, + md-list-item .md-list-item-inner .md-avatar { + -webkit-box-flex: 0; + -webkit-flex: none; + flex: none; + width: 40px; + height: 40px; } + md-list-item .md-avatar-icon, + md-list-item .md-list-item-inner .md-avatar-icon { + padding: 8px; } + md-list-item .md-avatar-icon svg, + md-list-item .md-list-item-inner .md-avatar-icon svg { + width: 24px; + height: 24px; } + md-list-item > md-checkbox, + md-list-item .md-list-item-inner > md-checkbox { + width: 24px; + margin-left: 3px; + margin-right: 29px; + margin-top: 16px; } + [dir=rtl] md-list-item > md-checkbox, [dir=rtl] + md-list-item .md-list-item-inner > md-checkbox { + margin-left: 29px; } + [dir=rtl] md-list-item > md-checkbox, [dir=rtl] + md-list-item .md-list-item-inner > md-checkbox { + margin-right: 3px; } + md-list-item .md-secondary-container, + md-list-item .md-list-item-inner .md-secondary-container { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-flex-shrink: 0; + flex-shrink: 0; + margin: auto; + margin-right: 0; + margin-left: auto; } + [dir=rtl] md-list-item .md-secondary-container, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container { + margin-right: auto; } + [dir=rtl] md-list-item .md-secondary-container, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container { + margin-left: 0; } + md-list-item .md-secondary-container .md-button:last-of-type, md-list-item .md-secondary-container .md-icon-button:last-of-type, + md-list-item .md-list-item-inner .md-secondary-container .md-button:last-of-type, + md-list-item .md-list-item-inner .md-secondary-container .md-icon-button:last-of-type { + margin-right: 0; } + [dir=rtl] md-list-item .md-secondary-container .md-button:last-of-type, [dir=rtl] md-list-item .md-secondary-container .md-icon-button:last-of-type, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container .md-button:last-of-type, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container .md-icon-button:last-of-type { + margin-right: auto; + margin-left: 0; } + md-list-item .md-secondary-container md-checkbox, + md-list-item .md-list-item-inner .md-secondary-container md-checkbox { + margin-top: 0; + margin-bottom: 0; } + md-list-item .md-secondary-container md-checkbox:last-child, + md-list-item .md-list-item-inner .md-secondary-container md-checkbox:last-child { + width: 24px; + margin-right: 0; } + [dir=rtl] md-list-item .md-secondary-container md-checkbox:last-child, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container md-checkbox:last-child { + margin-right: auto; + margin-left: 0; } + md-list-item .md-secondary-container md-switch, + md-list-item .md-list-item-inner .md-secondary-container md-switch { + margin-top: 0; + margin-bottom: 0; + margin-right: -6px; } + [dir=rtl] md-list-item .md-secondary-container md-switch, [dir=rtl] + md-list-item .md-list-item-inner .md-secondary-container md-switch { + margin-right: auto; + margin-left: -6px; } + md-list-item > p, md-list-item > .md-list-item-inner > p, + md-list-item .md-list-item-inner > p, + md-list-item .md-list-item-inner > .md-list-item-inner > p { + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + margin: 0; } + md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style, md-list-item.md-3-line, md-list-item.md-3-line > .md-no-style { + -webkit-box-align: start; + -webkit-align-items: flex-start; + align-items: flex-start; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; } + md-list-item.md-2-line.md-long-text, md-list-item.md-2-line > .md-no-style.md-long-text, md-list-item.md-3-line.md-long-text, md-list-item.md-3-line > .md-no-style.md-long-text { + margin-top: 8px; + margin-bottom: 8px; } + md-list-item.md-2-line .md-list-item-text, md-list-item.md-2-line > .md-no-style .md-list-item-text, md-list-item.md-3-line .md-list-item-text, md-list-item.md-3-line > .md-no-style .md-list-item-text { + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + margin: auto; + text-overflow: ellipsis; + overflow: hidden; } + md-list-item.md-2-line .md-list-item-text.md-offset, md-list-item.md-2-line > .md-no-style .md-list-item-text.md-offset, md-list-item.md-3-line .md-list-item-text.md-offset, md-list-item.md-3-line > .md-no-style .md-list-item-text.md-offset { + margin-left: 56px; } + [dir=rtl] md-list-item.md-2-line .md-list-item-text.md-offset, [dir=rtl] md-list-item.md-2-line > .md-no-style .md-list-item-text.md-offset, [dir=rtl] md-list-item.md-3-line .md-list-item-text.md-offset, [dir=rtl] md-list-item.md-3-line > .md-no-style .md-list-item-text.md-offset { + margin-left: auto; + margin-right: 56px; } + md-list-item.md-2-line .md-list-item-text h3, md-list-item.md-2-line > .md-no-style .md-list-item-text h3, md-list-item.md-3-line .md-list-item-text h3, md-list-item.md-3-line > .md-no-style .md-list-item-text h3 { + font-size: 16px; + font-weight: 400; + letter-spacing: 0.010em; + margin: 0 0 0px 0; + line-height: 1.2em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } + md-list-item.md-2-line .md-list-item-text h4, md-list-item.md-2-line > .md-no-style .md-list-item-text h4, md-list-item.md-3-line .md-list-item-text h4, md-list-item.md-3-line > .md-no-style .md-list-item-text h4 { + font-size: 14px; + letter-spacing: 0.010em; + margin: 3px 0 1px 0; + font-weight: 400; + line-height: 1.2em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } + md-list-item.md-2-line .md-list-item-text p, md-list-item.md-2-line > .md-no-style .md-list-item-text p, md-list-item.md-3-line .md-list-item-text p, md-list-item.md-3-line > .md-no-style .md-list-item-text p { + font-size: 14px; + font-weight: 500; + letter-spacing: 0.010em; + margin: 0 0 0 0; + line-height: 1.6em; } + md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style { + height: auto; + min-height: 72px; } + md-list-item.md-2-line::before, md-list-item.md-2-line > .md-no-style::before { + content: ''; + min-height: 72px; + visibility: hidden; + display: inline-block; } + md-list-item.md-2-line > .md-avatar, md-list-item.md-2-line .md-avatar-icon, md-list-item.md-2-line > .md-no-style > .md-avatar, md-list-item.md-2-line > .md-no-style .md-avatar-icon { + margin-top: 12px; } + md-list-item.md-2-line > md-icon:first-child, md-list-item.md-2-line > .md-no-style > md-icon:first-child { + -webkit-align-self: flex-start; + align-self: flex-start; } + md-list-item.md-2-line .md-list-item-text, md-list-item.md-2-line > .md-no-style .md-list-item-text { + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; } + md-list-item.md-3-line, md-list-item.md-3-line > .md-no-style { + height: auto; + min-height: 88px; } + md-list-item.md-3-line::before, md-list-item.md-3-line > .md-no-style::before { + content: ''; + min-height: 88px; + visibility: hidden; + display: inline-block; } + md-list-item.md-3-line > md-icon:first-child, + md-list-item.md-3-line > .md-avatar, md-list-item.md-3-line > .md-no-style > md-icon:first-child, + md-list-item.md-3-line > .md-no-style > .md-avatar { + margin-top: 16px; } + +.md-open-menu-container { + position: fixed; + left: 0; + top: 0; + z-index: 100; + opacity: 0; + border-radius: 2px; + max-height: calc(100vh - 10px); + overflow: auto; } + .md-open-menu-container md-menu-divider { + margin-top: 4px; + margin-bottom: 4px; + height: 1px; + min-height: 1px; + max-height: 1px; + width: 100%; } + .md-open-menu-container md-menu-content > * { + opacity: 0; } + .md-open-menu-container:not(.md-clickable) { + pointer-events: none; } + .md-open-menu-container.md-active { + opacity: 1; + -webkit-transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + -webkit-transition-duration: 200ms; + transition-duration: 200ms; } + .md-open-menu-container.md-active > md-menu-content > * { + opacity: 1; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + -webkit-transition-duration: 200ms; + transition-duration: 200ms; + -webkit-transition-delay: 100ms; + transition-delay: 100ms; } + .md-open-menu-container.md-leave { + opacity: 0; + -webkit-transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + -webkit-transition-duration: 250ms; + transition-duration: 250ms; } + +md-menu-content { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + padding: 8px 0; + max-height: 304px; + overflow-y: auto; } + md-menu-content.md-dense { + max-height: 208px; } + md-menu-content.md-dense md-menu-item { + height: 32px; + min-height: 0px; } + +md-menu-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + min-height: 48px; + height: 48px; + -webkit-align-content: center; + align-content: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + /* + * We cannot use flex on '; + } + } + + function postLink(scope, element, attr) { + $mdTheming(element); + $mdButtonInkRipple.attach(scope, element); + + // Use async expect to support possible bindings in the button label + $mdAria.expectWithoutText(element, 'aria-label'); + + // For anchor elements, we have to set tabindex manually when the + // element is disabled + if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) { + scope.$watch(attr.ngDisabled, function(isDisabled) { + element.attr('tabindex', isDisabled ? -1 : 0); + }); + } + + // disabling click event when disabled is true + element.on('click', function(e){ + if (attr.disabled === true) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + }); + + if (!element.hasClass('md-no-focus')) { + + element.on('focus', function() { + + // Only show the focus effect when being focused through keyboard interaction or programmatically + if (!$mdInteraction.isUserInvoked() || $mdInteraction.getLastInteractionType() === 'keyboard') { + element.addClass('md-focused'); + } + + }); + + element.on('blur', function() { + element.removeClass('md-focused'); + }); + } + + } + +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.card + * + * @description + * Card components. + */ +mdCardDirective.$inject = ["$mdTheming"]; +angular.module('material.components.card', [ + 'material.core' + ]) + .directive('mdCard', mdCardDirective); + + +/** + * @ngdoc directive + * @name mdCard + * @module material.components.card + * + * @restrict E + * + * @description + * The `` directive is a container element used within `` containers. + * + * An image included as a direct descendant will fill the card's width. If you want to avoid this, + * you can add the `md-image-no-fill` class to the parent element. The `` + * container will wrap text content and provide padding. An `` element can be + * optionally included to put content flush against the bottom edge of the card. + * + * Action buttons can be included in an `` element, similar to ``. + * You can then position buttons using layout attributes. + * + * Card is built with: + * * `` - Header for the card, holds avatar, text and squared image + * - `` - Card avatar + * - `md-user-avatar` - Class for user image + * - `` + * - `` - Contains elements for the card description + * - `md-title` - Class for the card title + * - `md-subhead` - Class for the card sub header + * * `` - Image for the card + * * `` - Card content title + * - `` + * - `md-headline` - Class for the card content title + * - `md-subhead` - Class for the card content sub header + * - `` - Squared image within the title + * - `md-media-sm` - Class for small image + * - `md-media-md` - Class for medium image + * - `md-media-lg` - Class for large image + * - `md-media-xl` - Class for extra large image + * * `` - Card content + * * `` - Card actions + * - `` - Icon actions + * + * Cards have constant width and variable heights; where the maximum height is limited to what can + * fit within a single view on a platform, but it can temporarily expand as needed. + * + * @usage + * ### Card with optional footer + * + * + * image caption + * + *

Card headline

+ *

Card content

+ *
+ * + * Card footer + * + *
+ *
+ * + * ### Card with actions + * + * + * image caption + * + *

Card headline

+ *

Card content

+ *
+ * + * Action 1 + * Action 2 + * + *
+ *
+ * + * ### Card with header, image, title actions and content + * + * + * + * + * + * + * + * Title + * Sub header + * + * + * image caption + * + * + * Card headline + * Card subheader + * + * + * + * Action 1 + * Action 2 + * + * + * + * + * + * + * + *

+ * Card content + *

+ *
+ *
+ *
+ */ +function mdCardDirective($mdTheming) { + return { + restrict: 'E', + link: function ($scope, $element, attr) { + $element.addClass('_md'); // private md component indicator for styling + $mdTheming($element); + } + }; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.chips + */ +/* + * @see js folder for chips implementation + */ +angular.module('material.components.chips', [ + 'material.core', + 'material.components.autocomplete' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.checkbox + * @description Checkbox module! + */ +MdCheckboxDirective.$inject = ["inputDirective", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$mdInteraction"]; +angular + .module('material.components.checkbox', ['material.core']) + .directive('mdCheckbox', MdCheckboxDirective); + +/** + * @ngdoc directive + * @name mdCheckbox + * @module material.components.checkbox + * @restrict E + * + * @description + * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D). + * + * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-color-schemes) + * the checkbox is in the accent color by default. The primary color palette may be used with + * the `md-primary` class. + * + * @param {string} ng-model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {expression=} ng-true-value The value to which the expression should be set when selected. + * @param {expression=} ng-false-value The value to which the expression should be set when not selected. + * @param {string=} ng-change AngularJS expression to be executed when input changes due to user interaction with the input element. + * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects + * @param {string=} aria-label Adds label to checkbox for accessibility. + * Defaults to checkbox's text. If no default text is found, a warning will be logged. + * @param {expression=} md-indeterminate This determines when the checkbox should be rendered as 'indeterminate'. + * If a truthy expression or no value is passed in the checkbox renders in the md-indeterminate state. + * If falsy expression is passed in it just looks like a normal unchecked checkbox. + * The indeterminate, checked, and unchecked states are mutually exclusive. A box cannot be in any two states at the same time. + * Adding the 'md-indeterminate' attribute overrides any checked/unchecked rendering logic. + * When using the 'md-indeterminate' attribute use 'ng-checked' to define rendering logic instead of using 'ng-model'. + * @param {expression=} ng-checked If this expression evaluates as truthy, the 'md-checked' css class is added to the checkbox and it + * will appear checked. + * + * @usage + * + * + * Finished ? + * + * + * + * No Ink Effects + * + * + * + * Disabled + * + * + * + * + */ +function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $mdUtil, $mdInteraction) { + inputDirective = inputDirective[0]; + + return { + restrict: 'E', + transclude: true, + require: ['^?mdInputContainer', '?ngModel', '?^form'], + priority: $mdConstant.BEFORE_NG_ARIA, + template: + '
' + + '
' + + '
' + + '
', + compile: compile + }; + + // ********************************************************** + // Private Methods + // ********************************************************** + + function compile (tElement, tAttrs) { + tAttrs.$set('tabindex', tAttrs.tabindex || '0'); + tAttrs.$set('type', 'checkbox'); + tAttrs.$set('role', tAttrs.type); + + return { + pre: function(scope, element) { + // Attach a click handler during preLink, in order to immediately stop propagation + // (especially for ng-click) when the checkbox is disabled. + element.on('click', function(e) { + if (this.hasAttribute('disabled')) { + e.stopImmediatePropagation(); + } + }); + }, + post: postLink + }; + + function postLink(scope, element, attr, ctrls) { + var isIndeterminate; + var containerCtrl = ctrls[0]; + var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel(); + var formCtrl = ctrls[2]; + + if (containerCtrl) { + var isErrorGetter = containerCtrl.isErrorGetter || function() { + return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted)); + }; + + containerCtrl.input = element; + + scope.$watch(isErrorGetter, containerCtrl.setInvalid); + } + + $mdTheming(element); + + // Redirect focus events to the root element, because IE11 is always focusing the container element instead + // of the md-checkbox element. This causes issues when using ngModelOptions: `updateOnBlur` + element.children().on('focus', function() { + element.focus(); + }); + + if ($mdUtil.parseAttributeBoolean(attr.mdIndeterminate)) { + setIndeterminateState(); + scope.$watch(attr.mdIndeterminate, setIndeterminateState); + } + + if (attr.ngChecked) { + scope.$watch(scope.$eval.bind(scope, attr.ngChecked), function(value) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + }); + } + + $$watchExpr('ngDisabled', 'tabindex', { + true: '-1', + false: attr.tabindex + }); + + $mdAria.expectWithText(element, 'aria-label'); + + // Reuse the original input[type=checkbox] directive from AngularJS core. + // This is a bit hacky as we need our own event listener and own render + // function. + inputDirective.link.pre(scope, { + on: angular.noop, + 0: {} + }, attr, [ngModelCtrl]); + + element.on('click', listener) + .on('keypress', keypressHandler) + .on('focus', function() { + if ($mdInteraction.getLastInteractionType() === 'keyboard') { + element.addClass('md-focused'); + } + }) + .on('blur', function() { + element.removeClass('md-focused'); + }); + + ngModelCtrl.$render = render; + + function $$watchExpr(expr, htmlAttr, valueOpts) { + if (attr[expr]) { + scope.$watch(attr[expr], function(val) { + if (valueOpts[val]) { + element.attr(htmlAttr, valueOpts[val]); + } + }); + } + } + + function keypressHandler(ev) { + var keyCode = ev.which || ev.keyCode; + if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) { + ev.preventDefault(); + element.addClass('md-focused'); + listener(ev); + } + } + + function listener(ev) { + // skipToggle boolean is used by the switch directive to prevent the click event + // when releasing the drag. There will be always a click if releasing the drag over the checkbox + if (element[0].hasAttribute('disabled') || scope.skipToggle) { + return; + } + + scope.$apply(function() { + // Toggle the checkbox value... + var viewValue = attr.ngChecked && attr.ngClick ? attr.checked : !ngModelCtrl.$viewValue; + + ngModelCtrl.$setViewValue(viewValue, ev && ev.type); + ngModelCtrl.$render(); + }); + } + + function render() { + // Cast the $viewValue to a boolean since it could be undefined + element.toggleClass('md-checked', !!ngModelCtrl.$viewValue && !isIndeterminate); + } + + function setIndeterminateState(newValue) { + isIndeterminate = newValue !== false; + if (isIndeterminate) { + element.attr('aria-checked', 'mixed'); + } + element.toggleClass('md-indeterminate', isIndeterminate); + } + } + } +} + +})(); +(function(){ +"use strict"; + +(function () { + "use strict"; + + /** + * Use a RegExp to check if the `md-colors=""` is static string + * or one that should be observed and dynamically interpolated. + */ + MdColorsDirective.$inject = ["$mdColors", "$mdUtil", "$log", "$parse"]; + MdColorsService.$inject = ["$mdTheming", "$mdUtil", "$log"]; + var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/; + var colorPalettes = null; + + /** + * @ngdoc module + * @name material.components.colors + * + * @description + * Define $mdColors service and a `md-colors=""` attribute directive + */ + angular + .module('material.components.colors', ['material.core']) + .directive('mdColors', MdColorsDirective) + .service('$mdColors', MdColorsService); + + /** + * @ngdoc service + * @name $mdColors + * @module material.components.colors + * + * @description + * With only defining themes, one couldn't get non AngularJS Material elements colored with Material colors, + * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply + * those values to element as CSS property values. + * + * @usage + * + * angular.controller('myCtrl', function ($mdColors) { + * var color = $mdColors.getThemeColor('myTheme-red-200-0.5'); + * ... + * }); + * + * + */ + function MdColorsService($mdTheming, $mdUtil, $log) { + colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES); + + // Publish service instance + return { + applyThemeColors: applyThemeColors, + getThemeColor: getThemeColor, + hasTheme: hasTheme + }; + + // ******************************************** + // Internal Methods + // ******************************************** + + /** + * @ngdoc method + * @name $mdColors#applyThemeColors + * + * @description + * Gets a color json object, keys are css properties and values are string of the wanted color + * Then calculate the rgba() values based on the theme color parts + * + * @param {DOMElement} element the element to apply the styles on. + * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color, + * for example: `{color: 'red-A200-0.3'}`. + * + * @usage + * + * app.directive('myDirective', function($mdColors) { + * return { + * ... + * link: function (scope, elem) { + * $mdColors.applyThemeColors(elem, {color: 'red'}); + * } + * } + * }); + * + */ + function applyThemeColors(element, colorExpression) { + try { + if (colorExpression) { + // Assign the calculate RGBA color values directly as inline CSS + element.css(interpolateColors(colorExpression)); + } + } catch (e) { + $log.error(e.message); + } + + } + + /** + * @ngdoc method + * @name $mdColors#getThemeColor + * + * @description + * Get parsed color from expression + * + * @param {string} expression string of a color expression (for instance `'red-700-0.8'`) + * + * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`) + * + * @usage + * + * angular.controller('myCtrl', function ($mdColors) { + * var color = $mdColors.getThemeColor('myTheme-red-200-0.5'); + * ... + * }); + * + */ + function getThemeColor(expression) { + var color = extractColorOptions(expression); + + return parseColor(color); + } + + /** + * Return the parsed color + * @param color hashmap of color definitions + * @param contrast whether use contrast color for foreground + * @returns rgba color string + */ + function parseColor(color, contrast) { + contrast = contrast || false; + var rgbValues = $mdTheming.PALETTES[color.palette][color.hue]; + + rgbValues = contrast ? rgbValues.contrast : rgbValues.value; + + return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})', + [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity] + ); + } + + /** + * Convert the color expression into an object with scope-interpolated values + * Then calculate the rgba() values based on the theme color parts + * + * @results Hashmap of CSS properties with associated `rgba( )` string vales + * + * + */ + function interpolateColors(themeColors) { + var rgbColors = {}; + + var hasColorProperty = themeColors.hasOwnProperty('color'); + + angular.forEach(themeColors, function (value, key) { + var color = extractColorOptions(value); + var hasBackground = key.indexOf('background') > -1; + + rgbColors[key] = parseColor(color); + if (hasBackground && !hasColorProperty) { + rgbColors.color = parseColor(color, true); + } + }); + + return rgbColors; + } + + /** + * Check if expression has defined theme + * e.g. + * 'myTheme-primary' => true + * 'red-800' => false + */ + function hasTheme(expression) { + return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]); + } + + /** + * For the evaluated expression, extract the color parts into a hash map + */ + function extractColorOptions(expression) { + var parts = expression.split('-'); + var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]); + var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme(); + + return { + theme: theme, + palette: extractPalette(parts, theme), + hue: extractHue(parts, theme), + opacity: parts[2] || 1 + }; + } + + /** + * Calculate the theme palette name + */ + function extractPalette(parts, theme) { + // If the next section is one of the palettes we assume it's a two word palette + // Two word palette can be also written in camelCase, forming camelCase to dash-case + + var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1; + var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + + if (isTwoWord) palette = parts[0] + '-' + parts.splice(1, 1); + + if (colorPalettes.indexOf(palette) === -1) { + // If the palette is not in the palette list it's one of primary/accent/warn/background + var scheme = $mdTheming.THEMES[theme].colors[palette]; + if (!scheme) { + throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette})); + } + palette = scheme.name; + } + + return palette; + } + + function extractHue(parts, theme) { + var themeColors = $mdTheming.THEMES[theme].colors; + + if (parts[1] === 'hue') { + var hueNumber = parseInt(parts.splice(2, 1)[0], 10); + + if (hueNumber < 1 || hueNumber > 3) { + throw new Error($mdUtil.supplant('mdColors: \'hue-{hueNumber}\' is not a valid hue, can be only \'hue-1\', \'hue-2\' and \'hue-3\'', {hueNumber: hueNumber})); + } + parts[1] = 'hue-' + hueNumber; + + if (!(parts[0] in themeColors)) { + throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', { + availableThemes: Object.keys(themeColors).join(', '), + usedTheme: parts[0] + })); + } + + return themeColors[parts[0]].hues[parts[1]]; + } + + return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default']; + } + } + + /** + * @ngdoc directive + * @name mdColors + * @module material.components.colors + * + * @restrict A + * + * @description + * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values. + * + * The format will be similar to our color defining in the scss files: + * + * ## `[?theme]-[palette]-[?hue]-[?opacity]` + * - [theme] - default value is the default theme + * - [palette] - can be either palette name or primary/accent/warn/background + * - [hue] - default is 500 (hue-x can be used with primary/accent/warn/background) + * - [opacity] - default is 1 + * + * > `?` indicates optional parameter + * + * @usage + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation + * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression + * to indicate a one-time data binding. + * + * + * + * + * + */ + function MdColorsDirective($mdColors, $mdUtil, $log, $parse) { + return { + restrict: 'A', + require: ['^?mdTheme'], + compile: function (tElem, tAttrs) { + var shouldWatch = shouldColorsWatch(); + + return function (scope, element, attrs, ctrl) { + var mdThemeController = ctrl[0]; + + var lastColors = {}; + + var parseColors = function (theme) { + if (typeof theme !== 'string') { + theme = ''; + } + + if (!attrs.mdColors) { + attrs.mdColors = '{}'; + } + + /** + * Json.parse() does not work because the keys are not quoted; + * use $parse to convert to a hash map + */ + var colors = $parse(attrs.mdColors)(scope); + + /** + * If mdTheme is defined up the DOM tree + * we add mdTheme theme to colors who doesn't specified a theme + * + * # example + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + * 'primary-600' will be 'myTheme-primary-600', + * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix + */ + if (mdThemeController) { + Object.keys(colors).forEach(function (prop) { + var color = colors[prop]; + if (!$mdColors.hasTheme(color)) { + colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color; + } + }); + } + + cleanElement(colors); + + return colors; + }; + + var cleanElement = function (colors) { + if (!angular.equals(colors, lastColors)) { + var keys = Object.keys(lastColors); + + if (lastColors.background && !keys.color) { + keys.push('color'); + } + + keys.forEach(function (key) { + element.css(key, ''); + }); + } + + lastColors = colors; + }; + + /** + * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes + */ + var unregisterChanges = angular.noop; + + if (mdThemeController) { + unregisterChanges = mdThemeController.registerChanges(function (theme) { + $mdColors.applyThemeColors(element, parseColors(theme)); + }); + } + + scope.$on('$destroy', function () { + unregisterChanges(); + }); + + try { + if (shouldWatch) { + scope.$watch(parseColors, angular.bind(this, + $mdColors.applyThemeColors, element + ), true); + } + else { + $mdColors.applyThemeColors(element, parseColors()); + } + + } + catch (e) { + $log.error(e.message); + } + + }; + + function shouldColorsWatch() { + // Simulate 1x binding and mark mdColorsWatch == false + var rawColorExpression = tAttrs.mdColors; + var bindOnce = rawColorExpression.indexOf('::') > -1; + var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors); + + // Remove it for the postLink... + tAttrs.mdColors = rawColorExpression.replace('::', ''); + + var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch); + + return (bindOnce || isStatic) ? false : + hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true; + } + } + }; + + } + + +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.content + * + * @description + * Scrollable content + */ +mdContentDirective.$inject = ["$mdTheming"]; +angular.module('material.components.content', [ + 'material.core' +]) + .directive('mdContent', mdContentDirective); + +/** + * @ngdoc directive + * @name mdContent + * @module material.components.content + * + * @restrict E + * + * @description + * + * The `` directive is a container element useful for scrollable content. It achieves + * this by setting the CSS `overflow` property to `auto` so that content can properly scroll. + * + * In general, `` components are not designed to be nested inside one another. If + * possible, it is better to make them siblings. This often results in a better user experience as + * having nested scrollbars may confuse the user. + * + * ## Troubleshooting + * + * In some cases, you may wish to apply the `md-no-momentum` class to ensure that Safari's + * momentum scrolling is disabled. Momentum scrolling can cause flickering issues while scrolling + * SVG icons and some other components. + * + * Additionally, we now also offer the `md-no-flicker` class which can be applied to any element + * and uses a Webkit-specific filter of `blur(0px)` that forces GPU rendering of all elements + * inside (which eliminates the flicker on iOS devices). + * + * _Note: Forcing an element to render on the GPU can have unintended side-effects, especially + * related to the z-index of elements. Please use with caution and only on the elements needed._ + * + * @usage + * + * Add the `[layout-padding]` attribute to make the content padded. + * + * + * + * Lorem ipsum dolor sit amet, ne quod novum mei. + * + * + */ + +function mdContentDirective($mdTheming) { + return { + restrict: 'E', + controller: ['$scope', '$element', ContentController], + link: function(scope, element) { + element.addClass('_md'); // private md component indicator for styling + + $mdTheming(element); + scope.$broadcast('$mdContentLoaded', element); + + iosScrollFix(element[0]); + } + }; + + function ContentController($scope, $element) { + this.$scope = $scope; + this.$element = $element; + } +} + +function iosScrollFix(node) { + // IOS FIX: + // If we scroll where there is no more room for the webview to scroll, + // by default the webview itself will scroll up and down, this looks really + // bad. So if we are scrolling to the very top or bottom, add/subtract one + angular.element(node).on('$md.pressdown', function(ev) { + // Only touch events + if (ev.pointer.type !== 't') return; + // Don't let a child content's touchstart ruin it for us. + if (ev.$materialScrollFixed) return; + ev.$materialScrollFixed = true; + + if (node.scrollTop === 0) { + node.scrollTop = 1; + } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) { + node.scrollTop -= 1; + } + }); +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.datepicker + * @description Module for the datepicker component. + */ + +angular.module('material.components.datepicker', [ + 'material.core', + 'material.components.icon', + 'material.components.virtualRepeat' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.dialog + */ +MdDialogDirective.$inject = ["$$rAF", "$mdTheming", "$mdDialog"]; +MdDialogProvider.$inject = ["$$interimElementProvider"]; +angular + .module('material.components.dialog', [ + 'material.core', + 'material.components.backdrop' + ]) + .directive('mdDialog', MdDialogDirective) + .provider('$mdDialog', MdDialogProvider); + +/** + * @ngdoc directive + * @name mdDialog + * @module material.components.dialog + * + * @restrict E + * + * @description + * `` - The dialog's template must be inside this element. + * + * Inside, use an `` element for the dialog's content, and use + * an `` element for the dialog's actions. + * + * ## CSS + * - `.md-dialog-content` - class that sets the padding on the content as the spec file + * + * ## Notes + * - If you specify an `id` for the ``, the `` will have the same `id` + * prefixed with `dialogContent_`. + * + * @usage + * ### Dialog template + * + * + * + * + * + *

Number {{item}}

+ *
+ *
+ *
+ * + * Close Dialog + * + *
+ *
+ */ +function MdDialogDirective($$rAF, $mdTheming, $mdDialog) { + return { + restrict: 'E', + link: function(scope, element) { + element.addClass('_md'); // private md component indicator for styling + + $mdTheming(element); + $$rAF(function() { + var images; + var content = element[0].querySelector('md-dialog-content'); + + if (content) { + images = content.getElementsByTagName('img'); + addOverflowClass(); + //-- delayed image loading may impact scroll height, check after images are loaded + angular.element(images).on('load', addOverflowClass); + } + + scope.$on('$destroy', function() { + $mdDialog.destroy(element); + }); + + /** + * + */ + function addOverflowClass() { + element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight); + } + + + }); + } + }; +} + +/** + * @ngdoc service + * @name $mdDialog + * @module material.components.dialog + * + * @description + * `$mdDialog` opens a dialog over the app to inform users about critical information or require + * them to make decisions. There are two approaches for setup: a simple promise API + * and regular object syntax. + * + * ## Restrictions + * + * - The dialog is always given an isolate scope. + * - The dialog's template must have an outer `` element. + * Inside, use an `` element for the dialog's content, and use + * an `` element for the dialog's actions. + * - Dialogs must cover the entire application to keep interactions inside of them. + * Use the `parent` option to change where dialogs are appended. + * + * ## Sizing + * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`. + * - Default max-width is 80% of the `rootElement` or `parent`. + * + * ## CSS + * - `.md-dialog-content` - class that sets the padding on the content as the spec file + * + * @usage + * + *
+ *
+ * + * Employee Alert! + * + *
+ *
+ * + * Custom Dialog + * + *
+ *
+ * + * Close Alert + * + *
+ *
+ * + * Greet Employee + * + *
+ *
+ *
+ * + * ### JavaScript: object syntax + * + * (function(angular, undefined){ + * "use strict"; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('AppCtrl', AppController); + * + * function AppController($scope, $mdDialog) { + * var alert; + * $scope.showAlert = showAlert; + * $scope.showDialog = showDialog; + * $scope.items = [1, 2, 3]; + * + * // Internal method + * function showAlert() { + * alert = $mdDialog.alert({ + * title: 'Attention', + * textContent: 'This is an example of how easy dialogs can be!', + * ok: 'Close' + * }); + * + * $mdDialog + * .show( alert ) + * .finally(function() { + * alert = undefined; + * }); + * } + * + * function showDialog($event) { + * var parentEl = angular.element(document.body); + * $mdDialog.show({ + * parent: parentEl, + * targetEvent: $event, + * template: + * '' + + * ' '+ + * ' '+ + * ' '+ + * '

Number {{item}}

' + + * ' '+ + * '
'+ + * '
' + + * ' ' + + * ' ' + + * ' Close Dialog' + + * ' ' + + * ' ' + + * '
', + * locals: { + * items: $scope.items + * }, + * controller: DialogController + * }); + * function DialogController($scope, $mdDialog, items) { + * $scope.items = items; + * $scope.closeDialog = function() { + * $mdDialog.hide(); + * } + * } + * } + * } + * })(angular); + *
+ * + * ### Multiple Dialogs + * Using the `multiple` option for the `$mdDialog` service allows developers to show multiple dialogs + * at the same time. + * + * + * // From plain options + * $mdDialog.show({ + * multiple: true + * }); + * + * // From a dialog preset + * $mdDialog.show( + * $mdDialog + * .alert() + * .multiple(true) + * ); + * + * + * + * ### Pre-Rendered Dialogs + * By using the `contentElement` option, it is possible to use an already existing element in the DOM. + * + * > Pre-rendered dialogs will be not linked to any scope and will not instantiate any new controller.
+ * > You can manually link the elements to a scope or instantiate a controller from the template (`ng-controller`) + * + * + * $scope.showPrerenderedDialog = function() { + * $mdDialog.show({ + * contentElement: '#myStaticDialog', + * parent: angular.element(document.body) + * }); + * }; + * + * + * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector. + * + * + *
+ *
+ * + * This is a pre-rendered dialog. + * + *
+ *
+ *
+ * + * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog + * will not show up. + * + * It also possible to use a DOM element for the `contentElement` option. + * - `contentElement: document.querySelector('#myStaticDialog')` + * - `contentElement: angular.element(TEMPLATE)` + * + * When using a `template` as content element, it will be not compiled upon open. + * This allows you to compile the element yourself and use it each time the dialog opens. + * + * ### Custom Presets + * Developers are also able to create their own preset, which can be easily used without repeating + * their options each time. + * + * + * $mdDialogProvider.addPreset('testPreset', { + * options: function() { + * return { + * template: + * '' + + * 'This is a custom preset' + + * '', + * controllerAs: 'dialog', + * bindToController: true, + * clickOutsideToClose: true, + * escapeToClose: true + * }; + * } + * }); + * + * + * After you created your preset at config phase, you can easily access it. + * + * + * $mdDialog.show( + * $mdDialog.testPreset() + * ); + * + * + * ### JavaScript: promise API syntax, custom dialog template + * + * (function(angular, undefined){ + * "use strict"; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('EmployeeController', EmployeeEditor) + * .controller('GreetingController', GreetingController); + * + * // Fictitious Employee Editor to show how to use simple and complex dialogs. + * + * function EmployeeEditor($scope, $mdDialog) { + * var alert; + * + * $scope.showAlert = showAlert; + * $scope.closeAlert = closeAlert; + * $scope.showGreeting = showCustomGreeting; + * + * $scope.hasAlert = function() { return !!alert }; + * $scope.userName = $scope.userName || 'Bobby'; + * + * // Dialog #1 - Show simple alert dialog and cache + * // reference to dialog instance + * + * function showAlert() { + * alert = $mdDialog.alert() + * .title('Attention, ' + $scope.userName) + * .textContent('This is an example of how easy dialogs can be!') + * .ok('Close'); + * + * $mdDialog + * .show( alert ) + * .finally(function() { + * alert = undefined; + * }); + * } + * + * // Close the specified dialog instance and resolve with 'finished' flag + * // Normally this is not needed, just use '$mdDialog.hide()' to close + * // the most recent dialog popup. + * + * function closeAlert() { + * $mdDialog.hide( alert, "finished" ); + * alert = undefined; + * } + * + * // Dialog #2 - Demonstrate more complex dialogs construction and popup. + * + * function showCustomGreeting($event) { + * $mdDialog.show({ + * targetEvent: $event, + * template: + * '' + + * + * ' Hello {{ employee }}!' + + * + * ' ' + + * ' ' + + * ' Close Greeting' + + * ' ' + + * ' ' + + * '', + * controller: 'GreetingController', + * onComplete: afterShowAnimation, + * locals: { employee: $scope.userName } + * }); + * + * // When the 'enter' animation finishes... + * + * function afterShowAnimation(scope, element, options) { + * // post-show code here: DOM element focus, etc. + * } + * } + * + * // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog + * // Here we used ng-controller="GreetingController as vm" and + * // $scope.vm === + * + * function showCustomGreeting() { + * + * $mdDialog.show({ + * clickOutsideToClose: true, + * + * scope: $scope, // use parent scope in template + * preserveScope: true, // do not forget this if use parent scope + + * // Since GreetingController is instantiated with ControllerAs syntax + * // AND we are passing the parent '$scope' to the dialog, we MUST + * // use 'vm.' in the template markup + * + * template: '' + + * ' ' + + * ' Hi There {{vm.employee}}' + + * ' ' + + * '', + * + * controller: function DialogController($scope, $mdDialog) { + * $scope.closeDialog = function() { + * $mdDialog.hide(); + * } + * } + * }); + * } + * + * } + * + * // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog + * + * function GreetingController($scope, $mdDialog, employee) { + * // Assigned from construction locals options... + * $scope.employee = employee; + * + * $scope.closeDialog = function() { + * // Easily hides most recent dialog shown... + * // no specific instance reference is needed. + * $mdDialog.hide(); + * }; + * } + * + * })(angular); + * + */ + +/** + * @ngdoc method + * @name $mdDialog#alert + * + * @description + * Builds a preconfigured dialog with the specified message. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * - $mdDialogPreset#title(string) - Sets the alert title. + * - $mdDialogPreset#textContent(string) - Sets the alert message. + * - $mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#ok(string) - Sets the alert "Okay" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the alert dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#confirm + * + * @description + * Builds a preconfigured dialog with the specified message. You can call show and the promise returned + * will be resolved only if the user clicks the confirm action on the dialog. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * Additionally, it supports the following methods: + * + * - $mdDialogPreset#title(string) - Sets the confirm title. + * - $mdDialogPreset#textContent(string) - Sets the confirm message. + * - $mdDialogPreset#htmlContent(string) - Sets the confirm message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#ok(string) - Sets the confirm "Okay" button text. + * - $mdDialogPreset#cancel(string) - Sets the confirm "Cancel" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the confirm dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#prompt + * + * @description + * Builds a preconfigured dialog with the specified message and input box. You can call show and the promise returned + * will be resolved only if the user clicks the prompt action on the dialog, passing the input value as the first argument. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * Additionally, it supports the following methods: + * + * - $mdDialogPreset#title(string) - Sets the prompt title. + * - $mdDialogPreset#textContent(string) - Sets the prompt message. + * - $mdDialogPreset#htmlContent(string) - Sets the prompt message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#placeholder(string) - Sets the placeholder text for the input. + * - $mdDialogPreset#initialValue(string) - Sets the initial value for the prompt input. + * - $mdDialogPreset#ok(string) - Sets the prompt "Okay" button text. + * - $mdDialogPreset#cancel(string) - Sets the prompt "Cancel" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the prompt dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#show + * + * @description + * Show a dialog with the specified options. + * + * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and + * `confirm()`, or an options object with the following properties: + * - `templateUrl` - `{string=}`: The url of a template that will be used as the content + * of the dialog. + * - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML + * with respect to Angular's [$sce service](https://docs.angularjs.org/api/ng/service/$sce). + * This template should **never** be constructed with any kind of user input or user data. + * - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled each time a + * dialog opens, you can also use a DOM element.
+ * * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch the element into + * the dialog and restores it at the old DOM position upon close. + * * When specifying a string, the string be used as a CSS selector, to lookup for the element in the DOM. + * - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a + * `` tag if one is not provided. Defaults to true. Can be disabled if you provide a + * custom dialog directive. + * - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * - `openFrom` - `{string|Element|object}`: The query selector, DOM element or the Rect object + * that is used to determine the bounds (top, left, height, width) from which the Dialog will + * originate. + * - `closeTo` - `{string|Element|object}`: The query selector, DOM element or the Rect object + * that is used to determine the bounds (top, left, height, width) to which the Dialog will + * target. + * - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, + * it will create a new isolate scope. + * This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true. + * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false + * - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open. + * Default true. + * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog. + * Default true. + * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to + * close it. Default false. + * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog. + * Default true. + * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if + * focusing some other way, as focus management is required for dialogs to be accessible. + * Defaults to true. + * - `controller` - `{function|string=}`: The controller to associate with the dialog. The controller + * will be injected with the local `$mdDialog`, which passes along a scope for the dialog. + * - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names + * of values to inject into the controller. For example, `locals: {three: 3}` would inject + * `three` into the controller, with the value 3. If `bindToController` is true, they will be + * copied to the controller instead. + * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in. + * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope. + * - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending + * to the root element of the application. + * - `onShowing` - `function(scope, element)`: Callback function used to announce the show() action is + * starting. + * - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is + * finished. + * - `onRemoving` - `function(element, removePromise)`: Callback function used to announce the + * close/hide() action is starting. This allows developers to run custom animations + * in parallel the close animations. + * - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen + * or not. Defaults to `false`. + * - `multiple` `{boolean=}`: An option to allow this dialog to display over one that's currently open. + * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or + * rejected with `$mdDialog.cancel()`. + */ + +/** + * @ngdoc method + * @name $mdDialog#hide + * + * @description + * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`. + * + * @param {*=} response An argument for the resolved promise. + * + * @returns {promise} A promise that is resolved when the dialog has been closed. + */ + +/** + * @ngdoc method + * @name $mdDialog#cancel + * + * @description + * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`. + * + * @param {*=} response An argument for the rejected promise. + * + * @returns {promise} A promise that is resolved when the dialog has been closed. + */ + +function MdDialogProvider($$interimElementProvider) { + // Elements to capture and redirect focus when the user presses tab at the dialog boundary. + MdDialogController.$inject = ["$mdDialog", "$mdConstant"]; + dialogDefaultOptions.$inject = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming", "$interpolate", "$mdInteraction"]; + var topFocusTrap, bottomFocusTrap; + + return $$interimElementProvider('$mdDialog') + .setDefaults({ + methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose', + 'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'multiple'], + options: dialogDefaultOptions + }) + .addPreset('alert', { + methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme', + 'css'], + options: advancedDialogOptions + }) + .addPreset('confirm', { + methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel', + 'theme', 'css'], + options: advancedDialogOptions + }) + .addPreset('prompt', { + methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel', + 'ok', 'cancel', 'theme', 'css', 'required'], + options: advancedDialogOptions + }); + + /* @ngInject */ + function advancedDialogOptions() { + return { + template: [ + '', + ' ', + '

{{ dialog.title }}

', + '
', + '
', + '

{{::dialog.mdTextContent}}

', + '
', + ' ', + ' ', + ' ', + '
', + ' ', + ' ', + ' {{ dialog.cancel }}', + ' ', + ' ', + ' {{ dialog.ok }}', + ' ', + ' ', + '
' + ].join('').replace(/\s\s+/g, ''), + controller: MdDialogController, + controllerAs: 'dialog', + bindToController: true, + }; + } + + /** + * Controller for the md-dialog interim elements + * @ngInject + */ + function MdDialogController($mdDialog, $mdConstant) { + // For compatibility with AngularJS 1.6+, we should always use the $onInit hook in + // interimElements. The $mdCompiler simulates the $onInit hook for all versions. + this.$onInit = function() { + var isPrompt = this.$type == 'prompt'; + + if (isPrompt && this.initialValue) { + this.result = this.initialValue; + } + + this.hide = function() { + $mdDialog.hide(isPrompt ? this.result : true); + }; + this.abort = function() { + $mdDialog.cancel(); + }; + this.keypress = function($event) { + if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { + $mdDialog.hide(this.result); + } + }; + }; + } + + /* @ngInject */ + function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement, + $log, $injector, $mdTheming, $interpolate, $mdInteraction) { + + return { + hasBackdrop: true, + isolateScope: true, + onCompiling: beforeCompile, + onShow: onShow, + onShowing: beforeShow, + onRemove: onRemove, + clickOutsideToClose: false, + escapeToClose: true, + targetEvent: null, + closeTo: null, + openFrom: null, + focusOnOpen: true, + disableParentScroll: true, + autoWrap: true, + fullscreen: false, + transformTemplate: function(template, options) { + // Make the dialog container focusable, because otherwise the focus will be always redirected to + // an element outside of the container, and the focus trap won't work probably.. + // Also the tabindex is needed for the `escapeToClose` functionality, because + // the keyDown event can't be triggered when the focus is outside of the container. + var startSymbol = $interpolate.startSymbol(); + var endSymbol = $interpolate.endSymbol(); + var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol; + return '
' + validatedTemplate(template) + '
'; + + /** + * The specified template should contain a wrapper element.... + */ + function validatedTemplate(template) { + if (options.autoWrap && !/<\/md-dialog>/g.test(template)) { + return '' + (template || '') + ''; + } else { + return template || ''; + } + } + } + }; + + function beforeCompile(options) { + // Automatically apply the theme, if the user didn't specify a theme explicitly. + // Those option changes need to be done, before the compilation has started, because otherwise + // the option changes will be not available in the $mdCompilers locales. + options.defaultTheme = $mdTheming.defaultTheme(); + + detectTheming(options); + } + + function beforeShow(scope, element, options, controller) { + + if (controller) { + var mdHtmlContent = controller.htmlContent || options.htmlContent || ''; + var mdTextContent = controller.textContent || options.textContent || + controller.content || options.content || ''; + + if (mdHtmlContent && !$injector.has('$sanitize')) { + throw Error('The ngSanitize module must be loaded in order to use htmlContent.'); + } + + if (mdHtmlContent && mdTextContent) { + throw Error('md-dialog cannot have both `htmlContent` and `textContent`'); + } + + // Only assign the content if nothing throws, otherwise it'll still be compiled. + controller.mdHtmlContent = mdHtmlContent; + controller.mdTextContent = mdTextContent; + } + } + + /** Show method for dialogs */ + function onShow(scope, element, options, controller) { + angular.element($document[0].body).addClass('md-dialog-is-showing'); + + var dialogElement = element.find('md-dialog'); + + // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work properly. + // This is a very common problem, so we have to notify the developer about this. + if (dialogElement.hasClass('ng-cloak')) { + var message = '$mdDialog: using `` will affect the dialog opening animations.'; + $log.warn( message, element[0] ); + } + + captureParentAndFromToElements(options); + configureAria(dialogElement, options); + showBackdrop(scope, element, options); + activateListeners(element, options); + + return dialogPopIn(element, options) + .then(function() { + lockScreenReader(element, options); + warnDeprecatedActions(); + focusOnOpen(); + }); + + /** + * Check to see if they used the deprecated .md-actions class and log a warning + */ + function warnDeprecatedActions() { + if (element[0].querySelector('.md-actions')) { + $log.warn('Using a class of md-actions is deprecated, please use .'); + } + } + + /** + * For alerts, focus on content... otherwise focus on + * the close button (or equivalent) + */ + function focusOnOpen() { + if (options.focusOnOpen) { + var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement; + target.focus(); + } + + /** + * If no element with class dialog-close, try to find the last + * button child in md-actions and assume it is a close button. + * + * If we find no actions at all, log a warning to the console. + */ + function findCloseButton() { + return element[0].querySelector('.dialog-close, md-dialog-actions button:last-child'); + } + } + } + + /** + * Remove function for all dialogs + */ + function onRemove(scope, element, options) { + options.deactivateListeners(); + options.unlockScreenReader(); + options.hideBackdrop(options.$destroy); + + // Remove the focus traps that we added earlier for keeping focus within the dialog. + if (topFocusTrap && topFocusTrap.parentNode) { + topFocusTrap.parentNode.removeChild(topFocusTrap); + } + + if (bottomFocusTrap && bottomFocusTrap.parentNode) { + bottomFocusTrap.parentNode.removeChild(bottomFocusTrap); + } + + // For navigation $destroy events, do a quick, non-animated removal, + // but for normal closes (from clicks, etc) animate the removal + return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean ); + + /** + * For normal closes, animate the removal. + * For forced closes (like $destroy events), skip the animations + */ + function animateRemoval() { + return dialogPopOut(element, options); + } + + /** + * Detach the element + */ + function detachAndClean() { + angular.element($document[0].body).removeClass('md-dialog-is-showing'); + + // Reverse the container stretch if using a content element. + if (options.contentElement) { + options.reverseContainerStretch(); + } + + // Exposed cleanup function from the $mdCompiler. + options.cleanupElement(); + + // Restores the focus to the origin element if the last interaction upon opening was a keyboard. + if (!options.$destroy && options.originInteraction === 'keyboard') { + options.origin.focus(); + } + } + } + + function detectTheming(options) { + // Once the user specifies a targetEvent, we will automatically try to find the correct + // nested theme. + var targetEl; + if (options.targetEvent && options.targetEvent.target) { + targetEl = angular.element(options.targetEvent.target); + } + + var themeCtrl = targetEl && targetEl.controller('mdTheme'); + + if (!themeCtrl) { + return; + } + + options.themeWatch = themeCtrl.$shouldWatch; + + var theme = options.theme || themeCtrl.$mdTheme; + + if (theme) { + options.scope.theme = theme; + } + + var unwatch = themeCtrl.registerChanges(function (newTheme) { + options.scope.theme = newTheme; + + if (!options.themeWatch) { + unwatch(); + } + }); + } + + /** + * Capture originator/trigger/from/to element information (if available) + * and the parent container for the dialog; defaults to the $rootElement + * unless overridden in the options.parent + */ + function captureParentAndFromToElements(options) { + options.origin = angular.extend({ + element: null, + bounds: null, + focus: angular.noop + }, options.origin || {}); + + options.parent = getDomElement(options.parent, $rootElement); + options.closeTo = getBoundingClientRect(getDomElement(options.closeTo)); + options.openFrom = getBoundingClientRect(getDomElement(options.openFrom)); + + if ( options.targetEvent ) { + options.origin = getBoundingClientRect(options.targetEvent.target, options.origin); + options.originInteraction = $mdInteraction.getLastInteractionType(); + } + + + /** + * Identify the bounding RECT for the target element + * + */ + function getBoundingClientRect (element, orig) { + var source = angular.element((element || {})); + if (source && source.length) { + // Compute and save the target element's bounding rect, so that if the + // element is hidden when the dialog closes, we can shrink the dialog + // back to the same position it expanded from. + // + // Checking if the source is a rect object or a DOM element + var bounds = {top:0,left:0,height:0,width:0}; + var hasFn = angular.isFunction(source[0].getBoundingClientRect); + + return angular.extend(orig || {}, { + element : hasFn ? source : undefined, + bounds : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]), + focus : angular.bind(source, source.focus), + }); + } + } + + /** + * If the specifier is a simple string selector, then query for + * the DOM element. + */ + function getDomElement(element, defaultElement) { + if (angular.isString(element)) { + element = $document[0].querySelector(element); + } + + // If we have a reference to a raw dom element, always wrap it in jqLite + return angular.element(element || defaultElement); + } + + } + + /** + * Listen for escape keys and outside clicks to auto close + */ + function activateListeners(element, options) { + var window = angular.element($window); + var onWindowResize = $mdUtil.debounce(function() { + stretchDialogContainerToViewport(element, options); + }, 60); + + var removeListeners = []; + var smartClose = function() { + // Only 'confirm' dialogs have a cancel button... escape/clickOutside will + // cancel or fallback to hide. + var closeFn = ( options.$type == 'alert' ) ? $mdDialog.hide : $mdDialog.cancel; + $mdUtil.nextTick(closeFn, true); + }; + + if (options.escapeToClose) { + var parentTarget = options.parent; + var keyHandlerFn = function(ev) { + if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + + smartClose(); + } + }; + + // Add keydown listeners + element.on('keydown', keyHandlerFn); + parentTarget.on('keydown', keyHandlerFn); + + // Queue remove listeners function + removeListeners.push(function() { + + element.off('keydown', keyHandlerFn); + parentTarget.off('keydown', keyHandlerFn); + + }); + } + + // Register listener to update dialog on window resize + window.on('resize', onWindowResize); + + removeListeners.push(function() { + window.off('resize', onWindowResize); + }); + + if (options.clickOutsideToClose) { + var target = element; + var sourceElem; + + // Keep track of the element on which the mouse originally went down + // so that we can only close the backdrop when the 'click' started on it. + // A simple 'click' handler does not work, + // it sets the target object as the element the mouse went down on. + var mousedownHandler = function(ev) { + sourceElem = ev.target; + }; + + // We check if our original element and the target is the backdrop + // because if the original was the backdrop and the target was inside the dialog + // we don't want to dialog to close. + var mouseupHandler = function(ev) { + if (sourceElem === target[0] && ev.target === target[0]) { + ev.stopPropagation(); + ev.preventDefault(); + + smartClose(); + } + }; + + // Add listeners + target.on('mousedown', mousedownHandler); + target.on('mouseup', mouseupHandler); + + // Queue remove listeners function + removeListeners.push(function() { + target.off('mousedown', mousedownHandler); + target.off('mouseup', mouseupHandler); + }); + } + + // Attach specific `remove` listener handler + options.deactivateListeners = function() { + removeListeners.forEach(function(removeFn) { + removeFn(); + }); + options.deactivateListeners = null; + }; + } + + /** + * Show modal backdrop element... + */ + function showBackdrop(scope, element, options) { + + if (options.disableParentScroll) { + // !! DO this before creating the backdrop; since disableScrollAround() + // configures the scroll offset; which is used by mdBackDrop postLink() + options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent); + } + + if (options.hasBackdrop) { + options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque"); + $animate.enter(options.backdrop, options.parent); + } + + /** + * Hide modal backdrop element... + */ + options.hideBackdrop = function hideBackdrop($destroy) { + if (options.backdrop) { + if ( !!$destroy ) options.backdrop.remove(); + else $animate.leave(options.backdrop); + } + + + if (options.disableParentScroll) { + options.restoreScroll && options.restoreScroll(); + delete options.restoreScroll; + } + + options.hideBackdrop = null; + }; + } + + /** + * Inject ARIA-specific attributes appropriate for Dialogs + */ + function configureAria(element, options) { + + var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog'; + var dialogContent = element.find('md-dialog-content'); + var existingDialogId = element.attr('id'); + var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid()); + + element.attr({ + 'role': role, + 'tabIndex': '-1' + }); + + if (dialogContent.length === 0) { + dialogContent = element; + // If the dialog element already had an ID, don't clobber it. + if (existingDialogId) { + dialogContentId = existingDialogId; + } + } + + dialogContent.attr('id', dialogContentId); + element.attr('aria-describedby', dialogContentId); + + if (options.ariaLabel) { + $mdAria.expect(element, 'aria-label', options.ariaLabel); + } + else { + $mdAria.expectAsync(element, 'aria-label', function() { + // If dialog title is specified, set aria-label with it + // See https://github.com/angular/material/issues/10582 + if (options.title) { + return options.title; + } else { + var words = dialogContent.text().split(/\s+/); + if (words.length > 3) words = words.slice(0, 3).concat('...'); + return words.join(' '); + } + }); + } + + // Set up elements before and after the dialog content to capture focus and + // redirect back into the dialog. + topFocusTrap = document.createElement('div'); + topFocusTrap.classList.add('md-dialog-focus-trap'); + topFocusTrap.tabIndex = 0; + + bottomFocusTrap = topFocusTrap.cloneNode(false); + + // When focus is about to move out of the dialog, we want to intercept it and redirect it + // back to the dialog element. + var focusHandler = function() { + element.focus(); + }; + topFocusTrap.addEventListener('focus', focusHandler); + bottomFocusTrap.addEventListener('focus', focusHandler); + + // The top focus trap inserted immeidately before the md-dialog element (as a sibling). + // The bottom focus trap is inserted at the very end of the md-dialog element (as a child). + element[0].parentNode.insertBefore(topFocusTrap, element[0]); + element.after(bottomFocusTrap); + } + + /** + * Prevents screen reader interaction behind modal window + * on swipe interfaces + */ + function lockScreenReader(element, options) { + var isHidden = true; + + // get raw DOM node + walkDOM(element[0]); + + options.unlockScreenReader = function() { + isHidden = false; + walkDOM(element[0]); + + options.unlockScreenReader = null; + }; + + /** + * Walk DOM to apply or remove aria-hidden on sibling nodes + * and parent sibling nodes + * + */ + function walkDOM(element) { + while (element.parentNode) { + if (element === document.body) { + return; + } + var children = element.parentNode.children; + for (var i = 0; i < children.length; i++) { + // skip over child if it is an ascendant of the dialog + // or a script or style tag + if (element !== children[i] && + !isNodeOneOf(children[i], ['SCRIPT', 'STYLE']) && + !children[i].hasAttribute('aria-live')) { + children[i].setAttribute('aria-hidden', isHidden); + } + } + + walkDOM(element = element.parentNode); + } + } + } + + /** + * Ensure the dialog container fill-stretches to the viewport + */ + function stretchDialogContainerToViewport(container, options) { + var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed'; + var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null; + var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0; + + var previousStyles = { + top: container.css('top'), + height: container.css('height') + }; + + // If the body is fixed, determine the distance to the viewport in relative from the parent. + var parentTop = Math.abs(options.parent[0].getBoundingClientRect().top); + + container.css({ + top: (isFixed ? parentTop : 0) + 'px', + height: height ? height + 'px' : '100%' + }); + + return function() { + // Reverts the modified styles back to the previous values. + // This is needed for contentElements, which should have the same styles after close + // as before. + container.css(previousStyles); + }; + } + + /** + * Dialog open and pop-in animation + */ + function dialogPopIn(container, options) { + // Add the `md-dialog-container` to the DOM + options.parent.append(container); + options.reverseContainerStretch = stretchDialogContainerToViewport(container, options); + + var dialogEl = container.find('md-dialog'); + var animator = $mdUtil.dom.animator; + var buildTranslateToOrigin = animator.calculateZoomToOrigin; + var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'}; + var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin)); + var to = animator.toTransformCss(""); // defaults to center display (or parent or $rootElement) + + dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen); + + return animator + .translate3d(dialogEl, from, to, translateOptions) + .then(function(animateReversal) { + + // Build a reversal translate function synced to this translation... + options.reverseAnimate = function() { + delete options.reverseAnimate; + + if (options.closeTo) { + // Using the opposite classes to create a close animation to the closeTo element + translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'}; + from = to; + to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo)); + + return animator + .translate3d(dialogEl, from, to,translateOptions); + } + + return animateReversal( + to = animator.toTransformCss( + // in case the origin element has moved or is hidden, + // let's recalculate the translateCSS + buildTranslateToOrigin(dialogEl, options.origin) + ) + ); + + }; + + // Function to revert the generated animation styles on the dialog element. + // Useful when using a contentElement instead of a template. + options.clearAnimate = function() { + delete options.clearAnimate; + + // Remove the transition classes, added from $animateCSS, since those can't be removed + // by reversely running the animator. + dialogEl.removeClass([ + translateOptions.transitionOutClass, + translateOptions.transitionInClass + ].join(' ')); + + // Run the animation reversely to remove the previous added animation styles. + return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {}); + }; + + return true; + }); + } + + /** + * Dialog close and pop-out animation + */ + function dialogPopOut(container, options) { + return options.reverseAnimate().then(function() { + if (options.contentElement) { + // When we use a contentElement, we want the element to be the same as before. + // That means, that we have to clear all the animation properties, like transform. + options.clearAnimate(); + } + }); + } + + /** + * Utility function to filter out raw DOM nodes + */ + function isNodeOneOf(elem, nodeTypeArray) { + if (nodeTypeArray.indexOf(elem.nodeName) !== -1) { + return true; + } + } + + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.divider + * @description Divider module! + */ +MdDividerDirective.$inject = ["$mdTheming"]; +angular.module('material.components.divider', [ + 'material.core' +]) + .directive('mdDivider', MdDividerDirective); + +/** + * @ngdoc directive + * @name mdDivider + * @module material.components.divider + * @restrict E + * + * @description + * Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions. This divider is a thin rule, lightweight enough to not distract the user from content. + * + * @param {boolean=} md-inset Add this attribute to activate the inset divider style. + * @usage + * + * + * + * + * + * + */ +function MdDividerDirective($mdTheming) { + return { + restrict: 'E', + link: $mdTheming + }; +} + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc module + * @name material.components.fabActions + */ + MdFabActionsDirective.$inject = ["$mdUtil"]; + angular + .module('material.components.fabActions', ['material.core']) + .directive('mdFabActions', MdFabActionsDirective); + + /** + * @ngdoc directive + * @name mdFabActions + * @module material.components.fabActions + * + * @restrict E + * + * @description + * The `` directive is used inside of a `` or + * `` directive to mark an element (or elements) as the actions and setup the + * proper event listeners. + * + * @usage + * See the `` or `` directives for example usage. + */ + function MdFabActionsDirective($mdUtil) { + return { + restrict: 'E', + + require: ['^?mdFabSpeedDial', '^?mdFabToolbar'], + + compile: function(element, attributes) { + var children = element.children(); + + var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat'); + + // Support both ng-repeat and static content + if (hasNgRepeat) { + children.addClass('md-fab-action-item'); + } else { + // Wrap every child in a new div and add a class that we can scale/fling independently + children.wrap('
'); + } + } + }; + } + +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + MdFabController.$inject = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"]; + angular.module('material.components.fabShared', ['material.core']) + .controller('MdFabController', MdFabController); + + function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) { + var vm = this; + var initialAnimationAttempts = 0; + + // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops + + vm.open = function() { + $scope.$evalAsync("vm.isOpen = true"); + }; + + vm.close = function() { + // Async eval to avoid conflicts with existing digest loops + $scope.$evalAsync("vm.isOpen = false"); + + // Focus the trigger when the element closes so users can still tab to the next item + $element.find('md-fab-trigger')[0].focus(); + }; + + // Toggle the open/close state when the trigger is clicked + vm.toggle = function() { + $scope.$evalAsync("vm.isOpen = !vm.isOpen"); + }; + + /* + * AngularJS Lifecycle hook for newer AngularJS versions. + * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook. + */ + vm.$onInit = function() { + setupDefaults(); + setupListeners(); + setupWatchers(); + + fireInitialAnimations(); + }; + + // For AngularJS 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned, + // manually call the $onInit hook. + if (angular.version.major === 1 && angular.version.minor <= 4) { + this.$onInit(); + } + + function setupDefaults() { + // Set the default direction to 'down' if none is specified + vm.direction = vm.direction || 'down'; + + // Set the default to be closed + vm.isOpen = vm.isOpen || false; + + // Start the keyboard interaction at the first action + resetActionIndex(); + + // Add an animations waiting class so we know not to run + $element.addClass('md-animations-waiting'); + } + + function setupListeners() { + var eventTypes = [ + 'click', 'focusin', 'focusout' + ]; + + // Add our listeners + angular.forEach(eventTypes, function(eventType) { + $element.on(eventType, parseEvents); + }); + + // Remove our listeners when destroyed + $scope.$on('$destroy', function() { + angular.forEach(eventTypes, function(eventType) { + $element.off(eventType, parseEvents); + }); + + // remove any attached keyboard handlers in case element is removed while + // speed dial is open + disableKeyboard(); + }); + } + + var closeTimeout; + function parseEvents(event) { + // If the event is a click, just handle it + if (event.type == 'click') { + handleItemClick(event); + } + + // If we focusout, set a timeout to close the element + if (event.type == 'focusout' && !closeTimeout) { + closeTimeout = $timeout(function() { + vm.close(); + }, 100, false); + } + + // If we see a focusin and there is a timeout about to run, cancel it so we stay open + if (event.type == 'focusin' && closeTimeout) { + $timeout.cancel(closeTimeout); + closeTimeout = null; + } + } + + function resetActionIndex() { + vm.currentActionIndex = -1; + } + + function setupWatchers() { + // Watch for changes to the direction and update classes/attributes + $scope.$watch('vm.direction', function(newDir, oldDir) { + // Add the appropriate classes so we can target the direction in the CSS + $animate.removeClass($element, 'md-' + oldDir); + $animate.addClass($element, 'md-' + newDir); + + // Reset the action index since it may have changed + resetActionIndex(); + }); + + var trigger, actions; + + // Watch for changes to md-open + $scope.$watch('vm.isOpen', function(isOpen) { + // Reset the action index since it may have changed + resetActionIndex(); + + // We can't get the trigger/actions outside of the watch because the component hasn't been + // linked yet, so we wait until the first watch fires to cache them. + if (!trigger || !actions) { + trigger = getTriggerElement(); + actions = getActionsElement(); + } + + if (isOpen) { + enableKeyboard(); + } else { + disableKeyboard(); + } + + var toAdd = isOpen ? 'md-is-open' : ''; + var toRemove = isOpen ? '' : 'md-is-open'; + + // Set the proper ARIA attributes + trigger.attr('aria-haspopup', true); + trigger.attr('aria-expanded', isOpen); + actions.attr('aria-hidden', !isOpen); + + // Animate the CSS classes + $animate.setClass($element, toAdd, toRemove); + }); + } + + function fireInitialAnimations() { + // If the element is actually visible on the screen + if ($element[0].scrollHeight > 0) { + // Fire our animation + $animate.addClass($element, '_md-animations-ready').then(function() { + // Remove the waiting class + $element.removeClass('md-animations-waiting'); + }); + } + + // Otherwise, try for up to 1 second before giving up + else if (initialAnimationAttempts < 10) { + $timeout(fireInitialAnimations, 100); + + // Increment our counter + initialAnimationAttempts = initialAnimationAttempts + 1; + } + } + + function enableKeyboard() { + $element.on('keydown', keyPressed); + + // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid + // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button) + $mdUtil.nextTick(function() { + angular.element(document).on('click touchend', checkForOutsideClick); + }); + + // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but + // this breaks accessibility, especially on mobile, since you have no arrow keys to press + //resetActionTabIndexes(); + } + + function disableKeyboard() { + $element.off('keydown', keyPressed); + angular.element(document).off('click touchend', checkForOutsideClick); + } + + function checkForOutsideClick(event) { + if (event.target) { + var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger'); + var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions'); + + if (!closestTrigger && !closestActions) { + vm.close(); + } + } + } + + function keyPressed(event) { + switch (event.which) { + case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false; + case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false; + case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false; + case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false; + case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false; + } + } + + function doActionPrev(event) { + focusAction(event, -1); + } + + function doActionNext(event) { + focusAction(event, 1); + } + + function focusAction(event, direction) { + var actions = resetActionTabIndexes(); + + // Increment/decrement the counter with restrictions + vm.currentActionIndex = vm.currentActionIndex + direction; + vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex); + vm.currentActionIndex = Math.max(0, vm.currentActionIndex); + + // Focus the element + var focusElement = angular.element(actions[vm.currentActionIndex]).children()[0]; + angular.element(focusElement).attr('tabindex', 0); + focusElement.focus(); + + // Make sure the event doesn't bubble and cause something else + event.preventDefault(); + event.stopImmediatePropagation(); + } + + function resetActionTabIndexes() { + // Grab all of the actions + var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item'); + + // Disable all other actions for tabbing + angular.forEach(actions, function(action) { + angular.element(angular.element(action).children()[0]).attr('tabindex', -1); + }); + + return actions; + } + + function doKeyLeft(event) { + if (vm.direction === 'left') { + doActionNext(event); + } else { + doActionPrev(event); + } + } + + function doKeyUp(event) { + if (vm.direction === 'down') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function doKeyRight(event) { + if (vm.direction === 'left') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function doKeyDown(event) { + if (vm.direction === 'up') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function isTrigger(element) { + return $mdUtil.getClosest(element, 'md-fab-trigger'); + } + + function isAction(element) { + return $mdUtil.getClosest(element, 'md-fab-actions'); + } + + function handleItemClick(event) { + if (isTrigger(event.target)) { + vm.toggle(); + } + + if (isAction(event.target)) { + vm.close(); + } + } + + function getTriggerElement() { + return $element.find('md-fab-trigger'); + } + + function getActionsElement() { + return $element.find('md-fab-actions'); + } + } +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * The duration of the CSS animation in milliseconds. + * + * @type {number} + */ + MdFabSpeedDialFlingAnimation.$inject = ["$timeout"]; + MdFabSpeedDialScaleAnimation.$inject = ["$timeout"]; + var cssAnimationDuration = 300; + + /** + * @ngdoc module + * @name material.components.fabSpeedDial + */ + angular + // Declare our module + .module('material.components.fabSpeedDial', [ + 'material.core', + 'material.components.fabShared', + 'material.components.fabActions' + ]) + + // Register our directive + .directive('mdFabSpeedDial', MdFabSpeedDialDirective) + + // Register our custom animations + .animation('.md-fling', MdFabSpeedDialFlingAnimation) + .animation('.md-scale', MdFabSpeedDialScaleAnimation) + + // Register a service for each animation so that we can easily inject them into unit tests + .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation) + .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation); + + /** + * @ngdoc directive + * @name mdFabSpeedDial + * @module material.components.fabSpeedDial + * + * @restrict E + * + * @description + * The `` directive is used to present a series of popup elements (usually + * ``s) for quick access to common actions. + * + * There are currently two animations available by applying one of the following classes to + * the component: + * + * - `md-fling` - The speed dial items appear from underneath the trigger and move into their + * appropriate positions. + * - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%. + * + * You may also easily position the trigger by applying one one of the following classes to the + * `` element: + * - `md-fab-top-left` + * - `md-fab-top-right` + * - `md-fab-bottom-left` + * - `md-fab-bottom-right` + * + * These CSS classes use `position: absolute`, so you need to ensure that the container element + * also uses `position: absolute` or `position: relative` in order for them to work. + * + * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to + * open or close the speed dial. However, if you wish to allow users to hover over the empty + * space where the actions will appear, you must also add the `md-hover-full` class to the speed + * dial element. Without this, the hover effect will only occur on top of the trigger. + * + * See the demos for more information. + * + * ## Troubleshooting + * + * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on + * the parent container to ensure that it is only visible once ready. We have plans to remove this + * necessity in the future. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param {string} md-direction From which direction you would like the speed dial to appear + * relative to the trigger element. + * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible. + */ + function MdFabSpeedDialDirective() { + return { + restrict: 'E', + + scope: { + direction: '@?mdDirection', + isOpen: '=?mdOpen' + }, + + bindToController: true, + controller: 'MdFabController', + controllerAs: 'vm', + + link: FabSpeedDialLink + }; + + function FabSpeedDialLink(scope, element) { + // Prepend an element to hold our CSS variables so we can use them in the animations below + element.prepend('
'); + } + } + + function MdFabSpeedDialFlingAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + + function runAnimation(element) { + // Don't run if we are still waiting and we are not ready + if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) { + return; + } + + var el = element[0]; + var ctrl = element.controller('mdFabSpeedDial'); + var items = el.querySelectorAll('.md-fab-action-item'); + + // Grab our trigger element + var triggerElement = el.querySelector('md-fab-trigger'); + + // Grab our element which stores CSS variables + var variablesElement = el.querySelector('._md-css-variables'); + + // Setup JS variables based on our CSS variables + var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex); + + // Always reset the items to their natural position/state + angular.forEach(items, function(item, index) { + var styles = item.style; + + styles.transform = styles.webkitTransform = ''; + styles.transitionDelay = ''; + styles.opacity = 1; + + // Make the items closest to the trigger have the highest z-index + styles.zIndex = (items.length - index) + startZIndex; + }); + + // Set the trigger to be above all of the actions so they disappear behind it. + triggerElement.style.zIndex = startZIndex + items.length + 1; + + // If the control is closed, hide the items behind the trigger + if (!ctrl.isOpen) { + angular.forEach(items, function(item, index) { + var newPosition, axis; + var styles = item.style; + + // Make sure to account for differences in the dimensions of the trigger verses the items + // so that we can properly center everything; this helps hide the item's shadows behind + // the trigger. + var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2; + var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2; + + switch (ctrl.direction) { + case 'up': + newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset); + axis = 'Y'; + break; + case 'down': + newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset); + axis = 'Y'; + break; + case 'left': + newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset); + axis = 'X'; + break; + case 'right': + newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset); + axis = 'X'; + break; + } + + var newTranslate = 'translate' + axis + '(' + newPosition + 'px)'; + + styles.transform = styles.webkitTransform = newTranslate; + }); + } + } + + return { + addClass: function(element, className, done) { + if (element.hasClass('md-fling')) { + runAnimation(element); + delayDone(done); + } else { + done(); + } + }, + removeClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + } + }; + } + + function MdFabSpeedDialScaleAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + + var delay = 65; + + function runAnimation(element) { + var el = element[0]; + var ctrl = element.controller('mdFabSpeedDial'); + var items = el.querySelectorAll('.md-fab-action-item'); + + // Grab our element which stores CSS variables + var variablesElement = el.querySelector('._md-css-variables'); + + // Setup JS variables based on our CSS variables + var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex); + + // Always reset the items to their natural position/state + angular.forEach(items, function(item, index) { + var styles = item.style, + offsetDelay = index * delay; + + styles.opacity = ctrl.isOpen ? 1 : 0; + styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)'; + styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms'; + + // Make the items closest to the trigger have the highest z-index + styles.zIndex = (items.length - index) + startZIndex; + }); + } + + return { + addClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + }, + + removeClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + } + }; + } +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc module + * @name material.components.fabToolbar + */ + angular + // Declare our module + .module('material.components.fabToolbar', [ + 'material.core', + 'material.components.fabShared', + 'material.components.fabActions' + ]) + + // Register our directive + .directive('mdFabToolbar', MdFabToolbarDirective) + + // Register our custom animations + .animation('.md-fab-toolbar', MdFabToolbarAnimation) + + // Register a service for the animation so that we can easily inject it into unit tests + .service('mdFabToolbarAnimation', MdFabToolbarAnimation); + + /** + * @ngdoc directive + * @name mdFabToolbar + * @module material.components.fabToolbar + * + * @restrict E + * + * @description + * + * The `` directive is used to present a toolbar of elements (usually ``s) + * for quick access to common actions when a floating action button is activated (via click or + * keyboard navigation). + * + * You may also easily position the trigger by applying one one of the following classes to the + * `` element: + * - `md-fab-top-left` + * - `md-fab-top-right` + * - `md-fab-bottom-left` + * - `md-fab-bottom-right` + * + * These CSS classes use `position: absolute`, so you need to ensure that the container element + * also uses `position: absolute` or `position: relative` in order for them to work. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param {string} md-direction From which direction you would like the toolbar items to appear + * relative to the trigger element. Supports `left` and `right` directions. + * @param {expression=} md-open Programmatically control whether or not the toolbar is visible. + */ + function MdFabToolbarDirective() { + return { + restrict: 'E', + transclude: true, + template: '
' + + '
' + + '
', + + scope: { + direction: '@?mdDirection', + isOpen: '=?mdOpen' + }, + + bindToController: true, + controller: 'MdFabController', + controllerAs: 'vm', + + link: link + }; + + function link(scope, element, attributes) { + // Add the base class for animations + element.addClass('md-fab-toolbar'); + + // Prepend the background element to the trigger's button + element.find('md-fab-trigger').find('button') + .prepend('
'); + } + } + + function MdFabToolbarAnimation() { + + function runAnimation(element, className, done) { + // If no className was specified, don't do anything + if (!className) { + return; + } + + var el = element[0]; + var ctrl = element.controller('mdFabToolbar'); + + // Grab the relevant child elements + var backgroundElement = el.querySelector('.md-fab-toolbar-background'); + var triggerElement = el.querySelector('md-fab-trigger button'); + var toolbarElement = el.querySelector('md-toolbar'); + var iconElement = el.querySelector('md-fab-trigger button md-icon'); + var actions = element.find('md-fab-actions').children(); + + // If we have both elements, use them to position the new background + if (triggerElement && backgroundElement) { + // Get our variables + var color = window.getComputedStyle(triggerElement).getPropertyValue('background-color'); + var width = el.offsetWidth; + var height = el.offsetHeight; + + // Make it twice as big as it should be since we scale from the center + var scale = 2 * (width / triggerElement.offsetWidth); + + // Set some basic styles no matter what animation we're doing + backgroundElement.style.backgroundColor = color; + backgroundElement.style.borderRadius = width + 'px'; + + // If we're open + if (ctrl.isOpen) { + // Turn on toolbar pointer events when closed + toolbarElement.style.pointerEvents = 'inherit'; + + backgroundElement.style.width = triggerElement.offsetWidth + 'px'; + backgroundElement.style.height = triggerElement.offsetHeight + 'px'; + backgroundElement.style.transform = 'scale(' + scale + ')'; + + // Set the next close animation to have the proper delays + backgroundElement.style.transitionDelay = '0ms'; + iconElement && (iconElement.style.transitionDelay = '.3s'); + + // Apply a transition delay to actions + angular.forEach(actions, function(action, index) { + action.style.transitionDelay = (actions.length - index) * 25 + 'ms'; + }); + } else { + // Turn off toolbar pointer events when closed + toolbarElement.style.pointerEvents = 'none'; + + // Scale it back down to the trigger's size + backgroundElement.style.transform = 'scale(1)'; + + // Reset the position + backgroundElement.style.top = '0'; + + if (element.hasClass('md-right')) { + backgroundElement.style.left = '0'; + backgroundElement.style.right = null; + } + + if (element.hasClass('md-left')) { + backgroundElement.style.right = '0'; + backgroundElement.style.left = null; + } + + // Set the next open animation to have the proper delays + backgroundElement.style.transitionDelay = '200ms'; + iconElement && (iconElement.style.transitionDelay = '0ms'); + + // Apply a transition delay to actions + angular.forEach(actions, function(action, index) { + action.style.transitionDelay = 200 + (index * 25) + 'ms'; + }); + } + } + } + + return { + addClass: function(element, className, done) { + runAnimation(element, className, done); + done(); + }, + + removeClass: function(element, className, done) { + runAnimation(element, className, done); + done(); + } + }; + } +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.icon + * @description + * Icon + */ +angular.module('material.components.icon', ['material.core']); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.gridList + */ +GridListController.$inject = ["$mdUtil"]; +GridLayoutFactory.$inject = ["$mdUtil"]; +GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"]; +GridTileDirective.$inject = ["$mdMedia"]; +angular.module('material.components.gridList', ['material.core']) + .directive('mdGridList', GridListDirective) + .directive('mdGridTile', GridTileDirective) + .directive('mdGridTileFooter', GridTileCaptionDirective) + .directive('mdGridTileHeader', GridTileCaptionDirective) + .factory('$mdGridLayout', GridLayoutFactory); + +/** + * @ngdoc directive + * @name mdGridList + * @module material.components.gridList + * @restrict E + * @description + * Grid lists are an alternative to standard list views. Grid lists are distinct + * from grids used for layouts and other visual presentations. + * + * A grid list is best suited to presenting a homogenous data type, typically + * images, and is optimized for visual comprehension and differentiating between + * like data types. + * + * A grid list is a continuous element consisting of tessellated, regular + * subdivisions called cells that contain tiles (`md-grid-tile`). + * + * Concept of grid explained visually + * Grid concepts legend + * + * Cells are arrayed vertically and horizontally within the grid. + * + * Tiles hold content and can span one or more cells vertically or horizontally. + * + * ### Responsive Attributes + * + * The `md-grid-list` directive supports "responsive" attributes, which allow + * different `md-cols`, `md-gutter` and `md-row-height` values depending on the + * currently matching media query. + * + * In order to set a responsive attribute, first define the fallback value with + * the standard attribute name, then add additional attributes with the + * following convention: `{base-attribute-name}-{media-query-name}="{value}"` + * (ie. `md-cols-lg="8"`) + * + * @param {number} md-cols Number of columns in the grid. + * @param {string} md-row-height One of + *
    + *
  • CSS length - Fixed height rows (eg. `8px` or `1rem`)
  • + *
  • `{width}:{height}` - Ratio of width to height (eg. + * `md-row-height="16:9"`)
  • + *
  • `"fit"` - Height will be determined by subdividing the available + * height by the number of rows
  • + *
+ * @param {string=} md-gutter The amount of space between tiles in CSS units + * (default 1px) + * @param {expression=} md-on-layout Expression to evaluate after layout. Event + * object is available as `$event`, and contains performance information. + * + * @usage + * Basic: + * + * + * + * + * + * + * Fixed-height rows: + * + * + * + * + * + * + * Fit rows: + * + * + * + * + * + * + * Using responsive attributes: + * + * + * + * + * + */ +function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) { + return { + restrict: 'E', + controller: GridListController, + scope: { + mdOnLayout: '&' + }, + link: postLink + }; + + function postLink(scope, element, attrs, ctrl) { + element.addClass('_md'); // private md component indicator for styling + + // Apply semantics + element.attr('role', 'list'); + + // Provide the controller with a way to trigger layouts. + ctrl.layoutDelegate = layoutDelegate; + + var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout), + unwatchAttrs = watchMedia(); + scope.$on('$destroy', unwatchMedia); + + /** + * Watches for changes in media, invalidating layout as necessary. + */ + function watchMedia() { + for (var mediaName in $mdConstant.MEDIA) { + $mdMedia(mediaName); // initialize + $mdMedia.getQuery($mdConstant.MEDIA[mediaName]) + .addListener(invalidateLayout); + } + return $mdMedia.watchResponsiveAttributes( + ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch); + } + + function unwatchMedia() { + ctrl.layoutDelegate = angular.noop; + + unwatchAttrs(); + for (var mediaName in $mdConstant.MEDIA) { + $mdMedia.getQuery($mdConstant.MEDIA[mediaName]) + .removeListener(invalidateLayout); + } + } + + /** + * Performs grid layout if the provided mediaName matches the currently + * active media type. + */ + function layoutIfMediaMatch(mediaName) { + if (mediaName == null) { + // TODO(shyndman): It would be nice to only layout if we have + // instances of attributes using this media type + ctrl.invalidateLayout(); + } else if ($mdMedia(mediaName)) { + ctrl.invalidateLayout(); + } + } + + var lastLayoutProps; + + /** + * Invokes the layout engine, and uses its results to lay out our + * tile elements. + * + * @param {boolean} tilesInvalidated Whether tiles have been + * added/removed/moved since the last layout. This is to avoid situations + * where tiles are replaced with properties identical to their removed + * counterparts. + */ + function layoutDelegate(tilesInvalidated) { + var tiles = getTileElements(); + var props = { + tileSpans: getTileSpans(tiles), + colCount: getColumnCount(), + rowMode: getRowMode(), + rowHeight: getRowHeight(), + gutter: getGutter() + }; + + if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) { + return; + } + + var performance = + $mdGridLayout(props.colCount, props.tileSpans, tiles) + .map(function(tilePositions, rowCount) { + return { + grid: { + element: element, + style: getGridStyle(props.colCount, rowCount, + props.gutter, props.rowMode, props.rowHeight) + }, + tiles: tilePositions.map(function(ps, i) { + return { + element: angular.element(tiles[i]), + style: getTileStyle(ps.position, ps.spans, + props.colCount, rowCount, + props.gutter, props.rowMode, props.rowHeight) + } + }) + } + }) + .reflow() + .performance(); + + // Report layout + scope.mdOnLayout({ + $event: { + performance: performance + } + }); + + lastLayoutProps = props; + } + + // Use $interpolate to do some simple string interpolation as a convenience. + + var startSymbol = $interpolate.startSymbol(); + var endSymbol = $interpolate.endSymbol(); + + // Returns an expression wrapped in the interpolator's start and end symbols. + function expr(exprStr) { + return startSymbol + exprStr + endSymbol; + } + + // The amount of space a single 1x1 tile would take up (either width or height), used as + // a basis for other calculations. This consists of taking the base size percent (as would be + // if evenly dividing the size between cells), and then subtracting the size of one gutter. + // However, since there are no gutters on the edges, each tile only uses a fration + // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per + // tile, and then breaking up the extra gutter on the edge evenly among the cells). + var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')'); + + // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value. + // The position comes the size of a 1x1 tile plus gutter for each previous tile in the + // row/column (offset). + var POSITION = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')'); + + // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account. + // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back + // in the space that the gutter would normally have used (which was already accounted for in + // the base unit calculation). + var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')'); + + /** + * Gets the styles applied to a tile element described by the given parameters. + * @param {{row: number, col: number}} position The row and column indices of the tile. + * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile. + * @param {number} colCount The number of columns. + * @param {number} rowCount The number of rows. + * @param {string} gutter The amount of space between tiles. This will be something like + * '5px' or '2em'. + * @param {string} rowMode The row height mode. Can be one of: + * 'fixed': all rows have a fixed size, given by rowHeight, + * 'ratio': row height defined as a ratio to width, or + * 'fit': fit to the grid-list element height, divinding evenly among rows. + * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and + * for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75). + * @returns {Object} Map of CSS properties to be applied to the style element. Will define + * values for top, left, width, height, marginTop, and paddingTop. + */ + function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) { + // TODO(shyndman): There are style caching opportunities here. + + // Percent of the available horizontal space that one column takes up. + var hShare = (1 / colCount) * 100; + + // Fraction of the gutter size that each column takes up. + var hGutterShare = (colCount - 1) / colCount; + + // Base horizontal size of a column. + var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter}); + + // The width and horizontal position of each tile is always calculated the same way, but the + // height and vertical position depends on the rowMode. + var ltr = document.dir != 'rtl' && document.body.dir != 'rtl'; + var style = ltr ? { + left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }), + width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }), + // resets + paddingTop: '', + marginTop: '', + top: '', + height: '' + } : { + right: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }), + width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }), + // resets + paddingTop: '', + marginTop: '', + top: '', + height: '' + }; + + switch (rowMode) { + case 'fixed': + // In fixed mode, simply use the given rowHeight. + style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter }); + style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter }); + break; + + case 'ratio': + // Percent of the available vertical space that one row takes up. Here, rowHeight holds + // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333. + var vShare = hShare / rowHeight; + + // Base veritcal size of a row. + var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter }); + + // padidngTop and marginTop are used to maintain the given aspect ratio, as + // a percentage-based value for these properties is applied to the *width* of the + // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties + style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter}); + style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter }); + break; + + case 'fit': + // Fraction of the gutter size that each column takes up. + var vGutterShare = (rowCount - 1) / rowCount; + + // Percent of the available vertical space that one row takes up. + var vShare = (1 / rowCount) * 100; + + // Base vertical size of a row. + var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter}); + + style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter}); + style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter}); + break; + } + + return style; + } + + function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) { + var style = {}; + + switch(rowMode) { + case 'fixed': + style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter }); + style.paddingBottom = ''; + break; + + case 'ratio': + // rowHeight is width / height + var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount, + hShare = (1 / colCount) * 100, + vShare = hShare * (1 / rowHeight), + vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter }); + + style.height = ''; + style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter}); + break; + + case 'fit': + // noop, as the height is user set + break; + } + + return style; + } + + function getTileElements() { + return [].filter.call(element.children(), function(ele) { + return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed; + }); + } + + /** + * Gets an array of objects containing the rowspan and colspan for each tile. + * @returns {Array<{row: number, col: number}>} + */ + function getTileSpans(tileElements) { + return [].map.call(tileElements, function(ele) { + var ctrl = angular.element(ele).controller('mdGridTile'); + return { + row: parseInt( + $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1, + col: parseInt( + $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1 + }; + }); + } + + function getColumnCount() { + var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10); + if (isNaN(colCount)) { + throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value'; + } + return colCount; + } + + function getGutter() { + return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1); + } + + function getRowHeight() { + var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height'); + if (!rowHeight) { + throw 'md-grid-list: md-row-height attribute was not found'; + } + + switch (getRowMode()) { + case 'fixed': + return applyDefaultUnit(rowHeight); + case 'ratio': + var whRatio = rowHeight.split(':'); + return parseFloat(whRatio[0]) / parseFloat(whRatio[1]); + case 'fit': + return 0; // N/A + } + } + + function getRowMode() { + var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height'); + if (!rowHeight) { + throw 'md-grid-list: md-row-height attribute was not found'; + } + + if (rowHeight == 'fit') { + return 'fit'; + } else if (rowHeight.indexOf(':') !== -1) { + return 'ratio'; + } else { + return 'fixed'; + } + } + + function applyDefaultUnit(val) { + return /\D$/.test(val) ? val : val + 'px'; + } + } +} + +/* @ngInject */ +function GridListController($mdUtil) { + this.layoutInvalidated = false; + this.tilesInvalidated = false; + this.$timeout_ = $mdUtil.nextTick; + this.layoutDelegate = angular.noop; +} + +GridListController.prototype = { + invalidateTiles: function() { + this.tilesInvalidated = true; + this.invalidateLayout(); + }, + + invalidateLayout: function() { + if (this.layoutInvalidated) { + return; + } + this.layoutInvalidated = true; + this.$timeout_(angular.bind(this, this.layout)); + }, + + layout: function() { + try { + this.layoutDelegate(this.tilesInvalidated); + } finally { + this.layoutInvalidated = false; + this.tilesInvalidated = false; + } + } +}; + + +/* @ngInject */ +function GridLayoutFactory($mdUtil) { + var defaultAnimator = GridTileAnimator; + + /** + * Set the reflow animator callback + */ + GridLayout.animateWith = function(customAnimator) { + defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator; + }; + + return GridLayout; + + /** + * Publish layout function + */ + function GridLayout(colCount, tileSpans) { + var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime; + + layoutTime = $mdUtil.time(function() { + layoutInfo = calculateGridFor(colCount, tileSpans); + }); + + return self = { + + /** + * An array of objects describing each tile's position in the grid. + */ + layoutInfo: function() { + return layoutInfo; + }, + + /** + * Maps grid positioning to an element and a set of styles using the + * provided updateFn. + */ + map: function(updateFn) { + mapTime = $mdUtil.time(function() { + var info = self.layoutInfo(); + gridStyles = updateFn(info.positioning, info.rowCount); + }); + return self; + }, + + /** + * Default animator simply sets the element.css( ). An alternate + * animator can be provided as an argument. The function has the following + * signature: + * + * function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>) + */ + reflow: function(animatorFn) { + reflowTime = $mdUtil.time(function() { + var animator = animatorFn || defaultAnimator; + animator(gridStyles.grid, gridStyles.tiles); + }); + return self; + }, + + /** + * Timing for the most recent layout run. + */ + performance: function() { + return { + tileCount: tileSpans.length, + layoutTime: layoutTime, + mapTime: mapTime, + reflowTime: reflowTime, + totalTime: layoutTime + mapTime + reflowTime + }; + } + }; + } + + /** + * Default Gridlist animator simple sets the css for each element; + * NOTE: any transitions effects must be manually set in the CSS. + * e.g. + * + * md-grid-tile { + * transition: all 700ms ease-out 50ms; + * } + * + */ + function GridTileAnimator(grid, tiles) { + grid.element.css(grid.style); + tiles.forEach(function(t) { + t.element.css(t.style); + }) + } + + /** + * Calculates the positions of tiles. + * + * The algorithm works as follows: + * An Array with length colCount (spaceTracker) keeps track of + * available tiling positions, where elements of value 0 represents an + * empty position. Space for a tile is reserved by finding a sequence of + * 0s with length <= than the tile's colspan. When such a space has been + * found, the occupied tile positions are incremented by the tile's + * rowspan value, as these positions have become unavailable for that + * many rows. + * + * If the end of a row has been reached without finding space for the + * tile, spaceTracker's elements are each decremented by 1 to a minimum + * of 0. Rows are searched in this fashion until space is found. + */ + function calculateGridFor(colCount, tileSpans) { + var curCol = 0, + curRow = 0, + spaceTracker = newSpaceTracker(); + + return { + positioning: tileSpans.map(function(spans, i) { + return { + spans: spans, + position: reserveSpace(spans, i) + }; + }), + rowCount: curRow + Math.max.apply(Math, spaceTracker) + }; + + function reserveSpace(spans, i) { + if (spans.col > colCount) { + throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' + + '(' + spans.col + ') that exceeds the column count ' + + '(' + colCount + ')'; + } + + var start = 0, + end = 0; + + // TODO(shyndman): This loop isn't strictly necessary if you can + // determine the minimum number of rows before a space opens up. To do + // this, recognize that you've iterated across an entire row looking for + // space, and if so fast-forward by the minimum rowSpan count. Repeat + // until the required space opens up. + while (end - start < spans.col) { + if (curCol >= colCount) { + nextRow(); + continue; + } + + start = spaceTracker.indexOf(0, curCol); + if (start === -1 || (end = findEnd(start + 1)) === -1) { + start = end = 0; + nextRow(); + continue; + } + + curCol = end + 1; + } + + adjustRow(start, spans.col, spans.row); + curCol = start + spans.col; + + return { + col: start, + row: curRow + }; + } + + function nextRow() { + curCol = 0; + curRow++; + adjustRow(0, colCount, -1); // Decrement row spans by one + } + + function adjustRow(from, cols, by) { + for (var i = from; i < from + cols; i++) { + spaceTracker[i] = Math.max(spaceTracker[i] + by, 0); + } + } + + function findEnd(start) { + var i; + for (i = start; i < spaceTracker.length; i++) { + if (spaceTracker[i] !== 0) { + return i; + } + } + + if (i === spaceTracker.length) { + return i; + } + } + + function newSpaceTracker() { + var tracker = []; + for (var i = 0; i < colCount; i++) { + tracker.push(0); + } + return tracker; + } + } +} + +/** + * @ngdoc directive + * @name mdGridTile + * @module material.components.gridList + * @restrict E + * @description + * Tiles contain the content of an `md-grid-list`. They span one or more grid + * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to + * display secondary content. + * + * ### Responsive Attributes + * + * The `md-grid-tile` directive supports "responsive" attributes, which allow + * different `md-rowspan` and `md-colspan` values depending on the currently + * matching media query. + * + * In order to set a responsive attribute, first define the fallback value with + * the standard attribute name, then add additional attributes with the + * following convention: `{base-attribute-name}-{media-query-name}="{value}"` + * (ie. `md-colspan-sm="4"`) + * + * @param {number=} md-colspan The number of columns to span (default 1). Cannot + * exceed the number of columns in the grid. Supports interpolation. + * @param {number=} md-rowspan The number of rows to span (default 1). Supports + * interpolation. + * + * @usage + * With header: + * + * + * + *

This is a header

+ *
+ *
+ *
+ * + * With footer: + * + * + * + *

This is a footer

+ *
+ *
+ *
+ * + * Spanning multiple rows/columns: + * + * + * + * + * + * Responsive attributes: + * + * + * + * + */ +function GridTileDirective($mdMedia) { + return { + restrict: 'E', + require: '^mdGridList', + template: '
', + transclude: true, + scope: {}, + // Simple controller that exposes attributes to the grid directive + controller: ["$attrs", function($attrs) { + this.$attrs = $attrs; + }], + link: postLink + }; + + function postLink(scope, element, attrs, gridCtrl) { + // Apply semantics + element.attr('role', 'listitem'); + + // If our colspan or rowspan changes, trigger a layout + var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'], + attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout)); + + // Tile registration/deregistration + gridCtrl.invalidateTiles(); + scope.$on('$destroy', function() { + // Mark the tile as destroyed so it is no longer considered in layout, + // even if the DOM element sticks around (like during a leave animation) + element[0].$$mdDestroyed = true; + unwatchAttrs(); + gridCtrl.invalidateLayout(); + }); + + if (angular.isDefined(scope.$parent.$index)) { + scope.$watch(function() { return scope.$parent.$index; }, + function indexChanged(newIdx, oldIdx) { + if (newIdx === oldIdx) { + return; + } + gridCtrl.invalidateTiles(); + }); + } + } +} + + +function GridTileCaptionDirective() { + return { + template: '
', + transclude: true + }; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.input + */ +mdInputContainerDirective.$inject = ["$mdTheming", "$parse"]; +inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"]; +mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"]; +placeholderDirective.$inject = ["$compile"]; +ngMessageDirective.$inject = ["$mdUtil"]; +mdSelectOnFocusDirective.$inject = ["$timeout"]; +mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"]; +ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"]; +ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"]; +var inputModule = angular.module('material.components.input', [ + 'material.core' + ]) + .directive('mdInputContainer', mdInputContainerDirective) + .directive('label', labelDirective) + .directive('input', inputTextareaDirective) + .directive('textarea', inputTextareaDirective) + .directive('mdMaxlength', mdMaxlengthDirective) + .directive('placeholder', placeholderDirective) + .directive('ngMessages', ngMessagesDirective) + .directive('ngMessage', ngMessageDirective) + .directive('ngMessageExp', ngMessageDirective) + .directive('mdSelectOnFocus', mdSelectOnFocusDirective) + + .animation('.md-input-invalid', mdInputInvalidMessagesAnimation) + .animation('.md-input-messages-animation', ngMessagesAnimation) + .animation('.md-input-message-animation', ngMessageAnimation); + +// If we are running inside of tests; expose some extra services so that we can test them +if (window._mdMocksIncluded) { + inputModule.service('$$mdInput', function() { + return { + // special accessor to internals... useful for testing + messages: { + show : showInputMessages, + hide : hideInputMessages, + getElement : getMessagesElement + } + } + }) + + // Register a service for each animation so that we can easily inject them into unit tests + .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation) + .service('mdInputMessagesAnimation', ngMessagesAnimation) + .service('mdInputMessageAnimation', ngMessageAnimation); +} + +/** + * @ngdoc directive + * @name mdInputContainer + * @module material.components.input + * + * @restrict E + * + * @description + * `` is the parent of any input or textarea element. + * + * Input and textarea elements will not behave properly unless the md-input-container + * parent is provided. + * + * A single `` should contain only one `` element, otherwise it will throw an error. + * + * Exception: Hidden inputs (``) are ignored and will not throw an error, so + * you may combine these with other inputs. + * + * Note: When using `ngMessages` with your input element, make sure the message and container elements + * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and + * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element. + * + * @param md-is-error {expression=} When the given expression evaluates to true, the input container + * will go into error state. Defaults to erroring if the input has been touched and is invalid. + * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating + * labels. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *

When disabling floating labels

+ * + * + * + * + * + * + * + */ +function mdInputContainerDirective($mdTheming, $parse) { + + ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"]; + var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT']; + + var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) { + return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]); + }, []).join(","); + + var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) { + return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']); + }, []).join(","); + + return { + restrict: 'E', + compile: compile, + controller: ContainerCtrl + }; + + function compile(tElement) { + // Check for both a left & right icon + var leftIcon = tElement[0].querySelector(LEFT_SELECTORS); + var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS); + + if (leftIcon) { tElement.addClass('md-icon-left'); } + if (rightIcon) { tElement.addClass('md-icon-right'); } + + return function postLink(scope, element) { + $mdTheming(element); + }; + } + + function ContainerCtrl($scope, $element, $attrs, $animate) { + var self = this; + + self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError); + + self.delegateClick = function() { + self.input.focus(); + }; + self.element = $element; + self.setFocused = function(isFocused) { + $element.toggleClass('md-input-focused', !!isFocused); + }; + self.setHasValue = function(hasValue) { + $element.toggleClass('md-input-has-value', !!hasValue); + }; + self.setHasPlaceholder = function(hasPlaceholder) { + $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder); + }; + self.setInvalid = function(isInvalid) { + if (isInvalid) { + $animate.addClass($element, 'md-input-invalid'); + } else { + $animate.removeClass($element, 'md-input-invalid'); + } + }; + $scope.$watch(function() { + return self.label && self.input; + }, function(hasLabelAndInput) { + if (hasLabelAndInput && !self.label.attr('for')) { + self.label.attr('for', self.input.attr('id')); + } + }); + } +} + +function labelDirective() { + return { + restrict: 'E', + require: '^?mdInputContainer', + link: function(scope, element, attr, containerCtrl) { + if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return; + + containerCtrl.label = element; + scope.$on('$destroy', function() { + containerCtrl.label = null; + }); + } + }; +} + +/** + * @ngdoc directive + * @name mdInput + * @restrict E + * @module material.components.input + * + * @description + * You can use any `` or ` + *
+ *
This is required!
+ *
That's too long!
+ *
+ *
+ * + * + * + * + * + * + * + * + * + *

Notes

+ * + * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages). + * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input). + * + * The `md-input` and `md-input-container` directives use very specific positioning to achieve the + * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the + * `` tags. Instead, use relative or absolute positioning. + * + * + *

Textarea directive

+ * The `textarea` element within a `md-input-container` has the following specific behavior: + * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow` + * attribute. + * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will + * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text + * high initially. If no rows are specified, the directive defaults to 1. + * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations + * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In + * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope. + * - If you want a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute. + * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically. + * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a + * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute. + */ + +function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) { + return { + restrict: 'E', + require: ['^?mdInputContainer', '?ngModel', '?^form'], + link: postLink + }; + + function postLink(scope, element, attr, ctrls) { + + var containerCtrl = ctrls[0]; + var hasNgModel = !!ctrls[1]; + var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel(); + var parentForm = ctrls[2]; + var isReadonly = angular.isDefined(attr.readonly); + var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk); + var tagName = element[0].tagName.toLowerCase(); + + + if (!containerCtrl) return; + if (attr.type === 'hidden') { + element.attr('aria-hidden', 'true'); + return; + } else if (containerCtrl.input) { + if (containerCtrl.input[0].contains(element[0])) { + return; + } else { + throw new Error(" can only have *one* , + * + * + * + */ +function mdSelectOnFocusDirective($timeout) { + + return { + restrict: 'A', + link: postLink + }; + + function postLink(scope, element, attr) { + if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return; + + var preventMouseUp = false; + + element + .on('focus', onFocus) + .on('mouseup', onMouseUp); + + scope.$on('$destroy', function() { + element + .off('focus', onFocus) + .off('mouseup', onMouseUp); + }); + + function onFocus() { + preventMouseUp = true; + + $timeout(function() { + // Use HTMLInputElement#select to fix firefox select issues. + // The debounce is here for Edge's sake, otherwise the selection doesn't work. + element[0].select(); + + // This should be reset from inside the `focus`, because the event might + // have originated from something different than a click, e.g. a keyboard event. + preventMouseUp = false; + }, 1, false); + } + + // Prevents the default action of the first `mouseup` after a focus. + // This is necessary, because browsers fire a `mouseup` right after the element + // has been focused. In some browsers (Firefox in particular) this can clear the + // selection. There are examples of the problem in issue #7487. + function onMouseUp(event) { + if (preventMouseUp) { + event.preventDefault(); + } + } + } +} + +var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault']; +function ngMessagesDirective() { + return { + restrict: 'EA', + link: postLink, + + // This is optional because we don't want target *all* ngMessage instances, just those inside of + // mdInputContainer. + require: '^^?mdInputContainer' + }; + + function postLink(scope, element, attrs, inputContainer) { + // If we are not a child of an input container, don't do anything + if (!inputContainer) return; + + // Add our animation class + element.toggleClass('md-input-messages-animation', true); + + // Add our md-auto-hide class to automatically hide/show messages when container is invalid + element.toggleClass('md-auto-hide', true); + + // If we see some known visibility directives, remove the md-auto-hide class + if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) { + element.toggleClass('md-auto-hide', false); + } + } + + function hasVisibiltyDirective(attrs) { + return visibilityDirectives.some(function(attr) { + return attrs[attr]; + }); + } +} + +function ngMessageDirective($mdUtil) { + return { + restrict: 'EA', + compile: compile, + priority: 100 + }; + + function compile(tElement) { + if (!isInsideInputContainer(tElement)) { + + // When the current element is inside of a document fragment, then we need to check for an input-container + // in the postLink, because the element will be later added to the DOM and is currently just in a temporary + // fragment, which causes the input-container check to fail. + if (isInsideFragment()) { + return function (scope, element) { + if (isInsideInputContainer(element)) { + // Inside of the postLink function, a ngMessage directive will be a comment element, because it's + // currently hidden. To access the shown element, we need to use the element from the compile function. + initMessageElement(tElement); + } + }; + } + } else { + initMessageElement(tElement); + } + + function isInsideFragment() { + var nextNode = tElement[0]; + while (nextNode = nextNode.parentNode) { + if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return true; + } + } + return false; + } + + function isInsideInputContainer(element) { + return !!$mdUtil.getClosest(element, "md-input-container"); + } + + function initMessageElement(element) { + // Add our animation class + element.toggleClass('md-input-message-animation', true); + } + } +} + +var $$AnimateRunner, $animateCss, $mdUtil, $log; + +function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log); + + return { + addClass: function(element, className, done) { + showInputMessages(element, done); + } + + // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire + }; +} + +function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log); + + return { + enter: function(element, done) { + showInputMessages(element, done); + }, + + leave: function(element, done) { + hideInputMessages(element, done); + }, + + addClass: function(element, className, done) { + if (className == "ng-hide") { + hideInputMessages(element, done); + } else { + done(); + } + }, + + removeClass: function(element, className, done) { + if (className == "ng-hide") { + showInputMessages(element, done); + } else { + done(); + } + } + }; +} + +function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log); + + return { + enter: function(element, done) { + var animator = showMessage(element); + + animator.start().done(done); + }, + + leave: function(element, done) { + var animator = hideMessage(element); + + animator.start().done(done); + } + }; +} + +function showInputMessages(element, done) { + var animators = [], animator; + var messages = getMessagesElement(element); + var children = messages.children(); + + if (messages.length == 0 || children.length == 0) { + $log.warn('mdInput messages show animation called on invalid messages element: ', element); + done(); + return; + } + + angular.forEach(children, function(child) { + animator = showMessage(angular.element(child)); + + animators.push(animator.start()); + }); + + $$AnimateRunner.all(animators, done); +} + +function hideInputMessages(element, done) { + var animators = [], animator; + var messages = getMessagesElement(element); + var children = messages.children(); + + if (messages.length == 0 || children.length == 0) { + $log.warn('mdInput messages hide animation called on invalid messages element: ', element); + done(); + return; + } + + angular.forEach(children, function(child) { + animator = hideMessage(angular.element(child)); + + animators.push(animator.start()); + }); + + $$AnimateRunner.all(animators, done); +} + +function showMessage(element) { + var height = parseInt(window.getComputedStyle(element[0]).height); + var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop); + + var messages = getMessagesElement(element); + var container = getInputElement(element); + + // Check to see if the message is already visible so we can skip + var alreadyVisible = (topMargin > -height); + + // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip + if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) { + return $animateCss(element, {}); + } + + return $animateCss(element, { + event: 'enter', + structural: true, + from: {"opacity": 0, "margin-top": -height + "px"}, + to: {"opacity": 1, "margin-top": "0"}, + duration: 0.3 + }); +} + +function hideMessage(element) { + var height = element[0].offsetHeight; + var styles = window.getComputedStyle(element[0]); + + // If we are already hidden, just return an empty animation + if (parseInt(styles.opacity) === 0) { + return $animateCss(element, {}); + } + + // Otherwise, animate + return $animateCss(element, { + event: 'leave', + structural: true, + from: {"opacity": 1, "margin-top": 0}, + to: {"opacity": 0, "margin-top": -height + "px"}, + duration: 0.3 + }); +} + +function getInputElement(element) { + var inputContainer = element.controller('mdInputContainer'); + + return inputContainer.element; +} + +function getMessagesElement(element) { + // If we ARE the messages element, just return ourself + if (element.hasClass('md-input-messages-animation')) { + return element; + } + + // If we are a ng-message element, we need to traverse up the DOM tree + if (element.hasClass('md-input-message-animation')) { + return angular.element($mdUtil.getClosest(element, function(node) { + return node.classList.contains('md-input-messages-animation'); + })); + } + + // Otherwise, we can traverse down + return angular.element(element[0].querySelector('.md-input-messages-animation')); +} + +function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) { + $$AnimateRunner = _$$AnimateRunner_; + $animateCss = _$animateCss_; + $mdUtil = _$mdUtil_; + $log = _$log_; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.list + * @description + * List module + */ +MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"]; +mdListDirective.$inject = ["$mdTheming"]; +mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"]; +angular.module('material.components.list', [ + 'material.core' +]) + .controller('MdListController', MdListController) + .directive('mdList', mdListDirective) + .directive('mdListItem', mdListItemDirective); + +/** + * @ngdoc directive + * @name mdList + * @module material.components.list + * + * @restrict E + * + * @description + * The `` directive is a list container for 1..n `` tags. + * + * @usage + * + * + * + * + *
+ *

{{item.title}}

+ *

{{item.description}}

+ *
+ *
+ *
+ *
+ */ + +function mdListDirective($mdTheming) { + return { + restrict: 'E', + compile: function(tEl) { + tEl[0].setAttribute('role', 'list'); + return $mdTheming; + } + }; +} +/** + * @ngdoc directive + * @name mdListItem + * @module material.components.list + * + * @restrict E + * + * @description + * A `md-list-item` element can be used to represent some information in a row.
+ * + * @usage + * ### Single Row Item + * + * + * Single Row Item + * + * + * + * ### Multiple Lines + * By using the following markup, you will be able to have two lines inside of one `md-list-item`. + * + * + * + *
+ *

First Line

+ *

Second Line

+ *
+ *
+ *
+ * + * It is also possible to have three lines inside of one list item. + * + * + * + *
+ *

First Line

+ *

Second Line

+ *

Third Line

+ *
+ *
+ *
+ * + * ### Secondary Items + * Secondary items are elements which will be aligned at the end of the `md-list-item`. + * + * + * + * Single Row Item + * + * Secondary Button + * + * + * + * + * It also possible to have multiple secondary items inside of one `md-list-item`. + * + * + * + * Single Row Item + * First Button + * Second Button + * + * + * + * ### Proxy Item + * Proxies are elements, which will execute their specific action on click
+ * Currently supported proxy items are + * - `md-checkbox` (Toggle) + * - `md-switch` (Toggle) + * - `md-menu` (Open) + * + * This means, when using a supported proxy item inside of `md-list-item`, the list item will + * automatically become clickable and executes the associated action of the proxy element on click. + * + * It is possible to disable this behavior by applying the `md-no-proxy` class to the list item. + * + * + * + * No Proxy List + * + * + * + * + * Here are a few examples of proxy elements inside of a list item. + * + * + * + * First Line + * + * + * + * + * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click. + * + * + * + * First Line + * + * + * + * + * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`. + * + * It is also possible to have a `md-menu` inside of a `md-list-item`. + * + * + *

Click anywhere to fire the secondary action

+ * + * + * + * + * + * + * + * Redial + * + * + * + * + * Check voicemail + * + * + * + * + * + * Notifications + * + * + * + * + *
+ *
+ * + * The menu will automatically open, when the users clicks on the `md-list-item`.
+ * + * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the + * position mode and applies it to the `md-menu`. + * + * ### Avatars + * Sometimes you may want to have some avatars inside of the `md-list-item `.
+ * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `` element. + * + * + * + * + * Alan Turing + * + * + * When using `` for an avatar, you have to use the `.md-avatar-icon` class. + * + * + * + * Timothy Kopra + * + * + * + * In cases, you have a `md-list-item`, which doesn't have any avatar, + * but you want to align it with the other avatar items, you have to use the `.md-offset` class. + * + * + * + * Jon Doe + * + * + * + * ### DOM modification + * The `md-list-item` component automatically detects if the list item should be clickable. + * + * --- + * If the `md-list-item` is clickable, we wrap all content inside of a `
` and create + * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`) + * + * We create an overlaying button, instead of wrapping all content inside of the button, + * because otherwise some elements may not be clickable inside of the button. + * + * --- + * When using a secondary item inside of your list item, the `md-list-item` component will automatically create + * a secondary container at the end of the `md-list-item`, which contains all secondary items. + * + * The secondary item container is not static, because otherwise the overflow will not work properly on the + * list item. + * + */ +function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { + var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu']; + return { + restrict: 'E', + controller: 'MdListController', + compile: function(tEl, tAttrs) { + + // Check for proxy controls (no ng-click on parent, and a control inside) + var secondaryItems = tEl[0].querySelectorAll('.md-secondary'); + var hasProxiedElement; + var proxyElement; + var itemContainer = tEl; + + tEl[0].setAttribute('role', 'listitem'); + + if (tAttrs.ngClick || tAttrs.ngDblclick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) { + wrapIn('button'); + } else if (!tEl.hasClass('md-no-proxy')) { + + for (var i = 0, type; type = proxiedTypes[i]; ++i) { + if (proxyElement = tEl[0].querySelector(type)) { + hasProxiedElement = true; + break; + } + } + + if (hasProxiedElement) { + wrapIn('div'); + } else { + tEl.addClass('md-no-proxy'); + } + + } + + wrapSecondaryItems(); + setupToggleAria(); + + if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") { + setupProxiedMenu(); + } + + function setupToggleAria() { + var toggleTypes = ['md-switch', 'md-checkbox']; + var toggle; + + for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) { + if (toggle = tEl.find(toggleType)[0]) { + if (!toggle.hasAttribute('aria-label')) { + var p = tEl.find('p')[0]; + if (!p) return; + toggle.setAttribute('aria-label', 'Toggle ' + p.textContent); + } + } + } + } + + function setupProxiedMenu() { + var menuEl = angular.element(proxyElement); + + var isEndAligned = menuEl.parent().hasClass('md-secondary-container') || + proxyElement.parentNode.firstElementChild !== proxyElement; + + var xAxisPosition = 'left'; + + if (isEndAligned) { + // When the proxy item is aligned at the end of the list, we have to set the origin to the end. + xAxisPosition = 'right'; + } + + // Set the position mode / origin of the proxied menu. + if (!menuEl.attr('md-position-mode')) { + menuEl.attr('md-position-mode', xAxisPosition + ' target'); + } + + // Apply menu open binding to menu button + var menuOpenButton = menuEl.children().eq(0); + if (!hasClickEvent(menuOpenButton[0])) { + menuOpenButton.attr('ng-click', '$mdMenu.open($event)'); + } + + if (!menuOpenButton.attr('aria-label')) { + menuOpenButton.attr('aria-label', 'Open List Menu'); + } + } + + function wrapIn(type) { + if (type == 'div') { + itemContainer = angular.element('
'); + itemContainer.append(tEl.contents()); + tEl.addClass('md-proxy-focus'); + } else { + // Element which holds the default list-item content. + itemContainer = angular.element( + '
'+ + '
'+ + '
' + ); + + // Button which shows ripple and executes primary action. + var buttonWrap = angular.element( + '' + ); + + copyAttributes(tEl[0], buttonWrap[0]); + + // If there is no aria-label set on the button (previously copied over if present) + // we determine the label from the content and copy it to the button. + if (!buttonWrap.attr('aria-label')) { + buttonWrap.attr('aria-label', $mdAria.getText(tEl)); + } + + // We allow developers to specify the `md-no-focus` class, to disable the focus style + // on the button executor. Once more classes should be forwarded, we should probably make the + // class forward more generic. + if (tEl.hasClass('md-no-focus')) { + buttonWrap.addClass('md-no-focus'); + } + + // Append the button wrap before our list-item content, because it will overlay in relative. + itemContainer.prepend(buttonWrap); + itemContainer.children().eq(1).append(tEl.contents()); + + tEl.addClass('_md-button-wrap'); + } + + tEl[0].setAttribute('tabindex', '-1'); + tEl.append(itemContainer); + } + + function wrapSecondaryItems() { + var secondaryItemsWrapper = angular.element('
'); + + angular.forEach(secondaryItems, function(secondaryItem) { + wrapSecondaryItem(secondaryItem, secondaryItemsWrapper); + }); + + itemContainer.append(secondaryItemsWrapper); + } + + function wrapSecondaryItem(secondaryItem, container) { + // If the current secondary item is not a button, but contains a ng-click attribute, + // the secondary item will be automatically wrapped inside of a button. + if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) { + + $mdAria.expect(secondaryItem, 'aria-label'); + var buttonWrapper = angular.element(''); + + // Copy the attributes from the secondary item to the generated button. + // We also support some additional attributes from the secondary item, + // because some developers may use a ngIf, ngHide, ngShow on their item. + copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']); + + secondaryItem.setAttribute('tabindex', '-1'); + buttonWrapper.append(secondaryItem); + + secondaryItem = buttonWrapper[0]; + } + + if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) { + // In this case we remove the secondary class, so we can identify it later, when we searching for the + // proxy items. + angular.element(secondaryItem).removeClass('md-secondary'); + } + + tEl.addClass('md-with-secondary'); + container.append(secondaryItem); + } + + /** + * Copies attributes from a source element to the destination element + * By default the function will copy the most necessary attributes, supported + * by the button executor for clickable list items. + * @param source Element with the specified attributes + * @param destination Element which will retrieve the attributes + * @param extraAttrs Additional attributes, which will be copied over. + */ + function copyAttributes(source, destination, extraAttrs) { + var copiedAttrs = $mdUtil.prefixer([ + 'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref', + 'href', 'ng-href', 'rel', 'target', 'ng-attr-ui-sref', 'ui-sref-opts' + ]); + + if (extraAttrs) { + copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs)); + } + + angular.forEach(copiedAttrs, function(attr) { + if (source.hasAttribute(attr)) { + destination.setAttribute(attr, source.getAttribute(attr)); + source.removeAttribute(attr); + } + }); + } + + function isProxiedElement(el) { + return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1; + } + + function isButton(el) { + var nodeName = el.nodeName.toUpperCase(); + + return nodeName == "MD-BUTTON" || nodeName == "BUTTON"; + } + + function hasClickEvent (element) { + var attr = element.attributes; + for (var i = 0; i < attr.length; i++) { + if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true; + } + return false; + } + + return postLink; + + function postLink($scope, $element, $attr, ctrl) { + $element.addClass('_md'); // private md component indicator for styling + + var proxies = [], + firstElement = $element[0].firstElementChild, + isButtonWrap = $element.hasClass('_md-button-wrap'), + clickChild = isButtonWrap ? firstElement.firstElementChild : firstElement, + hasClick = clickChild && hasClickEvent(clickChild), + noProxies = $element.hasClass('md-no-proxy'); + + computeProxies(); + computeClickable(); + + if (proxies.length) { + angular.forEach(proxies, function(proxy) { + proxy = angular.element(proxy); + + $scope.mouseActive = false; + proxy.on('mousedown', function() { + $scope.mouseActive = true; + $timeout(function(){ + $scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if ($scope.mouseActive === false) { $element.addClass('md-focused'); } + proxy.on('blur', function proxyOnBlur() { + $element.removeClass('md-focused'); + proxy.off('blur', proxyOnBlur); + }); + }); + }); + } + + + function computeProxies() { + + if (firstElement && firstElement.children && !hasClick && !noProxies) { + + angular.forEach(proxiedTypes, function(type) { + + // All elements which are not capable for being used a proxy have the .md-secondary class + // applied. These items had been sorted out in the secondary wrap function. + angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) { + proxies.push(child); + }); + }); + + } + } + + function computeClickable() { + if (proxies.length == 1 || hasClick) { + $element.addClass('md-clickable'); + + if (!hasClick) { + ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style'))); + } + } + } + + function isEventFromControl(event) { + var forbiddenControls = ['md-slider']; + + // If there is no path property in the event, then we can assume that the event was not bubbled. + if (!event.path) { + return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1; + } + + // We iterate the event path up and check for a possible component. + // Our maximum index to search, is the list item root. + var maxPath = event.path.indexOf($element.children()[0]); + + for (var i = 0; i < maxPath; i++) { + if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) { + return true; + } + } + } + + var clickChildKeypressListener = function(e) { + if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) { + var keyCode = e.which || e.keyCode; + if (keyCode == $mdConstant.KEY_CODE.SPACE) { + if (clickChild) { + clickChild.click(); + e.preventDefault(); + e.stopPropagation(); + } + } + } + }; + + if (!hasClick && !proxies.length) { + clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener); + } + + $element.off('click'); + $element.off('keypress'); + + if (proxies.length == 1 && clickChild) { + $element.children().eq(0).on('click', function(e) { + // When the event is coming from an control and it should not trigger the proxied element + // then we are skipping. + if (isEventFromControl(e)) return; + + var parentButton = $mdUtil.getClosest(e.target, 'BUTTON'); + if (!parentButton && clickChild.contains(e.target)) { + angular.forEach(proxies, function(proxy) { + if (e.target !== proxy && !proxy.contains(e.target)) { + if (proxy.nodeName === 'MD-MENU') { + proxy = proxy.children[0]; + } + angular.element(proxy).triggerHandler('click'); + } + }); + } + }); + } + + $scope.$on('$destroy', function () { + clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener); + }); + } + } + }; +} + +/* + * @private + * @ngdoc controller + * @name MdListController + * @module material.components.list + * + */ +function MdListController($scope, $element, $mdListInkRipple) { + var ctrl = this; + ctrl.attachRipple = attachRipple; + + function attachRipple (scope, element) { + var options = {}; + $mdListInkRipple.attach(scope, element, options); + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.menu + */ + +angular.module('material.components.menu', [ + 'material.core', + 'material.components.backdrop' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.menuBar + */ + +angular.module('material.components.menuBar', [ + 'material.core', + 'material.components.icon', + 'material.components.menu' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.navBar + */ + + +MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"]; +MdNavItem.$inject = ["$mdAria", "$$rAF"]; +MdNavItemController.$inject = ["$element"]; +MdNavBar.$inject = ["$mdAria", "$mdTheming"]; +angular.module('material.components.navBar', ['material.core']) + .controller('MdNavBarController', MdNavBarController) + .directive('mdNavBar', MdNavBar) + .controller('MdNavItemController', MdNavItemController) + .directive('mdNavItem', MdNavItem); + + +/***************************************************************************** + * PUBLIC DOCUMENTATION * + *****************************************************************************/ +/** + * @ngdoc directive + * @name mdNavBar + * @module material.components.navBar + * + * @restrict E + * + * @description + * The `` directive renders a list of material tabs that can be used + * for top-level page navigation. Unlike ``, it has no concept of a tab + * body and no bar pagination. + * + * Because it deals with page navigation, certain routing concepts are built-in. + * Route changes via via ng-href, ui-sref, or ng-click events are supported. + * Alternatively, the user could simply watch currentNavItem for changes. + * + * Accessibility functionality is implemented as a site navigator with a + * listbox, according to + * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style + * + * @param {string=} mdSelectedNavItem The name of the current tab; this must + * match the name attribute of `` + * @param {boolean=} mdNoInkBar If set to true, the ink bar will be hidden. + * @param {string=} navBarAriaLabel An aria-label for the nav-bar + * + * @usage + * + * + * + * Page One + * + * Page Two + * Page Three + * + * Page Four + * + * + * + * + * (function() { + * 'use strict'; + * + * $rootScope.$on('$routeChangeSuccess', function(event, current) { + * $scope.currentLink = getCurrentLinkFromRoute(current); + * }); + * }); + * + */ + +/***************************************************************************** + * mdNavItem + *****************************************************************************/ +/** + * @ngdoc directive + * @name mdNavItem + * @module material.components.navBar + * + * @restrict E + * + * @description + * `` describes a page navigation link within the `` + * component. It renders an md-button as the actual link. + * + * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required + * to be specified. + * + * @param {Function=} mdNavClick Function which will be called when the + * link is clicked to change the page. Renders as an `ng-click`. + * @param {string=} mdNavHref url to transition to when this link is clicked. + * Renders as an `ng-href`. + * @param {string=} mdNavSref Ui-router state to transition to when this link is + * clicked. Renders as a `ui-sref`. + * @param {!Object=} srefOpts Ui-router options that are passed to the + * `$state.go()` function. See the [Ui-router documentation for details] + * (https://ui-router.github.io/docs/latest/interfaces/transition.transitionoptions.html). + * @param {string=} name The name of this link. Used by the nav bar to know + * which link is currently selected. + * @param {string=} aria-label Adds alternative text for accessibility + * + * @usage + * See `` for usage. + */ + + +/***************************************************************************** + * IMPLEMENTATION * + *****************************************************************************/ + +function MdNavBar($mdAria, $mdTheming) { + return { + restrict: 'E', + transclude: true, + controller: MdNavBarController, + controllerAs: 'ctrl', + bindToController: true, + scope: { + 'mdSelectedNavItem': '=?', + 'mdNoInkBar': '=?', + 'navBarAriaLabel': '@?', + }, + template: + '
' + + '' + + '' + + '
', + link: function(scope, element, attrs, ctrl) { + $mdTheming(element); + if (!ctrl.navBarAriaLabel) { + $mdAria.expectAsync(element, 'aria-label', angular.noop); + } + }, + }; +} + +/** + * Controller for the nav-bar component. + * + * Accessibility functionality is implemented as a site navigator with a + * listbox, according to + * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style + * @param {!angular.JQLite} $element + * @param {!angular.Scope} $scope + * @param {!angular.Timeout} $timeout + * @param {!Object} $mdConstant + * @constructor + * @final + * @ngInject + */ +function MdNavBarController($element, $scope, $timeout, $mdConstant) { + // Injected variables + /** @private @const {!angular.Timeout} */ + this._$timeout = $timeout; + + /** @private @const {!angular.Scope} */ + this._$scope = $scope; + + /** @private @const {!Object} */ + this._$mdConstant = $mdConstant; + + // Data-bound variables. + /** @type {string} */ + this.mdSelectedNavItem; + + /** @type {string} */ + this.navBarAriaLabel; + + // State variables. + + /** @type {?angular.JQLite} */ + this._navBarEl = $element[0]; + + /** @type {?angular.JQLite} */ + this._inkbar; + + var self = this; + // need to wait for transcluded content to be available + var deregisterTabWatch = this._$scope.$watch(function() { + return self._navBarEl.querySelectorAll('._md-nav-button').length; + }, + function(newLength) { + if (newLength > 0) { + self._initTabs(); + deregisterTabWatch(); + } + }); +} + + + +/** + * Initializes the tab components once they exist. + * @private + */ +MdNavBarController.prototype._initTabs = function() { + this._inkbar = angular.element(this._navBarEl.querySelector('md-nav-ink-bar')); + + var self = this; + this._$timeout(function() { + self._updateTabs(self.mdSelectedNavItem, undefined); + }); + + this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) { + // Wait a digest before update tabs for products doing + // anything dynamic in the template. + self._$timeout(function() { + self._updateTabs(newValue, oldValue); + }); + }); +}; + +/** + * Set the current tab to be selected. + * @param {string|undefined} newValue New current tab name. + * @param {string|undefined} oldValue Previous tab name. + * @private + */ +MdNavBarController.prototype._updateTabs = function(newValue, oldValue) { + var self = this; + var tabs = this._getTabs(); + + // this._getTabs can return null if nav-bar has not yet been initialized + if(!tabs) + return; + + var oldIndex = -1; + var newIndex = -1; + var newTab = this._getTabByName(newValue); + var oldTab = this._getTabByName(oldValue); + + if (oldTab) { + oldTab.setSelected(false); + oldIndex = tabs.indexOf(oldTab); + } + + if (newTab) { + newTab.setSelected(true); + newIndex = tabs.indexOf(newTab); + } + + this._$timeout(function() { + self._updateInkBarStyles(newTab, newIndex, oldIndex); + }); +}; + +/** + * Repositions the ink bar to the selected tab. + * @private + */ +MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) { + this._inkbar.toggleClass('_md-left', newIndex < oldIndex) + .toggleClass('_md-right', newIndex > oldIndex); + + this._inkbar.css({display: newIndex < 0 ? 'none' : ''}); + + if (tab) { + var tabEl = tab.getButtonEl(); + var left = tabEl.offsetLeft; + + this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'}); + } +}; + +/** + * Returns an array of the current tabs. + * @return {!Array} + * @private + */ +MdNavBarController.prototype._getTabs = function() { + var controllers = Array.prototype.slice.call( + this._navBarEl.querySelectorAll('.md-nav-item')) + .map(function(el) { + return angular.element(el).controller('mdNavItem') + }); + return controllers.indexOf(undefined) ? controllers : null; +}; + +/** + * Returns the tab with the specified name. + * @param {string} name The name of the tab, found in its name attribute. + * @return {!NavItemController|undefined} + * @private + */ +MdNavBarController.prototype._getTabByName = function(name) { + return this._findTab(function(tab) { + return tab.getName() == name; + }); +}; + +/** + * Returns the selected tab. + * @return {!NavItemController|undefined} + * @private + */ +MdNavBarController.prototype._getSelectedTab = function() { + return this._findTab(function(tab) { + return tab.isSelected(); + }); +}; + +/** + * Returns the focused tab. + * @return {!NavItemController|undefined} + */ +MdNavBarController.prototype.getFocusedTab = function() { + return this._findTab(function(tab) { + return tab.hasFocus(); + }); +}; + +/** + * Find a tab that matches the specified function. + * @private + */ +MdNavBarController.prototype._findTab = function(fn) { + var tabs = this._getTabs(); + for (var i = 0; i < tabs.length; i++) { + if (fn(tabs[i])) { + return tabs[i]; + } + } + + return null; +}; + +/** + * Direct focus to the selected tab when focus enters the nav bar. + */ +MdNavBarController.prototype.onFocus = function() { + var tab = this._getSelectedTab(); + if (tab) { + tab.setFocused(true); + } +}; + +/** + * Move focus from oldTab to newTab. + * @param {!NavItemController} oldTab + * @param {!NavItemController} newTab + * @private + */ +MdNavBarController.prototype._moveFocus = function(oldTab, newTab) { + oldTab.setFocused(false); + newTab.setFocused(true); +}; + +/** + * Responds to keypress events. + * @param {!Event} e + */ +MdNavBarController.prototype.onKeydown = function(e) { + var keyCodes = this._$mdConstant.KEY_CODE; + var tabs = this._getTabs(); + var focusedTab = this.getFocusedTab(); + if (!focusedTab) return; + + var focusedTabIndex = tabs.indexOf(focusedTab); + + // use arrow keys to navigate between tabs + switch (e.keyCode) { + case keyCodes.UP_ARROW: + case keyCodes.LEFT_ARROW: + if (focusedTabIndex > 0) { + this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]); + } + break; + case keyCodes.DOWN_ARROW: + case keyCodes.RIGHT_ARROW: + if (focusedTabIndex < tabs.length - 1) { + this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]); + } + break; + case keyCodes.SPACE: + case keyCodes.ENTER: + // timeout to avoid a "digest already in progress" console error + this._$timeout(function() { + focusedTab.getButtonEl().click(); + }); + break; + } +}; + +/** + * @ngInject + */ +function MdNavItem($mdAria, $$rAF) { + return { + restrict: 'E', + require: ['mdNavItem', '^mdNavBar'], + controller: MdNavItemController, + bindToController: true, + controllerAs: 'ctrl', + replace: true, + transclude: true, + template: function(tElement, tAttrs) { + var hasNavClick = tAttrs.mdNavClick; + var hasNavHref = tAttrs.mdNavHref; + var hasNavSref = tAttrs.mdNavSref; + var hasSrefOpts = tAttrs.srefOpts; + var navigationAttribute; + var navigationOptions; + var buttonTemplate; + + // Cannot specify more than one nav attribute + if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) { + throw Error( + 'Must not specify more than one of the md-nav-click, md-nav-href, ' + + 'or md-nav-sref attributes per nav-item directive.' + ); + } + + if (hasNavClick) { + navigationAttribute = 'ng-click="ctrl.mdNavClick()"'; + } else if (hasNavHref) { + navigationAttribute = 'ng-href="{{ctrl.mdNavHref}}"'; + } else if (hasNavSref) { + navigationAttribute = 'ui-sref="{{ctrl.mdNavSref}}"'; + } + + navigationOptions = hasSrefOpts ? 'ui-sref-opts="{{ctrl.srefOpts}}" ' : ''; + + if (navigationAttribute) { + buttonTemplate = '' + + '' + + '' + + ''; + } + + return '' + + '
  • ' + + (buttonTemplate || '') + + '
  • '; + }, + scope: { + 'mdNavClick': '&?', + 'mdNavHref': '@?', + 'mdNavSref': '@?', + 'srefOpts': '=?', + 'name': '@', + }, + link: function(scope, element, attrs, controllers) { + // When accessing the element's contents synchronously, they + // may not be defined yet because of transclusion. There is a higher + // chance that it will be accessible if we wait one frame. + $$rAF(function() { + var mdNavItem = controllers[0]; + var mdNavBar = controllers[1]; + var navButton = angular.element(element[0].querySelector('._md-nav-button')); + + if (!mdNavItem.name) { + mdNavItem.name = angular.element(element[0] + .querySelector('._md-nav-button-text')).text().trim(); + } + + navButton.on('click', function() { + mdNavBar.mdSelectedNavItem = mdNavItem.name; + scope.$apply(); + }); + + $mdAria.expectWithText(element, 'aria-label'); + }); + } + }; +} + +/** + * Controller for the nav-item component. + * @param {!angular.JQLite} $element + * @constructor + * @final + * @ngInject + */ +function MdNavItemController($element) { + + /** @private @const {!angular.JQLite} */ + this._$element = $element; + + // Data-bound variables + + /** @const {?Function} */ + this.mdNavClick; + + /** @const {?string} */ + this.mdNavHref; + + /** @const {?string} */ + this.mdNavSref; + /** @const {?Object} */ + this.srefOpts; + /** @const {?string} */ + this.name; + + // State variables + /** @private {boolean} */ + this._selected = false; + + /** @private {boolean} */ + this._focused = false; +} + +/** + * Returns a map of class names and values for use by ng-class. + * @return {!Object} + */ +MdNavItemController.prototype.getNgClassMap = function() { + return { + 'md-active': this._selected, + 'md-primary': this._selected, + 'md-unselected': !this._selected, + 'md-focused': this._focused, + }; +}; + +/** + * Get the name attribute of the tab. + * @return {string} + */ +MdNavItemController.prototype.getName = function() { + return this.name; +}; + +/** + * Get the button element associated with the tab. + * @return {!Element} + */ +MdNavItemController.prototype.getButtonEl = function() { + return this._$element[0].querySelector('._md-nav-button'); +}; + +/** + * Set the selected state of the tab. + * @param {boolean} isSelected + */ +MdNavItemController.prototype.setSelected = function(isSelected) { + this._selected = isSelected; +}; + +/** + * @return {boolean} + */ +MdNavItemController.prototype.isSelected = function() { + return this._selected; +}; + +/** + * Set the focused state of the tab. + * @param {boolean} isFocused + */ +MdNavItemController.prototype.setFocused = function(isFocused) { + this._focused = isFocused; + + if (isFocused) { + this.getButtonEl().focus(); + } +}; + +/** + * @return {boolean} + */ +MdNavItemController.prototype.hasFocus = function() { + return this._focused; +}; + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.panel + */ +MdPanelService.$inject = ["presets", "$rootElement", "$rootScope", "$injector", "$window"]; +angular + .module('material.components.panel', [ + 'material.core', + 'material.components.backdrop' + ]) + .provider('$mdPanel', MdPanelProvider); + + +/***************************************************************************** + * PUBLIC DOCUMENTATION * + *****************************************************************************/ + + +/** + * @ngdoc service + * @name $mdPanelProvider + * @module material.components.panel + * + * @description + * `$mdPanelProvider` allows users to create configuration presets that will be + * stored within a cached presets object. When the configuration is needed, the + * user can request the preset by passing it as the first parameter in the + * `$mdPanel.create` or `$mdPanel.open` methods. + * + * @usage + * + * (function(angular, undefined) { + * 'use strict'; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .config(DemoConfig) + * .controller('DemoCtrl', DemoCtrl) + * .controller('DemoMenuCtrl', DemoMenuCtrl); + * + * function DemoConfig($mdPanelProvider) { + * $mdPanelProvider.definePreset('demoPreset', { + * attachTo: angular.element(document.body), + * controller: DemoMenuCtrl, + * controllerAs: 'ctrl', + * template: '' + + * '', + * panelClass: 'menu-panel-container', + * focusOnOpen: false, + * zIndex: 100, + * propagateContainerEvents: true, + * groupName: 'menus' + * }); + * } + * + * function PanelProviderCtrl($mdPanel) { + * this.navigation = { + * name: 'navigation', + * items: [ + * 'Home', + * 'About', + * 'Contact' + * ] + * }; + * this.favorites = { + * name: 'favorites', + * items: [ + * 'Add to Favorites' + * ] + * }; + * this.more = { + * name: 'more', + * items: [ + * 'Account', + * 'Sign Out' + * ] + * }; + * + * $mdPanel.newPanelGroup('menus', { + * maxOpen: 2 + * }); + * + * this.showMenu = function($event, menu) { + * $mdPanel.open('demoPreset', { + * id: 'menu_' + menu.name, + * position: $mdPanel.newPanelPosition() + * .relativeTo($event.srcElement) + * .addPanelPosition( + * $mdPanel.xPosition.ALIGN_START, + * $mdPanel.yPosition.BELOW + * ), + * locals: { + * items: menu.items + * }, + * openFrom: $event + * }); + * }; + * } + * + * function PanelMenuCtrl(mdPanelRef) { + * // The controller is provided with an import named 'mdPanelRef' + * this.closeMenu = function() { + * mdPanelRef && mdPanelRef.close(); + * }; + * } + * })(angular); + * + */ + +/** + * @ngdoc method + * @name $mdPanelProvider#definePreset + * @description + * Takes the passed in preset name and preset configuration object and adds it + * to the `_presets` object of the provider. This `_presets` object is then + * passed along to the `$mdPanel` service. + * + * @param {string} name Preset name. + * @param {!Object} preset Specific configuration object that can contain any + * and all of the parameters avaialble within the `$mdPanel.create` method. + * However, parameters that pertain to id, position, animation, and user + * interaction are not allowed and will be removed from the preset + * configuration. + */ + + +/***************************************************************************** + * MdPanel Service * + *****************************************************************************/ + + +/** + * @ngdoc service + * @name $mdPanel + * @module material.components.panel + * + * @description + * `$mdPanel` is a robust, low-level service for creating floating panels on + * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc. + * + * @usage + * + * (function(angular, undefined) { + * 'use strict'; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('DemoDialogController', DialogController); + * + * var panelRef; + * + * function showPanel($event) { + * var panelPosition = $mdPanel.newPanelPosition() + * .absolute() + * .top('50%') + * .left('50%'); + * + * var panelAnimation = $mdPanel.newPanelAnimation() + * .targetEvent($event) + * .defaultAnimation('md-panel-animate-fly') + * .closeTo('.show-button'); + * + * var config = { + * attachTo: angular.element(document.body), + * controller: DialogController, + * controllerAs: 'ctrl', + * position: panelPosition, + * animation: panelAnimation, + * targetEvent: $event, + * templateUrl: 'dialog-template.html', + * clickOutsideToClose: true, + * escapeToClose: true, + * focusOnOpen: true + * }; + * + * $mdPanel.open(config) + * .then(function(result) { + * panelRef = result; + * }); + * } + * + * function DialogController(MdPanelRef) { + * function closeDialog() { + * if (MdPanelRef) MdPanelRef.close(); + * } + * } + * })(angular); + * + */ + +/** + * @ngdoc method + * @name $mdPanel#create + * @description + * Creates a panel with the specified options. + * + * @param config {!Object=} Specific configuration object that may contain the + * following properties: + * + * - `id` - `{string=}`: An ID to track the panel by. When an ID is provided, + * the created panel is added to a tracked panels object. Any subsequent + * requests made to create a panel with that ID are ignored. This is useful + * in having the panel service not open multiple panels from the same user + * interaction when there is no backdrop and events are propagated. Defaults + * to an arbitrary string that is not tracked. + * - `template` - `{string=}`: HTML template to show in the panel. This + * **must** be trusted HTML with respect to AngularJS’s + * [$sce service](https://docs.angularjs.org/api/ng/service/$sce). + * - `templateUrl` - `{string=}`: The URL that will be used as the content of + * the panel. + * - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled + * element to be used as the panel's content. + * - `controller` - `{(function|string)=}`: The controller to associate with + * the panel. The controller can inject a reference to the returned + * panelRef, which allows the panel to be closed, hidden, and shown. Any + * fields passed in through locals or resolve will be bound to the + * controller. + * - `controllerAs` - `{string=}`: An alias to assign the controller to on + * the scope. + * - `bindToController` - `{boolean=}`: Binds locals to the controller + * instead of passing them in. Defaults to true, as this is a best + * practice. + * - `locals` - `{Object=}`: An object containing key/value pairs. The keys + * will be used as names of values to inject into the controller. For + * example, `locals: {three: 3}` would inject `three` into the controller, + * with the value 3. 'mdPanelRef' is a reserved key, and will always + * be set to the created MdPanelRef instance. + * - `resolve` - `{Object=}`: Similar to locals, except it takes promises as + * values. The panel will not open until all of the promises resolve. + * - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to + * attach the panel to. Defaults to appending to the root element of the + * application. + * - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch + * events should be allowed to propagate 'go through' the container, aka the + * wrapper, of the panel. Defaults to false. + * - `panelClass` - `{string=}`: A css class to apply to the panel element. + * This class should define any borders, box-shadow, etc. for the panel. + * - `zIndex` - `{number=}`: The z-index to place the panel at. + * Defaults to 80. + * - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that + * specifies the alignment of the panel. For more information, see + * `MdPanelPosition`. + * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click + * outside the panel to close it. Defaults to false. + * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to + * close the panel. Defaults to false. + * - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is + * called after the close successfully finishes. The first parameter passed + * into this function is the current panelRef and the 2nd is an optional + * string explaining the close reason. The currently supported closeReasons + * can be found in the MdPanelRef.closeReasons enum. These are by default + * passed along by the panel. + * - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the + * panel. If `trapFocus` is true, the user will not be able to interact + * with the rest of the page until the panel is dismissed. Defaults to + * false. + * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on + * open. Only disable if focusing some other way, as focus management is + * required for panels to be accessible. Defaults to true. + * - `fullscreen` - `{boolean=}`: Whether the panel should be full screen. + * Applies the class `._md-panel-fullscreen` to the panel on open. Defaults + * to false. + * - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that + * specifies the animation of the panel. For more information, see + * `MdPanelAnimation`. + * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop + * behind the panel. Defaults to false. + * - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the + * page behind the panel. Defaults to false. + * - `onDomAdded` - `{function=}`: Callback function used to announce when + * the panel is added to the DOM. + * - `onOpenComplete` - `{function=}`: Callback function used to announce + * when the open() action is finished. + * - `onRemoving` - `{function=}`: Callback function used to announce the + * close/hide() action is starting. + * - `onDomRemoved` - `{function=}`: Callback function used to announce when + * the panel is removed from the DOM. + * - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus + * on when the panel closes. This is commonly the element which triggered + * the opening of the panel. If you do not use `origin`, you need to control + * the focus manually. + * - `groupName` - `{(string|!Array)=}`: A group name or an array of + * group names. The group name is used for creating a group of panels. The + * group is used for configuring the number of open panels and identifying + * specific behaviors for groups. For instance, all tooltips could be + * identified using the same groupName. + * + * @returns {!MdPanelRef} panelRef + */ + +/** + * @ngdoc method + * @name $mdPanel#open + * @description + * Calls the create method above, then opens the panel. This is a shortcut for + * creating and then calling open manually. If custom methods need to be + * called when the panel is added to the DOM or opened, do not use this method. + * Instead create the panel, chain promises on the domAdded and openComplete + * methods, and call open from the returned panelRef. + * + * @param {!Object=} config Specific configuration object that may contain + * the properties defined in `$mdPanel.create`. + * @returns {!angular.$q.Promise} panelRef A promise that resolves + * to an instance of the panel. + */ + +/** + * @ngdoc method + * @name $mdPanel#newPanelPosition + * @description + * Returns a new instance of the MdPanelPosition object. Use this to create + * the position config object. + * + * @returns {!MdPanelPosition} panelPosition + */ + +/** + * @ngdoc method + * @name $mdPanel#newPanelAnimation + * @description + * Returns a new instance of the MdPanelAnimation object. Use this to create + * the animation config object. + * + * @returns {!MdPanelAnimation} panelAnimation + */ + +/** + * @ngdoc method + * @name $mdPanel#newPanelGroup + * @description + * Creates a panel group and adds it to a tracked list of panel groups. + * + * @param {string} groupName Name of the group to create. + * @param {!Object=} config Specific configuration object that may contain the + * following properties: + * + * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to + * be open within a defined panel group. + * + * @returns {!Object, + * openPanels: !Array, + * maxOpen: number}>} panelGroup + */ + +/** + * @ngdoc method + * @name $mdPanel#setGroupMaxOpen + * @description + * Sets the maximum number of panels in a group that can be opened at a given + * time. + * + * @param {string} groupName The name of the group to configure. + * @param {number} maxOpen The maximum number of panels that can be + * opened. Infinity can be passed in to remove the maxOpen limit. + */ + + +/***************************************************************************** + * MdPanelRef * + *****************************************************************************/ + + +/** + * @ngdoc type + * @name MdPanelRef + * @module material.components.panel + * @description + * A reference to a created panel. This reference contains a unique id for the + * panel, along with the following properties: + * + * - `id` - `{string}`: The unique id for the panel. This id is used to track + * when a panel was interacted with. + * - `config` - `{!Object=}`: The entire config object that was used in + * create. + * - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM. + * Visibility to the user does not factor into isAttached. + * - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the + * panel. This property is added in order to have access to the `addClass`, + * `removeClass`, `toggleClass`, etc methods. + * - `panelEl` - `{angular.JQLite}`: The panel element. This property is added + * in order to have access to the `addClass`, `removeClass`, `toggleClass`, + * etc methods. + */ + +/** + * @ngdoc method + * @name MdPanelRef#open + * @description + * Attaches and shows the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * opened. + */ + +/** + * @ngdoc method + * @name MdPanelRef#close + * @description + * Hides and detaches the panel. Note that this will **not** destroy the panel. + * If you don't intend on using the panel again, call the {@link #destroy + * destroy} method afterwards. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * closed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#attach + * @description + * Create the panel elements and attach them to the DOM. The panel will be + * hidden by default. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * attached. + */ + +/** + * @ngdoc method + * @name MdPanelRef#detach + * @description + * Removes the panel from the DOM. This will NOT hide the panel before removing + * it. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * detached. + */ + +/** + * @ngdoc method + * @name MdPanelRef#show + * @description + * Shows the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * shown and animations are completed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#hide + * @description + * Hides the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * hidden and animations are completed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#destroy + * @description + * Destroys the panel. The panel cannot be opened again after this is called. + */ + +/** + * @ngdoc method + * @name MdPanelRef#addClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Adds a class to the panel. DO NOT use this hide/show the panel. + * + * @param {string} newClass class to be added. + * @param {boolean} toElement Whether or not to add the class to the panel + * element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#removeClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Removes a class from the panel. DO NOT use this to hide/show the panel. + * + * @param {string} oldClass Class to be removed. + * @param {boolean} fromElement Whether or not to remove the class from the + * panel element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#toggleClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Toggles a class on the panel. DO NOT use this to hide/show the panel. + * + * @param {string} toggleClass Class to be toggled. + * @param {boolean} onElement Whether or not to remove the class from the panel + * element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#updatePosition + * @description + * Updates the position configuration of a panel. Use this to update the + * position of a panel that is open, without having to close and re-open the + * panel. + * + * @param {!MdPanelPosition} position + */ + +/** + * @ngdoc method + * @name MdPanelRef#addToGroup + * @description + * Adds a panel to a group if the panel does not exist within the group already. + * A panel can only exist within a single group. + * + * @param {string} groupName The name of the group to add the panel to. + */ + +/** + * @ngdoc method + * @name MdPanelRef#removeFromGroup + * @description + * Removes a panel from a group if the panel exists within that group. The group + * must be created ahead of time. + * + * @param {string} groupName The name of the group. + */ + +/** + * @ngdoc method + * @name MdPanelRef#registerInterceptor + * @description + * Registers an interceptor with the panel. The callback should return a promise, + * which will allow the action to continue when it gets resolved, or will + * prevent an action if it is rejected. The interceptors are called sequentially + * and it reverse order. `type` must be one of the following + * values available on `$mdPanel.interceptorTypes`: + * * `CLOSE` - Gets called before the panel begins closing. + * + * @param {string} type Type of interceptor. + * @param {!angular.$q.Promise} callback Callback to be registered. + * @returns {!MdPanelRef} + */ + +/** + * @ngdoc method + * @name MdPanelRef#removeInterceptor + * @description + * Removes a registered interceptor. + * + * @param {string} type Type of interceptor to be removed. + * @param {function(): !angular.$q.Promise} callback Interceptor to be removed. + * @returns {!MdPanelRef} + */ + +/** + * @ngdoc method + * @name MdPanelRef#removeAllInterceptors + * @description + * Removes all interceptors. If a type is supplied, only the + * interceptors of that type will be cleared. + * + * @param {string=} type Type of interceptors to be removed. + * @returns {!MdPanelRef} + */ + +/** + * @ngdoc method + * @name MdPanelRef#updateAnimation + * @description + * Updates the animation configuration for a panel. You can use this to change + * the panel's animation without having to re-create it. + * + * @param {!MdPanelAnimation} animation + */ + + +/***************************************************************************** + * MdPanelPosition * + *****************************************************************************/ + + +/** + * @ngdoc type + * @name MdPanelPosition + * @module material.components.panel + * @description + * + * Object for configuring the position of the panel. + * + * @usage + * + * #### Centering the panel + * + * + * new MdPanelPosition().absolute().center(); + * + * + * #### Overlapping the panel with an element + * + * + * new MdPanelPosition() + * .relativeTo(someElement) + * .addPanelPosition( + * $mdPanel.xPosition.ALIGN_START, + * $mdPanel.yPosition.ALIGN_TOPS + * ); + * + * + * #### Aligning the panel with the bottom of an element + * + * + * new MdPanelPosition() + * .relativeTo(someElement) + * .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW); + * + */ + +/** + * @ngdoc method + * @name MdPanelPosition#absolute + * @description + * Positions the panel absolutely relative to the parent element. If the parent + * is document.body, this is equivalent to positioning the panel absolutely + * within the viewport. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#relativeTo + * @description + * Positions the panel relative to a specific element. + * + * @param {string|!Element|!angular.JQLite} element Query selector, DOM element, + * or angular element to position the panel with respect to. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#top + * @description + * Sets the value of `top` for the panel. Clears any previously set vertical + * position. + * + * @param {string=} top Value of `top`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#bottom + * @description + * Sets the value of `bottom` for the panel. Clears any previously set vertical + * position. + * + * @param {string=} bottom Value of `bottom`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#start + * @description + * Sets the panel to the start of the page - `left` if `ltr` or `right` for + * `rtl`. Clears any previously set horizontal position. + * + * @param {string=} start Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#end + * @description + * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`. + * Clears any previously set horizontal position. + * + * @param {string=} end Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#left + * @description + * Sets the value of `left` for the panel. Clears any previously set + * horizontal position. + * + * @param {string=} left Value of `left`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#right + * @description + * Sets the value of `right` for the panel. Clears any previously set + * horizontal position. + * + * @param {string=} right Value of `right`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#centerHorizontally + * @description + * Centers the panel horizontally in the viewport. Clears any previously set + * horizontal position. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#centerVertically + * @description + * Centers the panel vertically in the viewport. Clears any previously set + * vertical position. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#center + * @description + * Centers the panel horizontally and vertically in the viewport. This is + * equivalent to calling both `centerHorizontally` and `centerVertically`. + * Clears any previously set horizontal and vertical positions. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#addPanelPosition + * @description + * Sets the x and y position for the panel relative to another element. Can be + * called multiple times to specify an ordered list of panel positions. The + * first position which allows the panel to be completely on-screen will be + * chosen; the last position will be chose whether it is on-screen or not. + * + * xPosition must be one of the following values available on + * $mdPanel.xPosition: + * + * + * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END + * + *
    + *    *************
    + *    *           *
    + *    *   PANEL   *
    + *    *           *
    + *    *************
    + *   A B    C    D E
    + *
    + * A: OFFSET_START (for LTR displays)
    + * B: ALIGN_START (for LTR displays)
    + * C: CENTER
    + * D: ALIGN_END (for LTR displays)
    + * E: OFFSET_END (for LTR displays)
    + * 
    + * + * yPosition must be one of the following values available on + * $mdPanel.yPosition: + * + * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW + * + *
    + *   F
    + *   G *************
    + *     *           *
    + *   H *   PANEL   *
    + *     *           *
    + *   I *************
    + *   J
    + *
    + * F: BELOW
    + * G: ALIGN_TOPS
    + * H: CENTER
    + * I: ALIGN_BOTTOMS
    + * J: ABOVE
    + * 
    + * + * @param {string} xPosition + * @param {string} yPosition + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#withOffsetX + * @description + * Sets the value of the offset in the x-direction. + * + * @param {string} offsetX + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#withOffsetY + * @description + * Sets the value of the offset in the y-direction. + * + * @param {string} offsetY + * @returns {!MdPanelPosition} + */ + + +/***************************************************************************** + * MdPanelAnimation * + *****************************************************************************/ + + +/** + * @ngdoc type + * @name MdPanelAnimation + * @module material.components.panel + * @description + * Animation configuration object. To use, create an MdPanelAnimation with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * @usage + * + * + * var panelAnimation = new MdPanelAnimation() + * .openFrom(myButtonEl) + * .duration(1337) + * .closeTo('.my-button') + * .withAnimation($mdPanel.animation.SCALE); + * + * $mdPanel.create({ + * animation: panelAnimation + * }); + * + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#openFrom + * @description + * Specifies where to start the open animation. `openFrom` accepts a + * click event object, query selector, DOM element, or a Rect object that + * is used to determine the bounds. When passed a click event, the location + * of the click will be used as the position to start the animation. + * + * @param {string|!Element|!Event|{top: number, left: number}} + * @returns {!MdPanelAnimation} + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#closeTo + * @description + * Specifies where to animate the panel close. `closeTo` accepts a + * query selector, DOM element, or a Rect object that is used to determine + * the bounds. + * + * @param {string|!Element|{top: number, left: number}} + * @returns {!MdPanelAnimation} + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#withAnimation + * @description + * Specifies the animation class. + * + * There are several default animations that can be used: + * ($mdPanel.animation) + * SLIDE: The panel slides in and out from the specified + * elements. It will not fade in or out. + * SCALE: The panel scales in and out. Slide and fade are + * included in this animation. + * FADE: The panel fades in and out. + * + * Custom classes will by default fade in and out unless + * "transition: opacity 1ms" is added to the to custom class. + * + * @param {string|{open: string, close: string}} cssClass + * @returns {!MdPanelAnimation} + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#duration + * @description + * Specifies the duration of the animation in milliseconds. The `duration` + * method accepts either a number or an object with separate open and close + * durations. + * + * @param {number|{open: number, close: number}} duration + * @returns {!MdPanelAnimation} + */ + + +/***************************************************************************** + * PUBLIC DOCUMENTATION * + *****************************************************************************/ + + +var MD_PANEL_Z_INDEX = 80; +var MD_PANEL_HIDDEN = '_md-panel-hidden'; +var FOCUS_TRAP_TEMPLATE = angular.element( + '
    '); + +var _presets = {}; + + +/** + * A provider that is used for creating presets for the panel API. + * @final @constructor @ngInject + */ +function MdPanelProvider() { + return { + 'definePreset': definePreset, + 'getAllPresets': getAllPresets, + 'clearPresets': clearPresets, + '$get': $getProvider() + }; +} + + +/** + * Takes the passed in panel configuration object and adds it to the `_presets` + * object at the specified name. + * @param {string} name Name of the preset to set. + * @param {!Object} preset Specific configuration object that can contain any + * and all of the parameters avaialble within the `$mdPanel.create` method. + * However, parameters that pertain to id, position, animation, and user + * interaction are not allowed and will be removed from the preset + * configuration. + */ +function definePreset(name, preset) { + if (!name || !preset) { + throw new Error('mdPanelProvider: The panel preset definition is ' + + 'malformed. The name and preset object are required.'); + } else if (_presets.hasOwnProperty(name)) { + throw new Error('mdPanelProvider: The panel preset you have requested ' + + 'has already been defined.'); + } + + // Delete any property on the preset that is not allowed. + delete preset.id; + delete preset.position; + delete preset.animation; + + _presets[name] = preset; +} + + +/** + * Gets a clone of the `_presets`. + * @return {!Object} + */ +function getAllPresets() { + return angular.copy(_presets); +} + + +/** + * Clears all of the stored presets. + */ +function clearPresets() { + _presets = {}; +} + + +/** + * Represents the `$get` method of the AngularJS provider. From here, a new + * reference to the MdPanelService is returned where the needed arguments are + * passed in including the MdPanelProvider `_presets`. + * @param {!Object} _presets + * @param {!angular.JQLite} $rootElement + * @param {!angular.Scope} $rootScope + * @param {!angular.$injector} $injector + * @param {!angular.$window} $window + */ +function $getProvider() { + return [ + '$rootElement', '$rootScope', '$injector', '$window', + function($rootElement, $rootScope, $injector, $window) { + return new MdPanelService(_presets, $rootElement, $rootScope, + $injector, $window); + } + ]; +} + + +/***************************************************************************** + * MdPanel Service * + *****************************************************************************/ + + +/** + * A service that is used for controlling/displaying panels on the screen. + * @param {!Object} presets + * @param {!angular.JQLite} $rootElement + * @param {!angular.Scope} $rootScope + * @param {!angular.$injector} $injector + * @param {!angular.$window} $window + * @final @constructor @ngInject + */ +function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) { + /** + * Default config options for the panel. + * Anything angular related needs to be done later. Therefore + * scope: $rootScope.$new(true), + * attachTo: $rootElement, + * are added later. + * @private {!Object} + */ + this._defaultConfigOptions = { + bindToController: true, + clickOutsideToClose: false, + disableParentScroll: false, + escapeToClose: false, + focusOnOpen: true, + fullscreen: false, + hasBackdrop: false, + propagateContainerEvents: false, + transformTemplate: angular.bind(this, this._wrapTemplate), + trapFocus: false, + zIndex: MD_PANEL_Z_INDEX + }; + + /** @private {!Object} */ + this._config = {}; + + /** @private {!Object} */ + this._presets = presets; + + /** @private @const */ + this._$rootElement = $rootElement; + + /** @private @const */ + this._$rootScope = $rootScope; + + /** @private @const */ + this._$injector = $injector; + + /** @private @const */ + this._$window = $window; + + /** @private @const */ + this._$mdUtil = this._$injector.get('$mdUtil'); + + /** @private {!Object} */ + this._trackedPanels = {}; + + /** + * @private {!Object, + * openPanels: !Array, + * maxOpen: number}>} + */ + this._groups = Object.create(null); + + /** + * Default animations that can be used within the panel. + * @type {enum} + */ + this.animation = MdPanelAnimation.animation; + + /** + * Possible values of xPosition for positioning the panel relative to + * another element. + * @type {enum} + */ + this.xPosition = MdPanelPosition.xPosition; + + /** + * Possible values of yPosition for positioning the panel relative to + * another element. + * @type {enum} + */ + this.yPosition = MdPanelPosition.yPosition; + + /** + * Possible values for the interceptors that can be registered on a panel. + * @type {enum} + */ + this.interceptorTypes = MdPanelRef.interceptorTypes; + + /** + * Possible values for closing of a panel. + * @type {enum} + */ + this.closeReasons = MdPanelRef.closeReasons; + + /** + * Possible values of absolute position. + * @type {enum} + */ + this.absPosition = MdPanelPosition.absPosition; +} + + +/** + * Creates a panel with the specified options. + * @param {string=} preset Name of a preset configuration that can be used to + * extend the panel configuration. + * @param {!Object=} config Configuration object for the panel. + * @returns {!MdPanelRef} + */ +MdPanelService.prototype.create = function(preset, config) { + if (typeof preset === 'string') { + preset = this._getPresetByName(preset); + } else if (typeof preset === 'object' && + (angular.isUndefined(config) || !config)) { + config = preset; + preset = {}; + } + + preset = preset || {}; + config = config || {}; + + // If the passed-in config contains an ID and the ID is within _trackedPanels, + // return the tracked panel after updating its config with the passed-in + // config. + if (angular.isDefined(config.id) && this._trackedPanels[config.id]) { + var trackedPanel = this._trackedPanels[config.id]; + angular.extend(trackedPanel.config, config); + return trackedPanel; + } + + // Combine the passed-in config, the _defaultConfigOptions, and the preset + // configuration into the `_config`. + this._config = angular.extend({ + // If no ID is set within the passed-in config, then create an arbitrary ID. + id: config.id || 'panel_' + this._$mdUtil.nextUid(), + scope: this._$rootScope.$new(true), + attachTo: this._$rootElement + }, this._defaultConfigOptions, config, preset); + + // Create the panelRef and add it to the `_trackedPanels` object. + var panelRef = new MdPanelRef(this._config, this._$injector); + this._trackedPanels[config.id] = panelRef; + + // Add the panel to each of its requested groups. + if (this._config.groupName) { + if (angular.isString(this._config.groupName)) { + this._config.groupName = [this._config.groupName]; + } + angular.forEach(this._config.groupName, function(group) { + panelRef.addToGroup(group); + }); + } + + this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach)); + + return panelRef; +}; + + +/** + * Creates and opens a panel with the specified options. + * @param {string=} preset Name of a preset configuration that can be used to + * extend the panel configuration. + * @param {!Object=} config Configuration object for the panel. + * @returns {!angular.$q.Promise} The panel created from create. + */ +MdPanelService.prototype.open = function(preset, config) { + var panelRef = this.create(preset, config); + return panelRef.open().then(function() { + return panelRef; + }); +}; + + +/** + * Gets a specific preset configuration object saved within `_presets`. + * @param {string} preset Name of the preset to search for. + * @returns {!Object} The preset configuration object. + */ +MdPanelService.prototype._getPresetByName = function(preset) { + if (!this._presets[preset]) { + throw new Error('mdPanel: The panel preset configuration that you ' + + 'requested does not exist. Use the $mdPanelProvider to create a ' + + 'preset before requesting one.'); + } + return this._presets[preset]; +}; + + +/** + * Returns a new instance of the MdPanelPosition. Use this to create the + * positioning object. + * @returns {!MdPanelPosition} + */ +MdPanelService.prototype.newPanelPosition = function() { + return new MdPanelPosition(this._$injector); +}; + + +/** + * Returns a new instance of the MdPanelAnimation. Use this to create the + * animation object. + * @returns {!MdPanelAnimation} + */ +MdPanelService.prototype.newPanelAnimation = function() { + return new MdPanelAnimation(this._$injector); +}; + + +/** + * Creates a panel group and adds it to a tracked list of panel groups. + * @param groupName {string} Name of the group to create. + * @param config {!Object=} Specific configuration object that may contain the + * following properties: + * + * - `maxOpen` - `{number=}`: The maximum number of panels that are allowed + * open within a defined panel group. + * + * @returns {!Object, + * openPanels: !Array, + * maxOpen: number}>} panelGroup + */ +MdPanelService.prototype.newPanelGroup = function(groupName, config) { + if (!this._groups[groupName]) { + config = config || {}; + var group = { + panels: [], + openPanels: [], + maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity + }; + this._groups[groupName] = group; + } + return this._groups[groupName]; +}; + + +/** + * Sets the maximum number of panels in a group that can be opened at a given + * time. + * @param {string} groupName The name of the group to configure. + * @param {number} maxOpen The maximum number of panels that can be + * opened. Infinity can be passed in to remove the maxOpen limit. + */ +MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) { + if (this._groups[groupName]) { + this._groups[groupName].maxOpen = maxOpen; + } else { + throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().'); + } +}; + + +/** + * Determines if the current number of open panels within a group exceeds the + * limit of allowed open panels. + * @param {string} groupName The name of the group to check. + * @returns {boolean} true if open count does exceed maxOpen and false if not. + * @private + */ +MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) { + if (this._groups[groupName]) { + var group = this._groups[groupName]; + return group.maxOpen > 0 && group.openPanels.length > group.maxOpen; + } + return false; +}; + + +/** + * Closes the first open panel within a specific group. + * @param {string} groupName The name of the group. + * @private + */ +MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) { + this._groups[groupName].openPanels[0].close(); +}; + + +/** + * Wraps the users template in two elements, md-panel-outer-wrapper, which + * covers the entire attachTo element, and md-panel, which contains only the + * template. This allows the panel control over positioning, animations, + * and similar properties. + * @param {string} origTemplate The original template. + * @returns {string} The wrapped template. + * @private + */ +MdPanelService.prototype._wrapTemplate = function(origTemplate) { + var template = origTemplate || ''; + + // The panel should be initially rendered offscreen so we can calculate + // height and width for positioning. + return '' + + '
    ' + + '
    ' + template + '
    ' + + '
    '; +}; + + +/** + * Wraps a content element in a md-panel-outer wrapper and + * positions it off-screen. Allows for proper control over positoning + * and animations. + * @param {!angular.JQLite} contentElement Element to be wrapped. + * @return {!angular.JQLite} Wrapper element. + * @private + */ +MdPanelService.prototype._wrapContentElement = function(contentElement) { + var wrapper = angular.element('
    '); + + contentElement.addClass('md-panel _md-panel-offscreen'); + wrapper.append(contentElement); + + return wrapper; +}; + + +/***************************************************************************** + * MdPanelRef * + *****************************************************************************/ + + +/** + * A reference to a created panel. This reference contains a unique id for the + * panel, along with properties/functions used to control the panel. + * @param {!Object} config + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelRef(config, $injector) { + // Injected variables. + /** @private @const {!angular.$q} */ + this._$q = $injector.get('$q'); + + /** @private @const {!angular.$mdCompiler} */ + this._$mdCompiler = $injector.get('$mdCompiler'); + + /** @private @const {!angular.$mdConstant} */ + this._$mdConstant = $injector.get('$mdConstant'); + + /** @private @const {!angular.$mdUtil} */ + this._$mdUtil = $injector.get('$mdUtil'); + + /** @private @const {!angular.$mdTheming} */ + this._$mdTheming = $injector.get('$mdTheming'); + + /** @private @const {!angular.Scope} */ + this._$rootScope = $injector.get('$rootScope'); + + /** @private @const {!angular.$animate} */ + this._$animate = $injector.get('$animate'); + + /** @private @const {!MdPanelRef} */ + this._$mdPanel = $injector.get('$mdPanel'); + + /** @private @const {!angular.$log} */ + this._$log = $injector.get('$log'); + + /** @private @const {!angular.$window} */ + this._$window = $injector.get('$window'); + + /** @private @const {!Function} */ + this._$$rAF = $injector.get('$$rAF'); + + // Public variables. + /** + * Unique id for the panelRef. + * @type {string} + */ + this.id = config.id; + + /** @type {!Object} */ + this.config = config; + + /** @type {!angular.JQLite|undefined} */ + this.panelContainer; + + /** @type {!angular.JQLite|undefined} */ + this.panelEl; + + /** + * Whether the panel is attached. This is synchronous. When attach is called, + * isAttached is set to true. When detach is called, isAttached is set to + * false. + * @type {boolean} + */ + this.isAttached = false; + + // Private variables. + /** @private {Array} */ + this._removeListeners = []; + + /** @private {!angular.JQLite|undefined} */ + this._topFocusTrap; + + /** @private {!angular.JQLite|undefined} */ + this._bottomFocusTrap; + + /** @private {!$mdPanel|undefined} */ + this._backdropRef; + + /** @private {Function?} */ + this._restoreScroll = null; + + /** + * Keeps track of all the panel interceptors. + * @private {!Object} + */ + this._interceptors = Object.create(null); + + /** + * Cleanup function, provided by `$mdCompiler` and assigned after the element + * has been compiled. When `contentElement` is used, the function is used to + * restore the element to it's proper place in the DOM. + * @private {!Function} + */ + this._compilerCleanup = null; + + /** + * Cache for saving and restoring element inline styles, CSS classes etc. + * @type {{styles: string, classes: string}} + */ + this._restoreCache = { + styles: '', + classes: '' + }; +} + + +MdPanelRef.interceptorTypes = { + CLOSE: 'onClose' +}; + + +/** + * Opens an already created and configured panel. If the panel is already + * visible, does nothing. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is opened and animations finish. + */ +MdPanelRef.prototype.open = function() { + var self = this; + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var show = self._simpleBind(self.show, self); + var checkGroupMaxOpen = function() { + if (self.config.groupName) { + angular.forEach(self.config.groupName, function(group) { + if (self._$mdPanel._openCountExceedsMaxOpen(group)) { + self._$mdPanel._closeFirstOpenedPanel(group); + } + }); + } + }; + + self.attach() + .then(show) + .then(checkGroupMaxOpen) + .then(done) + .catch(reject); + }); +}; + + +/** + * Closes the panel. + * @param {string} closeReason The event type that triggered the close. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is closed and animations finish. + */ +MdPanelRef.prototype.close = function(closeReason) { + var self = this; + + return this._$q(function(resolve, reject) { + self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() { + var done = self._done(resolve, self); + var detach = self._simpleBind(self.detach, self); + var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop; + onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason); + + self.hide() + .then(detach) + .then(done) + .then(onCloseSuccess) + .catch(reject); + }, reject); + }); +}; + + +/** + * Attaches the panel. The panel will be hidden afterwards. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is attached. + */ +MdPanelRef.prototype.attach = function() { + if (this.isAttached && this.panelEl) { + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onDomAdded = self.config['onDomAdded'] || angular.noop; + var addListeners = function(response) { + self.isAttached = true; + self._addEventListeners(); + return response; + }; + + self._$q.all([ + self._createBackdrop(), + self._createPanel() + .then(addListeners) + .catch(reject) + ]).then(onDomAdded) + .then(done) + .catch(reject); + }); +}; + + +/** + * Only detaches the panel. Will NOT hide the panel first. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is detached. + */ +MdPanelRef.prototype.detach = function() { + if (!this.isAttached) { + return this._$q.when(this); + } + + var self = this; + var onDomRemoved = self.config['onDomRemoved'] || angular.noop; + + var detachFn = function() { + self._removeEventListeners(); + + // Remove the focus traps that we added earlier for keeping focus within + // the panel. + if (self._topFocusTrap && self._topFocusTrap.parentNode) { + self._topFocusTrap.parentNode.removeChild(self._topFocusTrap); + } + + if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) { + self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap); + } + + if (self._restoreCache.classes) { + self.panelEl[0].className = self._restoreCache.classes; + } + + // Either restore the saved styles or clear the ones set by mdPanel. + self.panelEl[0].style.cssText = self._restoreCache.styles || ''; + + self._compilerCleanup(); + self.panelContainer.remove(); + self.isAttached = false; + return self._$q.when(self); + }; + + if (this._restoreScroll) { + this._restoreScroll(); + this._restoreScroll = null; + } + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + + self._$q.all([ + detachFn(), + self._backdropRef ? self._backdropRef.detach() : true + ]).then(onDomRemoved) + .then(done) + .catch(reject); + }); +}; + + +/** + * Destroys the panel. The Panel cannot be opened again after this. + */ +MdPanelRef.prototype.destroy = function() { + var self = this; + if (this.config.groupName) { + angular.forEach(this.config.groupName, function(group) { + self.removeFromGroup(group); + }); + } + this.config.scope.$destroy(); + this.config.locals = null; + this._interceptors = null; +}; + + +/** + * Shows the panel. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel has shown and animations finish. + */ +MdPanelRef.prototype.show = function() { + if (!this.panelContainer) { + return this._$q(function(resolve, reject) { + reject('mdPanel: Panel does not exist yet. Call open() or attach().'); + }); + } + + if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) { + return this._$q.when(this); + } + + var self = this; + var animatePromise = function() { + self.panelContainer.removeClass(MD_PANEL_HIDDEN); + return self._animateOpen(); + }; + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onOpenComplete = self.config['onOpenComplete'] || angular.noop; + var addToGroupOpen = function() { + if (self.config.groupName) { + angular.forEach(self.config.groupName, function(group) { + self._$mdPanel._groups[group].openPanels.push(self); + }); + } + }; + + self._$q.all([ + self._backdropRef ? self._backdropRef.show() : self, + animatePromise().then(function() { self._focusOnOpen(); }, reject) + ]).then(onOpenComplete) + .then(addToGroupOpen) + .then(done) + .catch(reject); + }); +}; + + +/** + * Hides the panel. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel has hidden and animations finish. + */ +MdPanelRef.prototype.hide = function() { + if (!this.panelContainer) { + return this._$q(function(resolve, reject) { + reject('mdPanel: Panel does not exist yet. Call open() or attach().'); + }); + } + + if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) { + return this._$q.when(this); + } + + var self = this; + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onRemoving = self.config['onRemoving'] || angular.noop; + var hidePanel = function() { + self.panelContainer.addClass(MD_PANEL_HIDDEN); + }; + var removeFromGroupOpen = function() { + if (self.config.groupName) { + var group, index; + angular.forEach(self.config.groupName, function(group) { + group = self._$mdPanel._groups[group]; + index = group.openPanels.indexOf(self); + if (index > -1) { + group.openPanels.splice(index, 1); + } + }); + } + }; + var focusOnOrigin = function() { + var origin = self.config['origin']; + if (origin) { + getElement(origin).focus(); + } + }; + + self._$q.all([ + self._backdropRef ? self._backdropRef.hide() : self, + self._animateClose() + .then(onRemoving) + .then(hidePanel) + .then(removeFromGroupOpen) + .then(focusOnOrigin) + .catch(reject) + ]).then(done, reject); + }); +}; + + +/** + * Add a class to the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} newClass Class to be added. + * @param {boolean} toElement Whether or not to add the class to the panel + * element instead of the container. + */ +MdPanelRef.prototype.addClass = function(newClass, toElement) { + this._$log.warn( + 'mdPanel: The addClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error( + 'mdPanel: Panel does not exist yet. Call open() or attach().'); + } + + if (!toElement && !this.panelContainer.hasClass(newClass)) { + this.panelContainer.addClass(newClass); + } else if (toElement && !this.panelEl.hasClass(newClass)) { + this.panelEl.addClass(newClass); + } +}; + + +/** + * Remove a class from the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} oldClass Class to be removed. + * @param {boolean} fromElement Whether or not to remove the class from the + * panel element instead of the container. + */ +MdPanelRef.prototype.removeClass = function(oldClass, fromElement) { + this._$log.warn( + 'mdPanel: The removeClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error( + 'mdPanel: Panel does not exist yet. Call open() or attach().'); + } + + if (!fromElement && this.panelContainer.hasClass(oldClass)) { + this.panelContainer.removeClass(oldClass); + } else if (fromElement && this.panelEl.hasClass(oldClass)) { + this.panelEl.removeClass(oldClass); + } +}; + + +/** + * Toggle a class on the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} toggleClass The class to toggle. + * @param {boolean} onElement Whether or not to toggle the class on the panel + * element instead of the container. + */ +MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) { + this._$log.warn( + 'mdPanel: The toggleClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error( + 'mdPanel: Panel does not exist yet. Call open() or attach().'); + } + + if (!onElement) { + this.panelContainer.toggleClass(toggleClass); + } else { + this.panelEl.toggleClass(toggleClass); + } +}; + + +/** + * Compiles the panel, according to the passed in config and appends it to + * the DOM. Helps normalize differences in the compilation process between + * using a string template and a content element. + * @returns {!angular.$q.Promise} Promise that is resolved when + * the element has been compiled and added to the DOM. + * @private + */ +MdPanelRef.prototype._compile = function() { + var self = this; + + // Compile the element via $mdCompiler. Note that when using a + // contentElement, the element isn't actually being compiled, rather the + // compiler saves it's place in the DOM and provides a way of restoring it. + return self._$mdCompiler.compile(self.config).then(function(compileData) { + var config = self.config; + + if (config.contentElement) { + var panelEl = compileData.element; + + // Since mdPanel modifies the inline styles and CSS classes, we need + // to save them in order to be able to restore on close. + self._restoreCache.styles = panelEl[0].style.cssText; + self._restoreCache.classes = panelEl[0].className; + + self.panelContainer = self._$mdPanel._wrapContentElement(panelEl); + self.panelEl = panelEl; + } else { + self.panelContainer = compileData.link(config['scope']); + self.panelEl = angular.element( + self.panelContainer[0].querySelector('.md-panel') + ); + } + + // Save a reference to the cleanup function from the compiler. + self._compilerCleanup = compileData.cleanup; + + // Attach the panel to the proper place in the DOM. + getElement(self.config['attachTo']).append(self.panelContainer); + + return self; + }); +}; + + +/** + * Creates a panel and adds it to the dom. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * created. + * @private + */ +MdPanelRef.prototype._createPanel = function() { + var self = this; + + return this._$q(function(resolve, reject) { + if (!self.config.locals) { + self.config.locals = {}; + } + + self.config.locals.mdPanelRef = self; + + self._compile().then(function() { + if (self.config['disableParentScroll']) { + self._restoreScroll = self._$mdUtil.disableScrollAround( + null, + self.panelContainer, + { disableScrollMask: true } + ); + } + + // Add a custom CSS class to the panel element. + if (self.config['panelClass']) { + self.panelEl.addClass(self.config['panelClass']); + } + + // Handle click and touch events for the panel container. + if (self.config['propagateContainerEvents']) { + self.panelContainer.css('pointer-events', 'none'); + self.panelEl.css('pointer-events', 'all'); + } + + // Panel may be outside the $rootElement, tell ngAnimate to animate + // regardless. + if (self._$animate.pin) { + self._$animate.pin( + self.panelContainer, + getElement(self.config['attachTo']) + ); + } + + self._configureTrapFocus(); + self._addStyles().then(function() { + resolve(self); + }, reject); + }, reject); + + }); +}; + + +/** + * Adds the styles for the panel, such as positioning and z-index. Also, + * themes the panel element and panel container using `$mdTheming`. + * @returns {!angular.$q.Promise} + * @private + */ +MdPanelRef.prototype._addStyles = function() { + var self = this; + return this._$q(function(resolve) { + self.panelContainer.css('z-index', self.config['zIndex']); + self.panelEl.css('z-index', self.config['zIndex'] + 1); + + var hideAndResolve = function() { + // Theme the element and container. + self._setTheming(); + + // Remove offscreen class and add hidden class. + self.panelEl.removeClass('_md-panel-offscreen'); + self.panelContainer.addClass(MD_PANEL_HIDDEN); + + resolve(self); + }; + + if (self.config['fullscreen']) { + self.panelEl.addClass('_md-panel-fullscreen'); + hideAndResolve(); + return; // Don't setup positioning. + } + + var positionConfig = self.config['position']; + if (!positionConfig) { + hideAndResolve(); + return; // Don't setup positioning. + } + + // Wait for angular to finish processing the template + self._$rootScope['$$postDigest'](function() { + // Position it correctly. This is necessary so that the panel will have a + // defined height and width. + self._updatePosition(true); + + // Theme the element and container. + self._setTheming(); + + resolve(self); + }); + }); +}; + + +/** + * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`. + * @private + */ +MdPanelRef.prototype._setTheming = function() { + this._$mdTheming(this.panelEl); + this._$mdTheming(this.panelContainer); +}; + + +/** + * Updates the position configuration of a panel + * @param {!MdPanelPosition} position + */ +MdPanelRef.prototype.updatePosition = function(position) { + if (!this.panelContainer) { + throw new Error( + 'mdPanel: Panel does not exist yet. Call open() or attach().'); + } + + this.config['position'] = position; + this._updatePosition(); +}; + + +/** + * Calculates and updates the position of the panel. + * @param {boolean=} init + * @private + */ +MdPanelRef.prototype._updatePosition = function(init) { + var positionConfig = this.config['position']; + + if (positionConfig) { + positionConfig._setPanelPosition(this.panelEl); + + // Hide the panel now that position is known. + if (init) { + this.panelEl.removeClass('_md-panel-offscreen'); + this.panelContainer.addClass(MD_PANEL_HIDDEN); + } + + this.panelEl.css( + MdPanelPosition.absPosition.TOP, + positionConfig.getTop() + ); + this.panelEl.css( + MdPanelPosition.absPosition.BOTTOM, + positionConfig.getBottom() + ); + this.panelEl.css( + MdPanelPosition.absPosition.LEFT, + positionConfig.getLeft() + ); + this.panelEl.css( + MdPanelPosition.absPosition.RIGHT, + positionConfig.getRight() + ); + } +}; + + +/** + * Focuses on the panel or the first focus target. + * @private + */ +MdPanelRef.prototype._focusOnOpen = function() { + if (this.config['focusOnOpen']) { + // Wait for the template to finish rendering to guarantee md-autofocus has + // finished adding the class md-autofocus, otherwise the focusable element + // isn't available to focus. + var self = this; + this._$rootScope['$$postDigest'](function() { + var target = self._$mdUtil.findFocusTarget(self.panelEl) || + self.panelEl; + target.focus(); + }); + } +}; + + +/** + * Shows the backdrop. + * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop + * is created and attached. + * @private + */ +MdPanelRef.prototype._createBackdrop = function() { + if (this.config.hasBackdrop) { + if (!this._backdropRef) { + var backdropAnimation = this._$mdPanel.newPanelAnimation() + .openFrom(this.config.attachTo) + .withAnimation({ + open: '_md-opaque-enter', + close: '_md-opaque-leave' + }); + + if (this.config.animation) { + backdropAnimation.duration(this.config.animation._rawDuration); + } + + var backdropConfig = { + animation: backdropAnimation, + attachTo: this.config.attachTo, + focusOnOpen: false, + panelClass: '_md-panel-backdrop', + zIndex: this.config.zIndex - 1 + }; + + this._backdropRef = this._$mdPanel.create(backdropConfig); + } + if (!this._backdropRef.isAttached) { + return this._backdropRef.attach(); + } + } +}; + + +/** + * Listen for escape keys and outside clicks to auto close. + * @private + */ +MdPanelRef.prototype._addEventListeners = function() { + this._configureEscapeToClose(); + this._configureClickOutsideToClose(); + this._configureScrollListener(); +}; + + +/** + * Remove event listeners added in _addEventListeners. + * @private + */ +MdPanelRef.prototype._removeEventListeners = function() { + this._removeListeners && this._removeListeners.forEach(function(removeFn) { + removeFn(); + }); + this._removeListeners = []; +}; + + +/** + * Setup the escapeToClose event listeners. + * @private + */ +MdPanelRef.prototype._configureEscapeToClose = function() { + if (this.config['escapeToClose']) { + var parentTarget = getElement(this.config['attachTo']); + var self = this; + + var keyHandlerFn = function(ev) { + if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + + self.close(MdPanelRef.closeReasons.ESCAPE); + } + }; + + // Add keydown listeners + this.panelContainer.on('keydown', keyHandlerFn); + parentTarget.on('keydown', keyHandlerFn); + + // Queue remove listeners function + this._removeListeners.push(function() { + self.panelContainer.off('keydown', keyHandlerFn); + parentTarget.off('keydown', keyHandlerFn); + }); + } +}; + + +/** + * Setup the clickOutsideToClose event listeners. + * @private + */ +MdPanelRef.prototype._configureClickOutsideToClose = function() { + if (this.config['clickOutsideToClose']) { + var target = this.config['propagateContainerEvents'] ? + angular.element(document.body) : + this.panelContainer; + var sourceEl; + + // Keep track of the element on which the mouse originally went down + // so that we can only close the backdrop when the 'click' started on it. + // A simple 'click' handler does not work, it sets the target object as the + // element the mouse went down on. + var mousedownHandler = function(ev) { + sourceEl = ev.target; + }; + + // We check if our original element and the target is the backdrop + // because if the original was the backdrop and the target was inside the + // panel we don't want to panel to close. + var self = this; + var mouseupHandler = function(ev) { + if (self.config['propagateContainerEvents']) { + + // We check if the sourceEl of the event is the panel element or one + // of it's children. If it is not, then close the panel. + if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) { + self.close(); + } + + } else if (sourceEl === target[0] && ev.target === target[0]) { + ev.stopPropagation(); + ev.preventDefault(); + + self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE); + } + }; + + // Add listeners + target.on('mousedown', mousedownHandler); + target.on('mouseup', mouseupHandler); + + // Queue remove listeners function + this._removeListeners.push(function() { + target.off('mousedown', mousedownHandler); + target.off('mouseup', mouseupHandler); + }); + } +}; + + +/** + * Configures the listeners for updating the panel position on scroll. + * @private +*/ +MdPanelRef.prototype._configureScrollListener = function() { + // No need to bind the event if scrolling is disabled. + if (!this.config['disableParentScroll']) { + var updatePosition = angular.bind(this, this._updatePosition); + var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition); + var self = this; + + var onScroll = function() { + debouncedUpdatePosition(); + }; + + // Add listeners. + this._$window.addEventListener('scroll', onScroll, true); + + // Queue remove listeners function. + this._removeListeners.push(function() { + self._$window.removeEventListener('scroll', onScroll, true); + }); + } +}; + + +/** + * Setup the focus traps. These traps will wrap focus when tabbing past the + * panel. When shift-tabbing, the focus will stick in place. + * @private + */ +MdPanelRef.prototype._configureTrapFocus = function() { + // Focus doesn't remain inside of the panel without this. + this.panelEl.attr('tabIndex', '-1'); + if (this.config['trapFocus']) { + var element = this.panelEl; + // Set up elements before and after the panel to capture focus and + // redirect back into the panel. + this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0]; + this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0]; + + // When focus is about to move out of the panel, we want to intercept it + // and redirect it back to the panel element. + var focusHandler = function() { + element.focus(); + }; + this._topFocusTrap.addEventListener('focus', focusHandler); + this._bottomFocusTrap.addEventListener('focus', focusHandler); + + // Queue remove listeners function + this._removeListeners.push(this._simpleBind(function() { + this._topFocusTrap.removeEventListener('focus', focusHandler); + this._bottomFocusTrap.removeEventListener('focus', focusHandler); + }, this)); + + // The top focus trap inserted immediately before the md-panel element (as + // a sibling). The bottom focus trap inserted immediately after the + // md-panel element (as a sibling). + element[0].parentNode.insertBefore(this._topFocusTrap, element[0]); + element.after(this._bottomFocusTrap); + } +}; + + +/** + * Updates the animation of a panel. + * @param {!MdPanelAnimation} animation + */ +MdPanelRef.prototype.updateAnimation = function(animation) { + this.config['animation'] = animation; + + if (this._backdropRef) { + this._backdropRef.config.animation.duration(animation._rawDuration); + } +}; + + +/** + * Animate the panel opening. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * animated open. + * @private + */ +MdPanelRef.prototype._animateOpen = function() { + this.panelContainer.addClass('md-panel-is-showing'); + var animationConfig = this.config['animation']; + if (!animationConfig) { + // Promise is in progress, return it. + this.panelContainer.addClass('_md-panel-shown'); + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve) { + var done = self._done(resolve, self); + var warnAndOpen = function() { + self._$log.warn( + 'mdPanel: MdPanel Animations failed. ' + + 'Showing panel without animating.'); + done(); + }; + + animationConfig.animateOpen(self.panelEl) + .then(done, warnAndOpen); + }); +}; + + +/** + * Animate the panel closing. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * animated closed. + * @private + */ +MdPanelRef.prototype._animateClose = function() { + var animationConfig = this.config['animation']; + if (!animationConfig) { + this.panelContainer.removeClass('md-panel-is-showing'); + this.panelContainer.removeClass('_md-panel-shown'); + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve) { + var done = function() { + self.panelContainer.removeClass('md-panel-is-showing'); + resolve(self); + }; + var warnAndClose = function() { + self._$log.warn( + 'mdPanel: MdPanel Animations failed. ' + + 'Hiding panel without animating.'); + done(); + }; + + animationConfig.animateClose(self.panelEl) + .then(done, warnAndClose); + }); +}; + + +/** + * Registers a interceptor with the panel. The callback should return a promise, + * which will allow the action to continue when it gets resolved, or will + * prevent an action if it is rejected. + * @param {string} type Type of interceptor. + * @param {!angular.$q.Promise} callback Callback to be registered. + * @returns {!MdPanelRef} + */ +MdPanelRef.prototype.registerInterceptor = function(type, callback) { + var error = null; + + if (!angular.isString(type)) { + error = 'Interceptor type must be a string, instead got ' + typeof type; + } else if (!angular.isFunction(callback)) { + error = 'Interceptor callback must be a function, instead got ' + typeof callback; + } + + if (error) { + throw new Error('MdPanel: ' + error); + } + + var interceptors = this._interceptors[type] = this._interceptors[type] || []; + + if (interceptors.indexOf(callback) === -1) { + interceptors.push(callback); + } + + return this; +}; + + +/** + * Removes a registered interceptor. + * @param {string} type Type of interceptor to be removed. + * @param {Function} callback Interceptor to be removed. + * @returns {!MdPanelRef} + */ +MdPanelRef.prototype.removeInterceptor = function(type, callback) { + var index = this._interceptors[type] ? + this._interceptors[type].indexOf(callback) : -1; + + if (index > -1) { + this._interceptors[type].splice(index, 1); + } + + return this; +}; + + +/** + * Removes all interceptors. + * @param {string=} type Type of interceptors to be removed. + * If ommited, all interceptors types will be removed. + * @returns {!MdPanelRef} + */ +MdPanelRef.prototype.removeAllInterceptors = function(type) { + if (type) { + this._interceptors[type] = []; + } else { + this._interceptors = Object.create(null); + } + + return this; +}; + + +/** + * Invokes all the interceptors of a certain type sequantially in + * reverse order. Works in a similar way to `$q.all`, except it + * respects the order of the functions. + * @param {string} type Type of interceptors to be invoked. + * @returns {!angular.$q.Promise} + * @private + */ +MdPanelRef.prototype._callInterceptors = function(type) { + var self = this; + var $q = self._$q; + var interceptors = self._interceptors && self._interceptors[type] || []; + + return interceptors.reduceRight(function(promise, interceptor) { + var isPromiseLike = interceptor && angular.isFunction(interceptor.then); + var response = isPromiseLike ? interceptor : null; + + /** + * For interceptors to reject/cancel subsequent portions of the chain, simply + * return a `$q.reject()` + */ + return promise.then(function() { + if (!response) { + try { + response = interceptor(self); + } catch(e) { + response = $q.reject(e); + } + } + + return response; + }); + }, $q.resolve(self)); +}; + + +/** + * Faster, more basic than angular.bind + * http://jsperf.com/angular-bind-vs-custom-vs-native + * @param {function} callback + * @param {!Object} self + * @return {function} Callback function with a bound self. + */ +MdPanelRef.prototype._simpleBind = function(callback, self) { + return function(value) { + return callback.apply(self, value); + }; +}; + + +/** + * @param {function} callback + * @param {!Object} self + * @return {function} Callback function with a self param. + */ +MdPanelRef.prototype._done = function(callback, self) { + return function() { + callback(self); + }; +}; + + +/** + * Adds a panel to a group if the panel does not exist within the group already. + * A panel can only exist within a single group. + * @param {string} groupName The name of the group. + */ +MdPanelRef.prototype.addToGroup = function(groupName) { + if (!this._$mdPanel._groups[groupName]) { + this._$mdPanel.newPanelGroup(groupName); + } + + var group = this._$mdPanel._groups[groupName]; + var index = group.panels.indexOf(this); + + if (index < 0) { + group.panels.push(this); + } +}; + + +/** + * Removes a panel from a group if the panel exists within that group. The group + * must be created ahead of time. + * @param {string} groupName The name of the group. + */ +MdPanelRef.prototype.removeFromGroup = function(groupName) { + if (!this._$mdPanel._groups[groupName]) { + throw new Error('mdPanel: The group ' + groupName + ' does not exist.'); + } + + var group = this._$mdPanel._groups[groupName]; + var index = group.panels.indexOf(this); + + if (index > -1) { + group.panels.splice(index, 1); + } +}; + + +/** + * Possible default closeReasons for the close function. + * @enum {string} + */ +MdPanelRef.closeReasons = { + CLICK_OUTSIDE: 'clickOutsideToClose', + ESCAPE: 'escapeToClose', +}; + + +/***************************************************************************** + * MdPanelPosition * + *****************************************************************************/ + + +/** + * Position configuration object. To use, create an MdPanelPosition with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * Example: + * + * var panelPosition = new MdPanelPosition() + * .relativeTo(myButtonEl) + * .addPanelPosition( + * $mdPanel.xPosition.CENTER, + * $mdPanel.yPosition.ALIGN_TOPS + * ); + * + * $mdPanel.create({ + * position: panelPosition + * }); + * + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelPosition($injector) { + /** @private @const {!angular.$window} */ + this._$window = $injector.get('$window'); + + /** @private {boolean} */ + this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl'; + + /** @private @const {!angular.$mdConstant} */ + this._$mdConstant = $injector.get('$mdConstant'); + + /** @private {boolean} */ + this._absolute = false; + + /** @private {!angular.JQLite} */ + this._relativeToEl; + + /** @private {string} */ + this._top = ''; + + /** @private {string} */ + this._bottom = ''; + + /** @private {string} */ + this._left = ''; + + /** @private {string} */ + this._right = ''; + + /** @private {!Array} */ + this._translateX = []; + + /** @private {!Array} */ + this._translateY = []; + + /** @private {!Array<{x:string, y:string}>} */ + this._positions = []; + + /** @private {?{x:string, y:string}} */ + this._actualPosition; +} + + +/** + * Possible values of xPosition. + * @enum {string} + */ +MdPanelPosition.xPosition = { + CENTER: 'center', + ALIGN_START: 'align-start', + ALIGN_END: 'align-end', + OFFSET_START: 'offset-start', + OFFSET_END: 'offset-end' +}; + + +/** + * Possible values of yPosition. + * @enum {string} + */ +MdPanelPosition.yPosition = { + CENTER: 'center', + ALIGN_TOPS: 'align-tops', + ALIGN_BOTTOMS: 'align-bottoms', + ABOVE: 'above', + BELOW: 'below' +}; + + +/** + * Possible values of absolute position. + * @enum {string} + */ +MdPanelPosition.absPosition = { + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left' +}; + +/** + * Margin between the edges of a panel and the viewport. + * @const {number} + */ +MdPanelPosition.viewportMargin = 8; + + +/** + * Sets absolute positioning for the panel. + * @return {!MdPanelPosition} + */ +MdPanelPosition.prototype.absolute = function() { + this._absolute = true; + return this; +}; + + +/** + * Sets the value of a position for the panel. Clears any previously set + * position. + * @param {string} position Position to set + * @param {string=} value Value of the position. Defaults to '0'. + * @returns {!MdPanelPosition} + * @private + */ +MdPanelPosition.prototype._setPosition = function(position, value) { + if (position === MdPanelPosition.absPosition.RIGHT || + position === MdPanelPosition.absPosition.LEFT) { + this._left = this._right = ''; + } else if ( + position === MdPanelPosition.absPosition.BOTTOM || + position === MdPanelPosition.absPosition.TOP) { + this._top = this._bottom = ''; + } else { + var positions = Object.keys(MdPanelPosition.absPosition).join() + .toLowerCase(); + + throw new Error('mdPanel: Position must be one of ' + positions + '.'); + } + + this['_' + position] = angular.isString(value) ? value : '0'; + + return this; +}; + + +/** + * Sets the value of `top` for the panel. Clears any previously set vertical + * position. + * @param {string=} top Value of `top`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.top = function(top) { + return this._setPosition(MdPanelPosition.absPosition.TOP, top); +}; + + +/** + * Sets the value of `bottom` for the panel. Clears any previously set vertical + * position. + * @param {string=} bottom Value of `bottom`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.bottom = function(bottom) { + return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom); +}; + + +/** + * Sets the panel to the start of the page - `left` if `ltr` or `right` for + * `rtl`. Clears any previously set horizontal position. + * @param {string=} start Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.start = function(start) { + var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT; + return this._setPosition(position, start); +}; + + +/** + * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`. + * Clears any previously set horizontal position. + * @param {string=} end Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.end = function(end) { + var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT; + return this._setPosition(position, end); +}; + + +/** + * Sets the value of `left` for the panel. Clears any previously set + * horizontal position. + * @param {string=} left Value of `left`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.left = function(left) { + return this._setPosition(MdPanelPosition.absPosition.LEFT, left); +}; + + +/** + * Sets the value of `right` for the panel. Clears any previously set + * horizontal position. + * @param {string=} right Value of `right`. Defaults to '0'. + * @returns {!MdPanelPosition} +*/ +MdPanelPosition.prototype.right = function(right) { + return this._setPosition(MdPanelPosition.absPosition.RIGHT, right); +}; + + +/** + * Centers the panel horizontally in the viewport. Clears any previously set + * horizontal position. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.centerHorizontally = function() { + this._left = '50%'; + this._right = ''; + this._translateX = ['-50%']; + return this; +}; + + +/** + * Centers the panel vertically in the viewport. Clears any previously set + * vertical position. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.centerVertically = function() { + this._top = '50%'; + this._bottom = ''; + this._translateY = ['-50%']; + return this; +}; + + +/** + * Centers the panel horizontally and vertically in the viewport. This is + * equivalent to calling both `centerHorizontally` and `centerVertically`. + * Clears any previously set horizontal and vertical positions. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.center = function() { + return this.centerHorizontally().centerVertically(); +}; + + +/** + * Sets element for relative positioning. + * @param {string|!Element|!angular.JQLite} element Query selector, DOM element, + * or angular element to set the panel relative to. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.relativeTo = function(element) { + this._absolute = false; + this._relativeToEl = getElement(element); + return this; +}; + + +/** + * Sets the x and y positions for the panel relative to another element. + * @param {string} xPosition must be one of the MdPanelPosition.xPosition + * values. + * @param {string} yPosition must be one of the MdPanelPosition.yPosition + * values. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) { + if (!this._relativeToEl) { + throw new Error('mdPanel: addPanelPosition can only be used with ' + + 'relative positioning. Set relativeTo first.'); + } + + this._validateXPosition(xPosition); + this._validateYPosition(yPosition); + + this._positions.push({ + x: xPosition, + y: yPosition, + }); + return this; +}; + + +/** + * Ensures that yPosition is a valid position name. Throw an exception if not. + * @param {string} yPosition + */ +MdPanelPosition.prototype._validateYPosition = function(yPosition) { + // empty is ok + if (yPosition == null) { + return; + } + + var positionKeys = Object.keys(MdPanelPosition.yPosition); + var positionValues = []; + for (var key, i = 0; key = positionKeys[i]; i++) { + var position = MdPanelPosition.yPosition[key]; + positionValues.push(position); + + if (position === yPosition) { + return; + } + } + + throw new Error('mdPanel: Panel y position only accepts the following ' + + 'values:\n' + positionValues.join(' | ')); +}; + + +/** + * Ensures that xPosition is a valid position name. Throw an exception if not. + * @param {string} xPosition + */ +MdPanelPosition.prototype._validateXPosition = function(xPosition) { + // empty is ok + if (xPosition == null) { + return; + } + + var positionKeys = Object.keys(MdPanelPosition.xPosition); + var positionValues = []; + for (var key, i = 0; key = positionKeys[i]; i++) { + var position = MdPanelPosition.xPosition[key]; + positionValues.push(position); + if (position === xPosition) { + return; + } + } + + throw new Error('mdPanel: Panel x Position only accepts the following ' + + 'values:\n' + positionValues.join(' | ')); +}; + + +/** + * Sets the value of the offset in the x-direction. This will add to any + * previously set offsets. + * @param {string|function(MdPanelPosition): string} offsetX + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.withOffsetX = function(offsetX) { + this._translateX.push(offsetX); + return this; +}; + + +/** + * Sets the value of the offset in the y-direction. This will add to any + * previously set offsets. + * @param {string|function(MdPanelPosition): string} offsetY + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.withOffsetY = function(offsetY) { + this._translateY.push(offsetY); + return this; +}; + + +/** + * Gets the value of `top` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getTop = function() { + return this._top; +}; + + +/** + * Gets the value of `bottom` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getBottom = function() { + return this._bottom; +}; + + +/** + * Gets the value of `left` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getLeft = function() { + return this._left; +}; + + +/** + * Gets the value of `right` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getRight = function() { + return this._right; +}; + + +/** + * Gets the value of `transform` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getTransform = function() { + var translateX = this._reduceTranslateValues('translateX', this._translateX); + var translateY = this._reduceTranslateValues('translateY', this._translateY); + + // It's important to trim the result, because the browser will ignore the set + // operation if the string contains only whitespace. + return (translateX + ' ' + translateY).trim(); +}; + + +/** + * Sets the `transform` value for a panel element. + * @param {!angular.JQLite} panelEl + * @returns {!angular.JQLite} + * @private + */ +MdPanelPosition.prototype._setTransform = function(panelEl) { + return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform()); +}; + + +/** + * True if the panel is completely on-screen with this positioning; false + * otherwise. + * @param {!angular.JQLite} panelEl + * @return {boolean} + * @private + */ +MdPanelPosition.prototype._isOnscreen = function(panelEl) { + // this works because we always use fixed positioning for the panel, + // which is relative to the viewport. + var left = parseInt(this.getLeft()); + var top = parseInt(this.getTop()); + + if (this._translateX.length || this._translateY.length) { + var prefixedTransform = this._$mdConstant.CSS.TRANSFORM; + var offsets = getComputedTranslations(panelEl, prefixedTransform); + left += offsets.x; + top += offsets.y; + } + + var right = left + panelEl[0].offsetWidth; + var bottom = top + panelEl[0].offsetHeight; + + return (left >= 0) && + (top >= 0) && + (bottom <= this._$window.innerHeight) && + (right <= this._$window.innerWidth); +}; + + +/** + * Gets the first x/y position that can fit on-screen. + * @returns {{x: string, y: string}} + */ +MdPanelPosition.prototype.getActualPosition = function() { + return this._actualPosition; +}; + + +/** + * Reduces a list of translate values to a string that can be used within + * transform. + * @param {string} translateFn + * @param {!Array} values + * @returns {string} + * @private + */ +MdPanelPosition.prototype._reduceTranslateValues = + function(translateFn, values) { + return values.map(function(translation) { + // TODO(crisbeto): this should add the units after #9609 is merged. + var translationValue = angular.isFunction(translation) ? + translation(this) : translation; + return translateFn + '(' + translationValue + ')'; + }, this).join(' '); + }; + + +/** + * Sets the panel position based on the created panel element and best x/y + * positioning. + * @param {!angular.JQLite} panelEl + * @private + */ +MdPanelPosition.prototype._setPanelPosition = function(panelEl) { + // Remove the "position adjusted" class in case it has been added before. + panelEl.removeClass('_md-panel-position-adjusted'); + + // Only calculate the position if necessary. + if (this._absolute) { + this._setTransform(panelEl); + return; + } + + if (this._actualPosition) { + this._calculatePanelPosition(panelEl, this._actualPosition); + this._setTransform(panelEl); + this._constrainToViewport(panelEl); + return; + } + + for (var i = 0; i < this._positions.length; i++) { + this._actualPosition = this._positions[i]; + this._calculatePanelPosition(panelEl, this._actualPosition); + this._setTransform(panelEl); + + if (this._isOnscreen(panelEl)) { + return; + } + } + + this._constrainToViewport(panelEl); +}; + + +/** + * Constrains a panel's position to the viewport. + * @param {!angular.JQLite} panelEl + * @private + */ +MdPanelPosition.prototype._constrainToViewport = function(panelEl) { + var margin = MdPanelPosition.viewportMargin; + var initialTop = this._top; + var initialLeft = this._left; + + if (this.getTop()) { + var top = parseInt(this.getTop()); + var bottom = panelEl[0].offsetHeight + top; + var viewportHeight = this._$window.innerHeight; + + if (top < margin) { + this._top = margin + 'px'; + } else if (bottom > viewportHeight) { + this._top = top - (bottom - viewportHeight + margin) + 'px'; + } + } + + if (this.getLeft()) { + var left = parseInt(this.getLeft()); + var right = panelEl[0].offsetWidth + left; + var viewportWidth = this._$window.innerWidth; + + if (left < margin) { + this._left = margin + 'px'; + } else if (right > viewportWidth) { + this._left = left - (right - viewportWidth + margin) + 'px'; + } + } + + // Class that can be used to re-style the panel if it was repositioned. + panelEl.toggleClass( + '_md-panel-position-adjusted', + this._top !== initialTop || this._left !== initialLeft + ); +}; + + +/** + * Switches between 'start' and 'end'. + * @param {string} position Horizontal position of the panel + * @returns {string} Reversed position + * @private + */ +MdPanelPosition.prototype._reverseXPosition = function(position) { + if (position === MdPanelPosition.xPosition.CENTER) { + return position; + } + + var start = 'start'; + var end = 'end'; + + return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start); +}; + + +/** + * Handles horizontal positioning in rtl or ltr environments. + * @param {string} position Horizontal position of the panel + * @returns {string} The correct position according the page direction + * @private + */ +MdPanelPosition.prototype._bidi = function(position) { + return this._isRTL ? this._reverseXPosition(position) : position; +}; + + +/** + * Calculates the panel position based on the created panel element and the + * provided positioning. + * @param {!angular.JQLite} panelEl + * @param {!{x:string, y:string}} position + * @private + */ +MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) { + + var panelBounds = panelEl[0].getBoundingClientRect(); + var panelWidth = panelBounds.width; + var panelHeight = panelBounds.height; + + var targetBounds = this._relativeToEl[0].getBoundingClientRect(); + + var targetLeft = targetBounds.left; + var targetRight = targetBounds.right; + var targetWidth = targetBounds.width; + + switch (this._bidi(position.x)) { + case MdPanelPosition.xPosition.OFFSET_START: + this._left = targetLeft - panelWidth + 'px'; + break; + case MdPanelPosition.xPosition.ALIGN_END: + this._left = targetRight - panelWidth + 'px'; + break; + case MdPanelPosition.xPosition.CENTER: + var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth); + this._left = left + 'px'; + break; + case MdPanelPosition.xPosition.ALIGN_START: + this._left = targetLeft + 'px'; + break; + case MdPanelPosition.xPosition.OFFSET_END: + this._left = targetRight + 'px'; + break; + } + + var targetTop = targetBounds.top; + var targetBottom = targetBounds.bottom; + var targetHeight = targetBounds.height; + + switch (position.y) { + case MdPanelPosition.yPosition.ABOVE: + this._top = targetTop - panelHeight + 'px'; + break; + case MdPanelPosition.yPosition.ALIGN_BOTTOMS: + this._top = targetBottom - panelHeight + 'px'; + break; + case MdPanelPosition.yPosition.CENTER: + var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight); + this._top = top + 'px'; + break; + case MdPanelPosition.yPosition.ALIGN_TOPS: + this._top = targetTop + 'px'; + break; + case MdPanelPosition.yPosition.BELOW: + this._top = targetBottom + 'px'; + break; + } +}; + + +/***************************************************************************** + * MdPanelAnimation * + *****************************************************************************/ + + +/** + * Animation configuration object. To use, create an MdPanelAnimation with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * Example: + * + * var panelAnimation = new MdPanelAnimation() + * .openFrom(myButtonEl) + * .closeTo('.my-button') + * .withAnimation($mdPanel.animation.SCALE); + * + * $mdPanel.create({ + * animation: panelAnimation + * }); + * + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelAnimation($injector) { + /** @private @const {!angular.$mdUtil} */ + this._$mdUtil = $injector.get('$mdUtil'); + + /** + * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}| + * undefined} + */ + this._openFrom; + + /** + * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}| + * undefined} + */ + this._closeTo; + + /** @private {string|{open: string, close: string}} */ + this._animationClass = ''; + + /** @private {number} */ + this._openDuration; + + /** @private {number} */ + this._closeDuration; + + /** @private {number|{open: number, close: number}} */ + this._rawDuration; +} + + +/** + * Possible default animations. + * @enum {string} + */ +MdPanelAnimation.animation = { + SLIDE: 'md-panel-animate-slide', + SCALE: 'md-panel-animate-scale', + FADE: 'md-panel-animate-fade' +}; + + +/** + * Specifies where to start the open animation. `openFrom` accepts a + * click event object, query selector, DOM element, or a Rect object that + * is used to determine the bounds. When passed a click event, the location + * of the click will be used as the position to start the animation. + * @param {string|!Element|!Event|{top: number, left: number}} openFrom + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.openFrom = function(openFrom) { + // Check if 'openFrom' is an Event. + openFrom = openFrom.target ? openFrom.target : openFrom; + + this._openFrom = this._getPanelAnimationTarget(openFrom); + + if (!this._closeTo) { + this._closeTo = this._openFrom; + } + return this; +}; + + +/** + * Specifies where to animate the panel close. `closeTo` accepts a + * query selector, DOM element, or a Rect object that is used to determine + * the bounds. + * @param {string|!Element|{top: number, left: number}} closeTo + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.closeTo = function(closeTo) { + this._closeTo = this._getPanelAnimationTarget(closeTo); + return this; +}; + + +/** + * Specifies the duration of the animation in milliseconds. + * @param {number|{open: number, close: number}} duration + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.duration = function(duration) { + if (duration) { + if (angular.isNumber(duration)) { + this._openDuration = this._closeDuration = toSeconds(duration); + } else if (angular.isObject(duration)) { + this._openDuration = toSeconds(duration.open); + this._closeDuration = toSeconds(duration.close); + } + } + + // Save the original value so it can be passed to the backdrop. + this._rawDuration = duration; + + return this; + + function toSeconds(value) { + if (angular.isNumber(value)) return value / 1000; + } +}; + + +/** + * Returns the element and bounds for the animation target. + * @param {string|!Element|{top: number, left: number}} location + * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}} + * @private + */ +MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) { + if (angular.isDefined(location.top) || angular.isDefined(location.left)) { + return { + element: undefined, + bounds: { + top: location.top || 0, + left: location.left || 0 + } + }; + } else { + return this._getBoundingClientRect(getElement(location)); + } +}; + + +/** + * Specifies the animation class. + * + * There are several default animations that can be used: + * (MdPanelAnimation.animation) + * SLIDE: The panel slides in and out from the specified + * elements. + * SCALE: The panel scales in and out. + * FADE: The panel fades in and out. + * + * @param {string|{open: string, close: string}} cssClass + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.withAnimation = function(cssClass) { + this._animationClass = cssClass; + return this; +}; + + +/** + * Animate the panel open. + * @param {!angular.JQLite} panelEl + * @returns {!angular.$q.Promise} A promise that is resolved when the open + * animation is complete. + */ +MdPanelAnimation.prototype.animateOpen = function(panelEl) { + var animator = this._$mdUtil.dom.animator; + + this._fixBounds(panelEl); + var animationOptions = {}; + + // Include the panel transformations when calculating the animations. + var panelTransform = panelEl[0].style.transform || ''; + + var openFrom = animator.toTransformCss(panelTransform); + var openTo = animator.toTransformCss(panelTransform); + + switch (this._animationClass) { + case MdPanelAnimation.animation.SLIDE: + // Slide should start with opacity: 1. + panelEl.css('opacity', '1'); + + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + + var openSlide = animator.calculateSlideToOrigin( + panelEl, this._openFrom) || ''; + openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.SCALE: + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + + var openScale = animator.calculateZoomToOrigin( + panelEl, this._openFrom) || ''; + openFrom = animator.toTransformCss(openScale + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.FADE: + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + break; + + default: + if (angular.isString(this._animationClass)) { + animationOptions = { + transitionInClass: this._animationClass + }; + } else { + animationOptions = { + transitionInClass: this._animationClass['open'], + transitionOutClass: this._animationClass['close'], + }; + } + } + + animationOptions.duration = this._openDuration; + + return animator + .translate3d(panelEl, openFrom, openTo, animationOptions); +}; + + +/** + * Animate the panel close. + * @param {!angular.JQLite} panelEl + * @returns {!angular.$q.Promise} A promise that resolves when the close + * animation is complete. + */ +MdPanelAnimation.prototype.animateClose = function(panelEl) { + var animator = this._$mdUtil.dom.animator; + var reverseAnimationOptions = {}; + + // Include the panel transformations when calculating the animations. + var panelTransform = panelEl[0].style.transform || ''; + + var closeFrom = animator.toTransformCss(panelTransform); + var closeTo = animator.toTransformCss(panelTransform); + + switch (this._animationClass) { + case MdPanelAnimation.animation.SLIDE: + // Slide should start with opacity: 1. + panelEl.css('opacity', '1'); + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-leave' + }; + + var closeSlide = animator.calculateSlideToOrigin( + panelEl, this._closeTo) || ''; + closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.SCALE: + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave' + }; + + var closeScale = animator.calculateZoomToOrigin( + panelEl, this._closeTo) || ''; + closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.FADE: + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave' + }; + break; + + default: + if (angular.isString(this._animationClass)) { + reverseAnimationOptions = { + transitionOutClass: this._animationClass + }; + } else { + reverseAnimationOptions = { + transitionInClass: this._animationClass['close'], + transitionOutClass: this._animationClass['open'] + }; + } + } + + reverseAnimationOptions.duration = this._closeDuration; + + return animator + .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions); +}; + + +/** + * Set the height and width to match the panel if not provided. + * @param {!angular.JQLite} panelEl + * @private + */ +MdPanelAnimation.prototype._fixBounds = function(panelEl) { + var panelWidth = panelEl[0].offsetWidth; + var panelHeight = panelEl[0].offsetHeight; + + if (this._openFrom && this._openFrom.bounds.height == null) { + this._openFrom.bounds.height = panelHeight; + } + if (this._openFrom && this._openFrom.bounds.width == null) { + this._openFrom.bounds.width = panelWidth; + } + if (this._closeTo && this._closeTo.bounds.height == null) { + this._closeTo.bounds.height = panelHeight; + } + if (this._closeTo && this._closeTo.bounds.width == null) { + this._closeTo.bounds.width = panelWidth; + } +}; + + +/** + * Identify the bounding RECT for the target element. + * @param {!angular.JQLite} element + * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}} + * @private + */ +MdPanelAnimation.prototype._getBoundingClientRect = function(element) { + if (element instanceof angular.element) { + return { + element: element, + bounds: element[0].getBoundingClientRect() + }; + } +}; + + +/***************************************************************************** + * Util Methods * + *****************************************************************************/ + + +/** + * Returns the angular element associated with a css selector or element. + * @param el {string|!angular.JQLite|!Element} + * @returns {!angular.JQLite} + */ +function getElement(el) { + var queryResult = angular.isString(el) ? + document.querySelector(el) : el; + return angular.element(queryResult); +} + + +/** + * Gets the computed values for an element's translateX and translateY in px. + * @param {!angular.JQLite|!Element} el + * @param {string} property + * @return {{x: number, y: number}} + */ +function getComputedTranslations(el, property) { + // The transform being returned by `getComputedStyle` is in the format: + // `matrix(a, b, c, d, translateX, translateY)` if defined and `none` + // if the element doesn't have a transform. + var transform = getComputedStyle(el[0] || el)[property]; + var openIndex = transform.indexOf('('); + var closeIndex = transform.lastIndexOf(')'); + var output = { x: 0, y: 0 }; + + if (openIndex > -1 && closeIndex > -1) { + var parsedValues = transform + .substring(openIndex + 1, closeIndex) + .split(', ') + .slice(-2); + + output.x = parseInt(parsedValues[0]); + output.y = parseInt(parsedValues[1]); + } + + return output; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.progressCircular + * @description Module for a circular progressbar + */ + +angular.module('material.components.progressCircular', ['material.core']); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.progressLinear + * @description Linear Progress module! + */ +MdProgressLinearDirective.$inject = ["$mdTheming", "$mdUtil", "$log"]; +angular.module('material.components.progressLinear', [ + 'material.core' +]) + .directive('mdProgressLinear', MdProgressLinearDirective); + +/** + * @ngdoc directive + * @name mdProgressLinear + * @module material.components.progressLinear + * @restrict E + * + * @description + * The linear progress directive is used to make loading content + * in your app as delightful and painless as possible by minimizing + * the amount of visual change a user sees before they can view + * and interact with content. + * + * Each operation should only be represented by one activity indicator + * For example: one refresh operation should not display both a + * refresh bar and an activity circle. + * + * For operations where the percentage of the operation completed + * can be determined, use a determinate indicator. They give users + * a quick sense of how long an operation will take. + * + * For operations where the user is asked to wait a moment while + * something finishes up, and it’s not necessary to expose what's + * happening behind the scenes and how long it will take, use an + * indeterminate indicator. + * + * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query. + * + * Note: if the `md-mode` value is set as undefined or specified as 1 of the four (4) valid modes, then `indeterminate` + * will be auto-applied as the mode. + * + * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. If `value=""` is also specified, however, + * then `md-mode="determinate"` would be auto-injected instead. + * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0 + * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0 + * @param {boolean=} ng-disabled Determines whether to disable the progress element. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + */ +function MdProgressLinearDirective($mdTheming, $mdUtil, $log) { + var MODE_DETERMINATE = "determinate"; + var MODE_INDETERMINATE = "indeterminate"; + var MODE_BUFFER = "buffer"; + var MODE_QUERY = "query"; + var DISABLED_CLASS = "_md-progress-linear-disabled"; + + return { + restrict: 'E', + template: '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + compile: compile + }; + + function compile(tElement, tAttrs, transclude) { + tElement.attr('aria-valuemin', 0); + tElement.attr('aria-valuemax', 100); + tElement.attr('role', 'progressbar'); + + return postLink; + } + function postLink(scope, element, attr) { + $mdTheming(element); + + var lastMode; + var isDisabled = attr.hasOwnProperty('disabled'); + var toVendorCSS = $mdUtil.dom.animator.toCss; + var bar1 = angular.element(element[0].querySelector('.md-bar1')); + var bar2 = angular.element(element[0].querySelector('.md-bar2')); + var container = angular.element(element[0].querySelector('.md-container')); + + element + .attr('md-mode', mode()) + .toggleClass(DISABLED_CLASS, isDisabled); + + validateMode(); + watchAttributes(); + + /** + * Watch the value, md-buffer-value, and md-mode attributes + */ + function watchAttributes() { + attr.$observe('value', function(value) { + var percentValue = clamp(value); + element.attr('aria-valuenow', percentValue); + + if (mode() != MODE_QUERY) animateIndicator(bar2, percentValue); + }); + + attr.$observe('mdBufferValue', function(value) { + animateIndicator(bar1, clamp(value)); + }); + + attr.$observe('disabled', function(value) { + if (value === true || value === false) { + isDisabled = !!value; + } else { + isDisabled = angular.isDefined(value); + } + + element.toggleClass(DISABLED_CLASS, isDisabled); + container.toggleClass(lastMode, !isDisabled); + }); + + attr.$observe('mdMode', function(mode) { + if (lastMode) container.removeClass( lastMode ); + + switch( mode ) { + case MODE_QUERY: + case MODE_BUFFER: + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + container.addClass( lastMode = "md-mode-" + mode ); + break; + default: + container.addClass( lastMode = "md-mode-" + MODE_INDETERMINATE ); + break; + } + }); + } + + /** + * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified + */ + function validateMode() { + if ( angular.isUndefined(attr.mdMode) ) { + var hasValue = angular.isDefined(attr.value); + var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE; + var info = "Auto-adding the missing md-mode='{0}' to the ProgressLinear element"; + + //$log.debug( $mdUtil.supplant(info, [mode]) ); + + element.attr("md-mode", mode); + attr.mdMode = mode; + } + } + + /** + * Is the md-mode a valid option? + */ + function mode() { + var value = (attr.mdMode || "").trim(); + if ( value ) { + switch(value) { + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + case MODE_BUFFER: + case MODE_QUERY: + break; + default: + value = MODE_INDETERMINATE; + break; + } + } + return value; + } + + /** + * Manually set CSS to animate the Determinate indicator based on the specified + * percentage value (0-100). + */ + function animateIndicator(target, value) { + if ( isDisabled || !mode() ) return; + + var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]); + var styles = toVendorCSS({ transform : to }); + angular.element(target).css( styles ); + } + } + + /** + * Clamps the value to be between 0 and 100. + * @param {number} value The value to clamp. + * @returns {number} + */ + function clamp(value) { + return Math.max(0, Math.min(value || 0, 100)); + } +} + + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.radioButton + * @description radioButton module! + */ +mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"]; +mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"]; +angular.module('material.components.radioButton', [ + 'material.core' +]) + .directive('mdRadioGroup', mdRadioGroupDirective) + .directive('mdRadioButton', mdRadioButtonDirective); + +/** + * @ngdoc directive + * @module material.components.radioButton + * @name mdRadioGroup + * + * @restrict E + * + * @description + * The `` directive identifies a grouping + * container for the 1..n grouped radio buttons; specified using nested + * `` tags. + * + * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application) + * the radio button is in the accent color by default. The primary color palette may be used with + * the `md-primary` class. + * + * Note: `` and `` handle tabindex differently + * than the native `` controls. Whereas the native controls + * force the user to tab through all the radio buttons, `` + * is focusable, and by default the ``s are not. + * + * @param {string} ng-model Assignable angular expression to data-bind to. + * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects. + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user + * interaction with the input element. + * + * @usage + * + * + * + * + * + * {{ d.label }} + * + * + * + * + * + * + */ +function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) { + RadioGroupController.prototype = createRadioGroupControllerProto(); + + return { + restrict: 'E', + controller: ['$element', RadioGroupController], + require: ['mdRadioGroup', '?ngModel'], + link: { pre: linkRadioGroup } + }; + + function linkRadioGroup(scope, element, attr, ctrls) { + element.addClass('_md'); // private md component indicator for styling + $mdTheming(element); + + var rgCtrl = ctrls[0]; + var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel(); + + rgCtrl.init(ngModelCtrl); + + scope.mouseActive = false; + + element + .attr({ + 'role': 'radiogroup', + 'tabIndex': element.attr('tabindex') || '0' + }) + .on('keydown', keydownListener) + .on('mousedown', function(event) { + scope.mouseActive = true; + $timeout(function() { + scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if(scope.mouseActive === false) { + rgCtrl.$element.addClass('md-focused'); + } + }) + .on('blur', function() { + rgCtrl.$element.removeClass('md-focused'); + }); + + /** + * + */ + function setFocus() { + if (!element.hasClass('md-focused')) { element.addClass('md-focused'); } + } + + /** + * + */ + function keydownListener(ev) { + var keyCode = ev.which || ev.keyCode; + + // Only listen to events that we originated ourselves + // so that we don't trigger on things like arrow keys in + // inputs. + + if (keyCode != $mdConstant.KEY_CODE.ENTER && + ev.currentTarget != ev.target) { + return; + } + + switch (keyCode) { + case $mdConstant.KEY_CODE.LEFT_ARROW: + case $mdConstant.KEY_CODE.UP_ARROW: + ev.preventDefault(); + rgCtrl.selectPrevious(); + setFocus(); + break; + + case $mdConstant.KEY_CODE.RIGHT_ARROW: + case $mdConstant.KEY_CODE.DOWN_ARROW: + ev.preventDefault(); + rgCtrl.selectNext(); + setFocus(); + break; + + case $mdConstant.KEY_CODE.ENTER: + var form = angular.element($mdUtil.getClosest(element[0], 'form')); + if (form.length > 0) { + form.triggerHandler('submit'); + } + break; + } + + } + } + + function RadioGroupController($element) { + this._radioButtonRenderFns = []; + this.$element = $element; + } + + function createRadioGroupControllerProto() { + return { + init: function(ngModelCtrl) { + this._ngModelCtrl = ngModelCtrl; + this._ngModelCtrl.$render = angular.bind(this, this.render); + }, + add: function(rbRender) { + this._radioButtonRenderFns.push(rbRender); + }, + remove: function(rbRender) { + var index = this._radioButtonRenderFns.indexOf(rbRender); + if (index !== -1) { + this._radioButtonRenderFns.splice(index, 1); + } + }, + render: function() { + this._radioButtonRenderFns.forEach(function(rbRender) { + rbRender(); + }); + }, + setViewValue: function(value, eventType) { + this._ngModelCtrl.$setViewValue(value, eventType); + // update the other radio buttons as well + this.render(); + }, + getViewValue: function() { + return this._ngModelCtrl.$viewValue; + }, + selectNext: function() { + return changeSelectedButton(this.$element, 1); + }, + selectPrevious: function() { + return changeSelectedButton(this.$element, -1); + }, + setActiveDescendant: function (radioId) { + this.$element.attr('aria-activedescendant', radioId); + }, + isDisabled: function() { + return this.$element[0].hasAttribute('disabled'); + } + }; + } + /** + * Change the radio group's selected button by a given increment. + * If no button is selected, select the first button. + */ + function changeSelectedButton(parent, increment) { + // Coerce all child radio buttons into an array, then wrap then in an iterator + var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true); + + if (buttons.count()) { + var validate = function (button) { + // If disabled, then NOT valid + return !angular.element(button).attr("disabled"); + }; + + var selected = parent[0].querySelector('md-radio-button.md-checked'); + var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first(); + + // Activate radioButton's click listener (triggerHandler won't create a real click event) + angular.element(target).triggerHandler('click'); + } + } + +} + +/** + * @ngdoc directive + * @module material.components.radioButton + * @name mdRadioButton + * + * @restrict E + * + * @description + * The ``directive is the child directive required to be used within `` elements. + * + * While similar to the `` directive, + * the `` directive provides ink effects, ARIA support, and + * supports use within named radio groups. + * + * @param {string} ngValue AngularJS expression which sets the value to which the expression should + * be set when selected. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} aria-label Adds label to radio button for accessibility. + * Defaults to radio button's text. If no text content is available, a warning will be logged. + * + * @usage + * + * + * + * Label 1 + * + * + * + * Green + * + * + * + * + */ +function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) { + + var CHECKED_CSS = 'md-checked'; + + return { + restrict: 'E', + require: '^mdRadioGroup', + transclude: true, + template: '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + link: link + }; + + function link(scope, element, attr, rgCtrl) { + var lastChecked; + + $mdTheming(element); + configureAria(element, scope); + + // ngAria overwrites the aria-checked inside a $watch for ngValue. + // We should defer the initialization until all the watches have fired. + // This can also be fixed by removing the `lastChecked` check, but that'll + // cause more DOM manipulation on each digest. + if (attr.ngValue) { + $mdUtil.nextTick(initialize, false); + } else { + initialize(); + } + + /** + * Initializes the component. + */ + function initialize() { + if (!rgCtrl) { + throw 'RadioButton: No RadioGroupController could be found.'; + } + + rgCtrl.add(render); + attr.$observe('value', render); + + element + .on('click', listener) + .on('$destroy', function() { + rgCtrl.remove(render); + }); + } + + /** + * On click functionality. + */ + function listener(ev) { + if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return; + + scope.$apply(function() { + rgCtrl.setViewValue(attr.value, ev && ev.type); + }); + } + + /** + * Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent). + * Update the `aria-activedescendant` attribute. + */ + function render() { + var checked = rgCtrl.getViewValue() == attr.value; + + if (checked === lastChecked) return; + + if (element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') { + // If the radioButton is inside a div, then add class so highlighting will work + element.parent().toggleClass(CHECKED_CSS, checked); + } + + if (checked) { + rgCtrl.setActiveDescendant(element.attr('id')); + } + + lastChecked = checked; + + element + .attr('aria-checked', checked) + .toggleClass(CHECKED_CSS, checked); + } + + /** + * Inject ARIA-specific attributes appropriate for each radio button + */ + function configureAria(element, scope){ + element.attr({ + id: attr.id || 'radio_' + $mdUtil.nextUid(), + role: 'radio', + 'aria-checked': 'false' + }); + + $mdAria.expectWithText(element, 'aria-label'); + } + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.select + */ + +/*************************************************** + + ### TODO - POST RC1 ### + - [ ] Abstract placement logic in $mdSelect service to $mdMenu service + + ***************************************************/ + +SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$parse", "$sce", "$injector"]; +SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"]; +OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil", "$mdTheming"]; +SelectProvider.$inject = ["$$interimElementProvider"]; +var SELECT_EDGE_MARGIN = 8; +var selectNextId = 0; +var CHECKBOX_SELECTION_INDICATOR = + angular.element('
    '); + +angular.module('material.components.select', [ + 'material.core', + 'material.components.backdrop' + ]) + .directive('mdSelect', SelectDirective) + .directive('mdSelectMenu', SelectMenuDirective) + .directive('mdOption', OptionDirective) + .directive('mdOptgroup', OptgroupDirective) + .directive('mdSelectHeader', SelectHeaderDirective) + .provider('$mdSelect', SelectProvider); + +/** + * @ngdoc directive + * @name mdSelect + * @restrict E + * @module material.components.select + * + * @description Displays a select box, bound to an ng-model. + * + * When the select is required and uses a floating label, then the label will automatically contain + * an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute. + * + * By default, the select will display with an underline to match other form elements. This can be + * disabled by applying the `md-no-underline` CSS class. + * + * ### Option Params + * + * When applied, `md-option-empty` will mark the option as "empty" allowing the option to clear the + * select and put it back in it's default state. You may supply this attribute on any option you + * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not + * defined. + * + * **Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` + * - `` + * + * **NOT Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` (this evaluates to the string `"undefined"`) + * - <md-option ng-value="{{someValueThatMightBeUndefined}}"> + * + * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this + * attribute) since you may wish this to be your "Not Available" or "None" option. + * + * **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so + * `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`. + * + * @param {expression} ng-model The model! + * @param {boolean=} multiple When set to true, allows for more than one option to be selected. The model is an array with the selected choices. + * @param {expression=} md-on-close Expression to be evaluated when the select is closed. + * @param {expression=} md-on-open Expression to be evaluated when opening the select. + * Will hide the select options and show a spinner until the evaluated promise resolves. + * @param {expression=} md-selected-text Expression to be evaluated that will return a string + * to be displayed as a placeholder in the select input box when it is closed. The value + * will be treated as *text* (not html). + * @param {expression=} md-selected-html Expression to be evaluated that will return a string + * to be displayed as a placeholder in the select input box when it is closed. The value + * will be treated as *html*. The value must either be explicitly marked as trustedHtml or + * the ngSanitize module must be loaded. + * @param {string=} placeholder Placeholder hint text. + * @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the + * floating label. **Note:** This attribute is only evaluated once; it is not watched. + * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or + * explicit label is present. + * @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container` + * element (for custom styling). + * + * @usage + * With a placeholder (label and aria-label are added dynamically) + * + * + * + * {{ opt }} + * + * + * + * + * With an explicit label + * + * + * + * + * {{ opt }} + * + * + * + * + * With a select-header + * + * When a developer needs to put more than just a text label in the + * md-select-menu, they should use the md-select-header. + * The user can put custom HTML inside of the header and style it to their liking. + * One common use case of this would be a sticky search bar. + * + * When using the md-select-header the labels that would previously be added to the + * OptGroupDirective are ignored. + * + * + * + * + * + * Neighborhoods - + * + * {{ opt }} + * + * + * + * + * ## Selects and object equality + * When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles + * equality. Consider the following example: + * + * angular.controller('MyCtrl', function($scope) { + * $scope.users = [ + * { id: 1, name: 'Bob' }, + * { id: 2, name: 'Alice' }, + * { id: 3, name: 'Steve' } + * ]; + * $scope.selectedUser = { id: 1, name: 'Bob' }; + * }); + * + * + *
    + * + * {{ user.name }} + * + *
    + *
    + * + * At first one might expect that the select should be populated with "Bob" as the selected user. However, + * this is not true. To determine whether something is selected, + * `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`; + * + * Javascript's `==` operator does not check for deep equality (ie. that all properties + * on the object are the same), but instead whether the objects are *the same object in memory*. + * In this case, we have two instances of identical objects, but they exist in memory as unique + * entities. Because of this, the select will have no value populated for a selected user. + * + * To get around this, `ngModelController` provides a `track by` option that allows us to specify a different + * expression which will be used for the equality operator. As such, we can update our `html` to + * make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select` + * element. This converts our equality expression to be + * `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));` + * which results in Bob being selected as desired. + * + * Working HTML: + * + *
    + * + * {{ user.name }} + * + *
    + *
    + */ +function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $parse, $sce, + $injector) { + var keyCodes = $mdConstant.KEY_CODE; + var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW]; + + return { + restrict: 'E', + require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'], + compile: compile, + controller: function() { + } // empty placeholder controller to be initialized in link + }; + + function compile(element, attr) { + // add the select value that will hold our placeholder or selected option value + var valueEl = angular.element(''); + valueEl.append(''); + valueEl.addClass('md-select-value'); + if (!valueEl[0].hasAttribute('id')) { + valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid()); + } + + // There's got to be an md-content inside. If there's not one, let's add it. + var mdContentEl = element.find('md-content'); + if (!mdContentEl.length) { + element.append(angular.element('').append(element.contents())); + } + mdContentEl.attr('role', 'presentation'); + + + // Add progress spinner for md-options-loading + if (attr.mdOnOpen) { + + // Show progress indicator while loading async + // Use ng-hide for `display:none` so the indicator does not interfere with the options list + element + .find('md-content') + .prepend(angular.element( + '
    ' + + ' ' + + '
    ' + )); + + // Hide list [of item options] while loading async + element + .find('md-option') + .attr('ng-show', '$$loadingAsyncDone'); + } + + if (attr.name) { + var autofillClone = angular.element(', ",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:n.bindType||q,l=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),l&&l.apply(g,c),l=k&&g[k],l&&l.apply&&o.acceptData(g)&&(b.result=l.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||n._default&&n._default.apply(p.pop(),c)!==!1||!o.acceptData(d)||k&&o.isFunction(d[q])&&!o.isWindow(d)&&(h=d[k],h&&(d[k]=null),o.event.triggered=q,d[q](),o.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=o.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=o.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=o.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((o.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?o(e,this).index(i)>=0:o.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return o.nodeName(a,"table")&&o.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)o.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=o.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&o.nodeName(a,b)?o.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}o.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=o.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||o.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,n=a.length;n>m;m++)if(e=a[m],e||0===e)if("object"===o.type(e))o.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;o.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===o.inArray(e,d))&&(i=o.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f,g,h=o.event.special,i=0;void 0!==(c=a[i]);i++){if(o.acceptData(c)&&(f=c[L.expando],f&&(b=L.cache[f]))){if(d=Object.keys(b.events||{}),d.length)for(g=0;void 0!==(e=d[g]);g++)h[e]?o.event.remove(c,e):o.removeEvent(c,e,b.handle);L.cache[f]&&delete L.cache[f]}delete M.cache[c[M.expando]]}}}),o.fn.extend({text:function(a){return J(this,function(a){return void 0===a?o.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?o.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||o.cleanData(ob(c)),c.parentNode&&(b&&o.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(o.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return o.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(o.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,o.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,n=k-1,p=a[0],q=o.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(c=o.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=o.map(ob(c,"script"),kb),g=f.length;k>j;j++)h=c,j!==n&&(h=o.clone(h,!0,!0),g&&o.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,o.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&o.contains(i,h)&&(h.src?o._evalUrl&&o._evalUrl(h.src):o.globalEval(h.textContent.replace(hb,"")))}return this}}),o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){o.fn[a]=function(a){for(var c,d=[],e=o(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),o(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d=o(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:o.css(d[0],"display");return d.detach(),e}function tb(a){var b=m,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||o("