Remove unused files from scss directory

pull/91/head
Francis Lachapelle 2015-02-03 08:55:17 -05:00
parent a191c7a382
commit 1a81abebaa
124 changed files with 0 additions and 9971 deletions

View File

@ -1,31 +0,0 @@
(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;
}
})();

View File

@ -1,285 +0,0 @@
(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;
}
}
}
}
})();

View File

@ -1,44 +0,0 @@
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);
}));
});
});

View File

@ -1,14 +0,0 @@
<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>

View File

@ -1,12 +0,0 @@
<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>

View File

@ -1,19 +0,0 @@
<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>

View File

@ -1,58 +0,0 @@
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);
};
});

View File

@ -1,64 +0,0 @@
/* 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');
}

View File

@ -1,87 +0,0 @@
(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);
});
}
}
}
})();

View File

@ -1,91 +0,0 @@
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");
}));
})
});

View File

@ -1,53 +0,0 @@
<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>

View File

@ -1,11 +0,0 @@
angular.module('buttonsDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.title1 = 'Button';
$scope.title4 = 'Warn';
$scope.isDisabled = true;
$scope.googleUrl = 'http://google.com';
});

View File

@ -1,30 +0,0 @@
/** 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;
}

View File

@ -1,51 +0,0 @@
(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);
}
};
}
})();

View File

@ -1,16 +0,0 @@
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);
}));
});

View File

@ -1,41 +0,0 @@
<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>

View File

@ -1,6 +0,0 @@
angular.module('cardDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
});

View File

@ -1,4 +0,0 @@
md-card {
min-height: 150px;
}

View File

@ -1,126 +0,0 @@
(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);
}
}
};
}
}
})();

View File

@ -1,172 +0,0 @@
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);
});
});
});

View File

@ -1,23 +0,0 @@
<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>

View File

@ -1,13 +0,0 @@
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;
});

View File

@ -1,4 +0,0 @@
body {
padding: 20px;
}

View File

@ -1,53 +0,0 @@
(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;
}
}
})();

View File

@ -1,54 +0,0 @@
<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>

View File

@ -1,6 +0,0 @@
angular.module('contentDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
})

View File

@ -1,32 +0,0 @@
<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>

View File

@ -1,24 +0,0 @@
<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>

View File

@ -1,59 +0,0 @@
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);
};
}

View File

@ -1,34 +0,0 @@
.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;
}

View File

@ -1,485 +0,0 @@
(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;
}
}
}
})();

View File

@ -1,479 +0,0 @@
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');
}));
});

View File

@ -1,51 +0,0 @@
<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>

View File

@ -1,34 +0,0 @@
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"
}];
});

View File

@ -1,6 +0,0 @@
.face {
border-radius: 30px;
border: 1px solid #ddd;
width: 48px;
margin: 16px;
}

View File

@ -1,41 +0,0 @@
(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]
};
}
})();

View File

@ -1,58 +0,0 @@
<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>

View File

@ -1,16 +0,0 @@
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'
};
});

View File

@ -1,189 +0,0 @@
(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';
}
}
}
}
})();

View File

@ -1,58 +0,0 @@
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'));
}));
});

View File

@ -1,25 +0,0 @@
<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>

View File

@ -1,44 +0,0 @@
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"
},
]
});

View File

@ -1,7 +0,0 @@
.face {
border-radius: 30px;
border: 1px solid #ddd;
width: 48px;
margin: 16px;
}

View File

@ -1,88 +0,0 @@
(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'
});
}
};
}
})();

View File

@ -1,11 +0,0 @@
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;
}
});

View File

@ -1,25 +0,0 @@
<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>

View File

@ -1,14 +0,0 @@
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);
}
]);

View File

@ -1,11 +0,0 @@
body {
padding: 20px;
}
h4 {
margin: 10px 0;
}
md-progress-circular {
margin-bottom:20px;
}

View File

@ -1,120 +0,0 @@
(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);
}
}
})();

View File

@ -1,18 +0,0 @@
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');
}));
});

View File

@ -1,12 +0,0 @@
<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>

View File

@ -1,21 +0,0 @@
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);
}]);

View File

@ -1,12 +0,0 @@
body {
padding: 20px;
}
h4 {
margin: 10px 0;
}
md-progress-linear {
padding-top:10px;
margin-bottom:20px;
}

View File

@ -1,121 +0,0 @@
(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 indicatorfor 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)';
}
})();
})();

View File

@ -1,68 +0,0 @@
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();
}));
});

View File

@ -1,33 +0,0 @@
<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>

View File

@ -1,31 +0,0 @@
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();
};
});

View File

@ -1,29 +0,0 @@
body {
padding: 20px;
}
hr {
margin-left:-20px; opacity:0.3;
}
md-radio-group {
width:150px;
}
p:last-child {
padding-bottom: 50px;
}
[ng-controller] {
padding-left: 20px;
}
.radioValue {
margin-left: 5px;
color: #0f9d58;
font-weight: bold;
padding:5px;
}

View File

@ -1,288 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.radioButton
* @description radioButton module!
*/
angular.module('material.components.radioButton', [
'material.core'
])
.directive('mdRadioGroup', mdRadioGroupDirective)
.directive('mdRadioButton', mdRadioButtonDirective);
/**
* @ngdoc directive
* @module material.components.radioButton
* @name mdRadioGroup
*
* @restrict E
*
* @description
* The `<md-radio-group>` directive identifies a grouping
* container for the 1..n grouped radio buttons; specified using nested
* `<md-radio-button>` tags.
*
* Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
* than the native `<input type='radio'>` controls. Whereas the native controls
* force the user to tab through all the radio buttons, `<md-radio-group>`
* is focusable, and by default the `<md-radio-button>`s are not.
*
* @param {string} ng-model Assignable angular expression to data-bind to.
* @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
*
* @usage
* <hljs lang="html">
* <md-radio-group ng-model="selected">
*
* <md-radio-button
* ng-repeat="d in colorOptions"
* ng-value="d.value" aria-label="{{ d.label }}">
*
* {{ d.label }}
*
* </md-radio-button>
*
* </md-radio-group>
* </hljs>
*
*/
function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming) {
RadioGroupController.prototype = createRadioGroupControllerProto();
return {
restrict: 'E',
controller: ['$element', RadioGroupController],
require: ['mdRadioGroup', '?ngModel'],
link: linkRadioGroup
};
function linkRadioGroup(scope, element, attr, ctrls) {
$mdTheming(element);
var rgCtrl = ctrls[0];
var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
function keydownListener(ev) {
switch(ev.keyCode) {
case $mdConstant.KEY_CODE.LEFT_ARROW:
case $mdConstant.KEY_CODE.UP_ARROW:
ev.preventDefault();
rgCtrl.selectPrevious();
break;
case $mdConstant.KEY_CODE.RIGHT_ARROW:
case $mdConstant.KEY_CODE.DOWN_ARROW:
ev.preventDefault();
rgCtrl.selectNext();
break;
case $mdConstant.KEY_CODE.ENTER:
var form = angular.element($mdUtil.getClosest(element[0], 'form'));
if (form.length > 0) {
form.triggerHandler('submit');
}
break;
}
}
rgCtrl.init(ngModelCtrl);
element.attr({
'role': 'radiogroup',
'tabIndex': element.attr('tabindex') || '0'
})
.on('keydown', keydownListener);
}
function RadioGroupController($element) {
this._radioButtonRenderFns = [];
this.$element = $element;
}
function createRadioGroupControllerProto() {
return {
init: function(ngModelCtrl) {
this._ngModelCtrl = ngModelCtrl;
this._ngModelCtrl.$render = angular.bind(this, this.render);
},
add: function(rbRender) {
this._radioButtonRenderFns.push(rbRender);
},
remove: function(rbRender) {
var index = this._radioButtonRenderFns.indexOf(rbRender);
if (index !== -1) {
this._radioButtonRenderFns.splice(index, 1);
}
},
render: function() {
this._radioButtonRenderFns.forEach(function(rbRender) {
rbRender();
});
},
setViewValue: function(value, eventType) {
this._ngModelCtrl.$setViewValue(value, eventType);
// update the other radio buttons as well
this.render();
},
getViewValue: function() {
return this._ngModelCtrl.$viewValue;
},
selectNext: function() {
return changeSelectedButton(this.$element, 1);
},
selectPrevious : function() {
return changeSelectedButton(this.$element, -1);
},
setActiveDescendant: function (radioId) {
this.$element.attr('aria-activedescendant', radioId);
}
};
}
/**
* Change the radio group's selected button by a given increment.
* If no button is selected, select the first button.
*/
function changeSelectedButton(parent, increment) {
// Coerce all child radio buttons into an array, then wrap then in an iterator
var buttons = $mdUtil.iterator(
Array.prototype.slice.call(parent[0].querySelectorAll('md-radio-button')),
true
);
if (buttons.count()) {
var validate = function (button) {
// If disabled, then NOT valid
return !angular.element(button).attr("disabled");
};
var selected = parent[0].querySelector('md-radio-button.md-checked');
var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
// Activate radioButton's click listener (triggerHandler won't create a real click event)
angular.element(target).triggerHandler('click');
}
}
}
/**
* @ngdoc directive
* @module material.components.radioButton
* @name mdRadioButton
*
* @restrict E
*
* @description
* The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
*
* While similar to the `<input type="radio" ng-model="" value="">` directive,
* the `<md-radio-button>` directive provides ink effects, ARIA support, and
* supports use within named radio groups.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
* @param {string} ngValue Angular expression which sets the value to which the expression should
* be set when selected.*
* @param {string} value The value to which the expression should be set when selected.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} ariaLabel Adds label to radio button for accessibility.
* Defaults to radio button's text. If no default text is found, a warning will be logged.
*
* @usage
* <hljs lang="html">
*
* <md-radio-button value="1" aria-label="Label 1">
* Label 1
* </md-radio-button>
*
* <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
* Green
* </md-radio-button>
*
* </hljs>
*
*/
function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
var CHECKED_CSS = 'md-checked';
return {
restrict: 'E',
require: '^mdRadioGroup',
transclude: true,
template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
'<div class="md-off"></div>' +
'<div class="md-on"></div>' +
'</div>' +
'<div ng-transclude class="md-label"></div>',
link: link
};
function link(scope, element, attr, rgCtrl) {
var lastChecked;
$mdTheming(element);
configureAria(element, scope);
rgCtrl.add(render);
attr.$observe('value', render);
element
.on('click', listener)
.on('$destroy', function() {
rgCtrl.remove(render);
});
function listener(ev) {
if (element[0].hasAttribute('disabled')) return;
scope.$apply(function() {
rgCtrl.setViewValue(attr.value, ev && ev.type);
});
}
function render() {
var checked = (rgCtrl.getViewValue() == attr.value);
if (checked === lastChecked) {
return;
}
lastChecked = checked;
element.attr('aria-checked', checked);
if (checked) {
element.addClass(CHECKED_CSS);
rgCtrl.setActiveDescendant(element.attr('id'));
} else {
element.removeClass(CHECKED_CSS);
}
}
/**
* Inject ARIA-specific attributes appropriate for each radio button
*/
function configureAria( element, scope ){
scope.ariaId = buildAriaID();
element.attr({
'id' : scope.ariaId,
'role' : 'radio',
'aria-checked' : 'false'
});
$mdAria.expectWithText(element, 'aria-label');
/**
* Build a unique ID for each radio button that will be used with aria-activedescendant.
* Preserve existing ID if already specified.
* @returns {*|string}
*/
function buildAriaID() {
return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
}
}
}
}
})();

View File

@ -1,258 +0,0 @@
describe('radioButton', function() {
var CHECKED_CSS = 'md-checked';
beforeEach(TestUtil.mockRaf);
beforeEach(module('material.components.radioButton'));
it('should set checked css class', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
$rootScope.$apply(function(){
$rootScope.color = 'green';
});
var rbElements = element.find('md-radio-button');
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toEqual(false);
expect(rbElements.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
}));
it('should support mixed values', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="value">' +
'<md-radio-button value="1"></md-radio-button>' +
'<md-radio-button value="2"></md-radio-button>' +
'</md-radio-group>')($rootScope);
$rootScope.$apply(function(){
$rootScope.value = 1;
});
var rbElements = element.find('md-radio-button');
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toEqual(true);
}));
it('should set roles', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
var rbGroupElement = element;
expect(rbGroupElement.eq(0).attr('role')).toEqual('radiogroup');
expect(rbGroupElement.find('md-radio-button').eq(0).attr('role')).toEqual('radio');
}));
it('should set aria states', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
$rootScope.$apply(function(){
$rootScope.color = 'green';
});
var rbElements = element.find('md-radio-button');
expect(rbElements.eq(0).attr('aria-checked')).toEqual('false');
expect(rbElements.eq(1).attr('aria-checked')).toEqual('true');
expect(element.attr('aria-activedescendant')).toEqual(rbElements.eq(1).attr('id'));
expect(element.attr('aria-activedescendant')).not.toEqual(rbElements.eq(0).attr('id'));
}));
it('should warn developers they need a label', inject(function($compile, $rootScope, $log){
spyOn($log, "warn");
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
expect($log.warn).toHaveBeenCalled();
}));
it('should create an aria label from provided text', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue">Blue</md-radio-button>' +
'<md-radio-button value="green">Green</md-radio-button>' +
'</md-radio-group>')($rootScope);
var rbElements = element.find('md-radio-button');
expect(rbElements.eq(0).attr('aria-label')).toEqual('Blue');
}));
it('should preserve tabindex', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile('<md-radio-group ng-model="color" tabindex="3">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
var rbGroupElement = element.eq(0);
expect(rbGroupElement.attr('tabindex')).toEqual('3');
}));
it('should be operable via arrow keys', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="blue"></md-radio-button>' +
'<md-radio-button value="green"></md-radio-button>' +
'</md-radio-group>')($rootScope);
$rootScope.$apply(function(){
$rootScope.color = 'blue';
});
var rbGroupElement = element.eq(0);
rbGroupElement.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
});
expect($rootScope.color).toEqual('green');
}));
describe('ng core radio button tests', function() {
it('should noop with no model', inject(function($compile, $rootScope) {
var el;
expect(function() {
el = $compile('<md-radio-group>' +
'<md-radio-button value="white">' +
'</md-radio-group>')($rootScope);
}).not.toThrow();
var rbElements = el.find('md-radio-button');
// Fire off the render function with no ngModel, make sure nothing
// goes unexpectedly.
expect(function() {
rbElements.eq(0).triggerHandler('click');
}).not.toThrow();
}));
it('should update the model', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="white"></md-radio-button>' +
'<md-radio-button value="red"></md-radio-button>' +
'<md-radio-button value="blue"></md-radio-button>' +
'</md-radio-group>')($rootScope);
var rbElements = element.find('md-radio-button');
$rootScope.$apply("color = 'white'");
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toBe(true);
expect(rbElements.eq(1).hasClass(CHECKED_CSS)).toBe(false);
expect(rbElements.eq(2).hasClass(CHECKED_CSS)).toBe(false);
$rootScope.$apply("color = 'red'");
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toBe(false);
expect(rbElements.eq(1).hasClass(CHECKED_CSS)).toBe(true);
expect(rbElements.eq(2).hasClass(CHECKED_CSS)).toBe(false);
rbElements.eq(2).triggerHandler('click');
expect($rootScope.color).toBe('blue');
}));
it('should trigger a submit', inject(function($compile, $rootScope, $mdConstant) {
$rootScope.testValue = false;
$rootScope.submitFn = function(){
$rootScope.testValue = true;
};
var element = $compile('<div><form ng-submit="submitFn()">' +
'<md-radio-group ng-model="color">' +
'<md-radio-button value="white"></md-radio-button>' +
'</md-radio-group>' +
'</form></div>')($rootScope);
var formElement = element.find('form'),
rbGroupElement = element.find('md-radio-group');
rbGroupElement.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.ENTER
});
expect($rootScope.testValue).toBe(true);
}));
it('should be disabled', inject(function($compile, $rootScope) {
var element = $compile('<md-radio-group ng-model="color">' +
'<md-radio-button value="white" ng-disabled="isDisabled"></md-radio-button>' +
'</md-radio-group>')($rootScope);
var radio = element.find('md-radio-button');
$rootScope.$apply('isDisabled = true');
$rootScope.$apply('color = null');
radio.triggerHandler('click');
expect($rootScope.color).toBe(null);
$rootScope.$apply('isDisabled = false');
radio.triggerHandler('click');
expect($rootScope.color).toBe('white');
}));
it('should skip disabled on arrow key', inject(function($compile, $rootScope, $mdConstant) {
var element = $compile(
'<md-radio-group ng-model="color">' +
' <md-radio-button value="red" ></md-radio-button>' +
' <md-radio-button value="white" ng-disabled="isDisabled"></md-radio-button>' +
' <md-radio-button value="blue" ></md-radio-button>' +
'</md-radio-group>'
)($rootScope);
var rbGroupElement = element.eq(0);
$rootScope.$apply('isDisabled = true');
$rootScope.$apply('color = "red"');
expect($rootScope.color).toBe("red");
rightArrow(); expect($rootScope.color).toEqual('blue');
rightArrow(); expect($rootScope.color).toEqual('red');
rightArrow(); expect($rootScope.color).toEqual('blue');
$rootScope.$apply('isDisabled = false');
rightArrow();
rightArrow(); expect($rootScope.color).toEqual('white');
rightArrow(); expect($rootScope.color).toEqual('blue');
function rightArrow() {
rbGroupElement.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
});
}
}));
it('should allow {{expr}} as value', inject(function($compile, $rootScope) {
$rootScope.some = 11;
var element = $compile('<md-radio-group ng-model="value">' +
'<md-radio-button value="{{some}}"></md-radio-button>' +
'<md-radio-button value="{{other}}"></<md-radio-button>' +
'</md-radio-group>')($rootScope);
var rbElements = element.find('md-radio-button');
$rootScope.$apply(function() {
$rootScope.value = 'blue';
$rootScope.some = 'blue';
$rootScope.other = 'red';
});
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toBe(true);
expect(rbElements.eq(1).hasClass(CHECKED_CSS)).toBe(false);
rbElements.eq(1).triggerHandler('click');
expect($rootScope.value).toBe('red');
$rootScope.$apply("other = 'non-red'");
expect(rbElements.eq(0).hasClass(CHECKED_CSS)).toBe(false);
expect(rbElements.eq(1).hasClass(CHECKED_CSS)).toBe(false);
}));
});
});

View File

@ -1,62 +0,0 @@
<div ng-controller="AppCtrl" layout="column" layout-fill>
<section layout="row" flex>
<md-sidenav class="md-sidenav-left md-whiteframe-z2" md-component-id="left" md-is-locked-open="$media('gt-md')">
<md-toolbar class="md-theme-indigo">
<h1 class="md-toolbar-tools">Sidenav Left</h1>
</md-toolbar>
<md-content class="md-padding" ng-controller="LeftCtrl">
<md-button ng-click="close()" class="md-primary" hide-gt-md>
Close Sidenav Left
</md-button>
<p hide-md show-gt-md>
This sidenav is locked open on your device. To go back to the default behavior,
narrow your display.
</p>
</md-content>
</md-sidenav>
<md-content flex class="md-padding">
<div layout="column" layout-fill layout-align="center center">
<p>
The left sidenav will 'lock open' on a medium (>=960px wide) device.
</p>
<div>
<md-button ng-click="toggleLeft()"
class="md-primary" hide-gt-md>
Toggle left
</md-button>
</div>
<div>
<md-button ng-click="toggleRight()"
class="md-primary">
Toggle right
</md-button>
</div>
</div>
</md-content>
<md-sidenav class="md-sidenav-right md-whiteframe-z2" md-component-id="right">
<md-toolbar class="md-theme-light">
<h1 class="md-toolbar-tools">Sidenav Right</h1>
</md-toolbar>
<md-content ng-controller="RightCtrl" class="md-padding">
<md-button ng-click="close()" class="md-primary">
Close Sidenav Right
</md-button>
</md-content>
</md-sidenav>
</section>
</div>

View File

@ -1,36 +0,0 @@
angular.module('sidenavDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope, $timeout, $mdSidenav, $log) {
$scope.toggleLeft = function() {
$mdSidenav('left').toggle()
.then(function(){
$log.debug("toggle left is done");
});
};
$scope.toggleRight = function() {
$mdSidenav('right').toggle()
.then(function(){
$log.debug("toggle RIGHT is done");
});
};
})
.controller('LeftCtrl', function($scope, $timeout, $mdSidenav, $log) {
$scope.close = function() {
$mdSidenav('left').close()
.then(function(){
$log.debug("close LEFT is done");
});
};
})
.controller('RightCtrl', function($scope, $timeout, $mdSidenav, $log) {
$scope.close = function() {
$mdSidenav('right').close()
.then(function(){
$log.debug("close RIGHT is done");
});
};
});

View File

@ -1,293 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.sidenav
*
* @description
* A Sidenav QP component.
*/
angular.module('material.components.sidenav', [
'material.core',
'material.components.backdrop'
])
.factory('$mdSidenav', SidenavService )
.directive('mdSidenav', SidenavDirective)
.controller('$mdSidenavController', SidenavController);
/**
* @private
* @ngdoc service
* @name $mdSidenav
* @module material.components.sidenav
*
* @description
* $mdSidenav makes it easy to interact with multiple sidenavs
* in an app.
*
* @usage
*
* ```javascript
* // Toggle the given sidenav
* $mdSidenav(componentId).toggle();
*
* // Open the given sidenav
* $mdSidenav(componentId).open();
*
* // Close the given sidenav
* $mdSidenav(componentId).close();
* ```
*/
function SidenavService($mdComponentRegistry, $q) {
return function(handle) {
var errorMsg = "SideNav '" + handle + "' is not available!";
// Lookup the controller instance for the specified sidNav instance
var instance = $mdComponentRegistry.get(handle);
if(!instance) {
$mdComponentRegistry.notFoundError(handle);
}
return {
isOpen: function() {
return instance && instance.isOpen();
},
toggle: function() {
return instance ? instance.toggle() : $q.reject(errorMsg);
},
open: function() {
return instance ? instance.open() : $q.reject(errorMsg);
},
close: function() {
return instance ? instance.close() : $q.reject(errorMsg);
}
};
};
}
/**
* @ngdoc directive
* @name mdSidenav
* @module material.components.sidenav
* @restrict E
*
* @description
*
* A Sidenav component that can be opened and closed programatically.
*
* By default, upon opening it will slide out on top of the main content area.
*
* @usage
* <hljs lang="html">
* <div layout="row" ng-controller="MyController">
* <md-sidenav md-component-id="left" class="md-sidenav-left">
* Left Nav!
* </md-sidenav>
*
* <md-content>
* Center Content
* <md-button ng-click="openLeftMenu()">
* Open Left Menu
* </md-button>
* </md-content>
*
* <md-sidenav md-component-id="right"
* md-is-locked-open="$media('min-width: 333px')"
* class="md-sidenav-right">
* Right Nav!
* </md-sidenav>
* </div>
* </hljs>
*
* <hljs lang="js">
* var app = angular.module('myApp', ['ngMaterial']);
* app.controller('MyController', function($scope, $mdSidenav) {
* $scope.openLeftMenu = function() {
* $mdSidenav('left').toggle();
* };
* });
* </hljs>
*
* @param {expression=} md-is-open A model bound to whether the sidenav is opened.
* @param {string=} md-component-id componentId to use with $mdSidenav service.
* @param {expression=} md-is-locked-open When this expression evalutes to true,
* the sidenav 'locks open': it falls into the content's flow instead
* of appearing over it. This overrides the `is-open` attribute.
*
* A $media() function is exposed to the is-locked-open attribute, which
* can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
* Examples:
*
* - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$media('min-width: 1000px')"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$media('sm')"></md-sidenav>` (locks open on small screens)
*/
function SidenavDirective($timeout, $animate, $parse, $mdMedia, $mdConstant, $compile, $mdTheming, $q, $document) {
return {
restrict: 'E',
scope: {
isOpen: '=?mdIsOpen'
},
controller: '$mdSidenavController',
compile: function(element) {
element.addClass('md-closed');
element.attr('tabIndex', '-1');
return postLink;
}
};
/**
* Directive Post Link function...
*/
function postLink(scope, element, attr, sidenavCtrl) {
var triggeringElement = null;
var promise = $q.when(true);
var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
var isLocked = function() {
return isLockedOpenParsed(scope.$parent, {
$media: $mdMedia
});
};
var backdrop = $compile(
'<md-backdrop class="md-sidenav-backdrop md-opaque ng-enter">'
)(scope);
element.on('$destroy', sidenavCtrl.destroy);
$mdTheming.inherit(backdrop, element);
scope.$watch(isLocked, updateIsLocked);
scope.$watch('isOpen', updateIsOpen);
// Publish special accessor for the Controller instance
sidenavCtrl.$toggleOpen = toggleOpen;
/**
* Toggle the DOM classes to indicate `locked`
* @param isLocked
*/
function updateIsLocked(isLocked, oldValue) {
if (isLocked === oldValue) {
element.toggleClass('md-locked-open', !!isLocked);
} else {
$animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
}
backdrop.toggleClass('md-locked-open', !!isLocked);
}
/**
* Toggle the SideNav view and attach/detach listeners
* @param isOpen
*/
function updateIsOpen(isOpen) {
var parent = element.parent();
parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
backdrop[isOpen ? 'on' : 'off']('click', close);
if ( isOpen ) {
// Capture upon opening..
triggeringElement = $document[0].activeElement;
}
return promise = $q.all([
$animate[isOpen ? 'enter' : 'leave'](backdrop, parent),
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed').then(function() {
// If we opened, and haven't closed again before the animation finished
if (scope.isOpen) {
element.focus();
}
})
]);
}
/**
* Toggle the sideNav view and publish a promise to be resolved when
* the view animation finishes.
*
* @param isOpen
* @returns {*}
*/
function toggleOpen( isOpen ) {
if (scope.isOpen == isOpen ) {
return $q.when(true);
} else {
var deferred = $q.defer();
// Toggle value to force an async `updateIsOpen()` to run
scope.isOpen = isOpen;
$timeout(function() {
// When the current `updateIsOpen()` animation finishes
promise.then(function(result){
if ( !scope.isOpen ) {
// reset focus to originating element (if available) upon close
triggeringElement && triggeringElement.focus();
triggeringElement = null;
}
deferred.resolve(result);
});
},0,false);
return deferred.promise;
}
}
/**
* Auto-close sideNav when the `escape` key is pressed.
* @param evt
*/
function onKeyDown(ev) {
var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
return isEscape ? close(ev) : $q.when(true);
}
/**
* With backdrop `clicks` or `escape` key-press, immediately
* apply the CSS close transition... Then notify the controller
* to close() and perform its own actions.
*/
function close(ev) {
ev.preventDefault();
ev.stopPropagation();
return sidenavCtrl.close();
}
}
}
/*
* @private
* @ngdoc controller
* @name SidenavController
* @module material.components.sidenav
*
*/
function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
var self = this;
// Use Default internal method until overridden by directive postLink
self.$toggleOpen = function() { return $q.when($scope.isOpen); };
self.isOpen = function() { return !!$scope.isOpen; };
self.open = function() { return self.$toggleOpen( true ); };
self.close = function() { return self.$toggleOpen( false ); };
self.toggle = function() { return self.$toggleOpen( !$scope.isOpen ); };
self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
}
})();

View File

@ -1,239 +0,0 @@
describe('mdSidenav', function() {
beforeEach(module('material.components.sidenav', 'ngAnimateMock', function($provide) {
$provide.value('$$rAF', function(cb) { cb(); });
}));
function setup(attrs) {
var el;
inject(function($compile, $rootScope) {
var parent = angular.element('<div>');
el = angular.element('<md-sidenav ' + (attrs||'') + '>');
parent.append(el);
$compile(parent)($rootScope);
$rootScope.$apply();
});
return el;
}
describe('directive', function() {
it('should bind isOpen attribute', inject(function($rootScope, $animate) {
var el = setup('md-is-open="show"');
$rootScope.$apply('show = true');
$animate.triggerCallbacks();
expect(el.hasClass('md-closed')).toBe(false);
expect(el.parent().find('md-backdrop').length).toBe(1);
$rootScope.$apply('show = false');
$animate.triggerCallbacks();
expect(el.hasClass('md-closed')).toBe(true);
expect(el.parent().find('md-backdrop').length).toBe(0);
}));
it('should close on escape', inject(function($rootScope, $animate, $mdConstant, $timeout) {
var el = setup('md-is-open="show"');
$rootScope.$apply('show = true');
$animate.triggerCallbacks();
el.parent().triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.ESCAPE
});
$timeout.flush();
expect($rootScope.show).toBe(false);
}));
it('should close on backdrop click', inject(function($rootScope, $animate, $timeout) {
var el = setup('md-is-open="show"');
$rootScope.$apply('show = true');
$animate.triggerCallbacks();
el.parent().find('md-backdrop').triggerHandler('click');
$timeout.flush();
expect($rootScope.show).toBe(false);
}));
it('should focus sidenav on open', inject(function($rootScope, $animate, $document) {
TestUtil.mockElementFocus(this);
var el = setup('md-is-open="show"');
$rootScope.$apply('show = true');
$animate.triggerCallbacks();
expect($document.activeElement).toBe(el[0]);
}));
it('should lock open when is-locked-open is true', inject(function($rootScope, $animate, $document) {
var el = setup('md-is-open="show" md-is-locked-open="lock"');
expect(el.hasClass('md-locked-open')).toBe(false);
$rootScope.$apply('lock = true');
expect(el.hasClass('md-locked-open')).toBe(true);
$rootScope.$apply('show = true');
$animate.triggerCallbacks();
expect(el.parent().find('md-backdrop').hasClass('md-locked-open')).toBe(true);
}));
it('should expose $mdMedia service as $media local in is-locked-open attribute', function() {
var mdMediaSpy = jasmine.createSpy('$mdMedia');
module(function($provide) {
$provide.value('$mdMedia', mdMediaSpy);
});
inject(function($rootScope, $animate, $document, $mdMedia) {
var el = setup('md-is-locked-open="$media(123)"');
expect($mdMedia).toHaveBeenCalledWith(123);
});
});
});
describe('controller', function() {
it('should create controller', function() {
var el = setup('');
var controller = el.controller('mdSidenav');
expect(controller).not.toBe(undefined);
});
it('should open and close and toggle', inject(function($timeout) {
var el = setup('');
var scope = el.isolateScope();
var controller = el.controller('mdSidenav');
// Should start closed
expect(el.hasClass('md-closed')).toBe(true);
controller.open();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(false);
controller.close();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(true);
controller.toggle();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(false);
}));
});
describe("controller Promise API", function() {
var $animate, $rootScope;
function flush() {
if ( !$rootScope.$$phase) {
$rootScope.$apply();
}
$animate.triggerCallbacks();
}
beforeEach( inject(function(_$animate_,_$rootScope_,_$timeout_) {
$animate = _$animate_;
$rootScope = _$rootScope_;
$timeout = _$timeout_;
}));
it('should open(), close(), and toggle() with promises', function () {
var el = setup('');
var scope = el.isolateScope();
var controller = el.controller('mdSidenav');
var openDone = 0, closeDone = 0, toggleDone = 0;
var onOpen = function() { openDone++; };
var onClose = function() { closeDone++; };
var onToggle = function() { toggleDone++; };
controller
.open()
.then(onOpen)
.then(controller.close)
.then(onClose);
flush();
expect(openDone).toBe(1);
flush();
expect(closeDone).toBe(1);
controller
.close()
.then(onClose);
flush();
expect(closeDone).toBe(2);
expect(scope.isOpen).toBe(false);
controller
.toggle()
.then(onToggle);
flush();
expect(toggleDone).toBe(1);
expect(scope.isOpen).toBe(true);
});
it('should open() to work multiple times before close()', function () {
var el = setup('');
var controller = el.controller('mdSidenav');
var openDone = 0, closeDone = 0;
var onOpen = function() { openDone++; };
var onClose = function() { closeDone++; };
controller
.open()
.then(onOpen)
.then(controller.open)
.then(onOpen);
flush();
expect(openDone).toBe(2);
expect(closeDone).toBe(0);
expect(el.hasClass('md-closed')).toBe(false);
controller
.close()
.then(onClose);
flush();
expect(openDone).toBe(2);
expect(closeDone).toBe(1);
expect(el.hasClass('md-closed')).toBe(true);
});
});
describe('$mdSidenav Service', function() {
it('should grab instance', inject(function($mdSidenav) {
var el = setup('md-component-id="left"');
var scope = el.isolateScope();
var instance = $mdSidenav('left');
expect(instance).toBeTruthy();
instance.open();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(false);
instance.close();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(true);
instance.toggle();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(false);
instance.toggle();
scope.$apply();
expect(el.hasClass('md-closed')).toBe(true);
}));
});
});

View File

@ -1,55 +0,0 @@
<div ng-controller="AppCtrl">
<md-content class="md-padding">
<h3>
RGB <span ng-attr-style="border: 1px solid #333; background: rgb({{color.red}},{{color.green}},{{color.blue}})">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
</h3>
<div layout>
<div flex="10" layout layout-align="center center">
<span>R</span>
</div>
<md-slider flex min="0" max="255" ng-model="color.red" aria-label="red" id="red-slider" class="md-warn">
</md-slider>
<div flex="20" layout layout-align="center center">
<input type="number" ng-model="color.red" aria-label="red" aria-controls="red-slider">
</div>
</div>
<div layout>
<div flex="10" layout layout-align="center center">
<span>G</span>
</div>
<md-slider flex ng-model="color.green" min="0" max="255" aria-label="green" id="green-slider" class="md-accent">
</md-slider>
<div flex="20" layout layout-align="center center">
<input type="number" ng-model="color.green" aria-label="green" aria-controls="green-slider">
</div>
</div>
<div layout>
<div flex="10" layout layout-align="center center">
<span>B</span>
</div>
<md-slider flex ng-model="color.blue" min="0" max="255" aria-label="blue" id="blue-slider">
</md-slider>
<div flex="20" layout layout-align="center center">
<input type="number" ng-model="color.blue" aria-label="blue" aria-controls="blue-slider">
</div>
</div>
<h3>Rating: {{rating}}/5</h3>
<md-slider md-discrete ng-model="rating" step="1" min="1" max="5" aria-label="rating">
</md-slider>
<h3>Disabled</h3>
<md-slider ng-model="disabled1" ng-disabled="true" aria-label="Disabled 1"></md-slider>
<md-slider ng-model="disabled2" ng-disabled="true" aria-label="Disabled 2"></md-slider>
<h3>Disabled, Discrete</h3>
<md-slider ng-model="disabled1" ng-disabled="true" step="3" md-discrete min="0" max="10" aria-label="Disabled discrete 1"></md-slider>
<md-slider ng-model="disabled2" ng-disabled="true" step="10" md-discrete aria-label="Disabled discrete 2"></md-slider>
</md-content>
</div>

View File

@ -1,16 +0,0 @@
angular.module('sliderDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.color = {
red: Math.floor(Math.random() * 255),
green: Math.floor(Math.random() * 255),
blue: Math.floor(Math.random() * 255)
};
$scope.rating = 3;
$scope.disabled1 = 0;
$scope.disabled2 = 70;
});

View File

@ -1,4 +0,0 @@
input[type="number"] {
text-align: center;
}

View File

@ -1,403 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.slider
*/
angular.module('material.components.slider', [
'material.core'
])
.directive('mdSlider', SliderDirective);
/**
* @ngdoc directive
* @name mdSlider
* @module material.components.slider
* @restrict E
* @description
* The `<md-slider>` component allows the user to choose from a range of
* values.
*
* It has two modes: 'normal' mode, where the user slides between a wide range
* of values, and 'discrete' mode, where the user slides between only a few
* select values.
*
* To enable discrete mode, add the `md-discrete` attribute to a slider,
* and use the `step` attribute to change the distance between
* values the user is allowed to pick.
*
* @usage
* <h4>Normal Mode</h4>
* <hljs lang="html">
* <md-slider ng-model="myValue" min="5" max="500">
* </md-slider>
* </hljs>
* <h4>Discrete Mode</h4>
* <hljs lang="html">
* <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
* </md-slider>
* </hljs>
*
* @param {boolean=} md-discrete Whether to enable discrete mode.
* @param {number=} step The distance between values the user is allowed to pick. Default 1.
* @param {number=} min The minimum value the user is allowed to pick. Default 0.
* @param {number=} max The maximum value the user is allowed to pick. Default 100.
*/
function SliderDirective($mdTheming) {
return {
scope: {},
require: ['?ngModel', 'mdSlider'],
controller: SliderController,
template:
'<div class="md-track-container">' +
'<div class="md-track"></div>' +
'<div class="md-track md-track-fill"></div>' +
'<div class="md-track-ticks"></div>' +
'</div>' +
'<div class="md-thumb-container">' +
'<div class="md-thumb"></div>' +
'<div class="md-focus-thumb"></div>' +
'<div class="md-focus-ring"></div>' +
'<div class="md-sign">' +
'<span class="md-thumb-text"></span>' +
'</div>' +
'<div class="md-disabled-thumb"></div>' +
'</div>',
link: postLink
};
function postLink(scope, element, attr, ctrls) {
$mdTheming(element);
var ngModelCtrl = ctrls[0] || {
// Mock ngModelController if it doesn't exist to give us
// the minimum functionality needed
$setViewValue: function(val) {
this.$viewValue = val;
this.$viewChangeListeners.forEach(function(cb) { cb(); });
},
$parsers: [],
$formatters: [],
$viewChangeListeners: []
};
var sliderCtrl = ctrls[1];
sliderCtrl.init(ngModelCtrl);
}
}
/**
* We use a controller for all the logic so that we can expose a few
* things to unit tests
*/
function SliderController($scope, $element, $attrs, $$rAF, $window, $mdAria, $mdUtil, $mdConstant) {
this.init = function init(ngModelCtrl) {
var thumb = angular.element($element[0].querySelector('.md-thumb'));
var thumbText = angular.element($element[0].querySelector('.md-thumb-text'));
var thumbContainer = thumb.parent();
var trackContainer = angular.element($element[0].querySelector('.md-track-container'));
var activeTrack = angular.element($element[0].querySelector('.md-track-fill'));
var tickContainer = angular.element($element[0].querySelector('.md-track-ticks'));
var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
// Default values, overridable by $attrss
$attrs.min ? $attrs.$observe('min', updateMin) : updateMin(0);
$attrs.max ? $attrs.$observe('max', updateMax) : updateMax(100);
$attrs.step ? $attrs.$observe('step', updateStep) : updateStep(1);
// We have to manually stop the $watch on ngDisabled because it exists
// on the parent $scope, and won't be automatically destroyed when
// the component is destroyed.
var stopDisabledWatch = angular.noop;
if ($attrs.ngDisabled) {
stopDisabledWatch = $scope.$parent.$watch($attrs.ngDisabled, updateAriaDisabled);
}
$mdAria.expect($element, 'aria-label');
$element.attr('tabIndex', 0);
$element.attr('role', 'slider');
$element.on('keydown', keydownListener);
var hammertime = new Hammer($element[0], {
recognizers: [
[Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
]
});
hammertime.on('hammer.input', onInput);
hammertime.on('panstart', onPanStart);
hammertime.on('pan', onPan);
hammertime.on('panend', onPanEnd);
// On resize, recalculate the slider's dimensions and re-render
function updateAll() {
refreshSliderDimensions();
ngModelRender();
redrawTicks();
}
setTimeout(updateAll);
var debouncedUpdateAll = $$rAF.debounce(updateAll);
angular.element($window).on('resize', debouncedUpdateAll);
$scope.$on('$destroy', function() {
angular.element($window).off('resize', debouncedUpdateAll);
hammertime.destroy();
stopDisabledWatch();
});
ngModelCtrl.$render = ngModelRender;
ngModelCtrl.$viewChangeListeners.push(ngModelRender);
ngModelCtrl.$formatters.push(minMaxValidator);
ngModelCtrl.$formatters.push(stepValidator);
/**
* Attributes
*/
var min;
var max;
var step;
function updateMin(value) {
min = parseFloat(value);
$element.attr('aria-valuemin', value);
updateAll();
}
function updateMax(value) {
max = parseFloat(value);
$element.attr('aria-valuemax', value);
updateAll();
}
function updateStep(value) {
step = parseFloat(value);
redrawTicks();
}
function updateAriaDisabled(isDisabled) {
$element.attr('aria-disabled', !!isDisabled);
}
// Draw the ticks with canvas.
// The alternative to drawing ticks with canvas is to draw one $element for each tick,
// which could quickly become a performance bottleneck.
var tickCanvas, tickCtx;
function redrawTicks() {
if (!angular.isDefined($attrs.mdDiscrete)) return;
var numSteps = Math.floor( (max - min) / step );
if (!tickCanvas) {
var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
tickCanvas = angular.element('<canvas style="position:absolute;">');
tickCtx = tickCanvas[0].getContext('2d');
tickCtx.fillStyle = trackTicksStyle.backgroundColor || 'black';
tickContainer.append(tickCanvas);
}
var dimensions = getSliderDimensions();
tickCanvas[0].width = dimensions.width;
tickCanvas[0].height = dimensions.height;
var distance;
for (var i = 0; i <= numSteps; i++) {
distance = Math.floor(dimensions.width * (i / numSteps));
tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
}
}
/**
* Refreshing Dimensions
*/
var sliderDimensions = {};
refreshSliderDimensions();
function refreshSliderDimensions() {
sliderDimensions = trackContainer[0].getBoundingClientRect();
}
function getSliderDimensions() {
throttledRefreshDimensions();
return sliderDimensions;
}
/**
* left/right arrow listener
*/
function keydownListener(ev) {
if($element[0].hasAttribute('disabled')) {
return;
}
var changeAmount;
if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
changeAmount = -step;
} else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
changeAmount = step;
}
if (changeAmount) {
if (ev.metaKey || ev.ctrlKey || ev.altKey) {
changeAmount *= 4;
}
ev.preventDefault();
ev.stopPropagation();
$scope.$evalAsync(function() {
setModelValue(ngModelCtrl.$viewValue + changeAmount);
});
}
}
/**
* ngModel setters and validators
*/
function setModelValue(value) {
ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
}
function ngModelRender() {
if (isNaN(ngModelCtrl.$viewValue)) {
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
}
var percent = (ngModelCtrl.$viewValue - min) / (max - min);
$scope.modelValue = ngModelCtrl.$viewValue;
$element.attr('aria-valuenow', ngModelCtrl.$viewValue);
setSliderPercent(percent);
thumbText.text( ngModelCtrl.$viewValue );
}
function minMaxValidator(value) {
if (angular.isNumber(value)) {
return Math.max(min, Math.min(max, value));
}
}
function stepValidator(value) {
if (angular.isNumber(value)) {
return Math.round(value / step) * step;
}
}
/**
* @param percent 0-1
*/
function setSliderPercent(percent) {
activeTrack.css('width', (percent * 100) + '%');
thumbContainer.css(
$mdConstant.CSS.TRANSFORM,
'translate3d(' + getSliderDimensions().width * percent + 'px,0,0)'
);
$element.toggleClass('md-min', percent === 0);
}
/**
* Slide listeners
*/
var isSliding = false;
var isDiscrete = angular.isDefined($attrs.mdDiscrete);
function onInput(ev) {
if (!isSliding && ev.eventType === Hammer.INPUT_START &&
!$element[0].hasAttribute('disabled')) {
isSliding = true;
$element.addClass('active');
$element[0].focus();
refreshSliderDimensions();
onPan(ev);
ev.srcEvent.stopPropagation();
} else if (isSliding && ev.eventType === Hammer.INPUT_END) {
if ( isSliding && isDiscrete ) onPanEnd(ev);
isSliding = false;
$element.removeClass('panning active');
}
}
function onPanStart() {
if (!isSliding) return;
$element.addClass('panning');
}
function onPan(ev) {
if (!isSliding) return;
// While panning discrete, update only the
// visual positioning but not the model value.
if ( isDiscrete ) adjustThumbPosition( ev.center.x );
else doSlide( ev.center.x );
ev.preventDefault();
ev.srcEvent.stopPropagation();
}
function onPanEnd(ev) {
if ( isDiscrete && !$element[0].hasAttribute('disabled') ) {
// Convert exact to closest discrete value.
// Slide animate the thumb... and then update the model value.
var exactVal = percentToValue( positionToPercent( ev.center.x ));
var closestVal = minMaxValidator( stepValidator(exactVal) );
setSliderPercent( valueToPercent(closestVal));
$$rAF(function(){
setModelValue( closestVal );
});
ev.preventDefault();
ev.srcEvent.stopPropagation();
}
}
/**
* Expose for testing
*/
this._onInput = onInput;
this._onPanStart = onPanStart;
this._onPan = onPan;
/**
* Slide the UI by changing the model value
* @param x
*/
function doSlide( x ) {
$scope.$evalAsync( function() {
setModelValue( percentToValue( positionToPercent(x) ));
});
}
/**
* Slide the UI without changing the model (while dragging/panning)
* @param x
*/
function adjustThumbPosition( x ) {
var exactVal = percentToValue( positionToPercent( x ));
var closestVal = minMaxValidator( stepValidator(exactVal) );
setSliderPercent( positionToPercent(x) );
thumbText.text( closestVal );
}
/**
* Convert horizontal position on slider to percentage value of offset from beginning...
* @param x
* @returns {number}
*/
function positionToPercent( x ) {
return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
}
/**
* Convert percentage offset on slide to equivalent model value
* @param percent
* @returns {*}
*/
function percentToValue( percent ) {
return (min + percent * (max - min));
}
function valueToPercent( val ) {
return (val - min)/(max - min);
}
};
}
})();

View File

@ -1,205 +0,0 @@
describe('md-slider', function() {
function simulateEventAt( centerX, eventType ) {
return {
eventType: eventType,
center: { x: centerX },
preventDefault: angular.noop,
srcEvent : {
stopPropagation : angular.noop
}
};
}
beforeEach(TestUtil.mockRaf);
beforeEach(module('ngAria'));
beforeEach(module('material.components.slider'));
it('should set model on press', inject(function($compile, $rootScope, $timeout) {
var slider = $compile('<md-slider ng-model="value" min="0" max="100">')($rootScope);
$rootScope.$apply('value = 50');
var sliderCtrl = slider.controller('mdSlider');
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
width: 100,
left: 0,
right: 0
});
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
$timeout.flush();
expect($rootScope.value).toBe(30);
//When going past max, it should clamp to max
sliderCtrl._onPan( simulateEventAt( 500 ));
$timeout.flush();
expect($rootScope.value).toBe(100);
sliderCtrl._onPan( simulateEventAt( 50 ));
$timeout.flush();
expect($rootScope.value).toBe(50);
}));
it('should set model on drag', inject(function($compile, $rootScope, $timeout) {
var slider = $compile('<md-slider ng-model="value" min="0" max="100" md-discrete>')($rootScope);
$rootScope.$apply('value = 50');
var sliderCtrl = slider.controller('mdSlider');
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
width: 100,
left: 0,
right: 0
});
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
$timeout.flush();
sliderCtrl._onPan( simulateEventAt( 80 ));
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('80');
}));
it('should increment model on right arrow', inject(function($compile, $rootScope, $timeout, $mdConstant) {
var slider = $compile(
'<md-slider min="100" max="104" step="2" ng-model="model">'
)($rootScope);
$rootScope.$apply('model = 100');
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(102);
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(104);
// Stays at max
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(104);
}));
it('should decrement model on left arrow', inject(function($compile, $rootScope, $timeout, $mdConstant) {
var slider = $compile(
'<md-slider min="100" max="104" step="2" ng-model="model">'
)($rootScope);
$rootScope.$apply('model = 104');
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(102);
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(100);
// Stays at min
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
});
$timeout.flush();
expect($rootScope.model).toBe(100);
}));
it('should update the thumb text', inject(function($compile, $rootScope, $timeout, $mdConstant) {
var slider = $compile('<md-slider ng-model="value" min="0" max="100">')($rootScope);
var sliderCtrl = slider.controller('mdSlider');
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
width: 100,
left: 0,
right: 0
});
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
$timeout.flush();
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('30');
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
});
$timeout.flush();
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('29');
sliderCtrl._onPan( simulateEventAt( 30 ));
$timeout.flush();
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('30');
}));
it('should update the thumb text with the model value when using ng-change', inject(function($compile, $rootScope, $timeout) {
$scope = $rootScope.$new();
$scope.stayAt50 = function () {
$scope.value = 50;
};
var slider = $compile('<md-slider ng-model="value" min="0" max="100" ng-change="stayAt50()">')($scope);
var sliderCtrl = slider.controller('mdSlider');
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
width: 100,
left: 0,
right: 0
});
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
$timeout.flush();
expect($scope.value).toBe(50);
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('50');
}));
it('should call $log.warn if aria-label isnt provided', inject(function($compile, $rootScope, $timeout, $log) {
spyOn($log, "warn");
var element = $compile(
'<md-slider min="100" max="104" step="2" ng-model="model"></md-slider>'
)($rootScope);
expect($log.warn).toHaveBeenCalled();
}));
it('should not call $log.warn if aria-label is provided', inject(function($compile, $rootScope, $timeout, $log) {
spyOn($log, "warn");
var element = $compile(
'<md-slider aria-label="banana" min="100" max="104" step="2" ng-model="model"></md-slider>'
)($rootScope);
expect($log.warn).not.toHaveBeenCalled();
}));
it('should add aria attributes', inject(function($compile, $rootScope, $timeout, $mdConstant){
var slider = $compile(
'<md-slider min="100" max="104" step="2" ng-model="model">'
)($rootScope);
$rootScope.$apply('model = 102');
expect(slider.attr('role')).toEqual('slider');
expect(slider.attr('aria-valuemin')).toEqual('100');
expect(slider.attr('aria-valuemax')).toEqual('104');
expect(slider.attr('aria-valuenow')).toEqual('102');
slider.triggerHandler({
type: 'keydown',
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
});
$timeout.flush();
expect(slider.attr('aria-valuenow')).toEqual('100');
}));
});

View File

@ -1,302 +0,0 @@
(function() {
'use strict';
/*
* @ngdoc module
* @name material.components.sticky
* @description
*
* Sticky effects for md
*/
angular.module('material.components.sticky', [
'material.core',
'material.components.content'
])
.factory('$mdSticky', MdSticky);
/*
* @ngdoc service
* @name $mdSticky
* @module material.components.sticky
*
* @description
* The `$mdSticky`service provides a mixin to make elements sticky.
*
* @returns A `$mdSticky` function that takes three arguments:
* - `scope`
* - `element`: The element that will be 'sticky'
* - `elementClone`: A clone of the element, that will be shown
* when the user starts scrolling past the original element.
* If not provided, it will use the result of `element.clone()`.
*/
function MdSticky($document, $mdConstant, $compile, $$rAF, $mdUtil) {
var browserStickySupport = checkStickySupport();
/**
* Registers an element as sticky, used internally by directives to register themselves
*/
return function registerStickyElement(scope, element, stickyClone) {
var contentCtrl = element.controller('mdContent');
if (!contentCtrl) return;
if (browserStickySupport) {
element.css({
position: browserStickySupport,
top: 0,
'z-index': 2
});
} else {
var $$sticky = contentCtrl.$element.data('$$sticky');
if (!$$sticky) {
$$sticky = setupSticky(contentCtrl);
contentCtrl.$element.data('$$sticky', $$sticky);
}
var deregister = $$sticky.add(element, stickyClone || element.clone());
scope.$on('$destroy', deregister);
}
};
function setupSticky(contentCtrl) {
var contentEl = contentCtrl.$element;
// Refresh elements is very expensive, so we use the debounced
// version when possible.
var debouncedRefreshElements = $$rAF.debounce(refreshElements);
// setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
// more reliable than `scroll` on android.
setupAugmentedScrollEvents(contentEl);
contentEl.on('$scrollstart', debouncedRefreshElements);
contentEl.on('$scroll', onScroll);
var self;
return self = {
prev: null,
current: null, //the currently stickied item
next: null,
items: [],
add: add,
refreshElements: refreshElements
};
/***************
* Public
***************/
// Add an element and its sticky clone to this content's sticky collection
function add(element, stickyClone) {
stickyClone.addClass('md-sticky-clone');
var item = {
element: element,
clone: stickyClone
};
self.items.push(item);
contentEl.parent().prepend(item.clone);
debouncedRefreshElements();
return function remove() {
self.items.forEach(function(item, index) {
if (item.element[0] === element[0]) {
self.items.splice(index, 1);
item.clone.remove();
}
});
debouncedRefreshElements();
};
}
function refreshElements() {
// Sort our collection of elements by their current position in the DOM.
// We need to do this because our elements' order of being added may not
// be the same as their order of display.
self.items.forEach(refreshPosition);
self.items = self.items.sort(function(a, b) {
return a.top < b.top ? -1 : 1;
});
// Find which item in the list should be active,
// based upon the content's current scroll position
var item;
var currentScrollTop = contentEl.prop('scrollTop');
for (var i = self.items.length - 1; i >= 0; i--) {
if (currentScrollTop > self.items[i].top) {
item = self.items[i];
break;
}
}
setCurrentItem(item);
}
/***************
* Private
***************/
// Find the `top` of an item relative to the content element,
// and also the height.
function refreshPosition(item) {
// Find the top of an item by adding to the offsetHeight until we reach the
// content element.
var current = item.element[0];
item.top = 0;
item.left = 0;
while (current && current !== contentEl[0]) {
item.top += current.offsetTop;
item.left += current.offsetLeft;
current = current.offsetParent;
}
item.height = item.element.prop('offsetHeight');
item.clone.css('margin-left', item.left + 'px');
}
// As we scroll, push in and select the correct sticky element.
function onScroll() {
var scrollTop = contentEl.prop('scrollTop');
var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
onScroll.prevScrollTop = scrollTop;
// At the top?
if (scrollTop === 0) {
setCurrentItem(null);
// Going to next item?
} else if (isScrollingDown && self.next) {
if (self.next.top - scrollTop <= 0) {
// Sticky the next item if we've scrolled past its position.
setCurrentItem(self.next);
} else if (self.current) {
// Push the current item up when we're almost at the next item.
if (self.next.top - scrollTop <= self.next.height) {
translate(self.current, self.next.top - self.next.height - scrollTop);
} else {
translate(self.current, null);
}
}
// Scrolling up with a current sticky item?
} else if (!isScrollingDown && self.current) {
if (scrollTop < self.current.top) {
// Sticky the previous item if we've scrolled up past
// the original position of the currently stickied item.
setCurrentItem(self.prev);
}
// Scrolling up, and just bumping into the item above (just set to current)?
// If we have a next item bumping into the current item, translate
// the current item up from the top as it scrolls into view.
if (self.current && self.next) {
if (scrollTop >= self.next.top - self.current.height) {
translate(self.current, self.next.top - scrollTop - self.current.height);
} else {
translate(self.current, null);
}
}
}
}
function setCurrentItem(item) {
if (self.current === item) return;
// Deactivate currently active item
if (self.current) {
translate(self.current, null);
setStickyState(self.current, null);
}
// Activate new item if given
if (item) {
setStickyState(item, 'active');
}
self.current = item;
var index = self.items.indexOf(item);
// If index === -1, index + 1 = 0. It works out.
self.next = self.items[index + 1];
self.prev = self.items[index - 1];
setStickyState(self.next, 'next');
setStickyState(self.prev, 'prev');
}
function setStickyState(item, state) {
if (!item || item.state === state) return;
if (item.state) {
item.clone.attr('sticky-prev-state', item.state);
item.element.attr('sticky-prev-state', item.state);
}
item.clone.attr('sticky-state', state);
item.element.attr('sticky-state', state);
item.state = state;
}
function translate(item, amount) {
if (!item) return;
if (amount === null || amount === undefined) {
if (item.translateY) {
item.translateY = null;
item.clone.css($mdConstant.CSS.TRANSFORM, '');
}
} else {
item.translateY = amount;
item.clone.css(
$mdConstant.CSS.TRANSFORM,
'translate3d(' + item.left + 'px,' + amount + 'px,0)'
);
}
}
}
// Function to check for browser sticky support
function checkStickySupport($el) {
var stickyProp;
var testEl = angular.element('<div>');
$document[0].body.appendChild(testEl[0]);
var stickyProps = ['sticky', '-webkit-sticky'];
for (var i = 0; i < stickyProps.length; ++i) {
testEl.css({position: stickyProps[i], top: 0, 'z-index': 2});
if (testEl.css('position') == stickyProps[i]) {
stickyProp = stickyProps[i];
break;
}
}
testEl.remove();
return stickyProp;
}
// Android 4.4 don't accurately give scroll events.
// To fix this problem, we setup a fake scroll event. We say:
// > If a scroll or touchmove event has happened in the last DELAY milliseconds,
// then send a `$scroll` event every animationFrame.
// Additionally, we add $scrollstart and $scrollend events.
function setupAugmentedScrollEvents(element) {
var SCROLL_END_DELAY = 200;
var isScrolling;
var lastScrollTime;
element.on('scroll touchmove', function() {
if (!isScrolling) {
isScrolling = true;
$$rAF(loopScrollEvent);
element.triggerHandler('$scrollstart');
}
element.triggerHandler('$scroll');
lastScrollTime = +$mdUtil.now();
});
function loopScrollEvent() {
if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
isScrolling = false;
element.triggerHandler('$scrollend');
} else {
element.triggerHandler('$scroll');
$$rAF(loopScrollEvent);
}
}
}
}
})();

View File

@ -1,154 +0,0 @@
/*
* TODO: adjust to work properly with refactors of original code
*/
/*
describe('$mdStickySpec', function() {
var $document, $compile, $rootScope, $mdSticky;
beforeEach(module('material.components.sticky', function($provide) {
var $$rAF = function(fn) { fn(); };
$$rAF.debounce = function(fn) { return function() { fn(); }; };
$provide.value('$$rAF', $$rAF);
}));
beforeEach(inject(function(_$document_, _$compile_, _$rootScope_, _$mdSticky_) {
$document = _$document_;
$rootScope = _$rootScope_;
$compile = _$compile_;
$mdSticky = _$mdSticky_;
}));
var $container, $firstSticky, $secondSticky, $sticky;
function setup(opts) {
opts = opts || {};
var TEST_HTML = '<md-content><h2>First sticky</h2><h2>Second sticky</h2></md-content>';
var scope = $rootScope.$new();
$container = $compile(TEST_HTML)(scope);
$firstSticky = $container.children().eq(0);
$secondSticky = $container.children().eq(1);
// Wire up our special $container instance;
$firstSticky.controller('mdContent').$element = $container;
$document.find('body').html('');
$document.find('body').append($container);
if(!opts.skipFirst) {
$mdSticky($rootScope.$new(), $firstSticky);
}
if(!opts.skipSecond) {
$mdSticky($rootScope.$new(), $secondSticky);
}
// Overwrite the scrollTop property to return the opts.containerScroll
if(opts.containerScroll) {
var originalProp = $container.prop;
$container.prop = function(prop) {
if(prop == 'scrollTop') {
return opts.containerScroll;
} else {
originalProp.call($container, prop);
}
};
}
// Overwrite children() to provide mock rect positions
if(opts.firstActual) {
$firstSticky[0].getBoundingClientRect = function() { return opts.firstActual; };
}
if(opts.secondActual) {
$secondSticky[0].getBoundingClientRect = function() { return opts.secondActual; };
}
if(opts.firstTarget) {
var $firstOuter = $firstSticky.parent();
$firstOuter[0].getBoundingClientRect = function() { return opts.firstTarget; };
}
if(opts.secondTarget) {
var $secondOuter = $secondSticky.parent();
$secondOuter[0].getBoundingClientRect = function() { return opts.secondTarget; };
}
$sticky = $container.data('$sticky');
if(opts.lastScroll) { $sticky.lastScroll = opts.lastScroll; }
scope.$digest();
}
it('throws an error if uses outside of md-content', inject(function($mdSticky, $compile, $rootScope) {
var html = '<h2>Hello world!</h2>';
function useWithoutMdContent() {
$mdSticky($rootScope.$new(), angular.element(html));
}
expect(useWithoutMdContent).toThrow('$mdSticky used outside of md-content');
}));
it('adds class md-sticky-active when an element would scroll off screen', function() {
var firstActual = { top: -10, bottom: 9, height: 19 };
setup({containerScroll: 10, firstActual: firstActual, skipSecond: true});
$sticky.check();
expect($firstSticky.hasClass('md-sticky-active')).toBe(true);
});
it('removes class md-sticky-active when an element is no longer sticky', function() {
var firstTarget = { top: 1, bottom: 10, height: 9 };
setup({
containerScroll: 10,
lastScroll: 11
});
$firstSticky.addClass('md-sticky-active');
$sticky.check();
expect($firstSticky.hasClass('md-sticky-active')).toBe(false);
});
it('pushes the active element when the next sticky element touches it', function() {
var firstTarget = { top: -10, bottom: 9, height: 19 };
var firstActual = { top: 0, bottom: 19, height: 19 };
var secondActual = { top: 18, bottom: 37, height: 19 };
setup({
containerScroll: 19,
firstActual: firstActual,
firstTarget: firstTarget,
secondActual: secondActual
});
$firstSticky.attr('md-sticky-active', true);
$sticky.check();
expect($firstSticky.data('translatedHeight')).toBe(-1);
});
it('increments the active element when it is pushed off screen', function() {
var firstActual = { top: -9, bottom: 0, height: 10 };
setup({
containerScroll: 10,
firstActual: firstActual
});
$firstSticky.addClass('md-sticky-active');
$sticky.check();
expect($firstSticky.hasClass('md-sticky-active')).toBe(false);
expect($sticky.targetIndex).toBe(1);
});
it('pulls the previous element when the sticky element losens', function() {
var firstActual = { top: -10, bottom: -1, height: 9 };
var firstTarget = { top: -50, bottom: -41, height: 9 };
var secondActual = { top: 0, bottom: 9, height: 9 };
setup({
containerScroll: 30,
lastScroll: 31,
firstActual: firstActual,
firstTarget: firstTarget,
secondTarget: secondActual,
secondActual: secondActual
});
$sticky.targetIndex = 0;
$firstSticky.data('translatedHeight', -10);
$firstSticky.addClass('md-sticky-active');
$sticky.check();
expect($firstSticky.data('translatedHeight')).toBe(-9);
});
});
*/

View File

@ -1,81 +0,0 @@
<div ng-controller="SubheaderAppCtrl" layout="column" flex layout-fill>
<md-content style="height: 600px;">
<section>
<md-subheader class="md-primary">Unread Messages</md-subheader>
<md-list layout="column">
<md-item ng-repeat="message in messages">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{message.face}}" class="face" alt="{{message.who}}">
</div>
<div class="md-tile-content">
<h3>{{message.what}}</h3>
<h4>{{message.who}}</h4>
<p>
{{message.notes}}
</p>
</div>
</md-item-content>
</md-item>
</md-list>
</section>
<section>
<md-subheader class="md-warn">Late Messages</md-subheader>
<md-list layout="column">
<md-item ng-repeat="message in messages">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{message.face}}" class="face" alt="{{message.who}}">
</div>
<div class="md-tile-content">
<h3>{{message.what}}</h3>
<h4>{{message.who}}</h4>
<p>
{{message.notes}}
</p>
</div>
</md-item-content>
</md-item>
</md-list>
</section>
<section>
<md-subheader>Read Messages</md-subheader>
<md-list layout="column">
<md-item ng-repeat="message in messages">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{message.face}}" class="face" alt="{{message.who}}">
</div>
<div class="md-tile-content">
<h3>{{message.what}}</h3>
<h4>{{message.who}}</h4>
<p>
{{message.notes}}
</p>
</div>
</md-item-content>
</md-item>
</md-list>
</section>
<section>
<md-subheader class="md-accent">Archived messages</md-subheader>
<md-list layout="column">
<md-item ng-repeat="message in messages">
<md-item-content>
<div class="md-tile-left">
<img ng-src="{{message.face}}" class="face" alt="{{message.who}}">
</div>
<div class="md-tile-content">
<h3>{{message.what}}</h3>
<h4>{{message.who}}</h4>
<p>
{{message.notes}}
</p>
</div>
</md-item-content>
</md-item>
</md-list>
</section>
</md-content>
</div>

View File

@ -1,84 +0,0 @@
angular.module('subheaderBasicDemo', ['ngMaterial'])
.controller('SubheaderAppCtrl', 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"
},
{
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"
},
{
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"
},
];
});

View File

@ -1,8 +0,0 @@
.face {
border-radius: 30px;
border: 1px solid #ddd;
width: 48px;
margin: 16px;
}

View File

@ -1,79 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.subheader
* @description
* SubHeader module
*
* Subheaders are special list tiles that delineate distinct sections of a
* list or grid list and are typically related to the current filtering or
* sorting criteria. Subheader tiles are either displayed inline with tiles or
* can be associated with content, for example, in an adjacent column.
*
* Upon scrolling, subheaders remain pinned to the top of the screen and remain
* pinned until pushed on or off screen by the next subheader. @see [Material
* Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
*
* > To improve the visual grouping of content, use the system color for your subheaders.
*
*/
angular.module('material.components.subheader', [
'material.core',
'material.components.sticky'
])
.directive('mdSubheader', MdSubheaderDirective);
/**
* @ngdoc directive
* @name mdSubheader
* @module material.components.subheader
*
* @restrict E
*
* @description
* The `<md-subheader>` directive is a subheader for a section
*
* @usage
* <hljs lang="html">
* <md-subheader>Online Friends</md-subheader>
* </hljs>
*/
function MdSubheaderDirective($mdSticky, $compile, $mdTheming) {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<h2 class="md-subheader">' +
'<span class="md-subheader-content"></span>' +
'</h2>',
compile: function(element, attr, transclude) {
var outerHTML = element[0].outerHTML;
return function postLink(scope, element, attr) {
$mdTheming(element);
function getContent(el) {
return angular.element(el[0].querySelector('.md-subheader-content'));
}
// Transclude the user-given contents of the subheader
// the conventional way.
transclude(scope, function(clone) {
getContent(element).append(clone);
});
// Create another clone, that uses the outer and inner contents
// of the element, that will be 'stickied' as the user scrolls.
transclude(scope, function(clone) {
var stickyClone = $compile(angular.element(outerHTML))(scope);
$mdTheming(stickyClone);
getContent(stickyClone).append(clone);
$mdSticky(scope, element, stickyClone);
});
};
}
};
}
})();

View File

@ -1,28 +0,0 @@
describe('mdSubheader', function() {
var $mdStickyMock,
basicHtml = '<md-subheader>Hello world!</md-header>';
beforeEach(module('material.components.subheader', function($provide) {
$mdStickyMock = function() {
$mdStickyMock.args = Array.prototype.slice.call(arguments);
};
$provide.value('$mdSticky', $mdStickyMock);
}));
it('should preserve content', inject(function($compile, $rootScope) {
var $scope = $rootScope.$new();
$scope.to = 'world';
var $el = $compile('<div><md-subheader>Hello {{ to }}!</md-subheader></div>')($scope);
$scope.$digest();
var $subHeader = $el.children();
expect($subHeader.text()).toEqual('Hello world!');
}));
it('should implement $mdSticky', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
var $el = $compile(basicHtml)(scope);
expect($mdStickyMock.args[0]).toBe(scope);
}));
});

View File

@ -1,205 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.swipe
* @description Swipe module!
*/
angular.module('material.components.swipe',[])
.factory('$mdSwipe', MdSwipeFactory)
.directive('mdSwipeLeft', MdSwipeLeftDirective)
.directive('mdSwipeRight', MdSwipeRightDirective);
/*
* @ngdoc service
* @module material.components.swipe
* @name $mdSwipe
* @description
* This service allows directives to easily attach swipe and pan listeners to
* the specified element.
*/
function MdSwipeFactory() {
// match expected API functionality
var attachNoop = function(){ return angular.noop; };
/**
* SwipeService constructor pre-captures scope and customized event types
*
* @param scope
* @param eventTypes
* @returns {*}
* @constructor
*/
return function SwipeService(scope, eventTypes) {
if ( !eventTypes ) eventTypes = "swipeleft swiperight";
// publish configureFor() method for specific element instance
return function configureFor(element, onSwipeCallback, attachLater ) {
var hammertime = new Hammer(element[0], {
recognizers : addRecognizers([], eventTypes )
});
// Attach swipe listeners now
if ( !attachLater ) attachSwipe();
// auto-disconnect during destroy
scope.$on('$destroy', function() {
hammertime.destroy();
});
return attachSwipe;
// **********************
// Internal methods
// **********************
/**
* Delegate swipe event to callback function
* and ensure $digest is triggered.
*
* @param ev HammerEvent
*/
function swipeHandler(ev) {
// Prevent triggering parent hammer listeners
ev.srcEvent.stopPropagation();
if ( angular.isFunction(onSwipeCallback) ) {
scope.$apply(function() {
onSwipeCallback(ev);
});
}
}
/**
* Enable listeners and return detach() fn
*/
function attachSwipe() {
hammertime.on(eventTypes, swipeHandler );
return function detachSwipe() {
hammertime.off( eventTypes );
};
}
/**
* Add optional recognizers such as panleft, panright
*/
function addRecognizers(list, events) {
var hasPanning = (events.indexOf("pan") > -1);
var hasSwipe = (events.indexOf("swipe") > -1);
if (hasPanning) {
list.push([ Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL } ]);
}
if (hasSwipe) {
list.push([ Hammer.Swipe, { direction: Hammer.DIRECTION_HORIZONTAL } ]);
}
return list;
}
};
};
}
/**
* @ngdoc directive
* @module material.components.swipe
* @name mdSwipeLeft
*
* @restrict A
*
* @description
* The `<div md-swipe-left="expression">` directive identifies an element on which
* HammerJS horizontal swipe left and pan left support will be active. The swipe/pan action
* can result in custom activity trigger by evaluating `expression`.
*
* @param {boolean=} mdNoPan Use of attribute indicates flag to disable detection of `panleft` activity
*
* @usage
* <hljs lang="html">
*
* <div class="animate-switch-container"
* ng-switch on="data.selectedIndex"
* md-swipe-left="data.selectedIndex+=1;"
* md-swipe-right="data.selectedIndex-=1;" >
*
* </div>
* </hljs>
*
*/
function MdSwipeLeftDirective($parse, $mdSwipe) {
return {
restrict: 'A',
link : swipePostLink( $parse, $mdSwipe, "SwipeLeft" )
};
}
/**
* @ngdoc directive
* @module material.components.swipe
* @name mdSwipeRight
*
* @restrict A
*
* @description
* The `<div md-swipe-right="expression">` directive identifies functionality
* that attaches HammerJS horizontal swipe right and pan right support to an element. The swipe/pan action
* can result in activity trigger by evaluating `expression`
*
* @param {boolean=} mdNoPan Use of attribute indicates flag to disable detection of `panright` activity
*
* @usage
* <hljs lang="html">
*
* <div class="animate-switch-container"
* ng-switch on="data.selectedIndex"
* md-swipe-left="data.selectedIndex+=1;"
* md-swipe-right="data.selectedIndex-=1;" >
*
* </div>
* </hljs>
*
*/
function MdSwipeRightDirective($parse, $mdSwipe) {
return {
restrict: 'A',
link : swipePostLink( $parse, $mdSwipe, "SwipeRight" )
};
}
/**
* Factory to build PostLink function specific to Swipe or Pan direction
*
* @param $parse
* @param $mdSwipe
* @param name
* @returns {Function}
*/
function swipePostLink($parse, $mdSwipe, name ) {
return function(scope, element, attrs) {
var direction = name.toLowerCase();
var directiveName= "md" + name;
var parentGetter = $parse(attrs[directiveName]) || angular.noop;
var configureSwipe = $mdSwipe(scope, direction);
var requestSwipe = function(locals) {
// build function to request scope-specific swipe response
parentGetter(scope, locals);
};
configureSwipe( element, function onHandleSwipe(ev) {
if ( ev.type == direction ) {
requestSwipe();
}
});
};
}
})();

View File

@ -1,26 +0,0 @@
<div class="inset" ng-controller="SwitchDemoCtrl">
<md-switch ng-model="data.cb1" aria-label="Switch 1">
Switch 1: {{ data.cb1 }}
</md-switch>
<md-switch ng-model="data.cb2" aria-label="Switch 2" ng-true-value="'yup'" ng-false-value="'nope'" class="md-warn">
Switch 2 (md-warn): {{ data.cb2 }}
</md-switch>
<md-switch ng-disabled="true" aria-label="Disabled switch">
Switch (Disabled)
</md-switch>
<md-switch ng-disabled="true" aria-label="Disabled active switch" ng-model="data.cb4">
Switch (Disabled, Active)
</md-switch>
<md-switch class="md-primary" md-no-ink aria-label="Switch No Ink">
Switch (md-primary): No Ink
</md-switch>
<md-switch ng-model="data.cb5" aria-label="Switch 5" ng-change="onChange(data.cb5)">
Switch 5 message: {{ message }}
</md-switch>
</div>

View File

@ -1,11 +0,0 @@
angular.module('switchDemo1', ['ngMaterial'])
.controller('SwitchDemoCtrl', function($scope) {
$scope.data = {
cb1: true,
cb4: true
};
$scope.onChange = function(cbState){
$scope.message = "The switch is now: " + cbState;
};
});

View File

@ -1,4 +0,0 @@
.inset {
padding-left: 25px;
padding-top:25px;
}

View File

@ -1,137 +0,0 @@
(function() {
'use strict';
/**
* @private
* @ngdoc module
* @name material.components.switch
*/
angular.module('material.components.switch', [
'material.core',
'material.components.checkbox'
])
.directive('mdSwitch', MdSwitch);
/**
* @private
* @ngdoc directive
* @module material.components.switch
* @name mdSwitch
* @restrict E
*
* The switch directive is used very much 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 Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
*
* @usage
* <hljs lang="html">
* <md-switch ng-model="isActive" aria-label="Finished?">
* Finished ?
* </md-switch>
*
* <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
* No Ink Effects
* </md-switch>
*
* <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
* Disabled
* </md-switch>
*
* </hljs>
*/
function MdSwitch(mdCheckboxDirective, $mdTheming, $mdUtil, $document, $mdConstant, $parse, $$rAF) {
var checkboxDirective = mdCheckboxDirective[0];
return {
restrict: 'E',
transclude: true,
template:
'<div class="md-container">' +
'<div class="md-bar"></div>' +
'<div class="md-thumb-container">' +
'<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
'</div>'+
'</div>' +
'<div ng-transclude class="md-label">' +
'</div>',
require: '?ngModel',
compile: compile
};
function compile(element, attr) {
var checkboxLink = checkboxDirective.compile(element, attr);
// no transition on initial load
element.addClass('md-dragging');
return function (scope, element, attr, ngModel) {
ngModel = ngModel || $mdUtil.fakeNgModel();
var disabledGetter = $parse(attr.ngDisabled);
var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
var switchContainer = angular.element(element[0].querySelector('.md-container'));
// no transition on initial load
$$rAF(function() {
element.removeClass('md-dragging');
});
// Tell the checkbox we don't want a click listener.
// Our drag listener tells us everything, using more granular events.
attr.mdNoClick = true;
checkboxLink(scope, element, attr, ngModel);
$mdUtil.attachDragBehavior(scope, switchContainer);
// These events are triggered by setup drag
switchContainer.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);
function onDragStart(ev, drag) {
// Don't go if ng-disabled===true
if (disabledGetter(scope)) return ev.preventDefault();
drag.width = thumbContainer.prop('offsetWidth');
element.addClass('md-dragging');
}
function onDrag(ev, drag) {
var percent = drag.distance / drag.width;
//if checked, start from right. else, start from left
var translate = ngModel.$viewValue ? 1 - percent : -percent;
// Make sure the switch stays inside its bounds, 0-1%
translate = Math.max(0, Math.min(1, translate));
thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
drag.translate = translate;
}
function onDragEnd(ev, drag) {
if (disabledGetter(scope)) return false;
element.removeClass('md-dragging');
thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
// We changed if there is no distance (this is a click a click),
// or if the drag distance is >50% of the total.
var isChanged = Math.abs(drag.distance || 0) < 2 ||
(ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5);
if (isChanged) {
scope.$apply(function() {
ngModel.$setViewValue(!ngModel.$viewValue);
ngModel.$render();
});
}
}
};
}
}
})();

View File

@ -1,101 +0,0 @@
describe('<md-switch>', function() {
var CHECKED_CSS = 'md-checked';
beforeEach(TestUtil.mockRaf);
beforeEach(module('ngAria', 'material.components.switch'));
it('should set checked css class and aria-checked attributes', inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<md-switch ng-model="blue">' +
'</md-switch>' +
'<md-switch ng-model="green">' +
'</md-switch>' +
'</div>')($rootScope);
$rootScope.$apply(function(){
$rootScope.blue = false;
$rootScope.green = true;
});
var switches = angular.element(element[0].querySelectorAll('md-switch'));
expect(switches.eq(0).hasClass(CHECKED_CSS)).toEqual(false);
expect(switches.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
expect(switches.eq(0).attr('aria-checked')).toEqual('false');
expect(switches.eq(1).attr('aria-checked')).toEqual('true');
expect(switches.eq(0).attr('role')).toEqual('checkbox');
$rootScope.$apply(function(){
$rootScope.blue = true;
$rootScope.green = false;
});
expect(switches.eq(1).hasClass(CHECKED_CSS)).toEqual(false);
expect(switches.eq(0).hasClass(CHECKED_CSS)).toEqual(true);
expect(switches.eq(1).attr('aria-checked')).toEqual('false');
expect(switches.eq(0).attr('aria-checked')).toEqual('true');
expect(switches.eq(1).attr('role')).toEqual('checkbox');
}));
it('should change on panstart/panend if no movement happened', inject(function($compile, $rootScope) {
var element = $compile('<md-switch ng-model="banana"></md-switch>')($rootScope);
var switchContainer = angular.element(element[0].querySelector('.md-container'));
$rootScope.$apply('banana = false');
expect($rootScope.banana).toBe(false);
expect(element.hasClass(CHECKED_CSS)).toBe(false);
switchContainer.triggerHandler('$md.dragstart', {});
switchContainer.triggerHandler('$md.dragend', {distance: 1});
expect($rootScope.banana).toBe(true);
expect(element.hasClass(CHECKED_CSS)).toBe(true);
switchContainer.triggerHandler('$md.dragstart', {});
switchContainer.triggerHandler('$md.dragend', {distance: 5});
expect($rootScope.banana).toBe(true);
expect(element.hasClass(CHECKED_CSS)).toBe(true);
switchContainer.triggerHandler('$md.dragstart', {});
switchContainer.triggerHandler('$md.dragend', {distance: -1});
expect($rootScope.banana).toBe(false);
expect(element.hasClass(CHECKED_CSS)).toBe(false);
}));
it('should check on panend if translate > 50%', inject(function($compile, $rootScope) {
var element = $compile('<md-switch ng-model="banana"></md-switch>')($rootScope);
var switchContainer = angular.element(element[0].querySelector('.md-container'));
var drag;
drag = { distance: -55 };
switchContainer.triggerHandler('$md.dragstart', {});
drag.width = 100;
switchContainer.triggerHandler('$md.drag', drag);
switchContainer.triggerHandler('$md.dragend', drag);
expect($rootScope.banana).toBe(true);
expect(element.hasClass(CHECKED_CSS)).toBe(true);
drag = { distance: 45 };
switchContainer.triggerHandler('$md.dragstart', {});
drag.width = 100;
switchContainer.triggerHandler('$md.drag', drag);
switchContainer.triggerHandler('$md.dragend', drag);
expect($rootScope.banana).toBe(true);
expect(element.hasClass(CHECKED_CSS)).toBe(true);
drag = { distance: 85 };
switchContainer.triggerHandler('$md.dragstart', {});
drag.width = 100;
switchContainer.triggerHandler('$md.drag', drag);
switchContainer.triggerHandler('$md.dragend', drag);
expect($rootScope.banana).toBe(false);
expect(element.hasClass(CHECKED_CSS)).toBe(false);
}));
});

View File

@ -1,31 +0,0 @@
<div ng-controller="AppCtrl" class="sample">
<md-tabs md-selected="selectedIndex">
<md-tab ng-repeat="tab in tabs"
ng-disabled="tab.disabled"
label="{{tab.title}}">
<div class="demo-tab tab{{$index%4}}" layout="column" layout-fill layout-align="space-around center">
<div ng-bind="tab.content"></div>
<md-button class="md-warn" ng-click="removeTab( tab )">
Remove Tab
</md-button>
</div>
</md-tab>
</md-tabs>
<form ng-submit="addTab(tTitle,tContent)" flex>
<div layout="row" layout-sm="column" layout-margin layout-align="left center">
<md-text-float label="Active Index" ng-model="selectedIndex" disabled></md-text-float>
<md-text-float label="Active Title" ng-model="tabs[selectedIndex].title"></md-text-float>
</div>
<div layout="row" layout-sm="column" layout-margin layout-align="left center">
<span class="title">Add a new Tab:</span>
<md-text-float label="Label" ng-model="tTitle"></md-text-float>
<md-text-float label="Content" ng-model="tContent" ></md-text-float>
<md-button class="add-tab md-primary" type="submit" style="max-height: 40px" >Add Tab</md-button>
</div>
</form>
</div>

View File

@ -1,39 +0,0 @@
angular.module('tabsDemo2', ['ngMaterial'])
.controller('AppCtrl', function ($scope, $log) {
var tabs = [
{ title: 'One', content: "Tabs will become paginated if there isn't enough room for them."},
{ title: 'Two', content: "You can swipe left and right on a mobile device to change tabs."},
{ title: 'Three', content: "You can bind the selected tab via the selected attribute on the md-tabs element."},
{ title: 'Four', content: "If you set the selected tab binding to -1, it will leave no tab selected."},
{ title: 'Five', content: "If you remove a tab, it will try to select a new one."},
{ title: 'Six', content: "There's an ink bar that follows the selected tab, you can turn it off if you want."},
{ title: 'Seven', content: "If you set ng-disabled on a tab, it becomes unselectable. If the currently selected tab becomes disabled, it will try to select the next tab."},
{ title: 'Eight', content: "If you look at the source, you're using tabs to look at a demo for tabs. Recursion!"},
{ title: 'Nine', content: "If you set md-theme=\"green\" on the md-tabs element, you'll get green tabs."},
{ title: 'Ten', content: "If you're still reading this, you should just go check out the API docs for tabs!"}
];
$scope.tabs = tabs;
$scope.selectedIndex = 2;
$scope.$watch('selectedIndex', function(current, old){
if ( old && (old != current)) $log.debug('Goodbye ' + tabs[old].title + '!');
if ( current ) $log.debug('Hello ' + tabs[current].title + '!');
});
$scope.addTab = function (title, view) {
view = view || title + " Content View";
tabs.push({ title: title, content: view, disabled: false});
};
$scope.removeTab = function (tab) {
for (var j = 0; j < tabs.length; j++) {
if (tab.title == tabs[j].title) {
$scope.tabs.splice(j, 1);
break;
}
}
};
});

View File

@ -1,67 +0,0 @@
.remove-tab {
margin-bottom: 40px;
}
.md-button {
display:block;
}
.demo-tab {
height: 300px;
text-align: center;
}
.tab0, .tab1, .tab2, .tab3 {
background-color: #bbdefb;
}
.md-header {
background-color: #1976D2 !important;
}
md-tab {
color: #90caf9 !important;
}
md-tab.active,
md-tab:focus {
color: white !important;
}
md-tab[disabled] {
opacity: 0.5;
}
.md-header .md-ripple {
border-color: #FFFF8D !important;
}
md-tabs-ink-bar {
background-color: #FFFF8D !important;
}
.title {
padding-top: 8px;
padding-right: 8px;
text-align: left;
text-transform: uppercase;
color: #888;
margin-top: 24px;
}
[layout-align] > * {
margin-left: 8px;
}
form > [layout] > * {
margin-left: 8px;
}
.long > input {
width: 264px;
}
.md-button {
max-height: 30px;
}
.md-button.add-tab {
margin-top:20px;
max-height:30px !important;
}

View File

@ -1,58 +0,0 @@
<div ng-controller="AppCtrl">
<md-tabs class="md-accent" md-selected="data.selectedIndex">
<md-tab id="tab1" aria-controls="tab1-content">
Item One
</md-tab>
<md-tab id="tab2" aria-controls="tab2-content"
ng-disabled="data.secondLocked">
{{data.secondLabel}}
</md-tab>
<md-tab id="tab3" aria-controls="tab3-content">
Item Three
</md-tab>
</md-tabs>
<ng-switch on="data.selectedIndex" class="tabpanel-container">
<div role="tabpanel"
id="tab1-content"
aria-labelledby="tab1"
ng-switch-when="0"
md-swipe-left="next()"
md-swipe-right="previous()" >
View for Item #1<br/>
data.selectedIndex = 0
</div>
<div role="tabpanel"
id="tab2-content"
aria-labelledby="tab2"
ng-switch-when="1"
md-swipe-left="next()"
md-swipe-right="previous()" >
View for {{data.secondLabel}}<br/>
data.selectedIndex = 1
</div>
<div role="tabpanel"
id="tab3-content"
aria-labelledby="tab3"
ng-switch-when="2"
md-swipe-left="next()"
md-swipe-right="previous()" >
View for Item #3<br/>
data.selectedIndex = 2
</div>
</ng-switch>
<div class="after-tabs-area" layout="row" layout-sm="column" layout-margin layout-align="left center">
<md-text-float type="number" label="Selected Index" ng-model="data.selectedIndex"></md-text-float>
<div flex></div>
<span>Item Two: </span>
<md-checkbox ng-model="data.secondLocked" aria-label="Disabled">
Disabled
</md-checkbox>
</div>
</div>

View File

@ -1,19 +0,0 @@
angular.module('tabsDemo1', ['ngMaterial'] )
.controller('AppCtrl', function( $scope ) {
$scope.data = {
selectedIndex : 0,
secondLocked : true,
secondLabel : "Item Two"
};
$scope.next = function() {
$scope.data.selectedIndex = Math.min($scope.data.selectedIndex + 1, 2) ;
};
$scope.previous = function() {
$scope.data.selectedIndex = Math.max($scope.data.selectedIndex - 1, 0);
};
});

View File

@ -1,85 +0,0 @@
#tab1-content {
background-color: #3F51B5;
}
#tab2-content {
background-color: #673AB7;
}
#tab3-content {
background-color: #00796B;
}
/*
* Animation styles
*/
.tabpanel-container {
display: block;
position: relative;
background: white;
border: 0px solid black;
height: 300px;
overflow: hidden;
}
[role="tabpanel"] {
color: white;
width: 100%;
height: 100%;
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
position:absolute;
}
[role="tabpanel"].ng-leave.ng-leave-active,
[role="tabpanel"].ng-enter {
top:-300px;
}
[role="tabpanel"].ng-leave,
[role="tabpanel"].ng-enter.ng-enter-active {
top:0;
}
[role="tabpanel"].ng-leave {
z-index: 100;
}
.tabpanel-container [role="tabpanel"] {
padding:20px;
}
.after-tabs-area {
padding: 25px;
}
.after-tabs-area > span {
margin-top:25px;
padding-right: 15px;
vertical-align: middle;
line-height: 30px;
height: 35px;
}
.after-tabs-area > md-checkbox {
margin-top:26px;
margin-left:0px;
}
.md-header {
background-color: #1976D2 !important;
}
md-tab {
color: #90caf9 !important;
}
md-tab.active,
md-tab:focus {
color: white !important;
}
md-tab[disabled] {
opacity: 0.5;
}
.md-header .md-ripple {
border-color: #FFFF8D !important;
}
md-tabs-ink-bar {
background-color: #FFFF8D !important;
}

View File

@ -1,57 +0,0 @@
(function() {
'use strict';
/**
* Conditionally configure ink bar animations when the
* tab selection changes. If `mdNoBar` then do not show the
* bar nor animate.
*/
angular.module('material.components.tabs')
.directive('mdTabsInkBar', MdTabInkDirective);
function MdTabInkDirective($$rAF) {
var lastIndex = 0;
return {
restrict: 'E',
require: ['^?mdNoBar', '^mdTabs'],
link: postLink
};
function postLink(scope, element, attr, ctrls) {
if (ctrls[0]) return;
var tabsCtrl = ctrls[1],
debouncedUpdateBar = $$rAF.debounce(updateBar);
tabsCtrl.inkBarElement = element;
scope.$on('$mdTabsPaginationChanged', debouncedUpdateBar);
function updateBar() {
var selected = tabsCtrl.getSelectedItem();
var hideInkBar = !selected || tabsCtrl.count() < 2;
element.css('display', hideInkBar ? 'none' : 'block');
if (hideInkBar) return;
if (scope.pagination && scope.pagination.tabData) {
var index = tabsCtrl.getSelectedIndex();
var data = scope.pagination.tabData.tabs[index] || { left: 0, right: 0, width: 0 };
var right = element.parent().prop('offsetWidth') - data.right;
var classNames = ['md-transition-left', 'md-transition-right', 'md-no-transition'];
var classIndex = lastIndex > index ? 0 : lastIndex < index ? 1 : 2;
element
.removeClass(classNames.join(' '))
.addClass(classNames[classIndex])
.css({ left: (data.left + 1) + 'px', right: right + 'px' });
lastIndex = index;
}
}
}
}
})();

View File

@ -1,246 +0,0 @@
(function() {
'use strict';
angular.module('material.components.tabs')
.directive('mdTabsPagination', TabPaginationDirective);
function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout, $mdMedia) {
// Must match (2 * width of paginators) in scss
var PAGINATORS_WIDTH = (8 * 4) * 2;
return {
restrict: 'A',
require: '^mdTabs',
link: postLink
};
function postLink(scope, element, attr, tabsCtrl) {
var tabs = element[0].getElementsByTagName('md-tab');
var debouncedUpdatePagination = $$rAF.debounce(updatePagination);
var tabsParent = element.children();
var state = scope.pagination = {
page: -1,
active: false,
clickNext: function() { userChangePage(+1); },
clickPrevious: function() { userChangePage(-1); }
};
scope.$on('$mdTabsChanged', debouncedUpdatePagination);
angular.element($window).on('resize', debouncedUpdatePagination);
scope.$on('$destroy', function() {
angular.element($window).off('resize', debouncedUpdatePagination);
});
scope.$watch(function() { return tabsCtrl.tabToFocus; }, onTabFocus);
// Make sure we don't focus an element on the next page
// before it's in view
function onTabFocus(tab, oldTab) {
if (!tab) return;
var pageIndex = getPageForTab(tab);
if (!state.active || pageIndex === state.page) {
tab.element.focus();
} else {
// Go to the new page, wait for the page transition to end, then focus.
oldTab && oldTab.element.blur();
setPage(pageIndex).then(function() { tab.element.focus(); });
}
}
// Called when page is changed by a user action (click)
function userChangePage(increment) {
var sizeData = state.tabData;
var newPage = Math.max(0, Math.min(sizeData.pages.length - 1, state.page + increment));
var newTabIndex = sizeData.pages[newPage][ increment > 0 ? 'firstTabIndex' : 'lastTabIndex' ];
var newTab = tabsCtrl.itemAt(newTabIndex);
onTabFocus(newTab);
}
function updatePagination() {
if (!element.prop('offsetParent')) {
var watcher = waitForVisible();
return;
}
var tabs = element.find('md-tab');
disablePagination();
var sizeData = state.tabData = calculateTabData();
var needPagination = state.active = sizeData.pages.length > 1;
if (needPagination) { enablePagination(); }
scope.$evalAsync(function () { scope.$broadcast('$mdTabsPaginationChanged'); });
function enablePagination() {
tabsParent.css('width', '9999px');
//-- apply filler margins
angular.forEach(sizeData.tabs, function (tab) {
angular.element(tab.element).css('margin-left', tab.filler + 'px');
});
setPage(getPageForTab(tabsCtrl.getSelectedItem()));
}
function disablePagination() {
slideTabButtons(0);
tabsParent.css('width', '');
tabs.css('width', '');
tabs.css('margin-left', '');
state.page = null;
state.active = false;
}
function waitForVisible() {
return watcher || scope.$watch(
function () {
$timeout(function () {
if (element[0].offsetParent) {
if (angular.isFunction(watcher)) {
watcher();
}
debouncedUpdatePagination();
watcher = null;
}
}, 0, false);
}
);
}
}
function slideTabButtons(x) {
if (tabsCtrl.pagingOffset === x) {
// Resolve instantly if no change
return $$q.when();
}
var deferred = $$q.defer();
tabsCtrl.$$pagingOffset = x;
tabsParent.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + x + 'px,0,0)');
tabsParent.on($mdConstant.CSS.TRANSITIONEND, onTabsParentTransitionEnd);
return deferred.promise;
function onTabsParentTransitionEnd(ev) {
// Make sure this event didn't bubble up from an animation in a child element.
if (ev.target === tabsParent[0]) {
tabsParent.off($mdConstant.CSS.TRANSITIONEND, onTabsParentTransitionEnd);
deferred.resolve();
}
}
}
function shouldStretchTabs() {
switch (scope.stretchTabs) {
case 'never': return false;
case 'always': return true;
default: return $mdMedia('sm');
}
}
function calculateTabData(noAdjust) {
var clientWidth = element.parent().prop('offsetWidth');
var tabsWidth = clientWidth - PAGINATORS_WIDTH - 1;
var $tabs = angular.element(tabs);
var totalWidth = 0;
var max = 0;
var tabData = [];
var pages = [];
var currentPage;
$tabs.css('max-width', '');
angular.forEach(tabs, function (tab, index) {
var tabWidth = Math.min(tabsWidth, tab.offsetWidth);
var data = {
element: tab,
left: totalWidth,
width: tabWidth,
right: totalWidth + tabWidth,
filler: 0
};
//-- This calculates the page for each tab. The first page will use the clientWidth, which
// does not factor in the pagination items. After the first page, tabsWidth is used
// because at this point, we know that the pagination buttons will be shown.
data.page = Math.ceil(data.right / ( pages.length === 1 && index === tabs.length - 1 ? clientWidth : tabsWidth )) - 1;
if (data.page >= pages.length) {
data.filler = (tabsWidth * data.page) - data.left;
data.right += data.filler;
data.left += data.filler;
currentPage = {
left: data.left,
firstTabIndex: index,
lastTabIndex: index,
tabs: [ data ]
};
pages.push(currentPage);
} else {
currentPage.lastTabIndex = index;
currentPage.tabs.push(data);
}
totalWidth = data.right;
max = Math.max(max, tabWidth);
tabData.push(data);
});
$tabs.css('max-width', tabsWidth + 'px');
if (!noAdjust && shouldStretchTabs()) {
return adjustForStretchedTabs();
} else {
return {
width: totalWidth,
max: max,
tabs: tabData,
pages: pages,
tabElements: tabs
};
}
function adjustForStretchedTabs() {
var canvasWidth = pages.length === 1 ? clientWidth : tabsWidth;
var tabsPerPage = Math.min(Math.floor(canvasWidth / max), tabs.length);
var tabWidth = Math.floor(canvasWidth / tabsPerPage);
$tabs.css('width', tabWidth + 'px');
return calculateTabData(true);
}
}
function getPageForTab(tab) {
var tabIndex = tabsCtrl.indexOf(tab);
if (tabIndex === -1) return 0;
var sizeData = state.tabData;
return sizeData ? sizeData.tabs[tabIndex].page : 0;
}
function setPage(page) {
if (page === state.page) return;
var lastPage = state.tabData.pages.length - 1;
if (page < 0) page = 0;
if (page > lastPage) page = lastPage;
state.hasPrev = page > 0;
state.hasNext = page < lastPage;
state.page = page;
scope.$broadcast('$mdTabsPaginationChanged');
return slideTabButtons(-state.tabData.pages[page].left);
}
}
}
})();

View File

@ -1,90 +0,0 @@
(function() {
'use strict';
angular.module('material.components.tabs')
.controller('$mdTab', TabItemController);
function TabItemController($scope, $element, $attrs, $compile, $animate, $mdUtil, $parse, $timeout) {
var self = this;
// Properties
self.contentContainer = angular.element('<div class="md-tab-content ng-hide">');
self.hammertime = new Hammer(self.contentContainer[0]);
self.element = $element;
// Methods
self.isDisabled = isDisabled;
self.onAdd = onAdd;
self.onRemove = onRemove;
self.onSelect = onSelect;
self.onDeselect = onDeselect;
var disabledParsed = $parse($attrs.ngDisabled);
function isDisabled() {
return disabledParsed($scope.$parent);
}
/**
* Add the tab's content to the DOM container area in the tabs,
* @param contentArea the contentArea to add the content of the tab to
*/
function onAdd(contentArea, shouldDisconnectScope) {
if (self.content.length) {
self.contentContainer.append(self.content);
self.contentScope = $scope.$parent.$new();
contentArea.append(self.contentContainer);
$compile(self.contentContainer)(self.contentScope);
if (shouldDisconnectScope === true) {
$timeout(function () {
$mdUtil.disconnectScope(self.contentScope);
}, 0, false);
}
}
}
function onRemove() {
self.hammertime.destroy();
$animate.leave(self.contentContainer).then(function() {
self.contentScope && self.contentScope.$destroy();
self.contentScope = null;
});
}
function toggleAnimationClass(rightToLeft) {
self.contentContainer[rightToLeft ? 'addClass' : 'removeClass']('md-transition-rtl');
}
function onSelect(rightToLeft) {
// Resume watchers and events firing when tab is selected
$mdUtil.reconnectScope(self.contentScope);
self.hammertime.on('swipeleft swiperight', $scope.onSwipe);
$element.addClass('active');
$element.attr('aria-selected', true);
$element.attr('tabIndex', 0);
toggleAnimationClass(rightToLeft);
$animate.removeClass(self.contentContainer, 'ng-hide');
$scope.onSelect();
}
function onDeselect(rightToLeft) {
// Stop watchers & events from firing while tab is deselected
$mdUtil.disconnectScope(self.contentScope);
self.hammertime.off('swipeleft swiperight', $scope.onSwipe);
$element.removeClass('active');
$element.attr('aria-selected', false);
// Only allow tabbing to the active tab
$element.attr('tabIndex', -1);
toggleAnimationClass(rightToLeft);
$animate.addClass(self.contentContainer, 'ng-hide');
$scope.onDeselect();
}
}
})();

View File

@ -1,244 +0,0 @@
(function() {
'use strict';
angular.module('material.components.tabs')
.directive('mdTab', MdTabDirective);
/**
* @ngdoc directive
* @name mdTab
* @module material.components.tabs
*
* @restrict E
*
* @description
* `<md-tab>` is the nested directive used [within `<md-tabs>`] to specify each tab with a **label** and optional *view content*.
*
* If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
* complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
* markup of the `<md-tab>` is used as the tab header markup.
*
* If a tab **label** has been identified, then any **non-**`<md-tab-label>` markup
* will be considered tab content and will be transcluded to the internal `<div class="md-tabs-content">` container.
*
* This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
* automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
* be initiated via data binding changes, programmatic invocation, or user gestures.
*
* @param {string=} label Optional attribute to specify a simple string as the tab label
* @param {boolean=} md-active When evaluteing to true, selects the tab.
* @param {boolean=} disabled If present, disabled tab selection.
* @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
* @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
*
*
* @usage
*
* <hljs lang="html">
* <md-tab label="" disabled="" md-on-select="" md-on-deselect="" >
* <h3>My Tab content</h3>
* </md-tab>
*
* <md-tab >
* <md-tab-label>
* <h3>My Tab content</h3>
* </md-tab-label>
* <p>
* Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
* totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
* dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
* sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
* </p>
* </md-tab>
* </hljs>
*
*/
function MdTabDirective($mdInkRipple, $compile, $mdUtil, $mdConstant, $timeout) {
return {
restrict: 'E',
require: ['mdTab', '^mdTabs'],
controller: '$mdTab',
scope: {
onSelect: '&mdOnSelect',
onDeselect: '&mdOnDeselect',
label: '@'
},
compile: compile
};
function compile(element, attr) {
var tabLabel = element.find('md-tab-label');
if (tabLabel.length) {
// If a tab label element is found, remove it for later re-use.
tabLabel.remove();
} else if (angular.isDefined(attr.label)) {
// Otherwise, try to use attr.label as the label
tabLabel = angular.element('<md-tab-label>').html(attr.label);
} else {
// If nothing is found, use the tab's content as the label
tabLabel = angular.element('<md-tab-label>')
.append(element.contents().remove());
}
// Everything that's left as a child is the tab's content.
var tabContent = element.contents().remove();
return function postLink(scope, element, attr, ctrls) {
var tabItemCtrl = ctrls[0]; // Controller for THIS tabItemCtrl
var tabsCtrl = ctrls[1]; // Controller for ALL tabs
scope.$watch(
function () { return attr.label; },
function () { $timeout(function () { tabsCtrl.scope.$broadcast('$mdTabsChanged'); }, 0, false); }
);
transcludeTabContent();
configureAria();
var detachRippleFn = $mdInkRipple.attachTabBehavior(scope, element, {
colorElement: tabsCtrl.inkBarElement
});
tabsCtrl.add(tabItemCtrl);
scope.$on('$destroy', function() {
detachRippleFn();
tabsCtrl.remove(tabItemCtrl);
});
element.on('$destroy', function () {
//-- wait for item to be removed from the dom
$timeout(function () {
tabsCtrl.scope.$broadcast('$mdTabsChanged');
}, 0, false);
});
if (!angular.isDefined(attr.ngClick)) {
element.on('click', defaultClickListener);
}
element.on('keydown', keydownListener);
scope.onSwipe = onSwipe;
if (angular.isNumber(scope.$parent.$index)) {
watchNgRepeatIndex();
}
if (angular.isDefined(attr.mdActive)) {
watchActiveAttribute();
}
watchDisabled();
function transcludeTabContent() {
// Clone the label we found earlier, and $compile and append it
var label = tabLabel.clone();
element.append(label);
$compile(label)(scope.$parent);
// Clone the content we found earlier, and mark it for later placement into
// the proper content area.
tabItemCtrl.content = tabContent.clone();
}
//defaultClickListener isn't applied if the user provides an ngClick expression.
function defaultClickListener() {
scope.$apply(function() {
tabsCtrl.select(tabItemCtrl);
tabsCtrl.focus(tabItemCtrl);
});
}
function keydownListener(ev) {
if (ev.keyCode == $mdConstant.KEY_CODE.SPACE || ev.keyCode == $mdConstant.KEY_CODE.ENTER ) {
// Fire the click handler to do normal selection if space is pressed
element.triggerHandler('click');
ev.preventDefault();
} else if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
scope.$evalAsync(function() {
tabsCtrl.focus(tabsCtrl.previous(tabItemCtrl));
});
} else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
scope.$evalAsync(function() {
tabsCtrl.focus(tabsCtrl.next(tabItemCtrl));
});
}
}
function onSwipe(ev) {
scope.$apply(function() {
if (ev.type === 'swipeleft') {
tabsCtrl.select(tabsCtrl.next());
} else {
tabsCtrl.select(tabsCtrl.previous());
}
});
}
// If tabItemCtrl is part of an ngRepeat, move the tabItemCtrl in our internal array
// when its $index changes
function watchNgRepeatIndex() {
// The tabItemCtrl has an isolate scope, so we watch the $index on the parent.
scope.$watch('$parent.$index', function $indexWatchAction(newIndex) {
tabsCtrl.move(tabItemCtrl, newIndex);
});
}
function watchActiveAttribute() {
var unwatch = scope.$parent.$watch('!!(' + attr.mdActive + ')', activeWatchAction);
scope.$on('$destroy', unwatch);
function activeWatchAction(isActive) {
var isSelected = tabsCtrl.getSelectedItem() === tabItemCtrl;
if (isActive && !isSelected) {
tabsCtrl.select(tabItemCtrl);
} else if (!isActive && isSelected) {
tabsCtrl.deselect(tabItemCtrl);
}
}
}
function watchDisabled() {
scope.$watch(tabItemCtrl.isDisabled, disabledWatchAction);
function disabledWatchAction(isDisabled) {
element.attr('aria-disabled', isDisabled);
// Auto select `next` tab when disabled
var isSelected = (tabsCtrl.getSelectedItem() === tabItemCtrl);
if (isSelected && isDisabled) {
tabsCtrl.select(tabsCtrl.next() || tabsCtrl.previous());
}
}
}
function configureAria() {
// Link together the content area and tabItemCtrl with an id
var tabId = attr.id || ('tab_' + $mdUtil.nextUid());
element.attr({
id: tabId,
role: 'tab',
tabIndex: -1 //this is also set on select/deselect in tabItemCtrl
});
// Only setup the contentContainer's aria attributes if tab content is provided
if (tabContent.length) {
var tabContentId = 'content_' + tabId;
if (!element.attr('aria-controls')) {
element.attr('aria-controls', tabContentId);
}
tabItemCtrl.contentContainer.attr({
id: tabContentId,
role: 'tabpanel',
'aria-labelledby': tabId
});
}
}
};
}
}
})();

View File

@ -1,138 +0,0 @@
(function() {
'use strict';
angular.module('material.components.tabs')
.controller('$mdTabs', MdTabsController);
function MdTabsController($scope, $element, $mdUtil, $timeout) {
var tabsList = $mdUtil.iterator([], false);
var self = this;
// Properties
self.$element = $element;
self.scope = $scope;
// The section containing the tab content $elements
var contentArea = self.contentArea = angular.element($element[0].querySelector('.md-tabs-content'));
// Methods from iterator
var inRange = self.inRange = tabsList.inRange;
var indexOf = self.indexOf = tabsList.indexOf;
var itemAt = self.itemAt = tabsList.itemAt;
self.count = tabsList.count;
self.getSelectedItem = getSelectedItem;
self.getSelectedIndex = getSelectedIndex;
self.add = add;
self.remove = remove;
self.move = move;
self.select = select;
self.focus = focus;
self.deselect = deselect;
self.next = next;
self.previous = previous;
$scope.$on('$destroy', function() {
deselect(getSelectedItem());
for (var i = tabsList.count() - 1; i >= 0; i--) {
remove(tabsList[i], true);
}
});
// Get the selected tab
function getSelectedItem() {
return itemAt($scope.selectedIndex);
}
function getSelectedIndex() {
return $scope.selectedIndex;
}
// Add a new tab.
// Returns a method to remove the tab from the list.
function add(tab, index) {
tabsList.add(tab, index);
// Select the new tab if we don't have a selectedIndex, or if the
// selectedIndex we've been waiting for is this tab
if ($scope.selectedIndex === -1 || !angular.isNumber($scope.selectedIndex) ||
$scope.selectedIndex === self.indexOf(tab)) {
tab.onAdd(self.contentArea, false);
self.select(tab);
} else {
tab.onAdd(self.contentArea, true);
}
$scope.$broadcast('$mdTabsChanged');
}
function remove(tab, noReselect) {
if (!tabsList.contains(tab)) return;
if (noReselect) return;
var isSelectedItem = getSelectedItem() === tab,
newTab = previous() || next();
deselect(tab);
tabsList.remove(tab);
tab.onRemove();
$scope.$broadcast('$mdTabsChanged');
if (isSelectedItem) { select(newTab); }
}
// Move a tab (used when ng-repeat order changes)
function move(tab, toIndex) {
var isSelected = getSelectedItem() === tab;
tabsList.remove(tab);
tabsList.add(tab, toIndex);
if (isSelected) select(tab);
$scope.$broadcast('$mdTabsChanged');
}
function select(tab, rightToLeft) {
if (!tab || tab.isSelected || tab.isDisabled()) return;
if (!tabsList.contains(tab)) return;
if (!angular.isDefined(rightToLeft)) {
rightToLeft = indexOf(tab) < $scope.selectedIndex;
}
deselect(getSelectedItem(), rightToLeft);
$scope.selectedIndex = indexOf(tab);
tab.isSelected = true;
tab.onSelect(rightToLeft);
$scope.$broadcast('$mdTabsChanged');
}
function focus(tab) {
// this variable is watched by pagination
self.tabToFocus = tab;
}
function deselect(tab, rightToLeft) {
if (!tab || !tab.isSelected) return;
if (!tabsList.contains(tab)) return;
$scope.selectedIndex = -1;
tab.isSelected = false;
tab.onDeselect(rightToLeft);
}
function next(tab, filterFn) {
return tabsList.next(tab || getSelectedItem(), filterFn || isTabEnabled);
}
function previous(tab, filterFn) {
return tabsList.previous(tab || getSelectedItem(), filterFn || isTabEnabled);
}
function isTabEnabled(tab) {
return tab && !tab.isDisabled();
}
}
})();

View File

@ -1,169 +0,0 @@
(function() {
'use strict';
angular.module('material.components.tabs')
.directive('mdTabs', TabsDirective);
/**
* @ngdoc directive
* @name mdTabs
* @module material.components.tabs
*
* @restrict E
*
* @description
* The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to produces a Tabs components.
* In turn, the nested `<md-tab>` directive is used to specify a tab label for the **header button** and a [optional] tab view
* content that will be associated with each tab button.
*
* Below is the markup for its simplest usage:
*
* <hljs lang="html">
* <md-tabs>
* <md-tab label="Tab #1"></md-tab>
* <md-tab label="Tab #2"></md-tab>
* <md-tab label="Tab #3"></md-tab>
* <md-tabs>
* </hljs>
*
* Tabs supports three (3) usage scenarios:
*
* 1. Tabs (buttons only)
* 2. Tabs with internal view content
* 3. Tabs with external view content
*
* **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any other components, content, or views.
* **Tabs with internal views** are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component.
* **Tabs with external view content** is often useful when content associated with each tab is independently managed and data-binding notifications announce tab selection changes.
*
* > As a performance bonus, if the tab content is managed internally then the non-active (non-visible) tab contents are temporarily disconnected from the `$scope.$digest()` processes; which restricts and optimizes DOM updates to only the currently active tab.
*
* Additional features also include:
*
* * Content can include any markup.
* * If a tab is disabled while active/selected, then the next tab will be auto-selected.
* * If the currently active tab is the last tab, then next() action will select the first tab.
* * Any markup (other than **`<md-tab>`** tags) will be transcluded into the tab header area BEFORE the tab buttons.
*
* ### Explanation of tab stretching
*
* Initially, tabs will have an inherent size. This size will either be defined by how much space is needed to accommodate their text or set by the user through CSS. Calculations will be based on this size.
*
* On mobile devices, tabs will be expanded to fill the available horizontal space. When this happens, all tabs will become the same size.
*
* On desktops, by default, stretching will never occur.
*
* This default behavior can be overridden through the `md-stretch-tabs` attribute. Here is a table showing when stretching will occur:
*
* `md-stretch-tabs` | mobile | desktop
* ------------------|-----------|--------
* `auto` | stretched | ---
* `always` | stretched | stretched
* `never` | --- | ---
*
* @param {integer=} md-selected Index of the active/selected tab
* @param {boolean=} md-no-ink If present, disables ink ripple effects.
* @param {boolean=} md-no-bar If present, disables the selection ink bar.
* @param {string=} md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
* @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
*
* @usage
* <hljs lang="html">
* <md-tabs md-selected="selectedIndex" >
* <img ng-src="img/angular.png" class="centered">
*
* <md-tab
* ng-repeat="tab in tabs | orderBy:predicate:reversed"
* md-on-select="onTabSelected(tab)"
* md-on-deselect="announceDeselected(tab)"
* disabled="tab.disabled" >
*
* <md-tab-label>
* {{tab.title}}
* <img src="img/removeTab.png"
* ng-click="removeTab(tab)"
* class="delete" >
* </md-tab-label>
*
* {{tab.content}}
*
* </md-tab>
*
* </md-tabs>
* </hljs>
*
*/
function TabsDirective($mdTheming) {
return {
restrict: 'E',
controller: '$mdTabs',
require: 'mdTabs',
transclude: true,
scope: {
selectedIndex: '=?mdSelected'
},
template:
'<section class="md-header" ' +
'ng-class="{\'md-paginating\': pagination.active}">' +
'<button class="md-paginator md-prev" ' +
'ng-if="pagination.active && pagination.hasPrev" ' +
'ng-click="pagination.clickPrevious()" ' +
'aria-hidden="true">' +
'</button>' +
// overflow: hidden container when paginating
'<div class="md-header-items-container" md-tabs-pagination>' +
// flex container for <md-tab> elements
'<div class="md-header-items">' +
'<md-tabs-ink-bar></md-tabs-ink-bar>' +
'</div>' +
'</div>' +
'<button class="md-paginator md-next" ' +
'ng-if="pagination.active && pagination.hasNext" ' +
'ng-click="pagination.clickNext()" ' +
'aria-hidden="true">' +
'</button>' +
'</section>' +
'<section class="md-tabs-content"></section>',
link: postLink
};
function postLink(scope, element, attr, tabsCtrl, transclude) {
scope.stretchTabs = attr.hasOwnProperty('mdStretchTabs') ? attr.mdStretchTabs || 'always' : 'auto';
$mdTheming(element);
configureAria();
watchSelected();
transclude(scope.$parent, function(clone) {
angular.element(element[0].querySelector('.md-header-items')).append(clone);
});
function configureAria() {
element.attr('role', 'tablist');
}
function watchSelected() {
scope.$watch('selectedIndex', function watchSelectedIndex(newIndex, oldIndex) {
if (oldIndex == newIndex) return;
var rightToLeft = oldIndex > newIndex;
tabsCtrl.deselect(tabsCtrl.itemAt(oldIndex), rightToLeft);
if (tabsCtrl.inRange(newIndex)) {
var newTab = tabsCtrl.itemAt(newIndex);
while (newTab && newTab.isDisabled()) {
newTab = newIndex > oldIndex
? tabsCtrl.next(newTab)
: tabsCtrl.previous(newTab);
}
tabsCtrl.select(newTab, rightToLeft);
}
});
}
}
}
})();

View File

@ -1,30 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.tabs
* @description
*
* Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
* The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
*
* Features include support for:
*
* - static or dynamic tabs,
* - responsive designs,
* - accessibility support (ARIA),
* - tab pagination,
* - external or internal tab content,
* - focus indicators and arrow-key navigations,
* - programmatic lookup and access to tab controllers, and
* - dynamic transitions through different tab contents.
*
*/
/*
* @see js folder for tabs implementation
*/
angular.module('material.components.tabs', [
'material.core'
]);
})();

View File

@ -1,294 +0,0 @@
describe('<md-tabs>', function() {
beforeEach(module('material.components.tabs'));
beforeEach(function() {
TestUtil.mockElementFocus(this);
this.addMatchers({
toBeActiveTab: function(checkFocus) {
var fails = [];
var actual = this.actual;
this.message = function() {
return 'Expected ' + angular.mock.dump(actual) + (this.isNot ? ' not ' : ' ') +
'to be the active tab. Failures: ' + fails.join(', ');
};
if (!actual.hasClass('active')) {
fails.push('does not have active class');
}
if (actual.attr('aria-selected') != 'true') {
fails.push('aria-selected is not true');
}
if (actual.attr('tabindex') != '0') {
fails.push('tabindex is not 0');
}
return fails.length === 0;
}
});
});
function setup(template) {
var el;
inject(function($compile, $rootScope) {
el = $compile(template)($rootScope.$new());
$rootScope.$apply();
});
return el;
}
function triggerKeydown(el, keyCode) {
return el.triggerHandler({
type: 'keydown',
keyCode: keyCode
});
}
describe('activating tabs', function() {
it('should select first tab by default', function() {
var tabs = setup('<md-tabs>' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'</md-tabs>');
expect(tabs.find('md-tab').eq(0)).toBeActiveTab();
});
it('should select & focus tab on click', inject(function($document) {
var tabs = setup('<md-tabs>' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'<md-tab ng-disabled="true"></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
tabs.find('md-tab').eq(1).triggerHandler('click');
expect(tabItems.eq(1)).toBeActiveTab();
expect($document.activeElement).toBe(tabItems[1]);
tabs.find('md-tab').eq(0).triggerHandler('click');
expect(tabItems.eq(0)).toBeActiveTab();
expect($document.activeElement).toBe(tabItems[0]);
}));
it('should focus tab on arrow if tab is enabled', inject(function($document, $mdConstant, $timeout) {
var tabs = setup('<md-tabs>' +
'<md-tab></md-tab>' +
'<md-tab ng-disabled="true"></md-tab>' +
'<md-tab></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
expect(tabItems.eq(0)).toBeActiveTab();
// Boundary case, do nothing
triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.LEFT_ARROW);
expect(tabItems.eq(0)).toBeActiveTab();
// Tab 0 should still be active, but tab 2 focused (skip tab 1 it's disabled)
triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW);
expect(tabItems.eq(0)).toBeActiveTab();
$timeout.flush();
expect($document.activeElement).toBe(tabItems[2]);
// Boundary case, do nothing
triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW);
expect(tabItems.eq(0)).toBeActiveTab();
$timeout.flush();
expect($document.activeElement).toBe(tabItems[2]);
// Skip tab 1 again, it's disabled
triggerKeydown(tabItems.eq(2), $mdConstant.KEY_CODE.LEFT_ARROW);
expect(tabItems.eq(0)).toBeActiveTab();
$timeout.flush();
expect($document.activeElement).toBe(tabItems[0]);
}));
it('should select tab on space or enter', inject(function($mdConstant) {
var tabs = setup('<md-tabs>' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
triggerKeydown(tabItems.eq(1), $mdConstant.KEY_CODE.ENTER);
expect(tabItems.eq(1)).toBeActiveTab();
triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.SPACE);
expect(tabItems.eq(0)).toBeActiveTab();
}));
it('the active tab\'s content should always be connected', inject(function($timeout) {
var tabs = setup('<md-tabs>' +
'<md-tab label="label1!">content1!</md-tab>' +
'<md-tab label="label2!">content2!</md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
var contents = angular.element(tabs[0].querySelectorAll('.md-tab-content'));
$timeout.flush();
expect(contents.eq(0).scope().$$disconnected).toBeFalsy();
expect(contents.eq(1).scope().$$disconnected).toBeTruthy();
tabItems.eq(1).triggerHandler('click');
expect(contents.eq(0).scope().$$disconnected).toBeTruthy();
expect(contents.eq(1).scope().$$disconnected).toBeFalsy();
}));
it('should bind to selected', function() {
var tabs = setup('<md-tabs md-selected="current">' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
expect(tabItems.eq(0)).toBeActiveTab();
expect(tabs.scope().current).toBe(0);
tabs.scope().$apply('current = 1');
expect(tabItems.eq(1)).toBeActiveTab();
tabItems.eq(2).triggerHandler('click');
expect(tabs.scope().current).toBe(2);
});
it('should use active binding', function() {
var tabs = setup('<md-tabs>' +
'<md-tab md-active="active0"></md-tab>' +
'<md-tab md-active="active1"></md-tab>' +
'<md-tab md-active="active2"></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
tabs.scope().$apply('active2 = true');
expect(tabItems.eq(2)).toBeActiveTab();
tabs.scope().$apply('active1 = true');
expect(tabItems.eq(1)).toBeActiveTab();
tabs.scope().$apply('active1 = false');
expect(tabItems.eq(1)).not.toBeActiveTab();
});
it('disabling active tab', function() {
var tabs = setup('<md-tabs>' +
'<md-tab ng-disabled="disabled0"></md-tab>' +
'<md-tab ng-disabled="disabled1"></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
expect(tabItems.eq(0)).toBeActiveTab();
tabs.scope().$apply('disabled0 = true');
expect(tabItems.eq(1)).toBeActiveTab();
expect(tabItems.eq(0).attr('aria-disabled')).toBe('true');
expect(tabItems.eq(1).attr('aria-disabled')).not.toBe('true');
tabs.scope().$apply('disabled0 = false; disabled1 = true');
expect(tabItems.eq(0)).toBeActiveTab();
expect(tabItems.eq(0).attr('aria-disabled')).not.toBe('true');
expect(tabItems.eq(1).attr('aria-disabled')).toBe('true');
});
it('swiping tabs', function() {
var tabs = setup('<md-tabs>' +
'<md-tab></md-tab>' +
'<md-tab></md-tab>' +
'</md-tabs>');
var tabItems = tabs.find('md-tab');
tabItems.eq(0).isolateScope().onSwipe({
type: 'swipeleft'
});
expect(tabItems.eq(1)).toBeActiveTab();
tabItems.eq(1).isolateScope().onSwipe({
type: 'swipeleft'
});
expect(tabItems.eq(1)).toBeActiveTab();
tabItems.eq(1).isolateScope().onSwipe({
type: 'swipeleft'
});
expect(tabItems.eq(1)).toBeActiveTab();
tabItems.eq(1).isolateScope().onSwipe({
type: 'swiperight'
});
expect(tabItems.eq(0)).toBeActiveTab();
tabItems.eq(0).isolateScope().onSwipe({
type: 'swiperight'
});
expect(tabItems.eq(0)).toBeActiveTab();
});
});
describe('tab label & content DOM', function() {
it('should support all 3 label types', function() {
var tabs1 = setup('<md-tabs>' +
'<md-tab label="<b>super</b> label"></md-tab>' +
'</md-tabs>');
expect(tabs1.find('md-tab-label').html()).toBe('<b>super</b> label');
var tabs2 = setup('<md-tabs>' +
'<md-tab><b>super</b> label</md-tab>' +
'</md-tabs>');
expect(tabs2.find('md-tab-label').html()).toBe('<b>super</b> label');
var tabs3 = setup('<md-tabs>' +
'<md-tab><md-tab-label><b>super</b> label</md-tab-label></md-tab>' +
'</md-tabs>');
expect(tabs3.find('md-tab-label').html()).toBe('<b>super</b> label');
});
it('should support content inside with each kind of label', function() {
var tabs1 = setup('<md-tabs>' +
'<md-tab label="label that!"><b>content</b> that!</md-tab>' +
'</md-tabs>');
expect(tabs1.find('md-tab-label').html()).toBe('label that!');
expect(tabs1[0].querySelector('.md-tabs-content .md-tab-content').innerHTML)
.toBe('<b>content</b> that!');
var tabs2 = setup('<md-tabs>' +
'<md-tab><md-tab-label>label that!</md-tab-label><b>content</b> that!</md-tab>' +
'</md-tabs>');
expect(tabs1.find('md-tab-label').html()).toBe('label that!');
expect(tabs1[0].querySelector('.md-tabs-content .md-tab-content').innerHTML)
.toBe('<b>content</b> that!');
});
it('should connect content with child of the outside scope', function() {
var tabs = setup('<md-tabs>' +
'<md-tab label="label!">content!</md-tab>' +
'</md-tabs>');
var content = angular.element(tabs[0].querySelector('.md-tab-content'));
expect(content.scope().$parent.$id).toBe(tabs.find('md-tab').scope().$id);
});
});
describe('aria', function() {
it('should link tab content to tabItem with auto-generated ids', function() {
var tabs = setup('<md-tabs>' +
'<md-tab label="label!">content!</md-tab>' +
'</md-tabs>');
var tabItem = tabs.find('md-tab');
var tabContent = angular.element(tabs[0].querySelector('.md-tab-content'));
expect(tabs.attr('role')).toBe('tablist');
expect(tabItem.attr('id')).toBeTruthy();
expect(tabItem.attr('role')).toBe('tab');
expect(tabItem.attr('aria-controls')).toBe(tabContent.attr('id'));
expect(tabContent.attr('id')).toBeTruthy();
expect(tabContent.attr('role')).toBe('tabpanel');
expect(tabContent.attr('aria-labelledby')).toBe(tabItem.attr('id'));
//Unique ids check
expect(tabContent.attr('id')).not.toEqual(tabItem.attr('id'));
});
});
});

View File

@ -1,134 +0,0 @@
(function() {
'use strict';
/**
* @ngdoc module
* @name material.components.textField
* @description
* Form
*/
angular.module('material.components.textField', [
'material.core'
])
.directive('mdInputGroup', mdInputGroupDirective)
.directive('mdInput', mdInputDirective)
.directive('mdTextFloat', mdTextFloatDirective);
function mdTextFloatDirective($mdTheming, $mdUtil, $parse, $log) {
return {
restrict: 'E',
replace: true,
scope : {
fid : '@?mdFid',
label : '@?',
value : '=ngModel'
},
compile : function(element, attr) {
$log.warn('<md-text-float> is deprecated. Please use `<md-input-container>` and `<input>`.' +
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
if ( angular.isUndefined(attr.mdFid) ) {
attr.mdFid = $mdUtil.nextUid();
}
return {
pre : function(scope, element, attrs) {
var disabledParsed = $parse(attrs.ngDisabled);
scope.isDisabled = function() {
return disabledParsed(scope.$parent);
};
scope.inputType = attrs.type || "text";
},
post: $mdTheming
};
},
template:
'<md-input-group tabindex="-1">' +
' <label for="{{fid}}" >{{label}}</label>' +
' <md-input id="{{fid}}" ng-disabled="isDisabled()" ng-model="value" type="{{inputType}}"></md-input>' +
'</md-input-group>'
};
}
function mdInputGroupDirective($log) {
return {
restrict: 'CE',
controller: ['$element', function($element) {
$log.warn('<md-input-group> is deprecated. Please use `<md-input-container>` and `<input>`.' +
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
this.setFocused = function(isFocused) {
$element.toggleClass('md-input-focused', !!isFocused);
};
this.setHasValue = function(hasValue) {
$element.toggleClass('md-input-has-value', hasValue );
};
}]
};
}
function mdInputDirective($mdUtil, $log) {
return {
restrict: 'E',
replace: true,
template: '<input >',
require: ['^?mdInputGroup', '?ngModel'],
link: function(scope, element, attr, ctrls) {
if ( !ctrls[0] ) return;
$log.warn('<md-input> is deprecated. Please use `<md-input-container>` and `<input>`.' +
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
var inputGroupCtrl = ctrls[0];
var ngModelCtrl = ctrls[1];
scope.$watch(scope.isDisabled, function(isDisabled) {
element.attr('aria-disabled', !!isDisabled);
element.attr('tabindex', !!isDisabled);
});
element.attr('type', attr.type || element.parent().attr('type') || "text");
// When the input value changes, check if it "has" a value, and
// set the appropriate class on the input group
if (ngModelCtrl) {
//Add a $formatter so we don't use up the render function
ngModelCtrl.$formatters.push(function(value) {
inputGroupCtrl.setHasValue( isNotEmpty(value) );
return value;
});
}
element
.on('input', function() {
inputGroupCtrl.setHasValue( isNotEmpty() );
})
.on('focus', function(e) {
// When the input focuses, add the focused class to the group
inputGroupCtrl.setFocused(true);
})
.on('blur', function(e) {
// When the input blurs, remove the focused class from the group
inputGroupCtrl.setFocused(false);
inputGroupCtrl.setHasValue( isNotEmpty() );
});
scope.$on('$destroy', function() {
inputGroupCtrl.setFocused(false);
inputGroupCtrl.setHasValue(false);
});
function isNotEmpty(value) {
value = angular.isUndefined(value) ? element.val() : value;
return (angular.isDefined(value) && (value!==null) &&
(value.toString().trim() !== ""));
}
}
};
}
})();

View File

@ -1,295 +0,0 @@
describe('Text Field directives', function() {
beforeEach(module('material.components.textField'));
describe('- mdInputGroup', function() {
var scope;
beforeEach(function() {
scope = {
user : {
firstName: 'Thomas',
lastName: 'Burleson',
email: 'ThomasBurleson@gmail.com',
password: 'your password is incorrect'
}
};
});
it('should set input class for focus & blur', function() {
var expressions = {label:"Firstname", model:"user.firstName"},
el = setupInputGroup( expressions, scope),
input = el.find('input');
input.triggerHandler('focus');
expect(el.hasClass('md-input-focused')).toBe(true);
input.triggerHandler('blur');
expect(el.hasClass('md-input-focused')).toBe(false);
});
it('should set input class for input event', function() {
var expressions = {label:"email", model:"user.email", type:"email"},
el = setupInputGroup( expressions, scope),
input = el.find('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
input.val('');
input.triggerHandler('input');
expect(el.hasClass('md-input-has-value')).toBe(false);
input.val('ThomasBurleson@gmail.com');
input.triggerHandler('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
});
it('should set input class for ngModel render', function() {
var expressions = {label:"Firstname", model:"user.firstName"},
el = setupInputGroup( expressions, scope),
input = el.find('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
input.scope().$apply('user.firstName = ""');
expect(el.hasClass('md-input-has-value')).toBe(false);
input.scope().$apply('user.firstName = "Thomas"');
expect(el.hasClass('md-input-has-value')).toBe(true);
});
});
describe(' - mdTextFloat', function() {
var model;
beforeEach(function() {
model = {
labels : {
firstName: 'FirstName',
lastName: 'LastName',
email: 'eMail',
password: 'Password'
},
user : {
firstName: 'Andrew',
lastName: 'Joslin',
email: 'AndrewJoslin@drifty.com',
password: 'public'
}
};
});
it('should set input type `password` properly', function() {
var el = setupTextFloat( { type:"password" }, model);
expect( el.find('input').attr('type')).toBe("password");
expect( el.find('input').val()).toBe("");
});
it('should set input type `email` properly', function() {
var el = setupTextFloat( { type:"email" }, model);
expect( el.find('input').attr('type')).toBe("email");
expect( el.find('input').val()).toBe("");
});
it('should set a static label properly', function() {
var el = setupTextFloat( { label:"motto" }, model);
expect( el.find('label').text() ).toBe("motto");
expect( el.find('input').attr('type')).toBe("text");
expect( el.find('input').val()).toBe("");
});
it('should update a label from model changes.', function() {
var markup ='<md-text-float ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
expect( el.find('input').val() ).toBe("Andrew");
expect( el.find('label').text() ).toBe("FirstName");
// Change model value of the `firstName` [field] label
// then check if the dom is updated
var val2 = "Corporate Title:";
el.find('label').scope().$apply(function(){
model.labels.firstName = val2;
});
expect( el.find('label').text() ).toBe( val2 );
});
it('should update an input value from model changes.', function() {
var markup ='<md-text-float ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
var input = el.find('input');
expect( input.val() ).toBe("Andrew");
var name = "AngularJS";
input.scope().$apply(function(){
model.user.firstName = name;
});
expect( input.val() ).toBe( name );
});
// Breaks on IE
xit('should update a model value from input changes.', function() {
var markup ='<md-text-float ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
var input = el.find('input');
expect( input.val() ).toBe( model.user.firstName );
input.val( "AngularJS" );
input.triggerHandler('input');
expect( model.user.firstName ).toBe( "AngularJS" );
});
it('should set input class for focus & blur', function() {
var expressions = {label:"Firstname", model:"user.firstName"},
el = setupTextFloat( expressions, model),
input = el.find('input');
input.triggerHandler('focus');
expect(el.hasClass('md-input-focused')).toBe(true);
input.triggerHandler('blur');
expect(el.hasClass('md-input-focused')).toBe(false);
});
it('should set input class for input event', function() {
var expressions = {label:"password", model:"user.password", type:"password"},
el = setupTextFloat( expressions, model),
input = el.find('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
input.val('');
input.triggerHandler('input');
expect(el.hasClass('md-input-has-value')).toBe(false);
input.val('ThomasBurleson@gmail.com');
input.triggerHandler('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
});
it('should set input class for ngModel changes', function() {
var expressions = {label:"Password", model:"user.password", type:"password"},
el = setupTextFloat( expressions, model),
input = el.find('input');
expect(el.hasClass('md-input-has-value')).toBe(true);
input.scope().$apply(function(){ model.user.password = ""; });
expect(el.hasClass('md-input-has-value')).toBe(false);
input.scope().$apply(function() { model.user.password = "hiddenValley"; });
expect(el.hasClass('md-input-has-value')).toBe(true);
});
it('should pair input and label for accessibility.', function() {
var markup ='<md-text-float ' +
' md-fid="093" ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
var input = el.find('input');
var label = el.find('label');
expect( label.attr('for') ).toBe( "093" );
expect( input.attr('id') ).toBe( label.attr('for') );
});
it('should auto-pair input and label for accessibility.', function() {
var markup ='<md-text-float ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
var input = el.find('input');
var label = el.find('label');
expect( label.attr('for') == "" ).toBe( false );
expect( input.attr('id') ).toBe( label.attr('for') );
});
it('should add an ARIA attribute for disabled inputs', function() {
var markup ='<md-text-float ng-disabled="true" ' +
' label="{{labels.firstName}}" ' +
' ng-model="user.firstName" >' +
'</md-text-float>';
var el = buildElement( markup, model);
var input = el.find('input');
expect( input.attr('aria-disabled') ).toBe( 'true' );
});
});
// ****************************************************************
// Utility `setup` methods
// ****************************************************************
var templates = {
md_text_float : '<md-text-float ' +
' type="{{type}}" ' +
' label="{{label}}" ' +
' ng-model="{{model}}" >' +
'</md-text-float>',
md_input_group: '<div class="md-input-group" tabindex="-1">' +
' <label>{{label}}</label>' +
' <md-input id="{{id}}" type="{{type}}" ng-model="{{model}}"></md-input>' +
'</div>'
};
/**
* Build a text float group using the `<md-input-group />` markup template
*/
function setupInputGroup(expressions, values) {
values = angular.extend({},{type:"text", id:''},values||{});
return buildElement( templates.md_input_group, values, angular.extend({}, expressions||{}));
}
/**
* Build a text float group using the `<md-text-float />` markup template
*/
function setupTextFloat(expressions, values) {
values = angular.extend({ modelValue:"",type:'text' }, values || {});
var defaults = {model:"modelValue", label:""};
var tokens = angular.extend({}, defaults, expressions||{} );
return buildElement( templates.md_text_float, values, tokens );
}
/**
* Use the specified template markup, $compile to the DOM build
* and link both the bindings and attribute assignments.
* @returns {*} DOM Element
*/
function buildElement( template, scope, interpolationScope ) {
var el;
inject(function($compile, $interpolate, $rootScope) {
scope = angular.extend( $rootScope.$new(), scope || {});
// First substitute interpolation values into the template... if any
if ( interpolationScope ) {
template = $interpolate( template )( interpolationScope );
}
// Compile the template using the scope model(s)
el = $compile( template )( scope );
$rootScope.$apply();
});
return el;
}
});

View File

@ -1,34 +0,0 @@
<div ng-controller="AppCtrl" layout-fill layout="column" class="inset">
<p>Toast can be dismissed with a swipe, a timer, or a button.</p>
<div layout="row" layout-sm="column" layout-align="space-around">
<div style="width:50px"></div>
<md-button ng-click="showCustomToast()">
Show Custom
</md-button>
<md-button ng-click="showSimpleToast()">
Show Simple
</md-button>
<md-button class="md-raised" ng-click="showActionToast()">
Show With Action
</md-button>
<div style="width:50px"></div>
</div>
<div>
<br/>
<b>Toast Position: "{{getToastPosition()}}"</b>
<br/>
<md-checkbox ng-repeat="(name, isSelected) in toastPosition"
aria-label="{{name}}"
ng-model="toastPosition[name]">
{{name}}
</md-checkbox>
<md-button class="md-primary md-fab md-fab-bottom-right">
FAB
</md-button>
<md-button class="md-primary md-fab md-fab-top-right" md-theme="green">
FAB
</md-button>
</div>
</div>

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