Restructure Sass files and folders for proper application Sass development

This commit is contained in:
iRouge
2015-01-21 11:18:53 -05:00
parent 6c6c8457b2
commit 039028db1d
207 changed files with 54 additions and 285 deletions
@@ -1,88 +0,0 @@
/**
*
* Article nav
*
**/
.article-nav {
overflow: hidden;
position: relative;
&::before {
content: '';
border-left: 2px solid $colorGrayKeyline;
height: 100%;
position: absolute;
top: 0;
left: 50%;
}
}
.article-nav-link {
padding: $lineHeight 32px;
float: left;
width: 50%;
position: relative;
&::before{
position: absolute;
top: 21px;
font-family: $fontHighlight;
font-size: $fontMedium;
font-weight: 400;
@include medium {
top: 25px;
font-size: $fontLarge;
display: block;
padding: 13px 10px;
color: #ffffff;
background: $colorBlue;
}
}
}
.article-nav p {
padding: 0;
margin: 0;
}
.article-nav-link--prev {
text-align: right;
// border-right-width: 1px;
&::before {
font-family: $fontIcon;
@extend .icon-chevron-left::before;
left: 32px;
}
p {
@include medium {
padding-left: 52px;
}
}
}
.article-nav-link--next {
// border-left-width: 1px;
&::before {
font-family: $fontIcon;
@extend .icon-chevron-right::before;
right: 32px;
}
p {
@include medium {
padding-right: 52px;
}
}
}
.article-nav-count {
@include type--large;
font-weight: 700;
@include medium {font-weight: 400;}
}
@@ -1,25 +0,0 @@
/**
*
* Articles section
*
**/
.articles-section {
background: $colorGrayBackground;
text-align: center;
padding: $lineHeight 0 $lineHeight*4;
}
.articles-count {
color: $colorBlue;
font-family: $fontHighlight;
font-weight: 400;
}
.article-section__icon {
top: -($lineHeight);
@include medium {
top: -($lineHeight + $lineHeight/2);
}
}
@@ -1,56 +0,0 @@
/**
*
* Text module
*
**/
.did-you-know {
ol {
@include medium {
padding-top: 0 !important;
}
}
.cta--primary {
margin-top: $lineHeight;
font-weight: 500;
}
&>.g--half {
position: relative;
padding-left: 0;
@include medium {padding-left: 32px}
}
}
.did-you-know__symbol {
padding-bottom: $lineHeight*12;
@include medium {padding-bottom: $lineHeight}
&::after {
content: $icon-question;
color: $colorBlue;
font-family: $fontIcon;
font-size: 300px;
top: 150px;
left: 30%;
position: relative;
display: block;
width: 0;
@include medium {
position: absolute;
font-size: 400px;
top: 200px;
left: 110%;
}
@include wide {
position: absolute;
font-size: 400px;
top: 200px;
left: 124%;
}
}
}
@@ -1,78 +0,0 @@
/**
*
* Editorial Header
*
**/
.editorial-header {
overflow: hidden;
.breadcrumbs {
color: $colorBlue;
a {
color: $colorBlue;
}
}
.container {
@include medium {
position: relative;
// Pseudo elements to add the background characters
&::before {
content: $icon-chevron-large;
font-family: $fontIcon;
font-size: 1000px;
line-height: 0;
display: block;
position: absolute;
top: 0;
right: 100%;
color: $colorGrayBackground;
margin: 168px -35px 0 0;
}
}
}
}
.editorial-header__excerpt {
@include type--medium(true);
font-family: $fontHighlight;
}
.editorial-header .tag{
padding-top: $lineHeight*2;
}
.editorial-header__subtitle {
@include type--xxlarge;
padding-top: 0;
@include medium {
padding-top: 0;
padding-bottom: $lineHeight;
}
color: $colorBlue;
}
.editorial-header__toc {
margin-top: $lineHeight;
ol {
padding-top: 0;
@include medium {
padding-top: 0;
}
}
}
.editorial-header__toc-title {
font-family: $fontHighlight;
border-bottom: 1px solid $colorGrayKeyline;
margin-bottom: 13px;
padding-bottom: 13px !important;
color: $colorBlue;
}
@@ -1,9 +0,0 @@
/**
*
* Editorial Header
*
**/
.featured-section {
background: $colorGrayBackground;
}
@@ -1,61 +0,0 @@
/**
*
* Editorial Header
*
**/
.featured-spotlight {
background: $colorGrayDark;
color: #ffffff;
overflow: hidden;
padding-bottom: $lineHeight * 3 - 1;
margin-top: $lineHeight * 2;
p {
padding-bottom: $lineHeight;
}
.cta--primary {
color: #ffffff;
&:hover {
color: #ffffff;
}
}
}
.featured-spotlight__container {
position: relative;
}
.featured-spotlight__img {
@include small-only {
padding-top: 58.4%;
padding-bottom: 0;
height: 0;
overflow: hidden;
position: relative;
width: 100%;
}
img {
margin: 0 auto;
display: block;
width: 100%;
position: absolute;
left: 0;
top: 0;
margin: 0;
@include medium {
width: auto;
max-width: none;
left: 100% + $mediumGutterWidth * 2;
}
@include wide {
left: 100% + $wideGutterWidth * 2;
}
}
}
@@ -1,5 +0,0 @@
.guides-section {
background: $colorGrayBackground;
text-align: center;
padding: $lineHeight 0 $lineHeight * 4;
}
@@ -1,269 +0,0 @@
/**
*
* Highlight
*
**/
.highlight-module {
overflow: hidden;
margin-top: $lineHeight * 2;
margin-bottom: $lineHeight;
position: relative;
&::after {
background: $colorGrayBackground;
content: '';
height: 100%;
position: absolute;
top: 0;
bottom: 0;
z-index: 0;
width: 100%;
right: 0;
left: 0;
}
ul,
ol {
padding-left: 0;
}
}
.highlight-module__container {
@include container;
padding-bottom: $lineHeight * 3;
z-index: 1;
@include highlight-symbol();
@include medium {
padding-bottom: $lineHeight * 2;
}
@include wide {
min-height: $lineHeight * 8;
}
}
.highlight-module__title {
@include type--huge;
padding-top: $lineHeight;
@include wide {
@include type--xxlarge;
}
}
.highlight-module__cta {
display: block;
}
/*========== LEARNING ==========*/
.highlight-module--learning {
color: #ffffff;
&::after {
background-color: $colorLearning;
}
a {
color: #ffffff;
text-decoration: underline;
}
}
/*========== REMEMBER ==========*/
.highlight-module--remember {
color: #ffffff;
&::after {
background-color: $colorRemember;
}
a {
color: #ffffff;
text-decoration: underline;
}
}
/*========== CODE ==========*/
.highlight-module--code {
overflow: visible;
margin-bottom: $lineHeight * 2;
pre {
margin: 0;
padding-top: $lineHeight;
font-size: $fontBase - 2;
line-height: $lineHeight;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
span {
margin: 0;
padding: 0;
display: inline-block;
}
}
code {
margin: 0;
padding: 0;
word-spacing: -2px;
display: block;
}
.highlight-module__container {
padding-bottom: 0;
}
.highlight-module__cta {
position: absolute;
bottom: -$lineHeight;
}
}
/*========== LEFT ==========*/
.highlight-module--left {
&::after {
@include wide {
width: 80%;
right: 20%;
}
}
}
/*========== RIGHT ==========*/
.highlight-module--right {
&::after {
@include wide {
width: 80%;
left: 20%;
}
}
&.highlight-module--code {
&::after {
@include wide {
width: 100%;
left: 0;
}
}
}
}
/*========== INLINE ==========*/
.highlight-module--inline {
color: $colorText;
overflow: visible;
margin: $lineHeight 0 0;
& .highlight-module__container {
padding-bottom: 0;
&::before {
display: none;
}
}
& .highlight-module__content {
border-color: $colorGrayKeyline;
border-style: solid;
border-width: 1px;
border-left-width: 0;
border-right-width: 0;
margin-bottom: -2px; // Offsetting 2px to considerate border top/bottom - baseline rules.
padding: 0 0 $lineHeight;
}
& .highlight-module__title {
@include type--large;
}
&.highlight-module--remember {
& .highlight-module__title,
& li::before {
color: $colorRemember;
}
}
&.highlight-module--learning {
& .highlight-module__title,
& li::before {
color: $colorLearning;
}
}
&::after {
display: none !important;
}
}
/*========== COLORS ==========*/
div.highlight > pre > code, code .highlight { background: transparent; }
div.highlight > pre > code .c, code .highlight .c { color: #999988; font-style: italic } /* Comment */
div.highlight > pre > code .err, code .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
div.highlight > pre > code .k, code .highlight .k { } /* Keyword */
div.highlight > pre > code .o, code .highlight .o { } /* Operator */
div.highlight > pre > code .cm, code .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
div.highlight > pre > code .cp, code .highlight .cp { color: $colorGray; } /* Comment.Preproc */
div.highlight > pre > code .c1, code .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
div.highlight > pre > code .cs, code .highlight .cs { color: $colorGray; font-style: italic } /* Comment.Special */
div.highlight > pre > code .gs, code .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
div.highlight > pre > code .gd .x, code .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
div.highlight > pre > code .ge, code .highlight .ge { font-style: italic } /* Generic.Emph */
div.highlight > pre > code .gr, code .highlight .gr { color: #aa0000 } /* Generic.Error */
div.highlight > pre > code .gh, code .highlight .gh { color: $colorGray } /* Generic.Heading */
div.highlight > pre > code .gi, code .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
div.highlight > pre > code .gi .x, code .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
div.highlight > pre > code .go, code .highlight .go { color: #888888 } /* Generic.Output */
div.highlight > pre > code .gp, code .highlight .gp { color: #555555 } /* Generic.Prompt */
div.highlight > pre > code .gs, code .highlight .gs { } /* Generic.Strong */
div.highlight > pre > code .gu, code .highlight .gu { color: #aaaaaa } /* Generic.Subheading */
div.highlight > pre > code .gt, code .highlight .gt { color: #aa0000 } /* Generic.Traceback */
div.highlight > pre > code .kc, code .highlight .kc { } /* Keyword.Constant */
div.highlight > pre > code .kd, code .highlight .kd { } /* Keyword.Declaration */
div.highlight > pre > code .kp, code .highlight .kp { } /* Keyword.Pseudo */
div.highlight > pre > code .kr, code .highlight .kr { } /* Keyword.Reserved */
div.highlight > pre > code .kt, code .highlight .kt { color: #445588; } /* Keyword.Type */
div.highlight > pre > code .m, code .highlight .m { color: #009999 } /* Literal.Number */
div.highlight > pre > code .s, code .highlight .s { color: $colorLearning } /* Literal.String */
div.highlight > pre > code .na, code .highlight .na { color: #008080 } /* Name.Attribute */
div.highlight > pre > code .nb, code .highlight .nb { color: #0086B3 } /* Name.Builtin */
div.highlight > pre > code .nc, code .highlight .nc { color: #445588; } /* Name.Class */
div.highlight > pre > code .no, code .highlight .no { color: #008080 } /* Name.Constant */
div.highlight > pre > code .ni, code .highlight .ni { color: #800080 } /* Name.Entity */
div.highlight > pre > code .ne, code .highlight .ne { color: #990000; } /* Name.Exception */
div.highlight > pre > code .nf, code .highlight .nf { color: #990000; } /* Name.Function */
div.highlight > pre > code .nn, code .highlight .nn { color: #555555 } /* Name.Namespace */
div.highlight > pre > code .nt, code .highlight .nt { color: $colorRemember } /* Name.Tag */
div.highlight > pre > code .nv, code .highlight .nv { color: #008080 } /* Name.Variable */
div.highlight > pre > code .ow, code .highlight .ow { } /* Operator.Word */
div.highlight > pre > code .w, code .highlight .w { color: #bbbbbb } /* Text.Whitespace */
div.highlight > pre > code .mf, code .highlight .mf { color: #009999 } /* Literal.Number.Float */
div.highlight > pre > code .mh, code .highlight .mh { color: #009999 } /* Literal.Number.Hex */
div.highlight > pre > code .mi, code .highlight .mi { color: #009999 } /* Literal.Number.Integer */
div.highlight > pre > code .mo, code .highlight .mo { color: #009999 } /* Literal.Number.Oct */
div.highlight > pre > code .sb, code .highlight .sb { color: $colorLearning } /* Literal.String.Backtick */
div.highlight > pre > code .sc, code .highlight .sc { color: $colorLearning } /* Literal.String.Char */
div.highlight > pre > code .sd, code .highlight .sd { color: $colorLearning } /* Literal.String.Doc */
div.highlight > pre > code .s2, code .highlight .s2 { color: $colorLearning } /* Literal.String.Double */
div.highlight > pre > code .se, code .highlight .se { color: $colorLearning } /* Literal.String.Escape */
div.highlight > pre > code .sh, code .highlight .sh { color: $colorLearning } /* Literal.String.Heredoc */
div.highlight > pre > code .si, code .highlight .si { color: $colorLearning } /* Literal.String.Interpol */
div.highlight > pre > code .sx, code .highlight .sx { color: $colorLearning } /* Literal.String.Other */
div.highlight > pre > code .sr, code .highlight .sr { color: #009926 } /* Literal.String.Regex */
div.highlight > pre > code .s1, code .highlight .s1 { color: $colorLearning } /* Literal.String.Single */
div.highlight > pre > code .ss, code .highlight .ss { color: #990073 } /* Literal.String.Symbol */
div.highlight > pre > code .bp, code .highlight .bp { color: $colorGray } /* Name.Builtin.Pseudo */
div.highlight > pre > code .vc, code .highlight .vc { color: #008080 } /* Name.Variable.Class */
div.highlight > pre > code .vg, code .highlight .vg { color: #008080 } /* Name.Variable.Global */
div.highlight > pre > code .vi, code .highlight .vi { color: #008080 } /* Name.Variable.Instance */
div.highlight > pre > code .il, code .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
@@ -1,15 +0,0 @@
/**
*
* In this guide
*
**/
.in-this-guide {
margin-top: - $lineHeight * 3;
}
.in-this-guide__title {
@include type--medium(true);
font-family: $fontHighlight;
margin-bottom: $lineHeight;
}
@@ -1,79 +0,0 @@
/**
*
* Next Lessons
*
**/
.next-lessons {
background: $colorGrayDark;
padding: $lineHeight $lineHeight $lineHeight * 2;
margin-top: $lineHeight;
color: #ffffff;
position: relative;
h3 {
i {
@include medium {
display: none;
}
}
}
&::before,
&::after {
color: rgba(255, 255, 255, 0.5);
position: absolute;
display: none;
@include medium {
display: inline-block;
}
}
&::before {
@include medium {
content: attr(data-current-lesson);
font-family: $fontHighlight;
font-size: $fontBase;
font-weight: 400;
line-height: 1;
background: $colorGrayDark;
display: inline-block;
padding: 5px 7px;
right: 127px;
top: 143px;
z-index: 1;
color: rgba(255, 255, 255, 0.5);
}
@include wide {
font-size: $fontMedium;
padding-left: 15px;
padding-right: 15px;
top: 126px;
right: 230px;
}
}
&::after {
@include medium {
content: $icon-lessons;
font-family: $fontIcon;
font-size: 150px;
right: 40px;
top: 185px;
}
@include wide {
font-size: 210px;
right: 120px;
}
}
}
@@ -1,32 +0,0 @@
/**
*
* Page header
*
**/
.page-header {
text-align: center;
.breadcrumbs {
text-align: left;
color: $colorBlue;
a {
color: $colorBlue;
}
}
h3 {
color: $colorGrayDark;
padding-top: $lineHeight * 2;
}
}
.page-header__excerpt {
position: relative;
padding-top: 0;
&:last-child {
padding-bottom: $lineHeight * 3;
}
}
@@ -1,44 +0,0 @@
/**
*
* Quote
*
**/
.quote__content {
position: relative;
font-family: $fontHighlight;
@include type--medium;
padding-top: $lineHeight * 4;
padding-left: $lineHeight;
@include medium {
padding-top: $lineHeight * 2;
padding-left: 0;
}
p {
border-top: 1px solid $colorGrayKeyline;
text-align: right;
font-weight: 500;
margin-top: $lineHeight/2 - 1;
padding-top: $lineHeight/2;
}
&::before {
content: open-quote;
display: block;
position: absolute;
font-family: $fontHighlight;
font-weight: 700;
color: $colorGrayBackground;
top: 90px;
left: $lineHeight;
font-size: 260px;
@include medium {
top: 225px;
left: -210px;
font-size: 540px;
}
}
}
@@ -1,41 +0,0 @@
/**
*
* Related items
*
**/
.related-guides {
margin-top: $lineHeight*3;
padding-bottom: ($lineHeight*2) - 2;
border-top: 2px solid $colorGrayKeyline;
padding-top: ($lineHeight*2) - 2;
}
.related-guides__list {
.list-links {
padding-top: 0;
}
a {
display: block;
}
}
.related-guides__title {
@include type--xlarge;
padding-top: 0;
@include medium {
padding-top: 0;
}
}
.related-guides__main-link {
text-transform: uppercase;
&::before {
content: '#';
display: inline-block;
padding-right: 2px;
}
}
@@ -1,19 +0,0 @@
/**
*
* Related items
*
**/
.related-items {
background-color: $colorGrayDark;
color: #ffffff;
padding-bottom: $lineHeight * 2;
margin-top: $lineHeight * 2;
.list-links {
a {
color: #ffffff;
}
}
}
@@ -1,29 +0,0 @@
/**
*
* Editorial Header
*
**/
.summary-header {
background-color: $colorBlue;
padding-bottom: $lineHeight * 3;
color: #ffffff;
margin-bottom: $lineHeight;
box-shadow: inset 0 2px 0 0 #fff;
.breadcrumbs__link {
color: #ffffff;
}
}
.summary-header__anchor-list {
margin-top: $lineHeight * 2;
}
.summary-header__anchors-item {
& a {
color: #ffffff;
}
}
@@ -1,34 +0,0 @@
/**
*
* Table of contents
*
**/
.toc__title {
@include type--medium;
font-family: $fontHighlight;
padding-bottom: $lineHeight/2;
margin-bottom: ($lineHeight/2) - 1;
border-bottom: 1px solid $colorGrayKeyline;
@include medium {
padding-bottom: $lineHeight/2;
margin-bottom: $lineHeight/2;
}
}
.toc__list {
padding-top: 0;
border-bottom: 1px solid $colorGrayKeyline;
padding-bottom: ($lineHeight/2) - 1;
margin-bottom: $lineHeight/2;
a {
display: block;
}
}
.toc__sublist {
padding-top: 0;
}
@@ -0,0 +1,4 @@
md-backdrop.md-opaque.md-THEME_NAME-theme {
background-color: '{{foreground-4-0.5}}';
position: absolute
}
@@ -0,0 +1,31 @@
(function() {
'use strict';
/*
* @ngdoc module
* @name material.components.backdrop
* @description Backdrop
*/
/**
* @ngdoc directive
* @name mdBackdrop
* @module material.components.backdrop
*
* @restrict E
*
* @description
* `<md-backdrop>` is a backdrop element used by other coponents, such as dialog and bottom sheet.
* Apply class `opaque` to make the backdrop use the theme backdrop color.
*
*/
angular.module('material.components.backdrop', [
'material.core'
])
.directive('mdBackdrop', BackdropDirective);
function BackdropDirective($mdTheming) {
return $mdTheming;
}
})();
@@ -0,0 +1,36 @@
md-backdrop {
z-index: $z-index-backdrop;
&.md-dialog-backdrop {
z-index: $z-index-dialog - 1;
}
&.md-bottom-sheet-backdrop {
z-index: $z-index-bottom-sheet - 1;
}
&.md-sidenav-backdrop {
z-index: $z-index-sidenav - 1;
}
background-color: rgba(0,0,0,0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
&.ng-enter {
animation: $swift-ease-out-timing-function mdBackdropFadeIn 0.5s both;
}
&.ng-leave {
animation: $swift-ease-in-timing-function mdBackdropFadeOut 0.2s both;
}
}
@keyframes mdBackdropFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes mdBackdropFadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@@ -0,0 +1,19 @@
md-bottom-sheet.md-THEME_NAME-theme {
background-color: '{{background-50}}';
border-top-color: '{{background-300}}';
&.md-list {
md-item {
color: '{{foreground-1}}';
}
}
.md-subheader {
background-color: '{{background-50}}';
}
.md-subheader {
color: '{{foreground-1}}';
}
}
@@ -0,0 +1,285 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.bottomSheet
* @description
* BottomSheet
*/
angular.module('material.components.bottomSheet', [
'material.core',
'material.components.backdrop'
])
.directive('mdBottomSheet', MdBottomSheetDirective)
.provider('$mdBottomSheet', MdBottomSheetProvider);
function MdBottomSheetDirective() {
return {
restrict: 'E'
};
}
/**
* @ngdoc service
* @name $mdBottomSheet
* @module material.components.bottomSheet
*
* @description
* `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
*
* ### Restrictions
*
* - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
* - Add the `md-grid` class to the bottom sheet for a grid layout.
* - Add the `md-list` class to the bottom sheet for a list layout.
*
* @usage
* <hljs lang="html">
* <div ng-controller="MyController">
* <md-button ng-click="openBottomSheet()">
* Open a Bottom Sheet!
* </md-button>
* </div>
* </hljs>
* <hljs lang="js">
* var app = angular.module('app', ['ngMaterial']);
* app.controller('MyController', function($scope, $mdBottomSheet) {
* $scope.openBottomSheet = function() {
* $mdBottomSheet.show({
* template: '<md-bottom-sheet>Hello!</md-bottom-sheet>'
* });
* };
* });
* </hljs>
*/
/**
* @ngdoc method
* @name $mdBottomSheet#show
*
* @description
* Show a bottom sheet with the specified options.
*
* @param {object} options An options object, with the following properties:
*
* - `templateUrl` - `{string=}`: The url of an html template file that will
* be used as the content of the bottom sheet. Restrictions: the template must
* have an outer `md-bottom-sheet` element.
* - `template` - `{string=}`: Same as templateUrl, except this is an actual
* template string.
* - `controller` - `{string=}`: The controller to associate with this bottom sheet.
* - `locals` - `{string=}`: 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
* of 3.
* - `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.
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
* and the bottom sheet will not open until the promises resolve.
* - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
* - `parent` - `{element=}`: The element to append the bottom sheet to. Defaults to appending
* to the root element of the application.
*
* @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
* rejected with `$mdBottomSheet.cancel()`.
*/
/**
* @ngdoc method
* @name $mdBottomSheet#hide
*
* @description
* Hide the existing bottom sheet and resolve the promise returned from
* `$mdBottomSheet.show()`.
*
* @param {*=} response An argument for the resolved promise.
*
*/
/**
* @ngdoc method
* @name $mdBottomSheet#cancel
*
* @description
* Hide the existing bottom sheet and reject the promise returned from
* `$mdBottomSheet.show()`.
*
* @param {*=} response An argument for the rejected promise.
*
*/
function MdBottomSheetProvider($$interimElementProvider) {
return $$interimElementProvider('$mdBottomSheet')
.setDefaults({
options: bottomSheetDefaults
});
/* @ngInject */
function bottomSheetDefaults($animate, $mdConstant, $timeout, $$rAF, $compile, $mdTheming, $mdBottomSheet, $rootElement) {
var backdrop;
return {
themable: true,
targetEvent: null,
onShow: onShow,
onRemove: onRemove,
escapeToClose: true
};
function onShow(scope, element, options) {
// Add a backdrop that will close on click
backdrop = $compile('<md-backdrop class="md-opaque md-bottom-sheet-backdrop">')(scope);
backdrop.on('click touchstart', function() {
$timeout($mdBottomSheet.cancel);
});
$mdTheming.inherit(backdrop, options.parent);
$animate.enter(backdrop, options.parent, null);
var bottomSheet = new BottomSheet(element);
options.bottomSheet = bottomSheet;
// Give up focus on calling item
options.targetEvent && angular.element(options.targetEvent.target).blur();
$mdTheming.inherit(bottomSheet.element, options.parent);
return $animate.enter(bottomSheet.element, options.parent)
.then(function() {
var focusable = angular.element(
element[0].querySelector('button') ||
element[0].querySelector('a') ||
element[0].querySelector('[ng-click]')
);
focusable.focus();
if (options.escapeToClose) {
options.rootElementKeyupCallback = function(e) {
if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
$timeout($mdBottomSheet.cancel);
}
};
$rootElement.on('keyup', options.rootElementKeyupCallback);
}
});
}
function onRemove(scope, element, options) {
var bottomSheet = options.bottomSheet;
$animate.leave(backdrop);
return $animate.leave(bottomSheet.element).then(function() {
bottomSheet.cleanup();
// Restore focus
options.targetEvent && angular.element(options.targetEvent.target).focus();
});
}
/**
* BottomSheet class to apply bottom-sheet behavior to an element
*/
function BottomSheet(element) {
var MAX_OFFSET = 80; // amount past the bottom of the element that we can drag down, this is same as in _bottomSheet.scss
var WIGGLE_AMOUNT = 20; // point where it starts to get "harder" to drag
var CLOSING_VELOCITY = 10; // how fast we need to flick down to close the sheet
var startY, lastY, velocity, transitionDelay, startTarget;
// coercion incase $mdCompiler returns multiple elements
element = element.eq(0);
element.on('touchstart', onTouchStart)
.on('touchmove', onTouchMove)
.on('touchend', onTouchEnd);
return {
element: element,
cleanup: function cleanup() {
element.off('touchstart', onTouchStart)
.off('touchmove', onTouchMove)
.off('touchend', onTouchEnd);
}
};
function onTouchStart(e) {
e.preventDefault();
startTarget = e.target;
startY = getY(e);
// Disable transitions on transform so that it feels fast
transitionDelay = element.css($mdConstant.CSS.TRANSITION_DURATION);
element.css($mdConstant.CSS.TRANSITION_DURATION, '0s');
}
function onTouchEnd(e) {
// Re-enable the transitions on transforms
element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDelay);
var currentY = getY(e);
// If we didn't scroll much, and we didn't change targets, assume its a click
if ( Math.abs(currentY - startY) < 5 && e.target == startTarget) {
angular.element(e.target).triggerHandler('click');
} else {
// If they went fast enough, trigger a close.
if (velocity > CLOSING_VELOCITY) {
$timeout($mdBottomSheet.cancel);
// Otherwise, untransform so that we go back to our normal position
} else {
setTransformY(undefined);
}
}
}
function onTouchMove(e) {
var currentY = getY(e);
var delta = currentY - startY;
velocity = currentY - lastY;
lastY = currentY;
// Do some conversion on delta to get a friction-like effect
delta = adjustedDelta(delta);
setTransformY(delta + MAX_OFFSET);
}
/**
* Helper function to find the Y aspect of various touch events.
**/
function getY(e) {
var touch = e.touches && e.touches.length ? e.touches[0] : e.changedTouches[0];
return touch.clientY;
}
/**
* Transform the element along the y-axis
**/
function setTransformY(amt) {
if (amt === null || amt === undefined) {
element.css($mdConstant.CSS.TRANSFORM, '');
} else {
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0, ' + amt + 'px, 0)');
}
}
// Returns a new value for delta that will never exceed MAX_OFFSET_AMOUNT
// Will get harder to exceed it as you get closer to it
function adjustedDelta(delta) {
if ( delta < 0 && delta < -MAX_OFFSET + WIGGLE_AMOUNT) {
delta = -delta;
var base = MAX_OFFSET - WIGGLE_AMOUNT;
delta = Math.max(-MAX_OFFSET, -Math.min(MAX_OFFSET - 5, base + ( WIGGLE_AMOUNT * (delta - base)) / MAX_OFFSET) - delta / 50);
}
return delta;
}
}
}
}
})();
@@ -0,0 +1,175 @@
$bottom-sheet-horizontal-padding: 2 * $baseline-grid !default;
$bottom-sheet-vertical-padding: 1 * $baseline-grid !default;
$bottom-sheet-icon-after-margin: 4 * $baseline-grid !default;
$bottom-sheet-list-item-height: 6 * $baseline-grid !default;
$bottom-sheet-hidden-bottom-padding: 80px !default;
$bottom-sheet-header-height: 7 * $baseline-grid !default;
$bottom-sheet-grid-font-weight: 300 !default;
md-bottom-sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: $bottom-sheet-vertical-padding $bottom-sheet-horizontal-padding $bottom-sheet-vertical-padding + $bottom-sheet-hidden-bottom-padding $bottom-sheet-horizontal-padding;
z-index: $z-index-bottom-sheet;
border-top: 1px solid;
transform: translate3d(0, $bottom-sheet-hidden-bottom-padding, 0);
transition: $swift-ease-out;
transition-property: transform;
&.md-has-header {
padding-top: 0;
}
&.ng-enter {
opacity: 0;
transform: translate3d(0, 100%, 0);
}
&.ng-enter-active {
opacity: 1;
display: block;
transform: translate3d(0, $bottom-sheet-hidden-bottom-padding, 0) !important;
}
&.ng-leave-active {
transform: translate3d(0, 100%, 0) !important;
transition: $swift-ease-in;
}
.md-subheader {
background-color: transparent;
font-family: $font-family;
line-height: $bottom-sheet-header-height;
padding: 0;
white-space: nowrap;
}
md-inline-icon {
display: inline-block;
height: 24px;
width: 24px;
fill: #444;
}
md-item {
display: flex;
outline: none;
&:hover {
cursor: pointer;
}
}
&.md-list {
md-item {
align-items: center;
height: $bottom-sheet-list-item-height;
div.md-icon-container {
display: inline-block;
height: 3 * $baseline-grid;
margin-right: $bottom-sheet-icon-after-margin;
}
}
}
&.md-grid {
padding-left: 3 * $baseline-grid;
padding-right: 3 * $baseline-grid;
padding-top: 0;
md-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
transition: all 0.5s;
align-items: center;
}
md-item {
flex-direction: column;
align-items: center;
transition: all 0.5s;
height: 12 * $baseline-grid;
margin-top: $baseline-grid;
margin-bottom: $baseline-grid;
/* Mixin for how many grid items to show per row */
@mixin grid-items-per-row($num, $alignEdges: false) {
$width: 100% / $num;
flex: 1 1 $width;
max-width: $width;
@if $alignEdges {
&:nth-of-type(#{$num}n + 1) {
align-items: flex-start;
}
&:nth-of-type(#{$num}n) {
align-items: flex-end;
}
}
}
@media screen and (max-width: $layout-breakpoint-sm) {
@include grid-items-per-row(3, true);
}
@media screen and (min-width: $layout-breakpoint-sm) and (max-width: $layout-breakpoint-md) {
@include grid-items-per-row(4);
}
@media screen and (min-width: $layout-breakpoint-md) and (max-width: $layout-breakpoint-lg) {
@include grid-items-per-row(6);
}
@media screen and (min-width: $layout-breakpoint-lg) {
@include grid-items-per-row(7);
}
.md-item-content {
display: flex;
flex-direction: column;
align-items: center;
width: 6 * $baseline-grid;
padding-bottom: 2 * $baseline-grid;
}
.md-grid-item-content {
display: flex;
flex-direction: column;
align-items: center;
width: 10 * $baseline-grid;
}
.md-icon-container {
display: inline-block;
box-sizing: border-box;
height: 6 * $baseline-grid;
width: 6 * $baseline-grid;
margin: 0 0;
}
p.md-grid-text {
font-weight: $bottom-sheet-grid-font-weight;
line-height: 2 * $baseline-grid;
font-size: 2 * $baseline-grid - 3;
margin: 0;
white-space: nowrap;
width: 8 * $baseline-grid;
text-align: center;
padding-top: 1 * $baseline-grid;
}
}
}
}
@@ -0,0 +1,44 @@
describe('$mdBottomSheet service', function() {
beforeEach(module('material.components.bottomSheet', 'ngAnimateMock'));
describe('#build()', function() {
it('should escapeToClose == true', inject(function($mdBottomSheet, $rootScope, $rootElement, $timeout, $animate, $mdConstant) {
var parent = angular.element('<div>');
$mdBottomSheet.show({
template: '<md-bottom-sheet>',
parent: parent,
escapeToClose: true
});
$rootScope.$apply();
$animate.triggerCallbacks();
expect(parent.find('md-bottom-sheet').length).toBe(1);
$rootElement.triggerHandler({type: 'keyup',
keyCode: $mdConstant.KEY_CODE.ESCAPE
});
$timeout.flush();
expect(parent.find('md-bottom-sheet').length).toBe(0);
}));
it('should escapeToClose == false', inject(function($mdBottomSheet, $rootScope, $rootElement, $timeout, $animate, $mdConstant) {
var parent = angular.element('<div>');
$mdBottomSheet.show({
template: '<md-bottom-sheet>',
parent: parent,
escapeToClose: false
});
$rootScope.$apply();
$animate.triggerCallbacks();
expect(parent.find('md-bottom-sheet').length).toBe(1);
$rootElement.triggerHandler({ type: 'keyup', keyCode: $mdConstant.KEY_CODE.ESCAPE });
expect(parent.find('md-bottom-sheet').length).toBe(1);
}));
});
});
@@ -0,0 +1,14 @@
<md-bottom-sheet class="md-grid">
<md-list>
<md-item ng-repeat="item in items">
<md-button class="md-grid-item-content" aria-label="{{item.name}}" ng-click="listItemClick($index)">
<div class="md-icon-container">
<md-inline-grid-icon icon="{{item.icon}}"></md-inline-grid-icon>
</div>
<p class="md-grid-text"> {{ item.name }} </p>
</md-button>
</md-item>
</md-list>
</md-bottom-sheet>
@@ -0,0 +1,12 @@
<md-bottom-sheet class="md-list md-has-header">
<md-subheader>Comment Actions</md-subheader>
<md-list>
<md-item ng-repeat="item in items">
<md-button aria-label="{{item.name}}" ng-click="listItemClick($index)">
<!-- Using custom inline icon until md-icon is ready. DONT USE ME! -->
<md-inline-list-icon icon="{{item.icon}}"></md-inline-list-icon>
<span class="md-inline-list-icon-label">{{ item.name }}</span>
</md-button>
</md-item>
</md-list>
</md-bottom-sheet>
@@ -0,0 +1,19 @@
<div ng-controller="BottomSheetExample">
<p style="padding-left: 20px;">
Bottom sheet can be dismissed with the service or a swipe down.
</p>
<div class="bottom-sheet-demo inset" layout="column" layout-sm="row" layout-align="center">
<md-button class="md-primary" ng-click="showListBottomSheet($event)">
Show as List
</md-button>
<div style="width:50px;"></div>
<md-button class="md-primary" ng-click="showGridBottomSheet($event)">
Show as Grid
</md-button>
</div>
<br/>
<b layout="row" layout-align="center center" layout-margin>
{{alert}}
</b>
</div>
@@ -0,0 +1,58 @@
angular.module('bottomSheetDemo1', ['ngMaterial'])
.controller('BottomSheetExample', function($scope, $timeout, $mdBottomSheet) {
$scope.alert = '';
$scope.showListBottomSheet = function($event) {
$scope.alert = '';
$mdBottomSheet.show({
templateUrl: 'bottom-sheet-list-template.html',
controller: 'ListBottomSheetCtrl',
targetEvent: $event
}).then(function(clickedItem) {
$scope.alert = clickedItem.name + ' clicked!';
});
};
$scope.showGridBottomSheet = function($event) {
$scope.alert = '';
$mdBottomSheet.show({
templateUrl: 'bottom-sheet-grid-template.html',
controller: 'GridBottomSheetCtrl',
targetEvent: $event
}).then(function(clickedItem) {
$scope.alert = clickedItem.name + ' clicked!';
});
};
})
.controller('ListBottomSheetCtrl', function($scope, $mdBottomSheet) {
$scope.items = [
{ name: 'Share', icon: 'share' },
{ name: 'Upload', icon: 'upload' },
{ name: 'Copy', icon: 'copy' },
{ name: 'Print this page', icon: 'print' },
];
$scope.listItemClick = function($index) {
var clickedItem = $scope.items[$index];
$mdBottomSheet.hide(clickedItem);
};
})
.controller('GridBottomSheetCtrl', function($scope, $mdBottomSheet) {
$scope.items = [
{ name: 'Hangout', icon: 'hangout' },
{ name: 'Mail', icon: 'mail' },
{ name: 'Message', icon: 'message' },
{ name: 'Copy', icon: 'copy' },
{ name: 'Facebook', icon: 'facebook' },
{ name: 'Twitter', icon: 'twitter' },
];
$scope.listItemClick = function($index) {
var clickedItem = $scope.items[$index];
$mdBottomSheet.hide(clickedItem);
};
});
@@ -0,0 +1,64 @@
/* Temporary fix until md-icon is working, DO NOT USE! */
md-inline-list-icon {
display: inline-block;
height: 24px;
width: 24px;
}
.md-inline-list-icon-label {
padding-left: 20px;
display: inline-block;
margin-top: -5px;
height: 24px;
vertical-align: middle;
}
md-inline-list-icon[icon=share] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMjRweCINCgkgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItNjE4IiB5PSItMTA4MCIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbCI+DQo8L2c+DQo8ZyBpZD0iSWNvbiI+DQoJPGc+DQoJCTxwYXRoIGZpbGw9IiM3ZDdkN2QiIGQ9Ik0yMSwxMWwtNy03djRDNyw5LDQsMTQsMywxOWMyLjUtMy41LDYtNS4xLDExLTUuMVYxOEwyMSwxMXoiLz4NCgkJPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkdyaWQiIGRpc3BsYXk9Im5vbmUiPg0KCTxnIGRpc3BsYXk9ImlubGluZSI+DQoJPC9nPg0KPC9nPg0KPC9zdmc+');
}
md-inline-list-icon[icon=upload] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMjRweCINCgkgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItNjE4IiB5PSItMjIzMiIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbCI+DQo8L2c+DQo8ZyBpZD0iSWNvbiI+DQoJPGc+DQoJCTxyZWN0IGZpbGw9Im5vbmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIvPg0KCQk8cGF0aCBmaWxsPSIjN2Q3ZDdkIiBkPSJNMTkuNCwxMGMtMC43LTMuNC0zLjctNi03LjQtNkM5LjEsNCw2LjYsNS42LDUuNCw4QzIuMyw4LjQsMCwxMC45LDAsMTRjMCwzLjMsMi43LDYsNiw2aDEzYzIuOCwwLDUtMi4yLDUtNQ0KCQkJQzI0LDEyLjQsMjEuOSwxMC4yLDE5LjQsMTB6IE0xNCwxM3Y0aC00di00SDdsNS01bDUsNUgxNHoiLz4NCgk8L2c+DQo8L2c+DQo8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+DQoJPGcgZGlzcGxheT0iaW5saW5lIj4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4=');
}
md-inline-list-icon[icon=copy] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMjRweCINCgkgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItNjE4IiB5PSItMTcyMCIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbHMiPg0KPC9nPg0KPGcgaWQ9Ikljb24iPg0KCTxnPg0KCQk8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4NCgkJPHBhdGggZmlsbD0iIzdkN2Q3ZCIgZD0iTTE2LDFINEMyLjksMSwyLDEuOSwyLDN2MTRoMlYzaDEyVjF6IE0xOSw1SDhDNi45LDUsNiw1LjksNiw3djE0YzAsMS4xLDAuOSwyLDIsMmgxMWMxLjEsMCwyLTAuOSwyLTJWNw0KCQkJQzIxLDUuOSwyMC4xLDUsMTksNXogTTE5LDIxSDhWN2gxMVYyMXoiLz4NCgk8L2c+DQo8L2c+DQo8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+DQoJPGcgZGlzcGxheT0iaW5saW5lIj4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4=');
}
md-inline-list-icon[icon=print] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMjRweCINCgkgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItNjE4IiB5PSItMTQ2NCIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbCI+DQo8L2c+DQo8ZyBpZD0iSWNvbiI+DQoJPGc+DQoJCTxnPg0KCQkJPHBhdGggZD0iTTE5LDhINWMtMS43LDAtMywxLjMtMywzdjZoNHY0aDEydi00aDR2LTZDMjIsOS4zLDIwLjcsOCwxOSw4eiBNMTYsMTlIOHYtNWg4VjE5eiBNMTksMTJjLTAuNiwwLTEtMC40LTEtMXMwLjQtMSwxLTENCgkJCQljMC42LDAsMSwwLjQsMSwxUzE5LjYsMTIsMTksMTJ6IE0xOCwzSDZ2NGgxMlYzeiIgZmlsbD0iIzdkN2Q3ZCIvPg0KCQk8L2c+DQoJCTxyZWN0IGZpbGw9Im5vbmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4NCgk8ZyBkaXNwbGF5PSJpbmxpbmUiPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg==');
}
.md-icon-container md-inline-grid-icon {
display: inline-block;
height: 48px;
width: 48px;
}
md-inline-grid-icon[icon=hangout] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItMjIzMiIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbHMiPg0KPC9nPg0KPGcgaWQ9Ikljb24iPg0KCTxnPg0KCQk8cGF0aCBmaWxsPSIjMTU5RjVDIiBkPSJNMjMsNEMxMy42LDQsNiwxMS42LDYsMjFzNy42LDE3LDE3LDE3aDF2N2M5LjctNC43LDE2LTE1LDE2LTI0QzQwLDExLjYsMzIuNCw0LDIzLDR6IE0yMiwyMmwtMiw0aC0zbDItNGgtM3YtNmg2VjIyeg0KCQkJIE0zMCwyMmwtMiw0aC0zbDItNGgtM3YtNmg2VjIyeiIvPg0KCQk8cmVjdCB4PSIwIiBmaWxsPSJub25lIiB3aWR0aD0iNDgiIGhlaWdodD0iNDgiLz4NCgk8L2c+DQo8L2c+DQo8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+DQoJPGcgZGlzcGxheT0iaW5saW5lIj4NCgkJPGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDBFNUZGIiBzdHJva2Utd2lkdGg9IjAuMSIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiB4MT0iNDIiIHkxPSItMjIzMiIgeDI9IjQyIiB5Mj0iMTMyMCIvPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K');
}
md-inline-grid-icon[icon=mail] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItMjg3MiIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbHMiPg0KPC9nPg0KPGcgaWQ9Ikljb24iPg0KCTxnPg0KCQk8cGF0aCBmaWxsPSIjN2Q3ZDdkIiBkPSJNNDAsOEg4Yy0yLjIsMC00LDEuOC00LDRsMCwyNGMwLDIuMiwxLjgsNCw0LDRoMzJjMi4yLDAsNC0xLjgsNC00VjEyQzQ0LDkuOCw0Mi4yLDgsNDAsOHogTTQwLDE2TDI0LDI2TDgsMTZ2LTRsMTYsMTANCgkJCWwxNi0xMFYxNnoiLz4NCgkJPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ii8+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkdyaWQiIGRpc3BsYXk9Im5vbmUiPg0KCTxnIGRpc3BsYXk9ImlubGluZSI+DQoJCTxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwRTVGRiIgc3Ryb2tlLXdpZHRoPSIwLjEiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgeDE9IjQyIiB5MT0iLTI4NzIiIHgyPSI0MiIgeTI9IjY4MCIvPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K');
}
md-inline-grid-icon[icon=message] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItMjc0NCIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbHMiPg0KPC9nPg0KPGcgaWQ9Ikljb24iPg0KCTxnPg0KCQk8cGF0aCBmaWxsPSIjN2Q3ZDdkIiBkPSJNNDAsNEg4QzUuOCw0LDQsNS44LDQsOGwwLDM2bDgtOGgyOGMyLjIsMCw0LTEuOCw0LTRWOEM0NCw1LjgsNDIuMiw0LDQwLDR6IE0zNiwyOEgxMnYtNGgyNFYyOHogTTM2LDIySDEydi00aDI0VjIyeg0KCQkJIE0zNiwxNkgxMnYtNGgyNFYxNnoiLz4NCgkJPHJlY3QgeD0iMCIgZmlsbD0ibm9uZSIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ii8+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkdyaWQiIGRpc3BsYXk9Im5vbmUiPg0KCTxnIGRpc3BsYXk9ImlubGluZSI+DQoJCTxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwRTVGRiIgc3Ryb2tlLXdpZHRoPSIwLjEiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgeDE9IjQyIiB5MT0iLTI3NDQiIHgyPSI0MiIgeTI9IjgwOCIvPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K');
}
md-inline-grid-icon[icon=copy] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItMTcyMCIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbHMiPg0KPC9nPg0KPGcgaWQ9Ikljb24iPg0KCTxnPg0KCQk8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iNDgiIGhlaWdodD0iNDgiLz4NCgkJPHBhdGggZmlsbD0iIzdkN2Q3ZCIgZD0iTTMyLDJIOEM1LjgsMiw0LDMuOCw0LDZ2MjhoNFY2aDI0VjJ6IE0zOCwxMEgxNmMtMi4yLDAtNCwxLjgtNCw0djI4YzAsMi4yLDEuOCw0LDQsNGgyMmMyLjIsMCw0LTEuOCw0LTRWMTQNCgkJCUM0MiwxMS44LDQwLjIsMTAsMzgsMTB6IE0zOCw0MkgxNlYxNGgyMlY0MnoiLz4NCgk8L2c+DQo8L2c+DQo8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+DQoJPGcgZGlzcGxheT0iaW5saW5lIj4NCgkJPGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDBFNUZGIiBzdHJva2Utd2lkdGg9IjAuMSIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiB4MT0iNDIiIHkxPSItMTcyMCIgeDI9IjQyIiB5Mj0iMTgzMiIvPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K');
}
md-inline-grid-icon[icon=facebook] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItMzI1NiIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYWJlbCI+DQo8L2c+DQo8ZyBpZD0iSWNvbiI+DQoJPGc+DQoJCTxnPg0KCQkJPHBhdGggZmlsbD0iIzdkN2Q3ZCIgZD0iTTQwLDRIOEM1LjgsNCw0LDUuOCw0LDhsMCwzMmMwLDIuMiwxLjgsNCw0LDRoMzJjMi4yLDAsNC0xLjgsNC00VjhDNDQsNS44LDQyLjIsNCw0MCw0eiBNMzgsOHY2aC00Yy0xLjEsMC0yLDAuOS0yLDJ2NA0KCQkJCWg2djZoLTZ2MTRoLTZWMjZoLTR2LTZoNHYtNWMwLTMuOSwzLjEtNyw3LTdIMzh6Ii8+DQoJCTwvZz4NCgkJPGc+DQoJCQk8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iNDgiIGhlaWdodD0iNDgiLz4NCgkJPC9nPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4NCgk8ZyBkaXNwbGF5PSJpbmxpbmUiPg0KCQk8bGluZSBmaWxsPSJub25lIiBzdHJva2U9IiMwMEU1RkYiIHN0cm9rZS13aWR0aD0iMC4xIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHgxPSI0MiIgeTE9Ii0zMjU2IiB4Mj0iNDIiIHkyPSIyOTYiLz4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4NCg==');
}
md-inline-grid-icon[icon=twitter] {
background-image: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDhweCINCgkgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQ4IDQ4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJIZWFkZXIiPg0KCTxnPg0KCQk8cmVjdCB4PSItODM4IiB5PSItOTUyIiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkxhYmVsIj4NCjwvZz4NCjxnIGlkPSJJY29uIj4NCgk8Zz4NCgkJPGc+DQoJCQk8cGF0aCBmaWxsPSIjN2Q3ZDdkIiBkPSJNNDAsNEg4QzUuOCw0LDQsNS44LDQsOGwwLDMyYzAsMi4yLDEuOCw0LDQsNGgzMmMyLjIsMCw0LTEuOCw0LTRWOEM0NCw1LjgsNDIuMiw0LDQwLDR6IE0zNS40LDE4LjcNCgkJCQljLTAuMSw5LjItNiwxNS42LTE0LjgsMTZjLTMuNiwwLjItNi4zLTEtOC42LTIuNWMyLjcsMC40LDYtMC42LDcuOC0yLjJjLTIuNi0wLjMtNC4yLTEuNi00LjktMy44YzAuOCwwLjEsMS42LDAuMSwyLjMtMC4xDQoJCQkJYy0yLjQtMC44LTQuMS0yLjMtNC4yLTUuM2MwLjcsMC4zLDEuNCwwLjYsMi4zLDAuNmMtMS44LTEtMy4xLTQuNy0xLjYtNy4yYzIuNiwyLjksNS44LDUuMywxMSw1LjZjLTEuMy01LjYsNi4xLTguNiw5LjItNC45DQoJCQkJYzEuMy0wLjMsMi40LTAuOCwzLjQtMS4zYy0wLjQsMS4zLTEuMiwyLjItMi4yLDIuOWMxLjEtMC4xLDIuMS0wLjQsMi45LTAuOEMzNy41LDE2LjksMzYuNCwxNy45LDM1LjQsMTguN3oiLz4NCgkJPC9nPg0KCQk8Zz4NCgkJCTxyZWN0IGZpbGw9Im5vbmUiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIvPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkdyaWQiIGRpc3BsYXk9Im5vbmUiPg0KCTxnIGRpc3BsYXk9ImlubGluZSI+DQoJCTxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwRTVGRiIgc3Ryb2tlLXdpZHRoPSIwLjEiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgeDE9IjQyIiB5MT0iLTk1MiIgeDI9IjQyIiB5Mj0iMjYwMCIvPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K');
}
@@ -0,0 +1,84 @@
$button-border-radius: 3px !default;
$button-fab-border-radius: 50% !default;
.md-button.md-THEME_NAME-theme {
border-radius: $button-border-radius;
&:not([disabled]) {
&:hover,
&:focus {
background-color: '{{background-500-0.2}}';
}
}
&.md-primary {
color: '{{primary-color}}';
&.md-raised,
&.md-fab {
color: '{{primary-contrast}}';
background-color: '{{primary-color}}';
&:not([disabled]) {
&:hover,
&:focus {
background-color: '{{primary-600}}';
}
}
}
}
&.md-fab {
border-radius: $button-fab-border-radius;
}
&.md-raised,
&.md-fab {
color: '{{background-contrast}}';
background-color: '{{background-500-0.185}}';
&:not([disabled]) {
&:hover,
&:focus {
background-color: '{{background-500-0.3}}';
}
}
}
&.md-warn {
color: '{{warn-color}}';
&.md-raised,
&.md-fab {
color: '{{warn-contrast}}';
background-color: '{{warn-color}}';
&:not([disabled]) {
&:hover,
&:focus {
background-color: '{{warn-700}}';
}
}
}
}
&.md-accent {
color: '{{accent-color}}';
&.md-raised,
&.md-fab {
color: '{{accent-contrast}}';
background-color: '{{accent-color}}';
&:not([disabled]) {
&:hover,
&:focus {
background-color: '{{accent-700}}';
}
}
}
}
&[disabled],
&.md-raised[disabled],
&.md-fab[disabled] {
color: '{{foreground-3}}';
background-color: transparent;
cursor: not-allowed;
}
}
@@ -0,0 +1,87 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.button
* @description
*
* Button
*/
angular.module('material.components.button', [
'material.core'
])
.directive('mdButton', MdButtonDirective);
/**
* @ngdoc directive
* @name mdButton
* @module material.components.button
*
* @restrict E
*
* @description
* `<md-button>` is a button directive with optional ink ripples (default enabled).
*
* If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it will
* become a `<button>` element.
*
* @param {boolean=} md-no-ink If present, disable ripple ink effects.
* @param {expression=} ng-disabled En/Disable based on the expression
* @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
* If no default text is found, a warning will be logged.
*
* @usage
* <hljs lang="html">
* <md-button>
* Button
* </md-button>
* <md-button href="http://google.com" class="md-button-colored">
* I'm a link
* </md-button>
* <md-button ng-disabled="true" class="md-colored">
* I'm a disabled button
* </md-button>
* </hljs>
*/
function MdButtonDirective($mdInkRipple, $mdTheming, $mdAria) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: getTemplate,
link: postLink
};
function isAnchor(attr) {
return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref);
}
function getTemplate(element, attr) {
return isAnchor(attr) ?
'<a class="md-button" ng-transclude></a>' :
'<button class="md-button" ng-transclude></button>';
}
function postLink(scope, element, attr) {
var node = element[0];
$mdTheming(element);
$mdInkRipple.attachButtonBehavior(scope, element);
var elementHasText = node.textContent.trim();
if (!elementHasText) {
$mdAria.expect(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);
});
}
}
}
})();
@@ -0,0 +1,173 @@
$button-line-height: 25px !default;
$button-padding: 2px 6px 3px !default;
// Fab buttons
$button-fab-width: 56px !default;
$button-fab-height: 56px !default;
$button-fab-padding: 16px !default;
$button-fab-toast-offset: $button-fab-height * 0.75;
/**
* Position a FAB button.
*/
@mixin fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) {
&.md-fab-#{$spot} {
top: $top;
right: $right;
bottom: $bottom;
left: $left;
position: absolute;
}
}
.md-button {
user-select: none;
position: relative; //for child absolute-positioned <canvas>
outline: none;
border: 0;
padding: 6px;
margin: 0;
background: transparent;
white-space: nowrap;
text-align: center;
// Always uppercase buttons
text-transform: uppercase;
font-weight: 500;
font-style: inherit;
font-variant: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
text-decoration: none;
cursor: pointer;
overflow: hidden; // for ink containment
transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function,
background-color $swift-ease-out-duration $swift-ease-out-timing-function,
transform $swift-ease-out-duration $swift-ease-out-timing-function;
&.ng-hide {
transition: none;
}
;
&.md-cornered {
border-radius: 0;
}
&.md-icon {
padding: 0;
background: none;
}
&.md-raised {
transform: translate3d(0, 0, 0);
&:not([disabled]) {
@extend .md-shadow-bottom-z-1;
}
}
&.md-fab {
@include fab-position(bottom-right, auto, ($button-fab-width - $button-fab-padding)/2, ($button-fab-height - $button-fab-padding)/2, auto);
@include fab-position(bottom-left, auto, auto, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2);
@include fab-position(top-right, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2, auto, auto);
@include fab-position(top-left, ($button-fab-height - $button-fab-padding)/2, auto, auto, ($button-fab-width - $button-fab-padding)/2);
z-index: $z-index-fab;
width: $button-fab-width;
height: $button-fab-height;
border-radius: 50%;
@extend .md-shadow-bottom-z-1;
border-radius: 50%;
overflow: hidden;
transform: translate3d(0,0,0);
transition: 0.2s linear;
transition-property: transform, box-shadow;
md-icon {
line-height: $button-fab-height;
margin-top: 0;
}
}
&:not([disabled]) {
&.md-raised,
&.md-fab {
&:focus,
&:hover {
transform: translate3d(0, -1px, 0);
@extend .md-shadow-bottom-z-2;
}
}
}
}
.md-toast-open-top {
.md-button.md-fab-top-left,
.md-button.md-fab-top-right {
transform: translate3d(0, $button-fab-toast-offset, 0);
&:not([disabled]) {
&:focus,
&:hover {
transform: translate3d(0, $button-fab-toast-offset - 1, 0);
}
}
}
}
.md-toast-open-bottom {
.md-button.md-fab-bottom-left,
.md-button.md-fab-bottom-right {
transform: translate3d(0, -$button-fab-toast-offset, 0);
&:not([disabled]) {
&:focus,
&:hover {
transform: translate3d(0, -$button-fab-toast-offset - 1, 0);
}
}
}
}
.md-button-group {
display: flex;
flex: 1;
width: 100%;
}
.md-button-group > .md-button {
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;
&:first-child {
border-radius: 2px 0px 0px 2px;
}
&:last-child {
border-right-width: 1px;
border-radius: 0px 2px 2px 0px;
}
}
@@ -0,0 +1,91 @@
describe('md-button', function() {
beforeEach(TestUtil.mockRaf);
beforeEach(module('material.components.button'));
it('should convert attributes on an md-button to attributes on the generated button', inject(function($compile, $rootScope) {
var button = $compile('<md-button hide hide-sm></md-button>')($rootScope);
$rootScope.$apply();
expect(button[0].hasAttribute('hide')).toBe(true);
expect(button[0].hasAttribute('hide-sm')).toBe(true);
}));
it('should only have one ripple container when a custom ripple color is set', inject(function ($compile, $rootScope, $timeout) {
var button = $compile('<md-button md-ink-ripple="#f00">button</md-button>')($rootScope);
var scope = button.eq(0).scope();
scope._onInput({ isFirst: true, eventType: Hammer.INPUT_START, center: { x: 0, y: 0 } });
expect(button[0].getElementsByClassName('md-ripple-container').length).toBe(1);
}));
it('should expect an aria-label if element has no text', inject(function($compile, $rootScope, $log) {
spyOn($log, 'warn');
var button = $compile('<md-button><md-icon></md-icon></md-button>')($rootScope);
$rootScope.$apply();
expect($log.warn).toHaveBeenCalled();
$log.warn.reset();
button = $compile('<md-button aria-label="something"><md-icon></md-icon></md-button>')($rootScope);
$rootScope.$apply();
expect($log.warn).not.toHaveBeenCalled();
}));
describe('with href or ng-href', function() {
it('should be anchor if href attr', inject(function($compile, $rootScope) {
var button = $compile('<md-button href="/link">')($rootScope.$new());
$rootScope.$apply();
expect(button[0].tagName.toLowerCase()).toEqual('a');
}));
it('should be anchor if ng-href attr', inject(function($compile, $rootScope) {
var button = $compile('<md-button ng-href="/link">')($rootScope.$new());
$rootScope.$apply();
expect(button[0].tagName.toLowerCase()).toEqual('a');
}));
it('should be button otherwise', inject(function($compile, $rootScope) {
var button = $compile('<md-button>')($rootScope.$new());
$rootScope.$apply();
expect(button[0].tagName.toLowerCase()).toEqual('button');
}));
});
describe('with ng-disabled', function() {
it('should not set `tabindex` when used without anchor attributes', inject(function ($compile, $rootScope, $timeout) {
var scope = angular.extend( $rootScope.$new(), { isDisabled : true } );
var button = $compile('<md-button ng-disabled="isDisabled">button</md-button>')(scope);
$rootScope.$apply();
expect(button[0].hasAttribute('tabindex')).toBe(false);
}));
it('should set `tabindex == -1` when used with href', inject(function ($compile, $rootScope, $timeout) {
var scope = angular.extend( $rootScope.$new(), { isDisabled : true } );
var button = $compile('<md-button ng-disabled="isDisabled" href="#nowhere">button</md-button>')(scope);
$rootScope.$apply();
expect(button.attr('tabindex')).toBe("-1");
$rootScope.$apply(function(){
scope.isDisabled = false;
});
expect(button.attr('tabindex')).toBe("0");
}));
it('should set `tabindex == -1` when used with ng-href', inject(function ($compile, $rootScope, $timeout) {
var scope = angular.extend( $rootScope.$new(), { isDisabled : true, url : "http://material.angularjs.org" });
var button = $compile('<md-button ng-disabled="isDisabled" ng-href="url">button</md-button>')(scope);
$rootScope.$apply();
expect(button.attr('tabindex')).toBe("-1");
}));
})
});
@@ -0,0 +1,53 @@
<div ng-controller="AppCtrl">
<md-content >
<section layout="row" layout-sm="column" layout-align="center center">
<md-button>{{title1}}</md-button>
<md-button md-no-ink class="md-primary">Primary (md-noink)</md-button>
<md-button ng-disabled="true" class="md-primary">Disabled</md-button>
<md-button class="md-warn">{{title4}}</md-button>
</section>
<section layout="row" layout-sm="column" layout-align="center center">
<md-button class="md-raised">Button</md-button>
<md-button class="md-raised md-primary">Primary</md-button>
<md-button ng-disabled="true" class="md-raised md-primary">Disabled</md-button>
<md-button class="md-raised md-warn">Warn</md-button>
<div class="label">raised</div>
</section>
<section layout="row" layout-sm="column" layout-align="center center">
<md-button class="md-fab" aria-label="Time">
<md-icon icon="/img/icons/ic_access_time_24px.svg" style="width: 24px; height: 24px;"></md-icon>
</md-button>
<md-button class="md-fab" aria-label="New document">
<md-icon icon="/img/icons/ic_insert_drive_file_24px.svg" style="width: 24px; height: 24px;"></md-icon>
</md-button>
<md-button class="md-fab" ng-disabled="true" aria-label="Comment">
<md-icon icon="/img/icons/ic_comment_24px.svg" style="width: 24px; height: 24px;"></md-icon>
</md-button>
<md-button class="md-fab md-primary" md-theme="cyan" aria-label="Profile">
<md-icon icon="/img/icons/ic_people_24px.svg" style="width: 24px; height: 24px;"></md-icon>
</md-button>
<div class="label">FAB</div>
</section>
<section layout="row" layout-sm="column" layout-align="center center">
<md-button ng-href="{{googleUrl}}" target="_blank">Go to Google</md-button>
<md-button>RSVP</md-button>
</section>
<section layout="row" layout-sm="column" layout-align="center center">
<md-button class="md-primary md-hue-1">Primary Hue 1</md-button>
<md-button class="md-warn md-raised md-hue-2">Warn Hue 2</md-button>
<md-button class="md-accent">Accent</md-button>
<md-button class="md-accent md-raised md-hue-3">Accent Hue 3</md-button>
<div class="label">Themed</div>
</section>
</md-content>
</div>
@@ -0,0 +1,11 @@
angular.module('buttonsDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.title1 = 'Button';
$scope.title4 = 'Warn';
$scope.isDisabled = true;
$scope.googleUrl = 'http://google.com';
});
@@ -0,0 +1,30 @@
/** From vulcanized demo **/
section {
background: #f7f7f7;
border-radius: 3px;
text-align: center;
margin: 1em;
position: relative !important;
padding-bottom: 10px;
}
md-content {
margin-right: 7px;
}
section .md-button:not(.md-fab) {
min-width: 10em;
}
section .md-button {
display: block;
margin: 1em;
line-height: 25px;
}
.label {
position: absolute;
bottom: 5px;
left: 7px;
color: #ccc;
font-size: 14px;
}
@@ -0,0 +1,9 @@
$card-border-radius: 2px !default;
md-card.md-THEME_NAME-theme {
border-radius: $card-border-radius;
.md-card-image {
border-radius: $card-border-radius $card-border-radius 0 0;
}
}
@@ -0,0 +1,51 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.card
*
* @description
* Card components.
*/
angular.module('material.components.card', [
'material.core'
])
.directive('mdCard', mdCardDirective);
/**
* @ngdoc directive
* @name mdCard
* @module material.components.card
*
* @restrict E
*
* @description
* The `<md-card>` directive is a container element used within `<md-content>` containers.
*
* 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
* <hljs lang="html">
* <md-card>
* <img src="img/washedout.png" class="md-card-image">
* <h2>Paracosm</h2>
* <p>
* The titles of Washed Out's breakthrough song and the first single from Paracosm share the * two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well...
* </p>
* </md-card>
* </hljs>
*
*/
function mdCardDirective($mdTheming) {
return {
restrict: 'E',
link: function($scope, $element, $attr) {
$mdTheming($element);
}
};
}
})();
@@ -0,0 +1,20 @@
$card-margin: 8px !default;
$card-box-shadow: $whiteframe-shadow-z1 !default;
md-card {
box-sizing: border-box;
display: flex;
flex-direction: column;
box-shadow: $card-box-shadow;
> img {
order: 0;
width: 100%;
}
md-card-content {
order: 1;
margin: $card-margin;
}
}
@@ -0,0 +1,16 @@
describe('mdCard directive', function() {
beforeEach(module('material.components.card'));
it('should have the default theme class when the md-theme attribute is not defined', inject(function($compile, $rootScope) {
var card = $compile('<md-card></md-card>')($rootScope.$new());
$rootScope.$apply();
expect(card.hasClass('md-default-theme')).toBe(true);
}));
it('should have the correct theme class when the md-theme attribute is defined', inject(function($compile, $rootScope) {
var card = $compile('<md-card md-theme="green"></md-card>')($rootScope.$new());
$rootScope.$apply();
expect(card.hasClass('md-green-theme')).toBe(true);
}));
});
@@ -0,0 +1,41 @@
<div ng-controller="AppCtrl">
<md-content>
<md-card>
<img src="img/washedout.png" alt="Washed Out">
<md-card-content>
<h2>Paracosm</h2>
<p>
The titles of Washed Out's breakthrough song and the first single from Paracosm share the
two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well...
</p>
</md-card-content>
</md-card>
<md-card>
<img src="img/washedout.png" alt="Washed Out">
<md-card-content>
<h2>Paracosm</h2>
<p>
The titles of Washed Out's breakthrough song and the first single from Paracosm share the
two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well...
</p>
</md-card-content>
</md-card>
<md-card>
<img src="img/washedout.png" alt="Washed Out">
<md-card-content>
<h2>Paracosm</h2>
<p>
The titles of Washed Out's breakthrough song and the first single from Paracosm share the
two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well...
</p>
</md-card-content>
</md-card>
</md-content>
</div>
@@ -0,0 +1,6 @@
angular.module('cardDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
});
@@ -0,0 +1,4 @@
md-card {
min-height: 150px;
}
@@ -0,0 +1,73 @@
md-checkbox.md-THEME_NAME-theme {
.md-ripple {
color: '{{accent-600}}';
}
&.md-checked .md-ripple {
color: '{{background-600}}';
}
.md-icon {
border-color: '{{foreground-2}}';
}
&.md-checked .md-icon {
background-color: '{{accent-color-0.87}}';
}
&.md-checked .md-icon:after {
border-color: '{{background-200}}';
}
&:not([disabled]) {
&.md-primary {
.md-ripple {
color: '{{primary-600}}';
}
&.md-checked .md-ripple {
color: '{{background-600}}';
}
.md-icon {
border-color: '{{foreground-2}}';
}
&.md-checked .md-icon {
background-color: '{{primary-color-0.87}}';
}
&.md-checked .md-icon:after {
border-color: '{{background-200}}';
}
}
&.md-warn {
.md-ripple {
color: '{{warn-600}}';
}
&.md-checked .md-ripple {
color: '{{background-600}}';
}
.md-icon {
border-color: '{{foreground-2}}';
}
&.md-checked .md-icon {
background-color: '{{warn-color-0.87}}';
}
&.md-checked .md-icon:after {
border-color: '{{background-200}}';
}
}
}
&[disabled] {
.md-icon {
border-color: '{{foreground-3}}';
}
&.md-checked .md-icon {
background-color: '{{foreground-3}}';
}
}
}
@@ -0,0 +1,126 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.checkbox
* @description Checkbox module!
*/
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).
*
* @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 Angular 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.
*
* @usage
* <hljs lang="html">
* <md-checkbox ng-model="isChecked" aria-label="Finished?">
* Finished ?
* </md-checkbox>
*
* <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
* No Ink Effects
* </md-checkbox>
*
* <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
* Disabled
* </md-checkbox>
*
* </hljs>
*
*/
function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant, $mdTheming, $mdUtil) {
inputDirective = inputDirective[0];
var CHECKED_CSS = 'md-checked';
return {
restrict: 'E',
transclude: true,
require: '?ngModel',
template:
'<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
'<div class="md-icon"></div>' +
'</div>' +
'<div ng-transclude class="md-label"></div>',
compile: compile
};
// **********************************************************
// Private Methods
// **********************************************************
function compile (tElement, tAttrs) {
tAttrs.type = 'checkbox';
tAttrs.tabIndex = 0;
tElement.attr('role', tAttrs.type);
return function postLink(scope, element, attr, ngModelCtrl) {
ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
var checked = false;
$mdTheming(element);
$mdAria.expectWithText(tElement, 'aria-label');
// Reuse the original input[type=checkbox] directive from Angular 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]);
// Used by switch. in Switch, we don't want click listeners; we have more granular
// touchup/touchdown listening.
if (!attr.mdNoClick) {
element.on('click', listener);
}
element.on('keypress', keypressHandler);
ngModelCtrl.$render = render;
function keypressHandler(ev) {
if(ev.which === $mdConstant.KEY_CODE.SPACE) {
ev.preventDefault();
listener(ev);
}
}
function listener(ev) {
if (element[0].hasAttribute('disabled')) return;
scope.$apply(function() {
checked = !checked;
ngModelCtrl.$setViewValue(checked, ev && ev.type);
ngModelCtrl.$render();
});
}
function render() {
checked = ngModelCtrl.$viewValue;
if(checked) {
element.addClass(CHECKED_CSS);
} else {
element.removeClass(CHECKED_CSS);
}
}
};
}
}
})();
@@ -0,0 +1,91 @@
$checkbox-width: 18px !default;
$checkbox-height: $checkbox-width !default;
md-checkbox {
display: block;
margin: 15px;
white-space: nowrap;
cursor: pointer;
outline: none;
user-select: none;
.md-container {
position: relative;
top: 4px;
display: inline-block;
width: $checkbox-width;
height: $checkbox-height;
&:after {
content: '';
position: absolute;
top: -10px;
right: -10px;
bottom: -10px;
left: -10px;
}
.md-ripple-container {
position: absolute;
display: block;
width: auto;
height: auto;
left: -15px;
top: -15px;
right: -15px;
bottom: -15px;
}
}
// unchecked
.md-icon {
transition: 240ms;
position: absolute;
top: 0;
left: 0;
width: $checkbox-width;
height: $checkbox-height;
border: 2px solid;
border-radius: 2px;
}
&.md-checked .md-icon {
border: none;
}
// disabled
&[disabled] {
cursor: no-drop;
}
// focus
&:focus .md-label:not(:empty) {
border-color: black;
}
&.md-checked .md-icon:after {
transform: rotate(45deg);
position: absolute;
left: 6px;
top: 2px;
display: table;
width: 6px;
height: 12px;
border: 2px solid;
border-top: 0;
border-left: 0;
content: ' ';
}
.md-label {
border: 1px dotted transparent;
position: relative;
display: inline-block;
margin-left: 10px;
vertical-align: middle;
white-space: normal;
pointer-events: none;
user-select: text;
}
}
@@ -0,0 +1,172 @@
describe('mdCheckbox', function() {
var CHECKED_CSS = 'md-checked';
beforeEach(module('material.components.checkbox'));
beforeEach(module('ngAria'));
beforeEach(TestUtil.mockRaf);
it('should warn developers they need a label', inject(function($compile, $rootScope, $log){
spyOn($log, "warn");
var element = $compile('<div>' +
'<md-checkbox ng-model="blue">' +
'</md-checkbox>' +
'</div>')($rootScope);
expect($log.warn).toHaveBeenCalled();
}));
it('should copy text content to aria-label', inject(function($compile, $rootScope){
var element = $compile('<div>' +
'<md-checkbox ng-model="blue">' +
'Some text' +
'</md-checkbox>' +
'</div>')($rootScope);
var cbElements = element.find('md-checkbox');
expect(cbElements.eq(0).attr('aria-label')).toBe('Some text');
}));
it('should set checked css class and aria-checked attributes', inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<md-checkbox ng-model="blue">' +
'</md-checkbox>' +
'<md-checkbox ng-model="green">' +
'</md-checkbox>' +
'</div>')($rootScope);
$rootScope.$apply(function(){
$rootScope.blue = false;
$rootScope.green = true;
});
var cbElements = element.find('md-checkbox');
expect(cbElements.eq(0).hasClass(CHECKED_CSS)).toEqual(false);
expect(cbElements.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
expect(cbElements.eq(0).attr('aria-checked')).toEqual('false');
expect(cbElements.eq(1).attr('aria-checked')).toEqual('true');
expect(cbElements.eq(0).attr('role')).toEqual('checkbox');
}));
it('should be disabled with disabled attr', inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<md-checkbox ng-disabled="isDisabled" ng-model="blue">' +
'</md-checkbox>' +
'</div>')($rootScope);
var checkbox = element.find('md-checkbox');
$rootScope.$apply('isDisabled = true');
$rootScope.$apply('blue = false');
checkbox.triggerHandler('click');
expect($rootScope.blue).toBe(false);
$rootScope.$apply('isDisabled = false');
checkbox.triggerHandler('click');
expect($rootScope.blue).toBe(true);
}));
describe('ng core checkbox tests', function() {
var inputElm;
var scope;
var $compile;
beforeEach(inject(function(_$compile_, _$rootScope_) {
scope = _$rootScope_;
$compile = _$compile_;
}));
function compileInput(html) {
inputElm = $compile(html)(scope);
}
function isChecked(cbEl) {
return cbEl.hasClass(CHECKED_CSS);
}
it('should format booleans', function() {
compileInput('<md-checkbox ng-model="name" />');
scope.$apply("name = false");
expect(isChecked(inputElm)).toBe(false);
scope.$apply("name = true");
expect(isChecked(inputElm)).toBe(true);
});
it('should support type="checkbox" with non-standard capitalization', function() {
compileInput('<md-checkbox ng-model="checkbox" />');
inputElm.triggerHandler('click');
expect(scope.checkbox).toBe(true);
inputElm.triggerHandler('click');
expect(scope.checkbox).toBe(false);
});
it('should allow custom enumeration', function() {
compileInput('<md-checkbox ng-model="name" ng-true-value="\'y\'" ' +
'ng-false-value="\'n\'">');
scope.$apply("name = 'y'");
expect(isChecked(inputElm)).toBe(true);
scope.$apply("name = 'n'");
expect(isChecked(inputElm)).toBe(false);
scope.$apply("name = 'something else'");
expect(isChecked(inputElm)).toBe(false);
inputElm.triggerHandler('click');
expect(scope.name).toEqual('y');
inputElm.triggerHandler('click');
expect(scope.name).toEqual('n');
});
it('should throw if ngTrueValue is present and not a constant expression', function() {
expect(function() {
compileInput('<md-checkbox ng-model="value" ng-true-value="yes" />');
}).toThrow();
});
it('should throw if ngFalseValue is present and not a constant expression', function() {
expect(function() {
compileInput('<md-checkbox ng-model="value" ng-false-value="no" />');
}).toThrow();
});
it('should not throw if ngTrueValue or ngFalseValue are not present', function() {
expect(function() {
compileInput('<md-checkbox ng-model="value" />');
}).not.toThrow();
});
it('should be required if false', function() {
compileInput('<md-checkbox ng:model="value" required />');
inputElm.triggerHandler('click');
expect(isChecked(inputElm)).toBe(true);
expect(inputElm.hasClass('ng-valid')).toBe(true);
inputElm.triggerHandler('click');
expect(isChecked(inputElm)).toBe(false);
expect(inputElm.hasClass('ng-invalid')).toBe(true);
});
});
});
@@ -0,0 +1,23 @@
<div ng-controller="AppCtrl">
<md-checkbox ng-model="data.cb1" aria-label="Checkbox 1">
Checkbox 1: {{ data.cb1 }}
</md-checkbox>
<md-checkbox ng-model="data.cb2" aria-label="Checkbox 2" ng-true-value="'yup'" ng-false-value="'nope'" class="md-warn">
Checkbox 2 (md-warn): {{ data.cb2 }}
</md-checkbox>
<md-checkbox ng-disabled="true" aria-label="Disabled checkbox" ng-model="data.cb3">
Checkbox: Disabled
</md-checkbox>
<md-checkbox ng-disabled="true" aria-label="Disabled checked checkbox" ng-model="data.cb4" ng-init="data.cb4=true">
Checkbox: Disabled, Checked
</md-checkbox>
<md-checkbox md-no-ink aria-label="Checkbox No Ink" ng-model="data.cb5" class="md-primary">
Checkbox (md-primary): No Ink
</md-checkbox>
</div>
@@ -0,0 +1,13 @@
angular.module('checkboxDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.data = {};
$scope.data.cb1 = true;
$scope.data.cb2 = false;
$scope.data.cb3 = false;
$scope.data.cb4 = false;
$scope.data.cb5 = false;
});
@@ -0,0 +1,4 @@
body {
padding: 20px;
}
@@ -0,0 +1,5 @@
$content-background-color: $background-color-base !default;
md-content.md-THEME_NAME-theme {
background-color: '{{background-hue-3}}';
}
@@ -0,0 +1,53 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.content
*
* @description
* Scrollable content
*/
angular.module('material.components.content', [
'material.core'
])
.directive('mdContent', mdContentDirective);
/**
* @ngdoc directive
* @name mdContent
* @module material.components.content
*
* @restrict E
*
* @description
* The `<md-content>` directive is a container element useful for scrollable content
*
* ### Restrictions
*
* - Add the `md-padding` class to make the content padded.
*
* @usage
* <hljs lang="html">
* <md-content class="md-padding">
* Lorem ipsum dolor sit amet, ne quod novum mei.
* </md-content>
* </hljs>
*
*/
function mdContentDirective($mdTheming) {
return {
restrict: 'E',
controller: ['$scope', '$element', ContentController],
link: function($scope, $element, $attr) {
$mdTheming($element);
$scope.$broadcast('$mdContentLoaded', $element);
}
};
function ContentController($scope, $element) {
this.$scope = $scope;
this.$element = $element;
}
}
})();
@@ -0,0 +1,28 @@
md-content {
display: block;
position: relative;
overflow: auto;
-webkit-overflow-scrolling: touch;
&[md-scroll-y] {
overflow-y: auto;
overflow-x: hidden;
}
&[md-scroll-x] {
overflow-x: auto;
overflow-y: hidden;
}
&[md-scroll-xy] {
}
&.md-padding {
padding: 8px;
}
}
@media (min-width: $layout-breakpoint-sm) {
md-content.md-padding {
padding: 16px;
}
}
@@ -0,0 +1,54 @@
<div ng-controller="AppCtrl" layout="column" style="padding-bottom: 15px;">
<md-toolbar class="md-accent">
<div class="md-toolbar-tools">
<span class="md-flex">Toolbar: md-accent</span>
</div>
</md-toolbar>
<md-content class="md-padding" style="height: 600px;">
Lorem ipsum dolor sit amet, ne quod novum mei. Sea omnium invenire mediocrem at, in lobortis conclusionemque nam. Ne deleniti appetere reprimique pro, inani labitur disputationi te sed. At vix sale omnesque, id pro labitur reformidans accommodare, cum labores honestatis eu. Nec quem lucilius in, eam praesent reformidans no. Sed laudem aliquam ne.
<p>
Facete delenit argumentum cum at. Pro rebum nostrum contentiones ad. Mel exerci tritani maiorum at, mea te audire phaedrum, mel et nibh aliquam. Malis causae equidem vel eu. Noster melius vis ea, duis alterum oporteat ea sea. Per cu vide munere fierent.
</p>
<p>
Ad sea dolor accusata consequuntur. Sit facete convenire reprehendunt et. Usu cu nonumy dissentiet, mei choro omnes fuisset ad. Te qui docendi accusam efficiantur, doming noster prodesset eam ei. In vel posse movet, ut convenire referrentur eum, ceteros singulis intellegam eu sit.
</p>
<p>
Sit saepe quaestio reprimique id, duo no congue nominati, cum id nobis facilisi. No est laoreet dissentias, idque consectetuer eam id. Clita possim assueverit cu his, solum virtute recteque et cum. Vel cu luptatum signiferumque, mel eu brute nostro senserit. Blandit euripidis consequat ex mei, atqui torquatos id cum, meliore luptatum ut usu. Cu zril perpetua gubergren pri. Accusamus rationibus instructior ei pro, eu nullam principes qui, reque justo omnes et quo.
</p>
<p>
Sint unum eam id. At sit fastidii theophrastus, mutat senserit repudiare et has. Atqui appareat repudiare ad nam, et ius alii incorrupte. Alii nullam libris his ei, meis aeterno at eum. Ne aeque tincidunt duo. In audire malorum mel, tamquam efficiantur has te.
</p>
<p>
Qui utamur tacimates quaestio ad, quod graece omnium ius ut. Pri ut vero debitis interpretaris, qui cu mentitum adipiscing disputationi. Voluptatum mediocritatem quo ut. Fabulas dolorem ei has, quem molestie persequeris et sit.
</p>
<p>
Est in vivendum comprehensam conclusionemque, alia cetero iriure no usu, te cibo deterruisset pro. Ludus epicurei quo id, ex cum iudicabit intellegebat. Ex modo deseruisse quo, mel noster menandri sententiae ea, duo et tritani malorum recteque. Nullam suscipit partiendo nec id, indoctum vulputate per ex. Et has enim habemus tibique. Cu latine electram cum, ridens propriae intellegat eu mea.
</p>
<p>
Duo at aliquid mnesarchum, nec ne impetus hendrerit. Ius id aeterno debitis atomorum, et sed feugait voluptua, brute tibique no vix. Eos modo esse ex, ei omittam imperdiet pro. Vel assum albucius incorrupte no. Vim viris prompta repudiare ne, vel ut viderer scripserit, dicant appetere argumentum mel ea. Eripuit feugait tincidunt pri ne, cu facilisi molestiae usu.
</p>
<p>
Qui utamur tacimates quaestio ad, quod graece omnium ius ut. Pri ut vero debitis interpretaris, qui cu mentitum adipiscing disputationi. Voluptatum mediocritatem quo ut. Fabulas dolorem ei has, quem molestie persequeris et sit.
</p>
<p>
Est in vivendum comprehensam conclusionemque, alia cetero iriure no usu, te cibo deterruisset pro. Ludus epicurei quo id, ex cum iudicabit intellegebat. Ex modo deseruisse quo, mel noster menandri sententiae ea, duo et tritani malorum recteque. Nullam suscipit partiendo nec id, indoctum vulputate per ex. Et has enim habemus tibique. Cu latine electram cum, ridens propriae intellegat eu mea.
</p>
<p>
Duo at aliquid mnesarchum, nec ne impetus hendrerit. Ius id aeterno debitis atomorum, et sed feugait voluptua, brute tibique no vix. Eos modo esse ex, ei omittam imperdiet pro. Vel assum albucius incorrupte no. Vim viris prompta repudiare ne, vel ut viderer scripserit, dicant appetere argumentum mel ea. Eripuit feugait tincidunt pri ne, cu facilisi molestiae usu.
</p>
</md-content>
</div>
@@ -0,0 +1,6 @@
angular.module('contentDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
})
@@ -0,0 +1,32 @@
<md-dialog aria-label="Mango (Fruit)">
<md-content>
<md-subheader class="md-sticky-no-effect">Mango (Fruit)</md-subheader>
<p>
The mango is a juicy stone fruit belonging to the genus Mangifera, consisting of numerous tropical fruiting trees, cultivated mostly for edible fruit. The majority of these species are found in nature as wild mangoes. They all belong to the flowering plant family Anacardiaceae. The mango is native to South and Southeast Asia, from where it has been distributed worldwide to become one of the most cultivated fruits in the tropics.
</p>
<img style="margin: auto; max-width: 100%;" src="img/mangues.jpg">
<p>
The highest concentration of Mangifera genus is in the western part of Malesia (Sumatra, Java and Borneo) and in Burma and India. While other Mangifera species (e.g. horse mango, M. foetida) are also grown on a more localized basis, Mangifera indica—the "common mango" or "Indian mango"—is the only mango tree commonly cultivated in many tropical and subtropical regions.
</p>
<p>
It originated in Indian subcontinent (present day India and Pakistan) and Burma. It is the national fruit of India, Pakistan, and the Philippines, and the national tree of Bangladesh. In several cultures, its fruit and leaves are ritually used as floral decorations at weddings, public celebrations, and religious ceremonies.
</p>
</md-content>
<div class="md-actions" layout="row">
<md-button href="http://en.wikipedia.org/wiki/Mango" target="_blank" hide show-md>
More on Wikipedia
</md-button>
<span flex></span>
<md-button ng-click="answer('not useful')">
Not Useful
</md-button>
<md-button ng-click="answer('useful')" class="md-primary">
Useful
</md-button>
</div>
</md-dialog>
@@ -0,0 +1,24 @@
<div ng-controller="AppCtrl" class="full" layout="column" layout-margin>
<p class="inset">
Open a dialog over the app's content. Press escape or click outside to close the dialog.
</p>
<div layout="column" layout-align="center" >
<md-button class="md-primary" ng-click="showAlert($event)">
Alert Dialog
</md-button>
<div class="gap"></div>
<md-button class="md-primary" ng-click="showConfirm($event)">
Confirm Dialog
</md-button>
<div class="gap"></div>
<md-button class="md-primary" ng-click="showAdvanced($event)">
Custom Dialog
</md-button>
</div>
<br/>
<b layout="row" layout-align="center center" layout-margin>
{{alert}}
</b>
</div>
@@ -0,0 +1,59 @@
angular.module('dialogDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope, $mdDialog) {
$scope.alert = '';
$scope.showAlert = function(ev) {
$mdDialog.show(
$mdDialog.alert()
.title('This is an alert title')
.content('You can specify some description text in here.')
.ariaLabel('Password notification')
.ok('Got it!')
.targetEvent(ev)
);
};
$scope.showConfirm = function(ev) {
var confirm = $mdDialog.confirm()
.title('Would you like to delete your debt?')
.content('All of the banks have agreed to forgive you your debts.')
.ariaLabel('Lucky day')
.ok('Please do it!')
.cancel('Sounds like a scam')
.targetEvent(ev);
$mdDialog.show(confirm).then(function() {
$scope.alert = 'You decided to get rid of your debt.';
}, function() {
$scope.alert = 'You decided to keep your debt.';
});
};
$scope.showAdvanced = function(ev) {
$mdDialog.show({
controller: DialogController,
templateUrl: 'dialog1.tmpl.html',
targetEvent: ev,
})
.then(function(answer) {
$scope.alert = 'You said the information was "' + answer + '".';
}, function() {
$scope.alert = 'You cancelled the dialog.';
});
};
});
function DialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
}
@@ -0,0 +1,34 @@
.full {
width: 100%;
height: 100%;
}
.gap {
width:50px;
}
.md-subheader {
background-color: #dcedc8;
margin: 0px;
}
h2.md-subheader {
margin: 0px;
margin-left: -24px;
margin-right: -24px;
margin-top: -24px;
}
h2.md-subheader.md-sticky-clone {
margin-right:0px;
margin-top:0px;
box-shadow: 0px 2px 4px 0 rgba(0,0,0,0.16);
}
h2 .md-subheader-content {
padding-left: 10px;
}
@@ -0,0 +1,11 @@
$dialog-border-radius: 4px !default;
md-dialog.md-THEME_NAME-theme {
border-radius: $dialog-border-radius;
background-color: '{{background-hue-3}}';
&.md-content-overflow .md-actions {
border-top-color: '{{foreground-4}}';
}
}
@@ -0,0 +1,485 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.dialog
*/
angular.module('material.components.dialog', [
'material.core',
'material.components.backdrop'
])
.directive('mdDialog', MdDialogDirective)
.provider('$mdDialog', MdDialogProvider);
function MdDialogDirective($$rAF, $mdTheming) {
return {
restrict: 'E',
link: function(scope, element, attr) {
$mdTheming(element);
$$rAF(function() {
var content = element[0].querySelector('md-content');
if (content && content.scrollHeight > content.clientHeight) {
element.addClass('md-content-overflow');
}
});
}
};
}
/**
* @ngdoc service
* @name $mdDialog
* @module material.components.dialog
*
* @description
* `$mdDialog` opens a dialog over the app and provides a simple promise API.
*
* ### Restrictions
*
* - The dialog is always given an isolate scope.
* - The dialog's template must have an outer `<md-dialog>` element.
* Inside, use an `<md-content>` element for the dialog's content, and use
* an element with class `md-actions` for the dialog's actions.
*
* @usage
* ##### HTML
*
* <hljs lang="html">
* <div ng-app="demoApp" ng-controller="EmployeeController">
* <md-button ng-click="showAlert()" class="md-raised md-warn">
* Employee Alert!
* </md-button>
* <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
* Close Alert
* </md-button>
* <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
* Greet Employee
* </md-button>
* </div>
* </hljs>
*
* ##### JavaScript
*
* <hljs lang="js">
* (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)
* .content('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:
* '<md-dialog>' +
*
* ' <md-content>Hello {{ employee }}!</md-content>' +
*
* ' <div class="md-actions">' +
* ' <md-button ng-click="closeDialog()">' +
* ' Close Greeting' +
*
* ' </md-button>' +
* ' </div>' +
* '</md-dialog>',
* 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.
* }
* }
* }
*
* // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
*
* function GreetingController($scope, $mdDialog, employee) {
* // Assigned from construction <code>locals</code> options...
* $scope.employee = employee;
*
* $scope.closeDialog = function() {
* // Easily hides most recent dialog shown...
* // no specific instance reference is needed.
* $mdDialog.hide();
* };
* }
*
* })(angular);
* </hljs>
*/
/**
* @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 title to string
* - $mdDialogPreset#content(string) - sets content / message to string
* - $mdDialogPreset#ok(string) - sets okay button text to string
*
*/
/**
* @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 title to string
* - $mdDialogPreset#content(string) - sets content / message to string
* - $mdDialogPreset#ok(string) - sets okay button text to string
* - $mdDialogPreset#cancel(string) - sets cancel button text to string
*
*/
/**
* @ngdoc method
* @name $mdDialog#show
*
* @description
* Show a dialog with the specified options.
*
* @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`,
* `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=}`: Same as templateUrl, except this is an actual template string.
* - `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.
* - `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 true.
* - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
* Default true.
* - `controller` - `{string=}`: The controller to associate with the dialog. The controller
* will be injected with the local `$hideDialog`, which is a function used to hide 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
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the
* dialog will not open until all of the promises resolve.
* - `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.
* - `onComplete` `{function=}`: Callback function used to announce when the show() action is
* finished.
*
* @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.
*/
/**
* @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.
*/
function MdDialogProvider($$interimElementProvider) {
var alertDialogMethods = ['title', 'content', 'ariaLabel', 'ok'];
return $$interimElementProvider('$mdDialog')
.setDefaults({
methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
options: dialogDefaultOptions
})
.addPreset('alert', {
methods: ['title', 'content', 'ariaLabel', 'ok'],
options: advancedDialogOptions
})
.addPreset('confirm', {
methods: ['title', 'content', 'ariaLabel', 'ok', 'cancel'],
options: advancedDialogOptions
});
/* @ngInject */
function advancedDialogOptions($mdDialog) {
return {
template: [
'<md-dialog aria-label="{{ dialog.ariaLabel }}">',
'<md-content>',
'<h2>{{ dialog.title }}</h2>',
'<p>{{ dialog.content }}</p>',
'</md-content>',
'<div class="md-actions">',
'<md-button ng-if="dialog.$type == \'confirm\'" ng-click="dialog.abort()">',
'{{ dialog.cancel }}',
'</md-button>',
'<md-button ng-click="dialog.hide()" class="md-primary">',
'{{ dialog.ok }}',
'</md-button>',
'</div>',
'</md-dialog>'
].join(''),
controller: function mdDialogCtrl() {
this.hide = function() {
$mdDialog.hide(true);
};
this.abort = function() {
$mdDialog.cancel();
};
},
controllerAs: 'dialog',
bindToController: true
};
}
/* @ngInject */
function dialogDefaultOptions($timeout, $rootElement, $compile, $animate, $mdAria, $document,
$mdUtil, $mdConstant, $mdTheming, $$rAF, $q, $mdDialog) {
return {
hasBackdrop: true,
isolateScope: true,
onShow: onShow,
onRemove: onRemove,
clickOutsideToClose: true,
escapeToClose: true,
targetEvent: null,
disableParentScroll: true,
transformTemplate: function(template) {
return '<div class="md-dialog-container">' + template + '</div>';
}
};
// On show method for dialogs
function onShow(scope, element, options) {
// Incase the user provides a raw dom element, always wrap it in jqLite
options.parent = angular.element(options.parent);
options.popInTarget = angular.element((options.targetEvent || {}).target);
var closeButton = findCloseButton();
configureAria(element.find('md-dialog'));
if (options.hasBackdrop) {
options.backdrop = angular.element('<md-backdrop class="md-dialog-backdrop md-opaque">');
$mdTheming.inherit(options.backdrop, options.parent);
$animate.enter(options.backdrop, options.parent);
}
if (options.disableParentScroll) {
options.oldOverflowStyle = options.parent.css('overflow');
options.parent.css('overflow', 'hidden');
}
return dialogPopIn(
element,
options.parent,
options.popInTarget && options.popInTarget.length && options.popInTarget
)
.then(function() {
if (options.escapeToClose) {
options.rootElementKeyupCallback = function(e) {
if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
$timeout($mdDialog.cancel);
}
};
$rootElement.on('keyup', options.rootElementKeyupCallback);
}
if (options.clickOutsideToClose) {
options.dialogClickOutsideCallback = function(e) {
// Only close if we click the flex container outside the backdrop
if (e.target === element[0]) {
$timeout($mdDialog.cancel);
}
};
element.on('click', options.dialogClickOutsideCallback);
}
closeButton.focus();
});
function findCloseButton() {
//If no element with class dialog-close, try to find the last
//button child in md-actions and assume it is a close button
var closeButton = element[0].querySelector('.dialog-close');
if (!closeButton) {
var actionButtons = element[0].querySelectorAll('.md-actions button');
closeButton = actionButtons[ actionButtons.length - 1 ];
}
return angular.element(closeButton);
}
}
// On remove function for all dialogs
function onRemove(scope, element, options) {
if (options.backdrop) {
$animate.leave(options.backdrop);
}
if (options.disableParentScroll) {
options.parent.css('overflow', options.oldOverflowStyle);
$document[0].removeEventListener('scroll', options.captureScroll, true);
}
if (options.escapeToClose) {
$rootElement.off('keyup', options.rootElementKeyupCallback);
}
if (options.clickOutsideToClose) {
element.off('click', options.dialogClickOutsideCallback);
}
return dialogPopOut(
element,
options.parent,
options.popInTarget && options.popInTarget.length && options.popInTarget
).then(function() {
options.scope.$destroy();
element.remove();
options.popInTarget && options.popInTarget.focus();
});
}
/**
* Inject ARIA-specific attributes appropriate for Dialogs
*/
function configureAria(element) {
element.attr({
'role': 'dialog'
});
var dialogContent = element.find('md-content');
if (dialogContent.length === 0){
dialogContent = element;
}
$mdAria.expectAsync(element, 'aria-label', function() {
var words = dialogContent.text().split(/\s+/);
if (words.length > 3) words = words.slice(0,3).concat('...');
return words.join(' ');
});
}
function dialogPopIn(container, parentElement, clickElement) {
var dialogEl = container.find('md-dialog');
parentElement.append(container);
transformToClickElement(dialogEl, clickElement);
$$rAF(function() {
dialogEl.addClass('transition-in')
.css($mdConstant.CSS.TRANSFORM, '');
});
return dialogTransitionEnd(dialogEl);
}
function dialogPopOut(container, parentElement, clickElement) {
var dialogEl = container.find('md-dialog');
dialogEl.addClass('transition-out').removeClass('transition-in');
transformToClickElement(dialogEl, clickElement);
return dialogTransitionEnd(dialogEl);
}
function transformToClickElement(dialogEl, clickElement) {
if (clickElement) {
var clickRect = clickElement[0].getBoundingClientRect();
var dialogRect = dialogEl[0].getBoundingClientRect();
var scaleX = Math.min(0.5, clickRect.width / dialogRect.width);
var scaleY = Math.min(0.5, clickRect.height / dialogRect.height);
dialogEl.css($mdConstant.CSS.TRANSFORM, 'translate3d(' +
(-dialogRect.left + clickRect.left + clickRect.width/2 - dialogRect.width/2) + 'px,' +
(-dialogRect.top + clickRect.top + clickRect.height/2 - dialogRect.height/2) + 'px,' +
'0) scale(' + scaleX + ',' + scaleY + ')'
);
}
}
function dialogTransitionEnd(dialogEl) {
var deferred = $q.defer();
dialogEl.on($mdConstant.CSS.TRANSITIONEND, finished);
function finished(ev) {
//Make sure this transitionend didn't bubble up from a child
if (ev.target === dialogEl[0]) {
dialogEl.off($mdConstant.CSS.TRANSITIONEND, finished);
deferred.resolve();
}
}
return deferred.promise;
}
}
}
})();
@@ -0,0 +1,65 @@
.md-dialog-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: $z-index-dialog;
}
md-dialog {
&.transition-in {
opacity: 1;
transition: $swift-ease-out;
transform: translate3d(0,0,0) scale(1.0);
}
&.transition-out {
transition: $swift-ease-out;
transform: translate3d(0,100%,0) scale(0.2);
}
opacity: 0;
min-width: 240px;
max-width: 80%;
max-height: 80%;
margin: auto;
position: relative;
overflow: hidden; // stop content from leaking out of dialog parent
box-shadow: $whiteframe-shadow-z5;
display: flex;
flex-direction: column;
md-content {
order: 1;
padding: 24px;
overflow: auto;
-webkit-overflow-scrolling: touch;
*:first-child {
margin-top: 0px;
}
}
.md-actions {
display: flex;
order: 2;
box-sizing: border-box;
align-items: center;
justify-content: flex-end;
padding: 16px 16px;
min-height: $baseline-grid * 5;
> * {
margin-left: 8px;
}
}
&.md-content-overflow .md-actions {
border-top: 1px solid;
}
}
@@ -0,0 +1,479 @@
describe('$mdDialog', function() {
beforeEach(TestUtil.mockRaf);
beforeEach(module('material.components.dialog', 'ngAnimateMock'));
beforeEach(inject(function spyOnMdEffects($$q, $animate) {
spyOn($animate, 'leave').andCallFake(function(element) {
element.remove();
return $$q.when();
});
spyOn($animate, 'enter').andCallFake(function(element, parent) {
parent.append(element);
return $$q.when();
});
}));
describe('#alert()', function() {
hasConfigurationMethods('alert', [
'title', 'content', 'ariaLabel',
'ok', 'targetEvent'
]);
it('shows a basic alert dialog', inject(function($animate, $rootScope, $mdDialog, $mdConstant) {
var parent = angular.element('<div>');
var resolved = false;
$mdDialog.show(
$mdDialog.alert({
parent: parent
})
.title('Title')
.content('Hello world')
.ok('Next')
).then(function() {
resolved = true;
});
$rootScope.$apply();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('transitionend');
$rootScope.$apply();
var title = angular.element(parent[0].querySelector('h2'));
expect(title.text()).toBe('Title');
var content = parent.find('p');
expect(content.text()).toBe('Hello world');
var buttons = parent.find('md-button');
expect(buttons.length).toBe(1);
expect(buttons.eq(0).text()).toBe('Next');
buttons.eq(0).triggerHandler('click');
$rootScope.$apply();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('h2').length).toBe(0);
expect(resolved).toBe(true);
}));
});
describe('#confirm()', function() {
hasConfigurationMethods('confirm', [
'title', 'content', 'ariaLabel',
'ok', 'cancel', 'targetEvent'
]);
it('shows a basic confirm dialog', inject(function($rootScope, $mdDialog, $animate, $mdConstant) {
var parent = angular.element('<div>');
var rejected = false;
$mdDialog.show(
$mdDialog.confirm({
parent: parent
})
.title('Title')
.content('Hello world')
.ok('Next')
.cancel('Forget it')
).catch(function() {
rejected = true;
});
$rootScope.$apply();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('transitionend');
$rootScope.$apply();
var title = parent.find('h2');
expect(title.text()).toBe('Title');
var content = parent.find('p');
expect(content.text()).toBe('Hello world');
var buttons = parent.find('md-button');
expect(buttons.length).toBe(2);
expect(buttons.eq(0).text()).toBe('Next');
expect(buttons.eq(1).text()).toBe('Forget it');
buttons.eq(1).triggerHandler('click');
$rootScope.$digest();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('h2').length).toBe(0);
expect(rejected).toBe(true);
}));
});
describe('#build()', function() {
it('should support onComplete callbacks within `show()`', inject(function($mdDialog, $rootScope, $timeout, $mdConstant) {
var template = '<md-dialog>Hello</md-dialog>';
var parent = angular.element('<div>');
var ready = false;
$mdDialog.show({
template: template,
parent: parent,
onComplete: function(scope, element, options) {
expect( arguments.length ).toEqual( 3 );
ready = true;
}
});
$rootScope.$apply();
expect(ready).toBe( false );
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
container = angular.element(parent[0].querySelector('.md-dialog-container'));
expect(container.length).toBe(1);
expect(ready).toBe( true );
}));
it('should append dialog with container', inject(function($mdDialog, $rootScope) {
var template = '<md-dialog>Hello</md-dialog>';
var parent = angular.element('<div>');
$mdDialog.show({
template: template,
parent: parent
});
$rootScope.$apply();
var container = parent[0].querySelectorAll('.md-dialog-container');
expect(container.length).toBe(1);
}));
it('should escapeToClose == true', inject(function($mdDialog, $rootScope, $rootElement, $timeout, $animate, $mdConstant) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
escapeToClose: true
});
$rootScope.$apply();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(1);
$rootElement.triggerHandler({type: 'keyup',
keyCode: $mdConstant.KEY_CODE.ESCAPE
});
$timeout.flush();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(0);
}));
it('should escapeToClose == false', inject(function($mdDialog, $rootScope, $rootElement, $timeout, $animate, $mdConstant) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
escapeToClose: false
});
$rootScope.$apply();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(1);
$rootElement.triggerHandler({ type: 'keyup', keyCode: $mdConstant.KEY_CODE.ESCAPE });
$timeout.flush();
$animate.triggerCallbacks();
expect(parent.find('md-dialog').length).toBe(1);
}));
it('should clickOutsideToClose == true', inject(function($mdDialog, $rootScope, $timeout, $animate, $mdConstant) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
clickOutsideToClose: true
});
$rootScope.$apply();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(1);
container.triggerHandler({
type: 'click',
target: container[0]
});
$timeout.flush();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(0);
}));
it('should clickOutsideToClose == false', inject(function($mdDialog, $rootScope, $timeout, $animate) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
clickOutsideToClose: false
});
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(1);
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('click');
$timeout.flush();
$animate.triggerCallbacks();
expect(parent[0].querySelectorAll('md-dialog').length).toBe(1);
}));
it('should disableParentScroll == true', inject(function($mdDialog, $animate, $rootScope) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
disableParentScroll: true
});
$rootScope.$apply();
$animate.triggerCallbacks();
$rootScope.$apply();
expect(parent.css('overflow')).toBe('hidden');
}));
it('should hasBackdrop == true', inject(function($mdDialog, $animate, $rootScope) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
hasBackdrop: true
});
$rootScope.$apply();
$animate.triggerCallbacks();
$rootScope.$apply();
expect(parent.find('md-dialog').length).toBe(1);
expect(parent.find('md-backdrop').length).toBe(1);
}));
it('should hasBackdrop == false', inject(function($mdDialog, $rootScope) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>',
parent: parent,
hasBackdrop: false
});
$rootScope.$apply();
expect(parent[0].querySelectorAll('md-dialog').length).toBe(1);
expect(parent[0].querySelectorAll('md-backdrop').length).toBe(0);
}));
it('should focus `md-button.dialog-close` on open', inject(function($mdDialog, $rootScope, $document, $timeout, $mdConstant) {
TestUtil.mockElementFocus(this);
var parent = angular.element('<div>');
$mdDialog.show({
template:
'<md-dialog>' +
'<div class="md-actions">' +
'<button class="dialog-close">Close</button>' +
'</div>' +
'</md-dialog>',
parent: parent
});
$rootScope.$apply();
$timeout.flush();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('transitionend');
$rootScope.$apply();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect($document.activeElement).toBe(parent[0].querySelector('.dialog-close'));
}));
it('should focus the last `md-button` in md-actions open if no `.dialog-close`', inject(function($mdDialog, $rootScope, $document, $timeout, $mdConstant) {
TestUtil.mockElementFocus(this);
var parent = angular.element('<div>');
$mdDialog.show({
template:
'<md-dialog>' +
'<div class="md-actions">' +
'<button id="a">A</md-button>' +
'<button id="focus-target">B</md-button>' +
'</div>' +
'</md-dialog>',
parent: parent
});
$rootScope.$apply();
$timeout.flush();
var container = angular.element(parent[0].querySelector('.md-dialog-container'));
container.triggerHandler('transitionend');
$rootScope.$apply();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect($document.activeElement).toBe(parent[0].querySelector('#focus-target'));
}));
it('should only allow one open at a time', inject(function($mdDialog, $rootScope) {
var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog class="one">',
parent: parent
});
$rootScope.$apply();
expect(parent[0].querySelectorAll('md-dialog.one').length).toBe(1);
expect(parent[0].querySelectorAll('md-dialog.two').length).toBe(0);
$mdDialog.show({
template: '<md-dialog class="two">',
parent: parent
});
$rootScope.$apply();
parent.find('md-dialog').triggerHandler('transitionend');
$rootScope.$apply();
expect(parent[0].querySelectorAll('md-dialog.one').length).toBe(0);
expect(parent[0].querySelectorAll('md-dialog.two').length).toBe(1);
}));
it('should have the dialog role', inject(function($mdDialog, $rootScope) {
var template = '<md-dialog>Hello</md-dialog>';
var parent = angular.element('<div>');
$mdDialog.show({
template: template,
parent: parent
});
$rootScope.$apply();
var dialog = angular.element(parent[0].querySelectorAll('md-dialog'));
expect(dialog.attr('role')).toBe('dialog');
}));
it('should create an ARIA label if one is missing', inject(function($mdDialog, $rootScope) {
var template = '<md-dialog>Hello</md-dialog>';
var parent = angular.element('<div>');
$mdDialog.show({
template: template,
parent: parent
});
$rootScope.$apply();
angular.element(parent[0].querySelector('.md-dialog-container')).triggerHandler('transitionend');
$rootScope.$apply();
var dialog = angular.element(parent[0].querySelector('md-dialog'));
expect(dialog.attr('aria-label')).toEqual(dialog.text());
}));
it('should not modify an existing ARIA label', inject(function($mdDialog, $rootScope){
var template = '<md-dialog aria-label="Some Other Thing">Hello</md-dialog>';
var parent = angular.element('<div>');
$mdDialog.show({
template: template,
parent: parent
});
$rootScope.$apply();
var dialog = angular.element(parent[0].querySelector('md-dialog'));
expect(dialog.attr('aria-label')).not.toEqual(dialog.text());
expect(dialog.attr('aria-label')).toEqual('Some Other Thing');
}));
});
function hasConfigurationMethods(preset, methods) {
angular.forEach(methods, function(method) {
return it('supports config method #' + method, inject(function($mdDialog) {
var dialog = $mdDialog[preset]();
expect(typeof dialog[method]).toBe('function');
expect(dialog[method]()).toEqual(dialog);
}));
});
}
});
describe('$mdDialog with custom interpolation symbols', function() {
beforeEach(TestUtil.mockRaf);
beforeEach(module('material.components.dialog', 'ngAnimateMock'));
beforeEach(module(function($interpolateProvider) {
$interpolateProvider.startSymbol('[[').endSymbol(']]');
}));
it('displays #alert() correctly', inject(function($mdDialog, $rootScope) {
var parent = angular.element('<div>');
var dialog = $mdDialog.
alert({parent: parent}).
ariaLabel('test alert').
title('Title').
content('Hello, world !').
ok('OK');
$mdDialog.show(dialog);
$rootScope.$digest();
var mdContainer = angular.element(parent[0].querySelector('.md-dialog-container'));
var mdDialog = mdContainer.find('md-dialog');
var mdContent = mdDialog.find('md-content');
var title = mdContent.find('h2');
var content = mdContent.find('p');
var mdActions = angular.element(mdDialog[0].querySelector('.md-actions'));
var buttons = mdActions.find('md-button');
expect(mdDialog.attr('aria-label')).toBe('test alert');
expect(title.text()).toBe('Title');
expect(content.text()).toBe('Hello, world !');
expect(buttons.eq(0).text()).toBe('OK');
}));
it('displays #confirm() correctly', inject(function($mdDialog, $rootScope) {
var parent = angular.element('<div>');
var dialog = $mdDialog.
confirm({parent: parent}).
ariaLabel('test alert').
title('Title').
content('Hello, world !').
cancel('CANCEL').
ok('OK');
$mdDialog.show(dialog);
$rootScope.$digest();
var mdContainer = angular.element(parent[0].querySelector('.md-dialog-container'));
var mdDialog = mdContainer.find('md-dialog');
var mdContent = mdDialog.find('md-content');
var title = mdContent.find('h2');
var content = mdContent.find('p');
var mdActions = angular.element(mdDialog[0].querySelector('.md-actions'));
var buttons = mdActions.find('md-button');
expect(mdDialog.attr('aria-label')).toBe('test alert');
expect(title.text()).toBe('Title');
expect(content.text()).toBe('Hello, world !');
expect(buttons.eq(0).text()).toBe('CANCEL');
expect(buttons.eq(1).text()).toBe('OK');
}));
});
@@ -0,0 +1,51 @@
<div ng-controller="AppCtrl">
<md-toolbar class="md-theme-light">
<h1 class="md-toolbar-tools">
<span>Full Bleed</span>
</h1>
</md-toolbar>
<md-content>
<md-list>
<md-item ng-repeat="item in messages">
<md-item-content>
<div class="md-tile-content">
<h3>{{item.what}}</h3>
<h4>{{item.who}}</h4>
<p>
{{item.notes}}
</p>
</div>
</md-item-content>
<md-divider ng-if="!$last"></md-divider>
</md-item>
</md-list>
</md-content>
<md-toolbar class="md-theme-light">
<h1 class="md-toolbar-tools">
<span>Inset</span>
</h1>
</md-toolbar>
<md-content>
<md-list>
<md-item ng-repeat="item in messages">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
</div>
<div class="md-tile-content">
<h3>{{item.what}}</h3>
<h4>{{item.who}}</h4>
<p>
{{item.notes}}
</p>
</div>
</md-item-content>
<md-divider md-inset ng-if="!$last"></md-divider>
</md-item>
</md-list>
</md-content>
</div>
@@ -0,0 +1,34 @@
angular.module('dividerDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.messages = [{
face: '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
}, {
face: '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
}, {
face: '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
}, {
face: '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
}, {
face: '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
}];
});
@@ -0,0 +1,6 @@
.face {
border-radius: 30px;
border: 1px solid #ddd;
width: 48px;
margin: 16px;
}
@@ -0,0 +1,3 @@
md-divider.md-THEME_NAME-theme {
border-top-color: '{{foreground-4}}';
}
@@ -0,0 +1,41 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.divider
* @description Divider module!
*/
angular.module('material.components.divider', [
'material.core'
])
.directive('mdDivider', MdDividerDirective);
function MdDividerController(){}
/**
* @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
* <hljs lang="html">
* <md-divider></md-divider>
*
* <md-divider md-inset></md-divider>
* </hljs>
*
*/
function MdDividerDirective($mdTheming) {
return {
restrict: 'E',
link: $mdTheming,
controller: [MdDividerController]
};
}
})();
@@ -0,0 +1,9 @@
md-divider {
display: block;
border-top: 1px solid;
margin: 0;
&[md-inset] {
margin-left: $baseline-grid * 9; // fix for vs-repeat
}
}
@@ -0,0 +1,44 @@
(function() {
'use strict';
/*
* @ngdoc module
* @name material.components.icon
* @description
* Icon
*/
angular.module('material.components.icon', [
'material.core'
])
.directive('mdIcon', mdIconDirective);
/*
* @ngdoc directive
* @name mdIcon
* @module material.components.icon
*
* @restrict E
*
* @description
* The `<md-icon>` directive is an element useful for SVG icons
*
* @usage
* <hljs lang="html">
* <md-icon icon="/img/icons/ic_access_time_24px.svg">
* </md-icon>
* </hljs>
*
*/
function mdIconDirective() {
return {
restrict: 'E',
template: '<object class="md-icon"></object>',
compile: function(element, attr) {
var object = angular.element(element[0].children[0]);
if(angular.isDefined(attr.icon)) {
object.attr('data', attr.icon);
}
}
};
}
})();
@@ -0,0 +1,18 @@
md-icon {
margin: auto;
padding: 0;
display: inline-block;
margin-top: 5px;
background-repeat: no-repeat no-repeat;
pointer-events: none;
}
svg, object {
fill: currentColor;
color: currentColor;
}
md-class-icon {
// display: block;
// margin: 0 auto;
}
@@ -0,0 +1,2 @@
describe('mdIcon directive', function() {
});
@@ -0,0 +1,58 @@
<div ng-app="inputBasicDemo" ng-controller="DemoCtrl" layout="column">
<md-content md-theme="docs-dark" class="md-padding" layout="row" layout-sm="column">
<md-input-container>
<label>Title</label>
<input ng-model="user.title">
</md-input-container>
<md-input-container>
<label>Email</label>
<input ng-model="user.email" type="email">
</md-input-container>
</md-content>
<md-content class="md-padding">
<md-input-container flex>
<label>Company (Disabled)</label>
<input ng-model="user.company" disabled>
</md-input-container>
<div layout layout-sm="column">
<md-input-container flex>
<label>First Name</label>
<input ng-model="user.firstName">
</md-input-container>
<md-input-container flex>
<label>Last Name</label>
<input ng-model="user.lastName">
</md-input-container>
</div>
<md-input-container flex>
<label>Address</label>
<input ng-model="user.address">
</md-input-container>
<div layout layout-sm="column">
<md-input-container flex>
<label>City</label>
<input ng-model="user.city">
</md-input-container>
<md-input-container flex>
<label>State</label>
<input ng-model="user.state">
</md-input-container>
<md-input-container flex>
<label>Postal Code</label>
<input ng-model="user.postalCode">
</md-input-container>
</div>
<md-input-container flex>
<label>Biography</label>
<textarea ng-model="user.biography" columns="1"></textarea>
</md-input-container>
</md-content>
</div>
@@ -0,0 +1,16 @@
angular.module('inputBasicDemo', ['ngMaterial'])
.controller('DemoCtrl', function($scope) {
$scope.user = {
title: 'Developer',
email: 'ipsum@lorem.com',
firstName: '',
lastName: '' ,
company: 'Google' ,
address: '1600 Amphitheatre Pkwy' ,
city: 'Mountain View' ,
state: 'CA' ,
biography: 'Loves kittens, snowboarding, and can type at 130 WPM. And rumor has it she bouldered up Castle Craig!',
postalCode : '94043'
};
});
@@ -0,0 +1,50 @@
md-input-container.md-THEME_NAME-theme {
.md-input {
@include input-placeholder-color('{{foreground-3}}');
color: '{{foreground-1}}';
border-color: '{{foreground-4}}';
text-shadow: '{{foreground-shadow}}';
}
label {
text-shadow: '{{foreground-shadow}}';
color: '{{foreground-3}}';
}
&.md-input-focused {
.md-input {
border-color: '{{primary-500}}';
}
label {
color: '{{primary-500}}';
}
&.md-accent {
.md-input {
border-color: '{{accent-500}}';
}
label {
color: '{{accent-500}}';
}
}
&.md-warn {
.md-input {
border-color: '{{warn-500}}';
}
label {
color: '{{warn-500}}';
}
}
}
&.md-input-has-value:not(.md-input-focused) {
label {
color: '{{foreground-2}}';
}
}
.md-input[disabled] {
border-bottom-color: transparent;
color: '{{foreground-3}}';
background-image: linear-gradient(to right, '{{foreground-4}}' 0%, '{{foreground-4}}' 33%, transparent 0%);
}
}
@@ -0,0 +1,189 @@
(function() {
/**
* @ngdoc module
* @name material.components.input
*/
angular.module('material.components.input', [
'material.core'
])
.directive('mdInputContainer', mdInputContainerDirective)
.directive('label', labelDirective)
.directive('input', inputTextareaDirective)
.directive('textarea', inputTextareaDirective);
/**
* @ngdoc directive
* @name mdInputContainer
* @module material.components.input
*
* @restrict E
*
* @description
* `<md-input-container>` 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.
*
* @usage
* <hljs lang="html">
*
* <md-input-container>
* <label>Username</label>
* <input type="text" ng-model="user.name">
* </md-input-container>
*
* <md-input-container>
* <label>Description</label>
* <textarea ng-model="user.description"></textarea>
* </md-input-container>
*
* </hljs>
*/
function mdInputContainerDirective($mdTheming) {
return {
restrict: 'E',
link: postLink,
controller: ContainerCtrl
};
function postLink(scope, element, attr) {
$mdTheming(element);
}
function ContainerCtrl($scope, $element, $mdUtil) {
var self = this;
self.setFocused = function(isFocused) {
$element.toggleClass('md-input-focused', !!isFocused);
};
self.setHasValue = function(hasValue) {
$element.toggleClass('md-input-has-value', !!hasValue);
};
$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) return;
containerCtrl.label = element;
scope.$on('$destroy', function() {
containerCtrl.label = null;
});
}
};
}
function inputTextareaDirective($mdUtil, $window) {
return {
restrict: 'E',
require: ['^?mdInputContainer', '?ngModel'],
compile: compile,
};
function compile(element) {
element.addClass('md-input');
return postLink;
}
function postLink(scope, element, attr, ctrls) {
var containerCtrl = ctrls[0];
var ngModelCtrl = ctrls[1];
if ( !containerCtrl ) return;
if (element[0].tagName.toLowerCase() === 'textarea') {
setupTextarea();
}
if (containerCtrl.input) {
throw new Error("<md-input-container> can only have *one* <input> or <textarea> child element!");
}
if (!element.attr('id')) {
element.attr('id', 'input_' + $mdUtil.nextUid());
}
containerCtrl.input = element;
// When the input value changes, check if it "has" a value, and
// set the appropriate class on the input group
if (ngModelCtrl) {
ngModelCtrl.$formatters.push(checkHasValue);
ngModelCtrl.$parsers.push(checkHasValue);
} else {
element.on('input', function() {
containerCtrl.setHasValue( (""+element.val()).length > 0 );
});
containerCtrl.setHasValue( (""+element.val()).length > 0 );
}
function checkHasValue(value) {
containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(value));
return value;
}
element
.on('focus', function(e) {
containerCtrl.setFocused(true);
})
.on('blur', function(e) {
containerCtrl.setFocused(false);
});
scope.$on('$destroy', function() {
containerCtrl.setFocused(false);
containerCtrl.setHasValue(false);
containerCtrl.input = null;
});
function setupTextarea() {
var node = element[0];
if (ngModelCtrl) {
ngModelCtrl.$formatters.push(growTextarea);
ngModelCtrl.$parsers.push(growTextarea);
} else {
element.on('input', growTextarea);
growTextarea();
}
element.on('keyup', growTextarea);
element.on('scroll', onScroll);
angular.element($window).on('resize', growTextarea);
scope.$on('$destroy', function() {
angular.element($window).off('resize', growTextarea);
});
function growTextarea(value) {
node.style.height = "auto";
var line = node.scrollHeight - node.offsetHeight;
node.scrollTop = 0;
var height = node.offsetHeight + (line > 0 ? line : 0);
node.style.height = height + 'px';
return value; // for $formatter/$parser
}
function onScroll(e) {
node.scrollTop = 0;
// for smooth new line adding
var line = node.scrollHeight - node.offsetHeight;
var height = node.offsetHeight + line;
node.style.height = height + 'px';
}
}
}
}
})();
@@ -0,0 +1,93 @@
$input-container-padding: 2px !default;
$input-label-default-offset: 24px !default;
$input-label-default-scale: 1.0 !default;
$input-label-float-offset: 4px !default;
$input-label-float-scale: 0.75 !default;
$input-border-width-default: 1px !default;
$input-border-width-focused: 2px !default;
$input-line-height: 26px !default;
$input-padding-top: 2px !default;
md-input-container {
display: flex;
position: relative;
flex-direction: column;
padding: $input-container-padding;
textarea,
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"] {
/* remove default appearance from all input/textarea */
-moz-appearance: none;
-webkit-appearance: none;
}
textarea {
resize: none;
overflow: hidden;
}
label {
order: 1;
pointer-events: none;
-webkit-font-smoothing: antialiased;
z-index: 1;
transform: translate3d(0, $input-label-default-offset, 0) scale($input-label-default-scale);
transform-origin: left top;
transition: all $swift-ease-out-timing-function 0.2s;
}
/*
* The .md-input class is added to the input/textarea
*/
.md-input {
flex: 1;
order: 2;
display: block;
background: none;
padding-top: $input-padding-top;
padding-bottom: $input-border-width-focused - $input-border-width-default;
border-width: 0 0 $input-border-width-default 0;
line-height: $input-line-height;
-ms-flex-preferred-size: $input-line-height; //IE fix
&:focus {
outline: none;
}
}
&.md-input-focused,
&.md-input-has-value {
label {
transform: translate3d(0,$input-label-float-offset,0) scale($input-label-float-scale);
}
}
&.md-input-focused {
.md-input {
padding-bottom: 0px; // Increase border width by 1px, decrease padding by 1
border-width: 0 0 $input-border-width-focused 0;
}
}
.md-input[disabled] {
background-position: 0 bottom;
// This background-size is coordinated with a linear-gradient set in input-theme.scss
// to create a dotted line under the input.
background-size: 3px 1px;
background-repeat: repeat-x;
}
}
@@ -0,0 +1,58 @@
describe('md-input-container directive', function() {
beforeEach(module('material.components.input'));
function setup(attrs) {
var container;
inject(function($rootScope, $compile) {
container = $compile('<md-input-container><input ' +(attrs||'')+ '><label></label></md-input-container>')($rootScope);
$rootScope.$apply();
});
return container;
}
it('should set focus class on container', function() {
var el = setup();
expect(el).not.toHaveClass('md-input-focused');
el.find('input').triggerHandler('focus');
expect(el).toHaveClass('md-input-focused');
el.find('input').triggerHandler('blur');
expect(el).not.toHaveClass('md-input-focused');
});
it('should set has-value class on container for non-ng-model input', function() {
var el = setup();
expect(el).not.toHaveClass('md-input-has-value');
el.find('input').val('123').triggerHandler('input');
expect(el).toHaveClass('md-input-has-value');
el.find('input').val('').triggerHandler('input');
expect(el).not.toHaveClass('md-input-has-value');
});
it('should set has-value class on container for ng-model input', inject(function($rootScope) {
$rootScope.value = 'test';
var el = setup('ng-model="$root.value"');
expect(el).toHaveClass('md-input-has-value');
$rootScope.$apply('value = "3"');
expect(el).toHaveClass('md-input-has-value');
$rootScope.$apply('value = null');
expect(el).not.toHaveClass('md-input-has-value');
}));
it('should match label to given input id', inject(function($rootScope) {
var el = setup('id="foo"');
expect(el.find('label').attr('for')).toBe('foo');
expect(el.find('input').attr('id')).toBe('foo');
}));
it('should match label to automatic input id', inject(function($rootScope) {
var el = setup();
expect(el.find('input').attr('id')).toBeTruthy();
expect(el.find('label').attr('for')).toBe(el.find('input').attr('id'));
}));
});
@@ -0,0 +1,25 @@
<div ng-controller="AppCtrl">
<md-content>
<md-list>
<md-item ng-repeat="item in todos">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
</div>
<div class="md-tile-content">
<h3>{{item.what}}</h3>
<h4>{{item.who}}</h4>
<p>
{{item.notes}}
</p>
</div>
</md-item-content>
</md-item>
</md-list>
</md-content>
</div>
@@ -0,0 +1,44 @@
angular.module('listDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.todos = [
{
face : '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
},
{
face : '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
},
{
face : '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
},
{
face : '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
},
{
face : '/img/list/60.jpeg',
what: 'Brunch this weekend?',
who: 'Min Li Chan',
when: '3:08PM',
notes: " I'll be in your neighborhood doing errands"
},
]
});
@@ -0,0 +1,7 @@
.face {
border-radius: 30px;
border: 1px solid #ddd;
width: 48px;
margin: 16px;
}
@@ -0,0 +1,88 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.list
* @description
* List module
*/
angular.module('material.components.list', [
'material.core'
])
.directive('mdList', mdListDirective)
.directive('mdItem', mdItemDirective);
/**
* @ngdoc directive
* @name mdList
* @module material.components.list
*
* @restrict E
*
* @description
* The `<md-list>` directive is a list container for 1..n `<md-item>` tags.
*
* @usage
* <hljs lang="html">
* <md-list>
* <md-item ng-repeat="item in todos">
* <md-item-content>
* <div class="md-tile-left">
* <img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
* </div>
* <div class="md-tile-content">
* <h3>{{item.what}}</h3>
* <h4>{{item.who}}</h4>
* <p>
* {{item.notes}}
* </p>
* </div>
* </md-item-content>
* </md-item>
* </md-list>
* </hljs>
*
*/
function mdListDirective() {
return {
restrict: 'E',
link: function($scope, $element, $attr) {
$element.attr({
'role' : 'list'
});
}
};
}
/**
* @ngdoc directive
* @name mdItem
* @module material.components.list
*
* @restrict E
*
* @description
* The `<md-item>` directive is a container intended for row items in a `<md-list>` container.
*
* @usage
* <hljs lang="html">
* <md-list>
* <md-item>
* Item content in list
* </md-item>
* </md-list>
* </hljs>
*
*/
function mdItemDirective() {
return {
restrict: 'E',
link: function($scope, $element, $attr) {
$element.attr({
'role' : 'listitem'
});
}
};
}
})();
@@ -0,0 +1,103 @@
$list-h3-font-size: 1.1em !default;
$list-h3-margin: 0 0 3px 0 !default;
$list-h3-font-weight: 400 !default;
$list-h4-font-size: 0.9em !default;
$list-h4-font-weight: 400 !default;
$list-h4-margin: 0 0 3px 0 !default;
$list-p-font-size: 0.75em !default;
$list-p-margin: 0 0 3px 0 !default;
$list-padding-top: $baseline-grid !default;
$list-padding-right: 0px !default;
$list-padding-left: 0px !default;
$list-padding-bottom: $baseline-grid !default;
$item-padding-top: 0px !default;
$item-padding-right: 0px !default;
$item-padding-left: 0px !default;
$item-padding-bottom: 0px !default;
md-list {
padding: $list-padding-top $list-padding-right $list-padding-bottom $list-padding-left;
}
md-item {
}
md-item-content {
display: flex;
align-items: center;
flex-direction: row;
box-sizing: border-box;
position: relative;
padding: $item-padding-top $item-padding-right $item-padding-bottom $item-padding-left;
}
/**
* The left tile for a list item.
*/
.md-tile-left {
min-width: 56px;
margin-right: -16px;
// for dev only
height: 56px;
background-color: grey;
}
/**
* The center content tile for a list item.
*/
.md-tile-content {
flex: 1;
padding: $baseline-grid * 2;
text-overflow: ellipsis;
h3 {
margin: $list-h3-margin;
font-weight: $list-h3-font-weight;
font-size: $list-h3-font-size;
}
h4 {
margin: $list-h4-margin;
font-weight: $list-h4-font-weight;
font-size: $list-h4-font-size;
}
p {
margin: $list-p-margin;
font-size: $list-p-font-size;
}
}
.sg {
&-tile-content {
@extend .md-tile-content;
margin-left: $layout-gutter-width; // dirty fix for vs-repeat damages
.name {
margin: $list-h3-margin;
font-weight: $list-h3-font-weight;
font-size: $list-h3-font-size;
}
.subject {
margin: $list-h4-margin;
font-weight: $list-h4-font-weight;
font-size: $list-h4-font-size;
// dirty fix for vs-repeat damages
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
p {
margin: $list-p-margin;
font-size: $list-p-font-size;
}
}
}
/**
* The right tile for a list item.
*/
.md-tile-right {
padding-right: $item-padding-right;
}
@@ -0,0 +1,11 @@
describe('mdList directive', function() {
function setup(attrs) {
module('material.components.list');
var el;
inject(function($compile, $rootScope) {
el = $compile('<md-list '+(attrs || '')+'></md-list>')($rootScope.$new());
$rootScope.$apply();
});
return el;
}
});
@@ -0,0 +1,25 @@
<div ng-controller="AppCtrl" layout="column" layout-margin style="padding:25px;">
<h4 style="margin-top:10px">Determinate</h4>
<p>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.</p>
<div layout="row" layout-sm="column" layout-align="space-around">
<md-progress-circular md-mode="determinate" value="{{determinateValue}}"></md-progress-circular>
</div>
<h4>Indeterminate</h4>
<p>For operations where the user is asked to wait a moment while something finishes up, and its not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.</p>
<div layout="row" layout-sm="column" layout-align="space-around">
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
</div>
<h4>Theming</h4>
<div layout="row" layout-sm="column" layout-align="space-around">
<md-progress-circular class="md-hue-2" md-mode="indeterminate"></md-progress-circular>
<md-progress-circular class="md-accent" md-mode="indeterminate"></md-progress-circular>
<md-progress-circular class="md-accent md-hue-1" md-mode="indeterminate"></md-progress-circular>
<md-progress-circular class="md-warn md-hue-3" md-mode="indeterminate"></md-progress-circular>
<md-progress-circular class="md-warn" md-mode="indeterminate"></md-progress-circular>
</div>
</div>
@@ -0,0 +1,14 @@
angular.module('progressCircularDemo1', ['ngMaterial'])
.controller('AppCtrl', ['$scope', '$interval',
function($scope, $interval) {
$scope.mode = 'query';
$scope.determinateValue = 30;
$interval(function() {
$scope.determinateValue += 1;
if ($scope.determinateValue > 100) {
$scope.determinateValue = 30;
}
}, 100, 0, true);
}
]);
@@ -0,0 +1,11 @@
body {
padding: 20px;
}
h4 {
margin: 10px 0;
}
md-progress-circular {
margin-bottom:20px;
}
@@ -0,0 +1,71 @@
md-progress-circular.md-THEME_NAME-theme {
background-color: transparent;
.md-inner {
.md-gap {
border-top-color: '{{primary-color}}';
border-bottom-color: '{{primary-color}}';
}
.md-left, .md-right {
.md-half-circle {
border-top-color: '{{primary-color}}';
}
}
.md-right {
.md-half-circle {
border-right-color: '{{primary-color}}';
}
}
.md-left {
.md-half-circle {
border-left-color: '{{primary-color}}';
}
}
}
&.md-warn {
.md-inner {
.md-gap {
border-top-color: '{{warn-color}}';
border-bottom-color: '{{warn-color}}';
}
.md-left, .md-right {
.md-half-circle {
border-top-color: '{{warn-color}}';
}
}
.md-right {
.md-half-circle {
border-right-color: '{{warn-color}}';
}
}
.md-left {
.md-half-circle {
border-left-color: '{{warn-color}}';
}
}
}
}
&.md-accent {
.md-inner {
.md-gap {
border-top-color: '{{accent-color}}';
border-bottom-color: '{{accent-color}}';
}
.md-left, .md-right {
.md-half-circle {
border-top-color: '{{accent-color}}';
}
}
.md-right {
.md-half-circle {
border-right-color: '{{accent-color}}';
}
}
.md-left {
.md-half-circle {
border-left-color: '{{accent-color}}';
}
}
}
}
}
@@ -0,0 +1,120 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.progressCircular
* @description Circular Progress module!
*/
angular.module('material.components.progressCircular', [
'material.core'
])
.directive('mdProgressCircular', MdProgressCircularDirective);
/**
* @ngdoc directive
* @name mdProgressCircular
* @module material.components.progressCircular
* @restrict E
*
* @description
* The circular 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.
*
* 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 its 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 two modes: determinate and indeterminate.
* @param {number=} value In determinate mode, this number represents the percentage of the circular progress. Default: 0
* @param {number=} md-diameter This specifies the diamter of the circular progress. Default: 48
*
* @usage
* <hljs lang="html">
* <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
*
* <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
*
* <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
*
* <md-progress-circular md-mode="indeterminate"></md-progress-circular>
* </hljs>
*/
function MdProgressCircularDirective($$rAF, $mdConstant, $mdTheming) {
var fillRotations = new Array(101),
fixRotations = new Array(101);
for (var i = 0; i < 101; i++) {
var percent = i / 100;
var rotation = Math.floor(percent * 180);
fillRotations[i] = 'rotate(' + rotation.toString() + 'deg)';
fixRotations[i] = 'rotate(' + (rotation * 2).toString() + 'deg)';
}
return {
restrict: 'E',
template:
'<div class="md-spinner-wrapper">' +
'<div class="md-inner">' +
'<div class="md-gap"></div>' +
'<div class="md-left">' +
'<div class="md-half-circle"></div>' +
'</div>' +
'<div class="md-right">' +
'<div class="md-half-circle"></div>' +
'</div>' +
'</div>' +
'</div>',
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 circle = element[0],
fill = circle.querySelectorAll('.md-fill, .md-mask.md-full'),
fix = circle.querySelectorAll('.md-fill.md-fix'),
i, clamped, fillRotation, fixRotation;
var diameter = attr.mdDiameter || 48;
var scale = diameter/48;
circle.style[$mdConstant.CSS.TRANSFORM] = 'scale(' + scale.toString() + ')';
attr.$observe('value', function(value) {
clamped = clamp(value);
fillRotation = fillRotations[clamped];
fixRotation = fixRotations[clamped];
element.attr('aria-valuenow', clamped);
for (i = 0; i < fill.length; i++) {
fill[i].style[$mdConstant.CSS.TRANSFORM] = fillRotation;
}
for (i = 0; i < fix.length; i++) {
fix[i].style[$mdConstant.CSS.TRANSFORM] = fixRotation;
}
});
}
function clamp(value) {
if (value > 100) {
return 100;
}
if (value < 0) {
return 0;
}
return Math.ceil(value || 0);
}
}
})();
@@ -0,0 +1,159 @@
$progress-circular-ease-in-out : cubic-bezier(0.35, 0, 0.25, 1) !default;
$progress-circular-duration : 5.25s !default;
$progress-circular-circle-duration : $progress-circular-duration * 0.25 !default;
$progress-circular-outer-duration : $progress-circular-duration * (5 / 9) !default;
$progress-circular-sporadic-duration : $progress-circular-duration !default;
$progress-circular-size : 50px !default;
@keyframes outer-rotate {
100% { transform: rotate(360deg); }
}
@keyframes left-wobble {
0%, 100% { transform: rotate(130deg); }
50% { transform: rotate( -5deg); }
}
@keyframes right-wobble {
0%, 100% { transform: rotate(-130deg); }
50% { transform: rotate( 5deg); }
}
@keyframes sporadic-rotate {
12.5% { transform: rotate( 135deg); }
25% { transform: rotate( 270deg); }
37.5% { transform: rotate( 405deg); }
50% { transform: rotate( 540deg); }
62.5% { transform: rotate( 675deg); }
75% { transform: rotate( 810deg); }
87.5% { transform: rotate( 945deg); }
100% { transform: rotate(1080deg); }
}
md-progress-circular {
width: $progress-circular-size;
height: $progress-circular-size;
display: block;
position: relative;
padding-top: 0 !important;
margin-bottom: 0 !important;
overflow: hidden;
.md-inner {
width: $progress-circular-size;
height: $progress-circular-size;
position: relative;
.md-gap {
position: absolute;
left: $progress-circular-size * 0.5 - 1;
right: $progress-circular-size * 0.5 - 1;
top: 0;
bottom: 0;
border-top: 5px solid black;
box-sizing: border-box;
}
.md-left, .md-right {
position: absolute;
top: 0;
height: $progress-circular-size;
width: $progress-circular-size * 0.5;
overflow: hidden;
.md-half-circle {
position: absolute;
top: 0;
width: $progress-circular-size;
height: $progress-circular-size;
box-sizing: border-box;
border-width: 5px;
border-style: solid;
border-color: black black transparent;
border-radius: 50%;
}
}
.md-left {
left: 0;
.md-half-circle {
left: 0;
border-right-color: transparent;
}
}
.md-right {
right: 0;
.md-half-circle {
right: 0;
border-left-color: transparent;
}
}
}
$i: 0;
@while $i <= 100 {
&[value="#{$i}"] {
.md-inner {
.md-left {
.md-half-circle {
@if $i <= 50 {
transform: rotate(135deg);
} @else {
transition: transform 0.1s linear;
$deg: ($i - 50) / 50 * 180 + 135;
transform: rotate(#{$deg}deg);
}
}
}
.md-right {
.md-half-circle {
@if $i <= 50 {
transition: transform 0.1s linear;
$deg: $i / 50 * 180 - 135;
transform: rotate(#{$deg}deg);
} @else {
transform: rotate(45deg);
}
}
}
.md-gap {
border-bottom-width: 5px;
border-bottom-style: solid;
@if $i <= 50 {
border-bottom-color: transparent !important;
} @else {
transition: border-bottom-color 0.1s linear;
}
}
}
}
$i: $i + 1;
}
&:not([md-mode=indeterminate]) {
.md-inner {
.md-left, .md-right {
.md-half-circle {
}
}
}
}
&[md-mode=indeterminate] {
.md-spinner-wrapper {
animation: outer-rotate $progress-circular-outer-duration linear infinite;
.md-inner {
animation: sporadic-rotate $progress-circular-sporadic-duration $progress-circular-ease-in-out infinite;
.md-left, .md-right {
.md-half-circle {
animation-iteration-count: infinite;
animation-duration: ($progress-circular-duration * 0.25);
animation-timing-function: $progress-circular-ease-in-out;
}
}
.md-left {
.md-half-circle {
animation-name: left-wobble;
}
}
.md-right {
.md-half-circle {
animation-name: right-wobble;
}
}
}
}
}
}
@@ -0,0 +1,18 @@
describe('mdProgressCircular', function() {
beforeEach(module('material.components.progressCircular'));
it('should update aria-valuenow', inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<md-progress-circular value="{{progress}}">' +
'</md-progress-circular>' +
'</div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.progress = 50;
});
var progress = element.find('md-progress-circular');
expect(progress.eq(0).attr('aria-valuenow')).toEqual('50');
}));
});
@@ -0,0 +1,12 @@
<div ng-controller="AppCtrl" layout="column" layout-margin style="padding:25px;">
<md-progress-linear md-mode="indeterminate"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="buffer" value="{{determinateValue}}" md-buffer-value="{{determinateValue2}}">
</md-progress-linear>
<md-progress-linear class="md-accent" md-mode="{{mode}}" value="{{determinateValue}}"></md-progress-linear>
<md-progress-linear md-theme="custom" md-mode="determinate" ng-value="determinateValue" ></md-progress-linear>
</div>
@@ -0,0 +1,21 @@
angular.module('progressLinearDemo1', ['ngMaterial'])
.config(function($mdThemingProvider) {
})
.controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
$scope.mode = 'query';
$scope.determinateValue = 30;
$scope.determinateValue2 = 30;
$interval(function() {
$scope.determinateValue += 1;
$scope.determinateValue2 += 1.5;
if ($scope.determinateValue > 100) {
$scope.determinateValue = 30;
$scope.determinateValue2 = 30;
}
}, 100, 0, true);
$interval(function() {
$scope.mode = ($scope.mode == 'query' ? 'determinate' : 'query');
}, 7200, 0, true);
}]);
@@ -0,0 +1,12 @@
body {
padding: 20px;
}
h4 {
margin: 10px 0;
}
md-progress-linear {
padding-top:10px;
margin-bottom:20px;
}
@@ -0,0 +1,44 @@
md-progress-linear.md-THEME_NAME-theme {
.md-container {
background-color: '{{primary-100}}';
}
.md-bar {
background-color: '{{primary-color}}';
}
&.md-warn .md-container {
background-color: '{{warn-100}}';
}
&.md-warn .md-bar {
background-color: '{{warn-color}}';
}
&.md-accent .md-container {
background-color: '{{accent-100}}';
}
&.md-accent .md-bar {
background-color: '{{accent-color}}';
}
&[md-mode=buffer] {
&.md-warn {
.md-bar1 {
background-color: '{{warn-100}}';
}
.md-dashed:before {
background: radial-gradient('{{warn-100}}' 0%, '{{warn-100}}' 16%, transparent 42%);
}
}
&.md-accent {
.md-bar1 {
background-color: '{{accent-100}}';
}
.md-dashed:before {
background: radial-gradient('{{accent-100}}' 0%, '{{accent-100}}' 16%, transparent 42%);
}
}
}
}
@@ -0,0 +1,121 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.progressLinear
* @description Linear Progress module!
*/
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 its 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.
* @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 precentage of the secondary progress bar. Default: 0
*
* @usage
* <hljs lang="html">
* <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
*
* <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
*
* <md-progress-linear md-mode="indeterminate"></md-progress-linear>
*
* <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
*
* <md-progress-linear md-mode="query"></md-progress-linear>
* </hljs>
*/
function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) {
return {
restrict: 'E',
template: '<div class="md-container">' +
'<div class="md-dashed"></div>' +
'<div class="md-bar md-bar1"></div>' +
'<div class="md-bar md-bar2"></div>' +
'</div>',
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 bar1Style = element[0].querySelector('.md-bar1').style,
bar2Style = element[0].querySelector('.md-bar2').style,
container = angular.element(element[0].querySelector('.md-container'));
attr.$observe('value', function(value) {
if (attr.mdMode == 'query') {
return;
}
var clamped = clamp(value);
element.attr('aria-valuenow', clamped);
bar2Style[$mdConstant.CSS.TRANSFORM] = transforms[clamped];
});
attr.$observe('mdBufferValue', function(value) {
bar1Style[$mdConstant.CSS.TRANSFORM] = transforms[clamp(value)];
});
$$rAF(function() {
container.addClass('md-ready');
});
}
function clamp(value) {
if (value > 100) {
return 100;
}
if (value < 0) {
return 0;
}
return Math.ceil(value || 0);
}
}
// **********************************************************
// Private Methods
// **********************************************************
var transforms = (function() {
var values = new Array(101);
for(var i = 0; i < 101; i++){
values[i] = makeTransform(i);
}
return values;
function makeTransform(value){
var scale = value/100;
var translateX = (value-100)/2;
return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)';
}
})();
})();
@@ -0,0 +1,201 @@
$progress-linear-bar-height:5px !default;
md-progress-linear {
display: block;
width: 100%;
height: $progress-linear-bar-height;
.md-container {
overflow: hidden;
position: relative;
height: $progress-linear-bar-height;
top: $progress-linear-bar-height;
transform: translate(0, 5px) scale(1, 0);
transition: all .3s linear;
}
.md-container.md-ready {
transform: translate(0, 0) scale(1, 1);
}
.md-bar {
height: $progress-linear-bar-height;
position: absolute;
width: 100%;
}
.md-bar1, .md-bar2 {
transition: all 0.2s linear;
}
&[md-mode=determinate] {
.md-bar1 {
display: none;
}
}
&[md-mode=indeterminate] {
.md-bar1 {
animation: indeterminate1 4s infinite linear;
}
.md-bar2 {
animation: indeterminate2 4s infinite linear;
}
}
&[md-mode=buffer] {
.md-container {
background-color: transparent !important;
}
.md-dashed:before {
content: "";
display: block;
height: $progress-linear-bar-height;
width: 100%;
margin-top: 0px;
position: absolute;
background-color: transparent;
background-size: 10px 10px !important;
background-position: 0px -23px;
animation: buffer 3s infinite linear;
}
}
&[md-mode=query] {
.md-bar2 {
animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000);
}
}
}
@keyframes indeterminate1 {
0% {
transform: translateX(-25%) scale(.5, 1);
}
10% {
transform: translateX(25%) scale(.5, 1);
}
19.99% {
transform: translateX(50%) scale(0, 1);
}
20% {
transform: translateX(-37.5%) scale(.25, 1);
}
30% {
transform: translateX(37.5%) scale(.25, 1);
}
34.99% {
transform: translateX(50%) scale(0, 1);
}
36.99% {
transform: translateX(50%) scale(0, 1);
}
37% {
transform: translateX(-37.5%) scale(.25, 1);
}
47% {
transform: translateX(20%) scale(.25, 1);
}
52% {
transform: translateX(35%) scale(.05, 1);
}
55% {
transform: translateX(35%) scale(.1, 1);
}
58% {
transform: translateX(50%) scale(.1, 1);
}
61.99% {
transform: translateX(50%) scale(0, 1);
}
69.99% {
transform: translateX(50%) scale(0, 1);
}
70% {
transform: translateX(-37.5%) scale(.25, 1);
}
80% {
transform: translateX(20%) scale(.25, 1);
}
85% {
transform: translateX(35%) scale(.05, 1);
}
88% {
transform: translateX(35%) scale(.1, 1);
}
91% {
transform: translateX(50%) scale(.1, 1);
}
92.99% {
transform: translateX(50%) scale(0, 1);
}
93% {
transform: translateX(-50%) scale(0, 1);
}
100% {
transform: translateX(-25%) scale(.5, 1);
}
}
@keyframes indeterminate2 {
0% {
transform: translateX(-50%) scale(0, 1);
}
25.99%{
transform: translateX(-50%) scale(0, 1);
}
28% {
transform: translateX(-37.5%) scale(.25, 1);
}
38% {
transform: translateX(37.5%) scale(.25, 1);
}
42.99% {
transform: translateX(50%) scale(0, 1);
}
46.99% {
transform: translateX(50%) scale(0, 1);
}
49.99% {
transform: translateX(50%) scale(0, 1);
}
50% {
transform: translateX(-50%) scale(0, 1);
}
60% {
transform: translateX(-25%) scale(.5, 1);
}
70% {
transform: translateX(25%) scale(.5, 1);
}
79.99% {
transform: translateX(50%) scale(0, 1);
}
}
@keyframes query {
0% {
opacity: 1;
transform: translateX(35%) scale(.3, 1);
}
100% {
opacity: 0;
transform: translateX(-50%) scale(0, 1);
}
}
@keyframes buffer {
0% {
opacity: 1;
background-position: 0px -23px;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
background-position: -200px -23px;
}
}
@@ -0,0 +1,68 @@
describe('mdProgressLinear', function() {
beforeEach(module('material.components.progressLinear'));
it('should set transform based on value', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile('<div>' +
'<md-progress-linear value="{{progress}}">' +
'</md-progress-linear>' +
'</div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.progress = 50;
});
var progress = element.find('md-progress-linear'),
bar2 = angular.element(progress[0].querySelectorAll('.md-bar2'))[0];
expect(bar2.style[$mdConstant.CSS.TRANSFORM]).toEqual('translateX(-25%) scale(0.5, 1)');
}));
it('should update aria-valuenow', inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<md-progress-linear value="{{progress}}">' +
'</md-progress-linear>' +
'</div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.progress = 50;
});
var progress = element.find('md-progress-linear');
expect(progress.eq(0).attr('aria-valuenow')).toEqual('50');
}));
it('should set transform based on buffer value', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile('<div>' +
'<md-progress-linear value="{{progress}}" md-buffer-value="{{progress2}}">' +
'</md-progress-linear>' +
'</div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.progress = 50;
$rootScope.progress2 = 75;
});
var progress = element.find('md-progress-linear'),
bar1 = angular.element(progress[0].querySelectorAll('.md-bar1'))[0];
expect(bar1.style[$mdConstant.CSS.TRANSFORM]).toEqual('translateX(-12.5%) scale(0.75, 1)');
}));
it('should not set transform in query mode', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile('<div>' +
'<md-progress-linear md-mode="query" value="{{progress}}">' +
'</md-progress-linear>' +
'</div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.progress = 80;
});
var progress = element.find('md-progress-linear'),
bar2 = angular.element(progress[0].querySelectorAll('.md-bar2'))[0];
expect(bar2.style[$mdConstant.CSS.TRANSFORM]).toBeFalsy();
}));
});
@@ -0,0 +1,33 @@
<form ng-submit="submit()" ng-controller="AppCtrl" >
<p>Selected Value: <span class="radioValue">{{ data.group1 }}</span> </p>
<md-radio-group ng-model="data.group1">
<md-radio-button value="Apple" aria-label="Label 1">Apple</md-radio-button>
<md-radio-button value="Banana"> Banana </md-radio-button>
<md-radio-button value="Mango" aria-label="Label 3">Mango</md-radio-button>
</md-radio-group>
<hr />
<p>Selected Value: <span class="radioValue">{{ data.group2 }}</span></p>
<md-radio-group ng-model="data.group2">
<md-radio-button ng-repeat="d in radioData"
ng-value="d.value"
ng-disabled=" d.isDisabled "
aria-label="{{ d.label }}">
{{ d.label }}
</md-radio-button>
</md-radio-group>
<p>
<md-button ng-click="addItem()">Add</md-button>
<md-button ng-click="removeItem()">Remove</md-button>
</p>
</form>
@@ -0,0 +1,31 @@
angular.module('radioDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.data = {
group1 : 'Banana',
group2 : '2'
};
$scope.radioData = [
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: '3', isDisabled: true },
{ label: '4', value: '4' }
];
$scope.submit = function() {
alert('submit');
};
$scope.addItem = function() {
var r = Math.ceil(Math.random() * 1000);
$scope.radioData.push({ label: r, value: r });
};
$scope.removeItem = function() {
$scope.radioData.pop();
};
});

Some files were not shown because too many files have changed in this diff Show More