diff --git a/config/karma.conf.js b/config/karma.conf.js index f109ce4ac04..eb5a7283737 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -18,8 +18,6 @@ module.exports = function(config) { // globbing. 'src/components/*/*.js', 'src/components/tabs/js/*.js', - 'src/services/*.js', - 'src/services/*/*.js', ], port: 9876, diff --git a/config/test-utils.js b/config/test-utils.js index 03da3751bb6..f9994e2d168 100644 --- a/config/test-utils.js +++ b/config/test-utils.js @@ -1,5 +1,5 @@ var TestUtil = { - /** + /** * Mocks angular.element#focus for the duration of the test * @example * it('some focus test', inject(function($document) { @@ -46,7 +46,7 @@ beforeEach(function() { // toHaveClass matcher from angularjs test helpers toHaveClass: function(clazz) { this.message = function() { - return "Expected '" + angular.mock.dump(this.actual) + + return "Expected '" + angular.mock.dump(this.actual) + (this.isNot ? ' not' : '') + " to have class '" + clazz + "'."; }; var classes = clazz.trim().split(/\s+/); @@ -56,6 +56,17 @@ beforeEach(function() { } } return true; + }, + /** + * A helper to match the type of a given value + * @example expect(1).toBeOfType('number') + */ + toBeOfType: function(type) { + this.message = function() { + return "Expected " + angular.mock.dump(this.actual) + " of type " + + (typeof this.actual) + (this.isNot ? ' not ' : '') + " to have type '" + type + "'."; + }; + return typeof this.actual == type; } }); diff --git a/docs/config/template/index.template.html b/docs/config/template/index.template.html index 85a99b99382..597b2c53675 100644 --- a/docs/config/template/index.template.html +++ b/docs/config/template/index.template.html @@ -1,5 +1,5 @@ - + Material Design diff --git a/gulpfile.js b/gulpfile.js index 640e214e5b5..d1c9715ff09 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,6 +21,7 @@ var insert = require('gulp-insert'); var jshint = require('gulp-jshint'); var lazypipe = require('lazypipe'); var minifyCss = require('gulp-minify-css'); +var ngAnnotate = require('gulp-ng-annotate'); var rename = require('gulp-rename'); var sass = require('gulp-sass'); var through2 = require('through2'); @@ -40,7 +41,7 @@ var config = { ' * @license MIT\n' + ' * v' + pkg.version + '\n' + ' */\n', - jsBaseFiles: ['src/core/core.js', 'src/core/util/*.js'], + jsBaseFiles: ['src/core/**/*.js', '!src/core/**/*.spec.js'], themeBaseFiles: ['src/core/style/color-palette.scss', 'src/core/style/variables.scss', 'src/core/style/mixins.scss'], scssBaseFiles: ['src/core/style/color-palette.scss', 'src/core/style/variables.scss', 'src/core/style/mixins.scss', 'src/core/style/{structure,layout}.scss'], paths: 'src/{components,services}/**', @@ -204,9 +205,8 @@ function buildTheme(theme) { gulp.task('build-scss', ['build-default-theme'], function() { var defaultThemeContents = fs.readFileSync('themes/_default-theme.scss'); - - var scssGlob = path.join(config.paths, '*.scss'); + gutil.log("Building css files..."); return gulp.src(config.scssBaseFiles.concat(scssGlob)) .pipe(filterNonCodeFiles()) @@ -234,6 +234,7 @@ gulp.task('build-js', function() { .pipe(insert.wrap('(function() {\n', '})();\n')) .pipe(concat('angular-material.js')) .pipe(insert.prepend(config.banner)) + .pipe(ngAnnotate()) .pipe(gulp.dest(config.outputDir)) .pipe(gulpif(IS_RELEASE_BUILD, lazypipe() .pipe(uglify) @@ -299,36 +300,31 @@ function buildModuleStyles(name) { return fs.readFileSync(fileName, 'utf8').toString(); }).join('\n'); return lazypipe() - .pipe(insert.prepend, baseStyles) - .pipe(gulpif, /theme.scss/, - rename(name + '-default-theme.scss'), concat(name + '.scss') - ) - .pipe(sass) - .pipe(autoprefix) - .pipe(gulpif, IS_RELEASE_BUILD, minifyCss()) - (); // invoke the returning fn to create our pipe + .pipe(insert.prepend, baseStyles) + .pipe(gulpif, /theme.scss/, + rename(name + '-default-theme.scss'), concat(name + '.scss') + ) + .pipe(sass) + .pipe(autoprefix) + .pipe(gulpif, IS_RELEASE_BUILD, minifyCss()) + (); // invoke the returning fn to create our pipe } function buildModuleJs(name) { return lazypipe() - .pipe(insert.wrap, '(function() {\n', '})();\n') - .pipe(concat, name + '.js') - .pipe(gulpif, IS_RELEASE_BUILD, uglify({preserveComments: 'some'})) - (); + .pipe(ngAnnotate()) + .pipe(concat, name + '.js') + .pipe(gulpif, IS_RELEASE_BUILD, uglify({preserveComments: 'some'})) + (); } /** * Preconfigured gulp plugin invocations */ - function filterNonCodeFiles() { return filter(function(file) { - if (/demo/.test(file.path)) return false; - if (/README/.test(file.path)) return false; - if (/module\.json/.test(file.path)) return false; - if (/\.spec\.js/.test(file.path)) return false; - return true; + return !/demo|module\.json|\.spec.js|README/.test(file.path); }); } diff --git a/package.json b/package.json index fd7ae2edd09..80286aa25bf 100644 --- a/package.json +++ b/package.json @@ -5,32 +5,26 @@ "url": "git://github.com/angular/material.git" }, "devDependencies": { - "batch": "^0.5.1", "canonical-path": "0.0.2", "conventional-changelog": "0.0.9", "dgeni": "^0.4.1", "dgeni-packages": "^0.10.3", "esprima": "^1.2.2", - "event-stream": "^3.1.5", "glob": "~4.0.2", "gulp": "^3.6.2", "gulp-autoprefixer": "^1.0.1", "gulp-concat": "^2.2.0", "gulp-filter": "^1.0.2", - "gulp-footer": "^1.0.4", - "gulp-header": "^1.0.2", "gulp-if": "^1.2.0", "gulp-insert": "^0.4.0", "gulp-jshint": "^1.5.5", "gulp-minify-css": "^0.3.4", - "gulp-minify-html": "^0.1.6", + "gulp-ng-annotate": "^0.3.4", "gulp-ng-html2js": "^0.1.8", "gulp-rename": "^1.2.0", "gulp-replace": "^0.3.0", "gulp-sass": "ajoslin/gulp-sass#master", - "gulp-strip-debug": "^0.3.0", "gulp-uglify": "^0.3.0", - "gulp-uncss": "^0.4.5", "gulp-util": "^3.0.1", "gulp-webserver": "^0.8.3", "jshint-summary": "^0.3.0", diff --git a/src/components/animate/_effects.scss b/src/components/animate/_effects.scss deleted file mode 100644 index 9d86b578f6e..00000000000 --- a/src/components/animate/_effects.scss +++ /dev/null @@ -1,50 +0,0 @@ -// Button ripple: keep same opacity, but expand to 0.75 scale. -// Then, fade out and expand to 2.0 scale. -@keyframes inkRippleButton { - 0% { - transform: scale(0); - opacity: 0.15; - } - 50% { - transform: scale(0.75); - opacity: 0.15; - } - 100% { - transform: scale(2.0); - opacity: 0; - } -} - -// Checkbox ripple: fully expand, then fade out. -@keyframes inkRippleCheckbox { - 0% { - transform: scale(0); - opacity: 0.4; - } - 50% { - transform: scale(1.0); - opacity: 0.4; - } - 100% { - transform: scale(1.0); - opacity: 0; - } -} - -/* - * A container inside of a rippling element (eg a button), - * which contains all of the individual ripples - */ -.md-ripple-container { - pointer-events: none; - position: absolute; - overflow: hidden; - left: 0; - top: 0; - width: 100%; - height: 100%; -} - -.md-ripple { - position: absolute; -} diff --git a/src/components/animate/effects.js b/src/components/animate/effects.js deleted file mode 100644 index cb661eb410d..00000000000 --- a/src/components/animate/effects.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * @ngdoc module - * @name material.components.animate - * @description - * - * Ink and Popup Effects - */ -angular.module('material.animations', ['material.core']) - .service('$mdEffects', [ - '$rootElement', - '$$rAF', - '$sniffer', - '$q', - MdEffects - ]); - -/* - * @ngdoc service - * @name $mdEffects - * @module material.components.animate - * - * @description - * The `$mdEffects` service provides a simple API for various - * Material Design effects. - * - * @returns A `$mdEffects` object with the following properties: - * - `{function(element,styles,duration)}` `inkBar` - starts ink bar - * animation on specified DOM element - * - `{function(element,parentElement,clickElement)}` `popIn` - animated show of element overlayed on parent element - * - `{function(element,parentElement)}` `popOut` - animated close of popup overlay - * - */ -function MdEffects($rootElement, $$rAF, $sniffer, $q) { - - var webkit = /webkit/i.test($sniffer.vendorPrefix); - function vendorProperty(name) { - return webkit ? - ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : - name; - } - - var self; - // Publish API for effects... - return self = { - popIn: popIn, - - /* Constants */ - TRANSITIONEND_EVENT: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''), - ANIMATIONEND_EVENT: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''), - - TRANSFORM: vendorProperty('transform'), - TRANSITION: vendorProperty('transition'), - TRANSITION_DURATION: vendorProperty('transitionDuration'), - ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'), - ANIMATION_DURATION: vendorProperty('animationDuration'), - ANIMATION_NAME: vendorProperty('animationName'), - ANIMATION_TIMING: vendorProperty('animationTimingFunction'), - ANIMATION_DIRECTION: vendorProperty('animationDirection') - }; - - // ********************************************************** - // API Methods - // ********************************************************** - function popIn(element, parentElement, clickElement) { - var deferred = $q.defer(); - parentElement.append(element); - - var startPos; - if (clickElement) { - var clickRect = clickElement[0].getBoundingClientRect(); - startPos = translateString( - clickRect.left - element[0].offsetWidth, - clickRect.top - element[0].offsetHeight, - 0 - ) + ' scale(0.2)'; - } else { - startPos = 'translate3d(0,100%,0) scale(0.5)'; - } - - element - .css(self.TRANSFORM, startPos) - .css('opacity', 0); - - $$rAF(function() { - $$rAF(function() { - element - .addClass('md-active') - .css(self.TRANSFORM, '') - .css('opacity', '') - .on(self.TRANSITIONEND_EVENT, finished); - }); - }); - - function finished(ev) { - //Make sure this transitionend didn't bubble up from a child - if (ev.target === element[0]) { - element.off(self.TRANSITIONEND_EVENT, finished); - deferred.resolve(); - } - } - - return deferred.promise; - } - - // ********************************************************** - // Utility Methods - // ********************************************************** - - - function translateString(x, y, z) { - return 'translate3d(' + Math.floor(x) + 'px,' + Math.floor(y) + 'px,' + Math.floor(z) + 'px)'; - } - -} - diff --git a/src/components/animate/noEffect.js b/src/components/animate/noEffect.js deleted file mode 100644 index 3d8185ba7b6..00000000000 --- a/src/components/animate/noEffect.js +++ /dev/null @@ -1,46 +0,0 @@ -angular.module('material.animations') - -/** - * noink/nobar/nostretch directive: make any element that has one of - * these attributes be given a controller, so that other directives can - * `require:` these and see if there is a `no` parent attribute. - * - * @usage - * - * - * - * - * - * - * - * - * myApp.directive('detectNo', function() { - * return { - * require: ['^?noink', ^?nobar'], - * link: function(scope, element, attr, ctrls) { - * var noinkCtrl = ctrls[0]; - * var nobarCtrl = ctrls[1]; - * if (noInkCtrl) { - * alert("the noink flag has been specified on an ancestor!"); - * } - * if (nobarCtrl) { - * alert("the nobar flag has been specified on an ancestor!"); - * } - * } - * }; - * }); - * - */ -.directive({ - noink: attrNoDirective(), - nobar: attrNoDirective(), - nostretch: attrNoDirective() -}); - -function attrNoDirective() { - return function() { - return { - controller: angular.noop - }; - }; -} diff --git a/src/components/backdrop/_backdrop.js b/src/components/backdrop/_backdrop.js index 72a0bb09924..5f84fed4b3d 100644 --- a/src/components/backdrop/_backdrop.js +++ b/src/components/backdrop/_backdrop.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /* * @ngdoc module * @name material.components.backdrop @@ -16,14 +19,13 @@ * Apply class `opaque` to make the backdrop use the theme backdrop color. * */ + angular.module('material.components.backdrop', [ - 'material.services.theming' + 'material.core' ]) -.directive('mdBackdrop', [ - '$mdTheming', - BackdropDirective -]); + .directive('mdBackdrop', BackdropDirective); function BackdropDirective($mdTheming) { return $mdTheming; } +})(); diff --git a/src/components/bottomSheet/bottomSheet.js b/src/components/bottomSheet/bottomSheet.js index b28df3f8e64..78cc6bf36f7 100644 --- a/src/components/bottomSheet/bottomSheet.js +++ b/src/components/bottomSheet/bottomSheet.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.bottomSheet @@ -5,23 +8,11 @@ * BottomSheet */ angular.module('material.components.bottomSheet', [ - 'material.components.backdrop', - 'material.services.interimElement', - 'material.services.theming' -]) -.directive('mdBottomSheet', [ - MdBottomSheetDirective + 'material.core', + 'material.components.backdrop' ]) -.factory('$mdBottomSheet', [ - '$$interimElement', - '$animate', - '$mdEffects', - '$timeout', - '$$rAF', - '$compile', - '$mdTheming', - MdBottomSheet -]); + .directive('mdBottomSheet', MdBottomSheetDirective) + .provider('$mdBottomSheet', MdBottomSheetProvider); function MdBottomSheetDirective() { return { @@ -38,7 +29,7 @@ function MdBottomSheetDirective() { * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API. * * ### Restrictions - * + * * - The bottom sheet's template must have an outer `` 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. @@ -79,10 +70,10 @@ function MdBottomSheetDirective() { * 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, + * 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, + * - `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 @@ -98,7 +89,7 @@ function MdBottomSheetDirective() { * @name $mdBottomSheet#hide * * @description - * Hide the existing bottom sheet and resolve the promise returned from + * Hide the existing bottom sheet and resolve the promise returned from * `$mdBottomSheet.show()`. * * @param {*=} response An argument for the resolved promise. @@ -110,153 +101,165 @@ function MdBottomSheetDirective() { * @name $mdBottomSheet#cancel * * @description - * Hide the existing bottom sheet and reject the promise returned from + * Hide the existing bottom sheet and reject the promise returned from * `$mdBottomSheet.show()`. * * @param {*=} response An argument for the rejected promise. * */ -function MdBottomSheet($$interimElement, $animate, $mdEffects, $timeout, $$rAF, $compile, $mdTheming) { - var backdrop; - - var $mdBottomSheet; - return $mdBottomSheet = $$interimElement({ - themable: true, - targetEvent: null, - onShow: onShow, - onRemove: onRemove, - }); - - function onShow(scope, element, options) { - // Add a backdrop that will close on click - backdrop = $compile('')(scope); - backdrop.on('click touchstart', function() { - $timeout($mdBottomSheet.cancel); +function MdBottomSheetProvider($$interimElementProvider) { + + return $$interimElementProvider('$mdBottomSheet') + .setDefaults({ + options: bottomSheetDefaults }); - $mdTheming.inherit(backdrop, options.parent); - $animate.enter(backdrop, options.parent, null); + /* @ngInject */ + function bottomSheetDefaults($animate, $mdConstant, $timeout, $$rAF, $compile, $mdTheming, + $mdBottomSheet) { + var backdrop; - var bottomSheet = new BottomSheet(element); - options.bottomSheet = bottomSheet; + return { + themable: true, + targetEvent: null, + onShow: onShow, + onRemove: onRemove, + }; - // Give up focus on calling item - options.targetEvent && angular.element(options.targetEvent.target).blur(); - $mdTheming.inherit(bottomSheet.element, options.parent); + function onShow(scope, element, options) { + // Add a backdrop that will close on click + backdrop = $compile('')(scope); + backdrop.on('click touchstart', function() { + $timeout($mdBottomSheet.cancel); + }); + $mdTheming.inherit(backdrop, options.parent); - return $animate.enter(bottomSheet.element, options.parent); + $animate.enter(backdrop, options.parent, null); - } + var bottomSheet = new BottomSheet(element); + options.bottomSheet = bottomSheet; - function onRemove(scope, element, options) { - var bottomSheet = options.bottomSheet; - $animate.leave(backdrop); - return $animate.leave(bottomSheet.element).then(function() { - bottomSheet.cleanup(); + // Give up focus on calling item + options.targetEvent && angular.element(options.targetEvent.target).blur(); + $mdTheming.inherit(bottomSheet.element, options.parent); - // Restore focus - options.targetEvent && angular.element(options.targetEvent.target).focus(); - }); - } + return $animate.enter(bottomSheet.element, options.parent); - /** - * 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); + function onRemove(scope, element, options) { + var bottomSheet = options.bottomSheet; + $animate.leave(backdrop); + return $animate.leave(bottomSheet.element).then(function() { + bottomSheet.cleanup(); - element.on('touchstart', onTouchStart); - element.on('touchmove', onTouchMove); - element.on('touchend', onTouchEnd); + // Restore focus + options.targetEvent && angular.element(options.targetEvent.target).focus(); + }); + } - return { - element: element, - cleanup: function cleanup() { - element.off('touchstart', onTouchStart); - element.off('touchmove', onTouchMove); - element.off('touchend', onTouchEnd); - } - }; + /** + * 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; - function onTouchStart(e) { - e.preventDefault(); - startTarget = e.target; - startY = getY(e); - - // Disable transitions on transform so that it feels fast - transitionDelay = element.css($mdEffects.TRANSITION_DURATION); - element.css($mdEffects.TRANSITION_DURATION, '0s'); - } + // 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); - function onTouchEnd(e) { - // Re-enable the transitions on transforms - element.css($mdEffects.TRANSITION_DURATION, transitionDelay); + // Disable transitions on transform so that it feels fast + transitionDelay = element.css($mdConstant.CSS.TRANSITION_DURATION); + element.css($mdConstant.CSS.TRANSITION_DURATION, '0s'); + } - 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); + function onTouchEnd(e) { + // Re-enable the transitions on transforms + element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDelay); - // Otherwise, untransform so that we go back to our normal position + 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 { - setTransformY(undefined); + // 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; + 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); - } + velocity = currentY - lastY; + lastY = currentY; - /** - * 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; - } + // Do some conversion on delta to get a friction-like effect + delta = adjustedDelta(delta); + setTransformY(delta + MAX_OFFSET); + } - /** - * Transform the element along the y-axis - **/ - function setTransformY(amt) { - if (amt === null || amt === undefined) { - element.css($mdEffects.TRANSFORM, ''); - } else { - element.css($mdEffects.TRANSFORM, 'translate3d(0, ' + amt + 'px, 0)'); + /** + * 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; } - } - // 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); + /** + * 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)'); + } } - return delta; + // 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; + } } + } } + +})(); diff --git a/src/components/button/_button.scss b/src/components/button/_button.scss index 6f16dab403f..7ca774d3880 100644 --- a/src/components/button/_button.scss +++ b/src/components/button/_button.scss @@ -24,7 +24,6 @@ $button-fab-toast-offset: $toast-height + $toast-margin; user-select: none; position: relative; //for child absolute-positioned - display: inline-block; outline: none; border: 0; diff --git a/src/components/button/button.js b/src/components/button/button.js index 729f1c78154..702fb434428 100644 --- a/src/components/button/button.js +++ b/src/components/button/button.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.button @@ -6,17 +9,9 @@ * Button */ angular.module('material.components.button', [ - 'material.core', - 'material.animations', - 'material.services.aria', - 'material.services.theming', + 'material.core' ]) - .directive('mdButton', [ - '$mdInkRipple', - '$mdTheming', - '$mdAria', - MdButtonDirective - ]); + .directive('mdButton', MdButtonDirective); /** * @ngdoc directive @@ -93,3 +88,4 @@ function MdButtonDirective($mdInkRipple, $mdTheming, $mdAria) { } } +})(); diff --git a/src/components/card/card.js b/src/components/card/card.js index 9bb32bbada3..04744c2c87f 100644 --- a/src/components/card/card.js +++ b/src/components/card/card.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.card @@ -6,10 +9,9 @@ * Card components. */ angular.module('material.components.card', [ + 'material.core' ]) - .directive('mdCard', [ - mdCardDirective - ]); + .directive('mdCard', mdCardDirective); @@ -45,3 +47,4 @@ function mdCardDirective() { } }; } +})(); diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js index e83143e4502..19abc88ebdc 100644 --- a/src/components/checkbox/checkbox.js +++ b/src/components/checkbox/checkbox.js @@ -1,22 +1,15 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.checkbox * @description Checkbox module! */ angular.module('material.components.checkbox', [ - 'material.core', - 'material.animations', - 'material.services.theming', - 'material.services.aria' + 'material.core' ]) - .directive('mdCheckbox', [ - 'inputDirective', - '$mdInkRipple', - '$mdAria', - '$mdConstant', - '$mdTheming', - MdCheckboxDirective - ]); + .directive('mdCheckbox', MdCheckboxDirective); /** * @ngdoc directive @@ -53,8 +46,8 @@ angular.module('material.components.checkbox', [ * * */ -function MdCheckboxDirective(inputDirectives, $mdInkRipple, $mdAria, $mdConstant, $mdTheming) { - var inputDirective = inputDirectives[0]; +function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant, $mdTheming) { + inputDirective = inputDirective[0]; var CHECKED_CSS = 'md-checked'; @@ -135,4 +128,4 @@ function MdCheckboxDirective(inputDirectives, $mdInkRipple, $mdAria, $mdConstant } } - +})(); diff --git a/src/components/content/content.js b/src/components/content/content.js index c90a5f3bfb0..d1d2b02b52a 100644 --- a/src/components/content/content.js +++ b/src/components/content/content.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.content @@ -6,13 +9,9 @@ * Scrollable content */ angular.module('material.components.content', [ - 'material.services.theming', - 'material.services.registry' + 'material.core' ]) - .directive('mdContent', [ - '$mdTheming', - mdContentDirective - ]); + .directive('mdContent', mdContentDirective); /** * @ngdoc directive @@ -51,3 +50,4 @@ function mdContentDirective($mdTheming) { this.$element = $element; } } +})(); diff --git a/src/components/dialog/demoBasicUsage/dialog1.tmpl.html b/src/components/dialog/demoBasicUsage/dialog1.tmpl.html index b2d54b63fb3..8064772ecfa 100644 --- a/src/components/dialog/demoBasicUsage/dialog1.tmpl.html +++ b/src/components/dialog/demoBasicUsage/dialog1.tmpl.html @@ -1,14 +1,32 @@ - + + -

Permissions

-

This app wants every permission you can offer, plus more. It's likely doing something fishy, like beaming your contact info to aliens.

+ Mango (Fruit) +

+ 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. +

+ + + +

+ 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. +

+

+ 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. +

-
- - Cancel + +
+ + More on Wikipedia - - Okay + + + Not Useful + + + Useful
+ diff --git a/src/components/dialog/demoBasicUsage/dialog2.tmpl.html b/src/components/dialog/demoBasicUsage/dialog2.tmpl.html deleted file mode 100644 index 8064772ecfa..00000000000 --- a/src/components/dialog/demoBasicUsage/dialog2.tmpl.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - Mango (Fruit) -

- 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. -

- - - -

- 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. -

-

- 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. -

-
- -
- - More on Wikipedia - - - - Not Useful - - - Useful - -
- -
diff --git a/src/components/dialog/demoBasicUsage/index.html b/src/components/dialog/demoBasicUsage/index.html index 8b3022223ad..c9e09b2c3c8 100644 --- a/src/components/dialog/demoBasicUsage/index.html +++ b/src/components/dialog/demoBasicUsage/index.html @@ -4,12 +4,16 @@

- - Simple Dialog + + Alert Dialog
- - Complex Dialog + + Confirm Dialog + +
+ + Custom Dialog
diff --git a/src/components/dialog/demoBasicUsage/script.js b/src/components/dialog/demoBasicUsage/script.js index 9391f1e5907..dcb5f3ff3ea 100644 --- a/src/components/dialog/demoBasicUsage/script.js +++ b/src/components/dialog/demoBasicUsage/script.js @@ -1,36 +1,47 @@ angular.module('dialogDemo1', ['ngMaterial']) - .controller('AppCtrl', function($scope, $mdDialog, $log) { - $scope.alert = ''; - - $scope.dialogBasic = function(ev) { - $mdDialog.show({ - templateUrl: 'dialog1.tmpl.html', - targetEvent: ev, - controller: DialogController - }).then(function() { - $scope.alert = 'You said "Okay".'; - }, function() { - $scope.alert = 'You cancelled the dialog.'; - }); - }; - - $scope.dialogAdvanced = function(ev) { - $log.debug("dialogAdvanced() preparing to show..."); - $mdDialog.show({ - templateUrl: 'dialog2.tmpl.html', - targetEvent: ev, - controller: DialogController, - onComplete:function(){ - $log.debug("dialogAdvanced() now shown!"); - } - }).then(function(answer) { - $scope.alert = 'You said the information was "' + answer + '".'; - }, function() { - $scope.alert = 'You cancelled the dialog.'; - }); - }; - }); +.controller('AppCtrl', function($scope, $mdDialog) { + $scope.alert = ''; + + $scope.showAlert = function(ev) { + $mdDialog.show( + $mdDialog.alert() + .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() { diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index 3e44903eede..aa34e95923b 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -1,34 +1,16 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.dialog */ angular.module('material.components.dialog', [ 'material.core', - 'material.animations', - 'material.components.backdrop', - 'material.services.compiler', - 'material.services.aria', - 'material.services.interimElement', - 'material.services.theming', + 'material.components.backdrop' ]) - .directive('mdDialog', [ - '$$rAF', - '$mdTheming', - MdDialogDirective - ]) - .factory('$mdDialog', [ - '$timeout', - '$rootElement', - '$compile', - '$mdEffects', - '$animate', - '$mdAria', - '$$interimElement', - '$mdUtil', - '$mdConstant', - '$mdTheming', - MdDialogService - ]); + .directive('mdDialog', MdDialogDirective) + .provider('$mdDialog', MdDialogProvider); function MdDialogDirective($$rAF, $mdTheming) { return { @@ -58,7 +40,7 @@ function MdDialogDirective($$rAF, $mdTheming) { * - The dialog is always given an isolate scope. * - The dialog's template must have an outer `` element. * Inside, use an `` element for the dialog's content, and use - * an element with class `md-actions` for the dialog's actions. + * an element with class `md-actions` for the dialog's actions. * * @usage * @@ -73,50 +55,62 @@ function MdDialogDirective($$rAF, $mdTheming) { * var app = angular.module('app', ['ngMaterial']); * app.controller('MyController', function($scope, $mdDialog) { * $scope.openDialog = function($event) { - * $mdDialog.show({ - * targetEvent: $event, - * template: - * '' + - * ' Hello {{ userName }}!' + - * '
' + - * ' ' + - * ' Close' + - * ' ' + - * '
' + - * '
', - * controller: 'DialogController', - * onComplete: afterShowAnimation, - * locals: { name: 'Bobby' } - * }); - * - * // When the 'enter' animation finishes... - * function afterShowAnimation(scope, element, options) { - * // post-show code here: DOM element focus, etc. - * } - * }); - * app.controller('DialogController', function($scope, $mdDialog, name) { - * $scope.userName = name; - * $scope.closeDialog = function() { - * $mdDialog.hide(); - * }; - * }); + * $mdDialog.show( + * $mdDialog.alert() + * .title('Hello, ' + $scope.userName) + * .content('This is an example of how easy dialogs can be!') + * ); + * }); *
+ */ + + /** + * @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} options An options object, with the following properties: + * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `alert()`, + * `confirm()`, and `build()`, 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. + * 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, + * - `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. * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog. @@ -129,7 +123,9 @@ function MdDialogDirective($$rAF, $mdTheming) { * 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. + * `three` into the controller, with the value 3. If `bindToController` is true, they will be + * coppied 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 * toast will not open until all of the promises resolve. * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope. @@ -150,7 +146,6 @@ function MdDialogDirective($$rAF, $mdTheming) { * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`. * * @param {*=} response An argument for the resolved promise. - * */ /** @@ -161,114 +156,210 @@ function MdDialogDirective($$rAF, $mdTheming) { * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`. * * @param {*=} response An argument for the rejected promise. - * */ -function MdDialogService($timeout, $rootElement, $compile, $mdEffects, $animate, $mdAria, $$interimElement, $mdUtil, $mdConstant, $mdTheming) { - - var $dialogService; - return $dialogService = $$interimElement({ - hasBackdrop: true, - isolateScope: true, - onShow: onShow, - onRemove: onRemove, - clickOutsideToClose: true, - escapeToClose: true, - targetEvent: null, - transformTemplate: function(template) { - return '
' + template + '
'; - } - }); +function MdDialogProvider($$interimElementProvider) { - function onShow(scope, element, options) { - // Incase the user provides a raw dom element, always wrap it in jqLite - options.parent = angular.element(options.parent); + var alertDialogMethods = ['title', 'content', 'ariaLabel', 'ok']; - options.popInTarget = angular.element((options.targetEvent || {}).target); - var closeButton = findCloseButton(); + return $$interimElementProvider('$mdDialog') + .setDefaults({ + methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'], + options: dialogDefaultOptions + }) + .addPreset('alert', { + methods: alertDialogMethods, + options: advancedDialogOptions + }) + .addPreset('confirm', { + methods: alertDialogMethods.concat('cancel'), + options: advancedDialogOptions + }); - configureAria(element.find('md-dialog')); + /* @ngInject */ + function advancedDialogOptions($mdDialog) { + return { + template: [ + '', + '', + '

{{ dialog.title }}

', + '

{{ dialog.content }}

', + '
', + '
', + '', + '{{ dialog.cancel }}', + '', + '', + '{{ dialog.ok }}', + '', + '
', + '
' + ].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, + $mdUtil, $mdConstant, $mdTheming, $$rAF, $q, $mdDialog) { + return { + hasBackdrop: true, + isolateScope: true, + onShow: onShow, + onRemove: onRemove, + clickOutsideToClose: true, + escapeToClose: true, + targetEvent: null, + transformTemplate: function(template) { + return '
' + template + '
'; + } + }; + + // 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 = $compile('')(scope); + $mdTheming.inherit(options.backdrop, options.parent); + $animate.enter(options.backdrop, options.parent, null); + } + + return dialogPopIn( + element, + options.parent, + 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); + } - if (options.hasBackdrop) { - options.backdrop = $compile('')(scope); - $mdTheming.inherit(options.backdrop, options.parent); - $animate.enter(options.backdrop, options.parent, null); } - return $mdEffects.popIn( - element, - options.parent, - options.popInTarget.length && options.popInTarget - ) - .then(function() { + // On remove function for all dialogs + function onRemove(scope, element, options) { + + if (options.backdrop) { + $animate.leave(options.backdrop); + element.data('backdrop', undefined); + } if (options.escapeToClose) { - options.rootElementKeyupCallback = function(e) { - if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) { - $timeout($dialogService.cancel); - } - }; - $rootElement.on('keyup', options.rootElementKeyupCallback); + $rootElement.off('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($dialogService.cancel); - } - }; - element.on('click', options.dialogClickOutsideCallback); + element.off('click', options.dialogClickOutsideCallback); } - closeButton.focus(); - }); + return $animate.leave(element).then(function() { + element.remove(); + options.popInTarget && options.popInTarget.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 ]; + /** + * 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; } - return angular.element(closeButton); + $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(element, parentElement, clickElement) { + var deferred = $q.defer(); + parentElement.append(element); - function onRemove(scope, element, options) { + var startPos; + if (clickElement) { + var clickRect = clickElement[0].getBoundingClientRect(); + startPos = 'translate3d(' + + (clickRect.left - element[0].offsetWidth) + 'px,' + + (clickRect.top - element[0].offsetHeight) + 'px,' + + '0) scale(0.2)'; + } else { + startPos = 'translate3d(0,100%,0) scale(0.5)'; + } - if (options.backdrop) { - $animate.leave(options.backdrop); - element.data('backdrop', undefined); - } - if (options.escapeToClose) { - $rootElement.off('keyup', options.rootElementKeyupCallback); - } - if (options.clickOutsideToClose) { - element.off('click', options.dialogClickOutsideCallback); - } - return $animate.leave(element).then(function() { - element.remove(); - options.popInTarget && options.popInTarget.focus(); - }); + element + .css($mdConstant.CSS.TRANSFORM, startPos) + .css('opacity', 0); - } + $$rAF(function() { + $$rAF(function() { + element + .addClass('md-active') + .css($mdConstant.CSS.TRANSFORM, '') + .css('opacity', '') + .on($mdConstant.CSS.TRANSITIONEND, finished); + }); + }); - /** - * Inject ARIA-specific attributes appropriate for Dialogs - */ - function configureAria(element) { - element.attr({ - 'role': 'dialog' - }); + function finished(ev) { + //Make sure this transitionend didn't bubble up from a child + if (ev.target === element[0]) { + element.off($mdConstant.CSS.TRANSITIONEND, finished); + deferred.resolve(); + } + } - var dialogContent = element.find('md-content'); - if (dialogContent.length === 0){ - dialogContent = element; + return deferred.promise; } - $mdAria.expectAsync(element, 'aria-label', function() { - return $mdUtil.stringFromTextBody(dialogContent.text(), 3); - }); } } + +})(); diff --git a/src/components/dialog/dialog.spec.js b/src/components/dialog/dialog.spec.js index 20582c0a881..ae7e64603f8 100644 --- a/src/components/dialog/dialog.spec.js +++ b/src/components/dialog/dialog.spec.js @@ -1,288 +1,402 @@ describe('$mdDialog', function() { - + beforeEach(TestUtil.mockRaf); beforeEach(module('material.components.dialog', 'ngAnimateMock')); - beforeEach(inject(function spyOnMdEffects($mdEffects, $$q, $animate) { - spyOn($mdEffects, 'popIn').andCallFake(function(element, parent, targetEvent) { - parent.append(element); - return $$q.when(); - }); + 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(); }); })); - it('should support onComplete callbacks within `show()`', inject(function($mdDialog, $rootScope, $timeout) { - - var template = 'Hello'; - var parent = angular.element('
'); - 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 ); - - $timeout.flush(); - - var 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 = 'Hello'; - var parent = angular.element('
'); - - $mdDialog.show({ - template: template, - parent: parent - }); - - $rootScope.$apply(); - - var container = angular.element(parent[0].querySelector('.md-dialog-container')); - expect(container.length).toBe(1); - })); - - it('should escapeToClose == true', inject(function($mdDialog, $rootScope, $rootElement, $timeout, $mdEffects, $animate, $mdConstant) { - var parent = angular.element('
'); - $mdDialog.show({ - template: '', - parent: parent, - escapeToClose: true - }); - - $rootScope.$apply(); - $timeout.flush(); - expect(parent.find('md-dialog').length).toBe(1); - - $rootElement.triggerHandler({ - type: 'keyup', - keyCode: $mdConstant.KEY_CODE.ESCAPE - }); - - $timeout.flush(); - expect(parent.find('md-dialog').length).toBe(0); - })); - - it('should escapeToClose == false', inject(function($mdDialog, $rootScope, $rootElement, $timeout, $mdEffects, $animate, $mdConstant) { - var parent = angular.element('
'); - $mdDialog.show({ - template: '', - parent: parent, - escapeToClose: false - }); - - $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, $mdEffects, $animate) { - - var parent = angular.element('
'); - $mdDialog.show({ - template: '', - parent: parent, - clickOutsideToClose: true - }); - - $rootScope.$apply(); - expect(parent.find('md-dialog').length).toBe(1); - $timeout.flush(); - - var container = angular.element(parent[0].querySelector('.md-dialog-container')); - container.triggerHandler({ - type: 'click', - target: container[0] - }); - $timeout.flush(); - - expect(parent.find('md-dialog').length).toBe(0); - })); - - it('should clickOutsideToClose == false', inject(function($mdDialog, $rootScope, $timeout, $mdEffects, $animate) { - - var parent = angular.element('
'); - $mdDialog.show({ - template: '', - parent: parent, - clickOutsideToClose: false - }); - - $rootScope.$apply(); - expect(parent.find('md-dialog').length).toBe(1); - - var container = angular.element(parent[0].querySelectorAll('.md-dialog-container')); - container.triggerHandler({ - type: 'click', - target: container[0] - }); - $timeout.flush(); - $animate.triggerCallbacks(); - - expect(parent.find('md-dialog').length).toBe(1); - })); - - it('should hasBackdrop == true', inject(function($mdDialog, $animate, $rootScope) { - var parent = angular.element('
'); - $mdDialog.show({ - template: '', - 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('
'); - $mdDialog.show({ - template: '', - parent: parent, - hasBackdrop: false - }); - - $rootScope.$apply(); - expect(parent.find('md-dialog').length).toBe(1); - expect(parent.find('md-backdrop').length).toBe(0); - })); - - it('should focus `md-button.dialog-close` on open', inject(function($mdDialog, $rootScope, $document, $timeout) { - TestUtil.mockElementFocus(this); - - var parent = angular.element('
'); - $mdDialog.show({ - template: - '' + - '
' + - '' + - '
' + + describe('#alert()', function() { + hasConfigurationMethods([ + 'title', 'content', 'ariaLabel', + 'ok', 'targetEvent' + ]); + + it('shows a basic alert dialog', inject(function($animate, $rootScope, $mdDialog, $mdConstant) { + var parent = angular.element('
'); + 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(); + $animate.triggerCallbacks(); + expect(parent.find('h2').length).toBe(0); + expect(resolved).toBe(true); + })); + + function hasConfigurationMethods(methods) { + angular.forEach(methods, function(method) { + return it('supports config method #' + method, inject(function($mdDialog) { + var alert = $mdDialog.alert(); + expect(typeof alert[method]).toBe('function'); + expect(alert[method]()).toEqual(alert); + })); + }); + } + }); + + describe('#confirm()', function() { + hasConfigurationMethods([ + 'title', 'content', 'ariaLabel', + 'ok', 'cancel', 'targetEvent' + ]); + + it('shows a basic confirm dialog', inject(function($rootScope, $mdDialog, $animate, $mdConstant) { + var parent = angular.element('
'); + 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(); + $animate.triggerCallbacks(); + expect(parent.find('h2').length).toBe(0); + expect(rejected).toBe(true); + })); + + function hasConfigurationMethods(methods) { + angular.forEach(methods, function(method) { + return it('supports config method #' + method, inject(function($mdDialog) { + var alert = $mdDialog.confirm(); + expect(typeof alert[method]).toBe('function'); + expect(alert[method]()).toEqual(alert); + })); + }); + } + }); + + describe('#build()', function() { + it('should support onComplete callbacks within `show()`', inject(function($mdDialog, $rootScope, $timeout, $mdConstant) { + + var template = 'Hello'; + var parent = angular.element('
'); + 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')); + container.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 = 'Hello'; + var parent = angular.element('
'); + + $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('
'); + $mdDialog.show({ + template: '', + parent: parent, + escapeToClose: true + }); + $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(); + expect(parent.find('md-dialog').length).toBe(0); + })); + + it('should escapeToClose == false', inject(function($mdDialog, $rootScope, $rootElement, $timeout, $animate, $mdConstant) { + var parent = angular.element('
'); + $mdDialog.show({ + template: '', + 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('
'); + $mdDialog.show({ + template: '', + parent: parent, + clickOutsideToClose: true + }); + $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); + + container.triggerHandler({ + type: 'click', + target: container[0] + }); + $timeout.flush(); + container.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('
'); + $mdDialog.show({ + template: '', + 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 hasBackdrop == true', inject(function($mdDialog, $animate, $rootScope) { + var parent = angular.element('
'); + $mdDialog.show({ + template: '', + 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('
'); + $mdDialog.show({ + template: '', + 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('
'); + $mdDialog.show({ + template: + '' + + '
' + + '' + + '
' + + '
', + parent: parent + }); + + $rootScope.$apply(); + $timeout.flush(); + var container = angular.element(parent[0].querySelector('.md-dialog-container')); + container.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('
'); + $mdDialog.show({ + template: + '' + + '
' + + '
' + '
', - parent: parent - }); - - $rootScope.$apply(); - $timeout.flush(); - - 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) { - TestUtil.mockElementFocus(this); - - var parent = angular.element('
'); - $mdDialog.show({ - template: - '' + - '
' + - '
' + - '
', - parent: parent - }); - - $rootScope.$apply(); - $timeout.flush(); - - 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('
'); - $mdDialog.show({ - template: '', - parent: parent - }); - - $rootScope.$apply(); - expect(angular.element(parent[0].querySelectorAll('md-dialog.one')).length).toBe(1); - expect(angular.element(parent[0].querySelectorAll('md-dialog.two')).length).toBe(0); - - $mdDialog.show({ - template: '', - parent: parent - }); - - $rootScope.$apply(); - expect(angular.element(parent[0].querySelectorAll('md-dialog.one')).length).toBe(0); - expect(angular.element(parent[0].querySelectorAll('md-dialog.two')).length).toBe(1); - })); - - it('should have the dialog role', inject(function($mdDialog, $rootScope) { - var template = 'Hello'; - var parent = angular.element('
'); - - $mdDialog.show({ - template: template, - parent: parent - }); - - $rootScope.$apply(); - - var dialog = parent.find('md-dialog'); - expect(dialog.attr('role')).toBe('dialog'); - })); - - it('should create an ARIA label if one is missing', inject(function($mdDialog, $rootScope) { - var template = 'Hello'; - var parent = angular.element('
'); - - $mdDialog.show({ - template: template, - parent: parent - }); - - $rootScope.$apply(); - - var dialog = parent.find('md-dialog'); - expect(dialog.attr('aria-label')).toEqual(dialog.text()); - })); - - it('should not modify an existing ARIA label', inject(function($mdDialog, $rootScope){ - var template = 'Hello'; - var parent = angular.element('
'); - - $mdDialog.show({ - template: template, - parent: parent - }); - - $rootScope.$apply(); - - var dialog = parent.find('md-dialog'); - expect(dialog.attr('aria-label')).not.toEqual(dialog.text()); - expect(dialog.attr('aria-label')).toEqual('Some Other Thing'); - })); + parent: parent + }); + + $rootScope.$apply(); + $timeout.flush(); + + var container = angular.element(parent[0].querySelector('.md-dialog-container')); + container.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('
'); + $mdDialog.show({ + template: '', + 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: '', + parent: parent + }); + + $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 = 'Hello'; + var parent = angular.element('
'); + + $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 = 'Hello'; + var parent = angular.element('
'); + + $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 = 'Hello'; + var parent = angular.element('
'); + + $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'); + })); + }); }); diff --git a/src/components/divider/divider.js b/src/components/divider/divider.js index f9e6a391326..78ae8b4800a 100644 --- a/src/components/divider/divider.js +++ b/src/components/divider/divider.js @@ -1,17 +1,15 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.divider * @description Divider module! */ angular.module('material.components.divider', [ - 'material.animations', - 'material.services.aria', - 'material.services.theming' + 'material.core' ]) -.directive('mdDivider', [ - '$mdTheming', - MdDividerDirective -]); + .directive('mdDivider', MdDividerDirective); function MdDividerController(){} @@ -40,3 +38,4 @@ function MdDividerDirective($mdTheming) { controller: [MdDividerController] }; } +})(); diff --git a/src/components/icon/icon.js b/src/components/icon/icon.js index d25d2177697..e43b8d1c55b 100644 --- a/src/components/icon/icon.js +++ b/src/components/icon/icon.js @@ -1,13 +1,16 @@ +(function() { +'use strict'; + /* * @ngdoc module * @name material.components.icon * @description * Icon */ -angular.module('material.components.icon', []) - .directive('mdIcon', [ - mdIconDirective - ]); +angular.module('material.components.icon', [ + 'material.core' +]) + .directive('mdIcon', mdIconDirective); /* * @ngdoc directive @@ -38,3 +41,4 @@ function mdIconDirective() { } }; } +})(); diff --git a/src/components/list/list.js b/src/components/list/list.js index f1420ffc69f..3c5c07edf6d 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -1,17 +1,17 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.list * @description * List module */ -angular.module('material.components.list', []) - -.directive('mdList', [ - mdListDirective +angular.module('material.components.list', [ + 'material.core' ]) -.directive('mdItem', [ - mdItemDirective -]); + .directive('mdList', mdListDirective) + .directive('mdItem', mdItemDirective); /** * @ngdoc directive @@ -84,3 +84,4 @@ function mdItemDirective() { } }; } +})(); diff --git a/src/components/progressCircular/README.md b/src/components/progressCircular/README.md deleted file mode 100644 index 77e00748385..00000000000 --- a/src/components/progressCircular/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. Each operation should only be represented by one activity indicator—for example, one refresh operation should not display both a refresh bar and an activity circle. - -[Circular Progress Indicators](https://www.google.com/design/spec/components/progress-activity.html#progress-activity-types-of-indicators) are created with the `` directive. - diff --git a/src/components/progressCircular/module.json b/src/components/progressCircular/module.json deleted file mode 100644 index 6b31d7542ce..00000000000 --- a/src/components/progressCircular/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.progressCircular", - "name": "Progress - Circular", - "demos": { - "demo1": { - "name": "Circular Progress Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/progressCircular/progressCircular.js b/src/components/progressCircular/progressCircular.js index f0f4f900299..613d7a8780f 100644 --- a/src/components/progressCircular/progressCircular.js +++ b/src/components/progressCircular/progressCircular.js @@ -1,19 +1,15 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.progressCircular * @description Circular Progress module! */ angular.module('material.components.progressCircular', [ - 'material.animations', - 'material.services.aria', - 'material.services.theming', + 'material.core' ]) - .directive('mdProgressCircular', [ - '$$rAF', - '$mdEffects', - '$mdTheming', - MdProgressCircularDirective - ]); + .directive('mdProgressCircular', MdProgressCircularDirective); /** * @ngdoc directive @@ -43,7 +39,7 @@ angular.module('material.components.progressCircular', [ * * */ -function MdProgressCircularDirective($$rAF, $mdEffects, $mdTheming) { +function MdProgressCircularDirective($$rAF, $mdConstant, $mdTheming) { var fillRotations = new Array(101), fixRotations = new Array(101); @@ -90,7 +86,7 @@ function MdProgressCircularDirective($$rAF, $mdEffects, $mdTheming) { var diameter = attr.diameter || 48; var scale = diameter/48; - circle.style[$mdEffects.TRANSFORM] = 'scale(' + scale.toString() + ')'; + circle.style[$mdConstant.CSS.TRANSFORM] = 'scale(' + scale.toString() + ')'; attr.$observe('value', function(value) { clamped = clamp(value); @@ -100,11 +96,11 @@ function MdProgressCircularDirective($$rAF, $mdEffects, $mdTheming) { element.attr('aria-valuenow', clamped); for (i = 0; i < fill.length; i++) { - fill[i].style[$mdEffects.TRANSFORM] = fillRotation; + fill[i].style[$mdConstant.CSS.TRANSFORM] = fillRotation; } for (i = 0; i < fix.length; i++) { - fix[i].style[$mdEffects.TRANSFORM] = fixRotation; + fix[i].style[$mdConstant.CSS.TRANSFORM] = fixRotation; } }); } @@ -121,3 +117,4 @@ function MdProgressCircularDirective($$rAF, $mdEffects, $mdTheming) { return Math.ceil(value || 0); } } +})(); diff --git a/src/components/progressLinear/README.md b/src/components/progressLinear/README.md deleted file mode 100644 index b9e6c04f73e..00000000000 --- a/src/components/progressLinear/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. Each operation should only be represented by one activity indicator—for example, one refresh operation should not display both a refresh bar and an activity circle. - -[Linear Progress Bars](https://www.google.com/design/spec/components/progress-activity.html#progress-activity-types-of-indicators) are created with the `` directives. diff --git a/src/components/progressLinear/module.json b/src/components/progressLinear/module.json deleted file mode 100644 index bafa6442e1c..00000000000 --- a/src/components/progressLinear/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.progressLinear", - "name": "Progress - Linear", - "demos": { - "demo1": { - "name": "Linear Progress Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/progressLinear/progressLinear.js b/src/components/progressLinear/progressLinear.js index 40ed6a50cf6..e77fb6e93dd 100644 --- a/src/components/progressLinear/progressLinear.js +++ b/src/components/progressLinear/progressLinear.js @@ -1,19 +1,16 @@ +(function() { +'use strict'; + + /** * @ngdoc module * @name material.components.progressLinear * @description Linear Progress module! */ angular.module('material.components.progressLinear', [ - 'material.animations', - 'material.services.theming', - 'material.services.aria' + 'material.core' ]) -.directive('mdProgressLinear', [ - '$$rAF', - '$mdEffects', - '$mdTheming', - MdProgressLinearDirective -]); + .directive('mdProgressLinear', MdProgressLinearDirective); /** * @ngdoc directive @@ -45,7 +42,7 @@ angular.module('material.components.progressLinear', [ * * */ -function MdProgressLinearDirective($$rAF, $mdEffects, $mdTheming) { +function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) { return { restrict: 'E', @@ -77,11 +74,11 @@ function MdProgressLinearDirective($$rAF, $mdEffects, $mdTheming) { var clamped = clamp(value); element.attr('aria-valuenow', clamped); - bar2Style[$mdEffects.TRANSFORM] = progressLinearTransforms[clamped]; + bar2Style[$mdConstant.CSS.TRANSFORM] = transforms[clamped]; }); attr.$observe('secondaryvalue', function(value) { - bar1Style[$mdEffects.TRANSFORM] = progressLinearTransforms[clamp(value)]; + bar1Style[$mdConstant.CSS.TRANSFORM] = transforms[clamp(value)]; }); $$rAF(function() { @@ -106,7 +103,7 @@ function MdProgressLinearDirective($$rAF, $mdEffects, $mdTheming) { // ********************************************************** // Private Methods // ********************************************************** -var progressLinearTransforms = (function() { +var transforms = (function() { var values = new Array(101); for(var i = 0; i < 101; i++){ values[i] = makeTransform(i); @@ -120,3 +117,5 @@ var progressLinearTransforms = (function() { return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)'; } })(); + +})(); diff --git a/src/components/progressLinear/progressLinear.spec.js b/src/components/progressLinear/progressLinear.spec.js index d75ce9a04aa..d8c0228a518 100644 --- a/src/components/progressLinear/progressLinear.spec.js +++ b/src/components/progressLinear/progressLinear.spec.js @@ -2,7 +2,7 @@ describe('mdProgressLinear', function() { beforeEach(module('material.components.progressLinear')); - it('should set transform based on value', inject(function($compile, $rootScope, $mdEffects) { + it('should set transform based on value', inject(function($compile, $rootScope, $mdConstant) { var element = $compile('
' + '' + '' + @@ -15,7 +15,7 @@ describe('mdProgressLinear', function() { var progress = element.find('md-progress-linear'), bar2 = angular.element(progress[0].querySelectorAll('.md-bar2'))[0]; - expect(bar2.style[$mdEffects.TRANSFORM]).toEqual('translateX(-25%) scale(0.5, 1)'); + expect(bar2.style[$mdConstant.CSS.TRANSFORM]).toEqual('translateX(-25%) scale(0.5, 1)'); })); it('should update aria-valuenow', inject(function($compile, $rootScope) { @@ -33,7 +33,7 @@ describe('mdProgressLinear', function() { expect(progress.eq(0).attr('aria-valuenow')).toEqual('50'); })); - it('should set transform based on secondaryvalue', inject(function($compile, $rootScope, $mdEffects) { + it('should set transform based on secondaryvalue', inject(function($compile, $rootScope, $mdConstant) { var element = $compile('
' + '' + '' + @@ -47,10 +47,10 @@ describe('mdProgressLinear', function() { var progress = element.find('md-progress-linear'), bar1 = angular.element(progress[0].querySelectorAll('.md-bar1'))[0]; - expect(bar1.style[$mdEffects.TRANSFORM]).toEqual('translateX(-12.5%) scale(0.75, 1)'); + 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, $mdEffects) { + it('should not set transform in query mode', inject(function($compile, $rootScope, $mdConstant) { var element = $compile('
' + '' + '' + @@ -63,6 +63,6 @@ describe('mdProgressLinear', function() { var progress = element.find('md-progress-linear'), bar2 = angular.element(progress[0].querySelectorAll('.md-bar2'))[0]; - expect(bar2.style[$mdEffects.TRANSFORM]).toBeFalsy(); + expect(bar2.style[$mdConstant.CSS.TRANSFORM]).toBeFalsy(); })); }); diff --git a/src/components/radioButton/README.md b/src/components/radioButton/README.md deleted file mode 100644 index 65d2ae3f0d4..00000000000 --- a/src/components/radioButton/README.md +++ /dev/null @@ -1 +0,0 @@ -Radio buttons, created using the `` parent with `` children. diff --git a/src/components/radioButton/module.json b/src/components/radioButton/module.json deleted file mode 100644 index 2f0ab993ede..00000000000 --- a/src/components/radioButton/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.radioButton", - "name": "Radio Button", - "demos": { - "demo1": { - "name": "Radio Button Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/radioButton/radioButton.js b/src/components/radioButton/radioButton.js index 79055804d47..8ae10671570 100644 --- a/src/components/radioButton/radioButton.js +++ b/src/components/radioButton/radioButton.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module @@ -5,23 +8,10 @@ * @description radioButton module! */ angular.module('material.components.radioButton', [ - 'material.core', - 'material.animations', - 'material.services.aria', - 'material.services.theming' + 'material.core' ]) - .directive('mdRadioGroup', [ - '$mdUtil', - '$mdConstant', - '$mdTheming', - mdRadioGroupDirective - ]) - .directive('mdRadioButton', [ - '$mdAria', - '$mdUtil', - '$mdTheming', - mdRadioButtonDirective - ]); + .directive('mdRadioGroup', mdRadioGroupDirective) + .directive('mdRadioButton', mdRadioButtonDirective); /** * @ngdoc directive @@ -272,4 +262,4 @@ function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) { } } - +})(); diff --git a/src/components/sidenav/README.md b/src/components/sidenav/README.md deleted file mode 100644 index 01f9c9ee9fe..00000000000 --- a/src/components/sidenav/README.md +++ /dev/null @@ -1 +0,0 @@ -Left and right side navigation, created using the `` directive. diff --git a/src/services/media/media.spec.js b/src/components/sidenav/media.spec.js similarity index 95% rename from src/services/media/media.spec.js rename to src/components/sidenav/media.spec.js index 3a70d2d58f9..01704b4ff32 100644 --- a/src/services/media/media.spec.js +++ b/src/components/sidenav/media.spec.js @@ -1,6 +1,6 @@ describe('$mdMedia', function() { - beforeEach(module('material.services.media')); + beforeEach(module('material.components.sidenav')); var matchMediaResult = false; beforeEach(inject(function($window) { diff --git a/src/components/sidenav/module.json b/src/components/sidenav/module.json deleted file mode 100644 index 4ee227e0af5..00000000000 --- a/src/components/sidenav/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.sidenav", - "name": "Side Navigation", - "demos": { - "demo1": { - "name": "Side Navigation Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/services/registry/registry.spec.js b/src/components/sidenav/registry.spec.js similarity index 94% rename from src/services/registry/registry.spec.js rename to src/components/sidenav/registry.spec.js index 6f95b5ea501..9f4558c845a 100644 --- a/src/services/registry/registry.spec.js +++ b/src/components/sidenav/registry.spec.js @@ -1,5 +1,5 @@ describe('$mdComponentRegistry Service', function() { - beforeEach(module('material.services.registry')); + beforeEach(module('material.components.sidenav')); it('should print error on no handle', inject(function($mdComponentRegistry, $log) { spyOn($log, 'error'); diff --git a/src/components/sidenav/sidenav.js b/src/components/sidenav/sidenav.js index 7ac2aa5a521..f2f348d0632 100644 --- a/src/components/sidenav/sidenav.js +++ b/src/components/sidenav/sidenav.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.sidenav @@ -7,36 +10,14 @@ */ angular.module('material.components.sidenav', [ 'material.core', - 'material.services.registry', - 'material.services.media', - 'material.components.backdrop', - 'material.services.theming', - 'material.animations' + 'material.components.backdrop' ]) - .factory('$mdSidenav', [ - '$mdComponentRegistry', - mdSidenavService - ]) - .directive('mdSidenav', [ - '$timeout', - '$animate', - '$parse', - '$mdMedia', - '$mdConstant', - '$compile', - '$mdTheming', - mdSidenavDirective - ]) - .controller('$mdSidenavController', [ - '$scope', - '$element', - '$attrs', - '$timeout', - '$mdSidenav', - '$mdComponentRegistry', - mdSidenavController - ]); - + .factory('$mdSidenav', mdSidenavService ) + .directive('mdSidenav', mdSidenavDirective) + .controller('$mdSidenavController', mdSidenavController) + .factory('$mdMedia', mdMediaFactory) + .factory('$mdComponentRegistry', mdComponentRegistry); + /* * @private * @ngdoc object @@ -249,3 +230,108 @@ function mdSidenavDirective($timeout, $animate, $parse, $mdMedia, $mdConstant, $ } } + +/** + * Exposes a function on the '$mdMedia' service which will return true or false, + * whether the given media query matches. Re-evaluates on resize. Allows presets + * for 'sm', 'md', 'lg'. + * + * @example $mdMedia('sm') == true if device-width <= sm + * @example $mdMedia('(min-width: 1200px)') == true if device-width >= 1200px + * @example $mdMedia('max-width: 300px') == true if device-width <= 300px (sanitizes input, adding parens) + */ +function mdMediaFactory($window, $mdUtil, $timeout) { + var cache = $mdUtil.cacheFactory('$mdMedia', { capacity: 15 }); + var presets = { + sm: '(min-width: 600px)', + md: '(min-width: 960px)', + lg: '(min-width: 1200px)' + }; + + angular.element($window).on('resize', updateAll); + + return $mdMedia; + + function $mdMedia(query) { + query = validate(query); + var result; + if ( !angular.isDefined(result = cache.get(query)) ) { + return add(query); + } + return result; + } + + function validate(query) { + return presets[query] || ( + query.charAt(0) != '(' ? ('(' + query + ')') : query + ); + } + + function add(query) { + return cache.put(query, !!$window.matchMedia(query).matches); + } + + function updateAll() { + var keys = cache.keys(); + if (keys.length) { + for (var i = 0, ii = keys.length; i < ii; i++) { + cache.put(keys[i], !!$window.matchMedia(keys[i]).matches); + } + // trigger a $digest() + $timeout(angular.noop); + } + } + +} + +function mdComponentRegistry($log) { + var instances = []; + + return { + /** + * Used to print an error when an instance for a handle isn't found. + */ + notFoundError: function(handle) { + $log.error('No instance found for handle', handle); + }, + /** + * Return all registered instances as an array. + */ + getInstances: function() { + return instances; + }, + + /** + * Get a registered instance. + * @param handle the String handle to look up for a registered instance. + */ + get: function(handle) { + var i, j, instance; + for(i = 0, j = instances.length; i < j; i++) { + instance = instances[i]; + if(instance.$$mdHandle === handle) { + return instance; + } + } + return null; + }, + + /** + * Register an instance. + * @param instance the instance to register + * @param handle the handle to identify the instance under. + */ + register: function(instance, handle) { + instance.$$mdHandle = handle; + instances.push(instance); + + return function deregister() { + var index = instances.indexOf(instance); + if (index !== -1) { + instances.splice(index, 1); + } + }; + } + }; +} +})(); diff --git a/src/components/slider/slider.js b/src/components/slider/slider.js index e81b033ee3a..86c08e74ea2 100644 --- a/src/components/slider/slider.js +++ b/src/components/slider/slider.js @@ -1,17 +1,14 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.slider */ angular.module('material.components.slider', [ - 'material.core', - 'material.animations', - 'material.services.aria', - 'material.services.theming' + 'material.core' ]) -.directive('mdSlider', [ - '$mdTheming', - SliderDirective -]); + .directive('mdSlider', SliderDirective); /** * @ngdoc directive @@ -51,18 +48,7 @@ function SliderDirective($mdTheming) { return { scope: {}, require: ['?ngModel', 'mdSlider'], - controller: [ - '$scope', - '$element', - '$attrs', - '$$rAF', - '$window', - '$mdEffects', - '$mdAria', - '$mdUtil', - '$mdConstant', - SliderController - ], + controller: SliderController, template: '
' + '
' + @@ -104,38 +90,38 @@ function SliderDirective($mdTheming) { * We use a controller for all the logic so that we can expose a few * things to unit tests */ -function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdAria, $mdUtil, $mdConstant) { +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 thumb = angular.element($element[0].querySelector('.md-thumb')); 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 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 attrs - attr.min ? attr.$observe('min', updateMin) : updateMin(0); - attr.max ? attr.$observe('max', updateMax) : updateMax(100); - attr.step ? attr.$observe('step', updateStep) : updateStep(1); + // 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 + // on the parent $scope, and won't be automatically destroyed when // the component is destroyed. var stopDisabledWatch = angular.noop; - if (attr.ngDisabled) { - stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled); + if ($attrs.ngDisabled) { + stopDisabledWatch = $scope.$parent.$watch($attrs.ngDisabled, updateAriaDisabled); } else { - updateAriaDisabled(!!attr.disabled); + updateAriaDisabled(!!$attrs.disabled); } - $mdAria.expect(element, 'aria-label'); + $mdAria.expect($element, 'aria-label'); - element.attr('tabIndex', 0); - element.attr('role', 'slider'); - element.on('keydown', keydownListener); + $element.attr('tabIndex', 0); + $element.attr('role', 'slider'); + $element.on('keydown', keydownListener); - var hammertime = new Hammer(element[0], { + var hammertime = new Hammer($element[0], { recognizers: [ [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }] ] @@ -156,7 +142,7 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA var debouncedUpdateAll = $$rAF.debounce(updateAll); angular.element($window).on('resize', debouncedUpdateAll); - scope.$on('$destroy', function() { + $scope.$on('$destroy', function() { angular.element($window).off('resize', debouncedUpdateAll); hammertime.destroy(); stopDisabledWatch(); @@ -175,26 +161,26 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA var step; function updateMin(value) { min = parseFloat(value); - element.attr('aria-valuemin', value); + $element.attr('aria-valuemin', value); } function updateMax(value) { max = parseFloat(value); - element.attr('aria-valuemax', value); + $element.attr('aria-valuemax', value); } function updateStep(value) { step = parseFloat(value); redrawTicks(); } function updateAriaDisabled(isDisabled) { - element.attr('aria-disabled', !!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, + // 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(attr.discrete)) return; + if (!angular.isDefined($attrs.discrete)) return; var numSteps = Math.floor( (max - min) / step ); if (!tickCanvas) { @@ -232,7 +218,7 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA * left/right arrow listener */ function keydownListener(ev) { - if(element[0].hasAttribute('disabled')) { + if($element[0].hasAttribute('disabled')) { return; } @@ -248,7 +234,7 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA } ev.preventDefault(); ev.stopPropagation(); - scope.$evalAsync(function() { + $scope.$evalAsync(function() { setModelValue(ngModelCtrl.$viewValue + changeAmount); }); } @@ -267,8 +253,8 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA } var percent = (ngModelCtrl.$viewValue - min) / (max - min); - scope.modelValue = ngModelCtrl.$viewValue; - element.attr('aria-valuenow', ngModelCtrl.$viewValue); + $scope.modelValue = ngModelCtrl.$viewValue; + $element.attr('aria-valuenow', ngModelCtrl.$viewValue); setSliderPercent(percent); } @@ -289,10 +275,10 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA function setSliderPercent(percent) { activeTrack.css('width', (percent * 100) + '%'); thumbContainer.css( - $mdEffects.TRANSFORM, + $mdConstant.CSS.TRANSFORM, 'translate3d(' + getSliderDimensions().width * percent + 'px,0,0)' ); - element.toggleClass('md-min', percent === 0); + $element.toggleClass('md-min', percent === 0); } @@ -300,16 +286,16 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA * Slide listeners */ var isSliding = false; - var isDiscrete = angular.isDefined(attr.discrete); + var isDiscrete = angular.isDefined($attrs.discrete); function onInput(ev) { if (!isSliding && ev.eventType === Hammer.INPUT_START && - !element[0].hasAttribute('disabled')) { + !$element[0].hasAttribute('disabled')) { isSliding = true; - element.addClass('active'); - element[0].focus(); + $element.addClass('active'); + $element[0].focus(); refreshSliderDimensions(); onPan(ev); @@ -321,12 +307,12 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA if ( isSliding && isDiscrete ) onPanEnd(ev); isSliding = false; - element.removeClass('panning active'); + $element.removeClass('panning active'); } } function onPanStart() { if (!isSliding) return; - element.addClass('panning'); + $element.addClass('panning'); } function onPan(ev) { if (!isSliding) return; @@ -342,7 +328,7 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA } function onPanEnd(ev) { - if ( isDiscrete && !element[0].hasAttribute('disabled') ) { + if ( isDiscrete && !$element[0].hasAttribute('disabled') ) { // Convert exact to closest discrete value. // Slide animate the thumb... and then update the model value. @@ -371,7 +357,7 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA * @param x */ function doSlide( x ) { - scope.$evalAsync( function() { + $scope.$evalAsync( function() { setModelValue( percentToValue( positionToPercent(x) )); }); } @@ -408,3 +394,4 @@ function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdA }; } +})(); diff --git a/src/components/sticky/sticky.js b/src/components/sticky/sticky.js index 06f6d32a2a2..62aeaa7895a 100644 --- a/src/components/sticky/sticky.js +++ b/src/components/sticky/sticky.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /* * @ngdoc module * @name material.components.sticky @@ -8,17 +11,9 @@ angular.module('material.components.sticky', [ 'material.core', - 'material.components.content', - 'material.animations' + 'material.components.content' ]) -.factory('$mdSticky', [ - '$document', - '$mdEffects', - '$compile', - '$$rAF', - '$mdUtil', - MdSticky -]); + .factory('$mdSticky', MdSticky); /* * @ngdoc service @@ -36,7 +31,7 @@ angular.module('material.components.sticky', [ * If not provided, it will use the result of `element.clone()`. */ -function MdSticky($document, $mdEffects, $compile, $$rAF, $mdUtil) { +function MdSticky($document, $mdConstant, $compile, $$rAF, $mdUtil) { var browserStickySupport = checkStickySupport(); @@ -246,12 +241,12 @@ function MdSticky($document, $mdEffects, $compile, $$rAF, $mdUtil) { if (amount === null || amount === undefined) { if (item.translateY) { item.translateY = null; - item.clone.css($mdEffects.TRANSFORM, ''); + item.clone.css($mdConstant.CSS.TRANSFORM, ''); } } else { item.translateY = amount; item.clone.css( - $mdEffects.TRANSFORM, + $mdConstant.CSS.TRANSFORM, 'translate3d(' + item.left + 'px,' + amount + 'px,0)' ); } @@ -307,3 +302,4 @@ function MdSticky($document, $mdEffects, $compile, $$rAF, $mdUtil) { } } +})(); diff --git a/src/components/subheader/subheader.js b/src/components/subheader/subheader.js index 4d2d5be0016..64c800cbfcd 100644 --- a/src/components/subheader/subheader.js +++ b/src/components/subheader/subheader.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.subheader @@ -5,15 +8,10 @@ * SubHeader module */ angular.module('material.components.subheader', [ - 'material.components.sticky', - 'material.services.theming' + 'material.core', + 'material.components.sticky' ]) -.directive('mdSubheader', [ - '$mdSticky', - '$compile', - '$mdTheming', - MdSubheaderDirective -]); + .directive('mdSubheader', MdSubheaderDirective); /** * @ngdoc directive @@ -66,3 +64,4 @@ function MdSubheaderDirective($mdSticky, $compile, $mdTheming) { } }; } +})(); diff --git a/src/components/swipe/swipe.js b/src/components/swipe/swipe.js index 8f39ab24f63..2407c4456e8 100644 --- a/src/components/swipe/swipe.js +++ b/src/components/swipe/swipe.js @@ -1,206 +1,205 @@ (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; }; /** - * @ngdoc module - * @name material.components.swipe - * @description Swipe module! + * SwipeService constructor pre-captures scope and customized event types + * + * @param scope + * @param eventTypes + * @returns {*} + * @constructor */ - angular.module('material.components.swipe',['ng']) + return function SwipeService(scope, eventTypes) { + if ( !eventTypes ) eventTypes = "swipeleft swiperight"; - /* - * @ngdoc service - * @module material.components.swipe - * @name $mdSwipe - * @description - * This service allows directives to easily attach swipe and pan listeners to - * the specified element. - */ - .factory("$mdSwipe", function() { + // publish configureFor() method for specific element instance + return function configureFor(element, onSwipeCallback, attachLater ) { + var hammertime = new Hammer(element[0], { + recognizers : addRecognizers([], eventTypes ) + }); - // match expected API functionality - var attachNoop = function(){ return angular.noop; }; + // Attach swipe listeners now + if ( !attachLater ) attachSwipe(); + + // auto-disconnect during destroy + scope.$on('$destroy', function() { + hammertime.destroy(); + }); + + return attachSwipe; + + // ********************** + // Internal methods + // ********************** /** - * SwipeService constructor pre-captures scope and customized event types + * Delegate swipe event to callback function + * and ensure $digest is triggered. * - * @param scope - * @param eventTypes - * @returns {*} - * @constructor + * @param ev HammerEvent */ - 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 ) - }); + function swipeHandler(ev) { - // Attach swipe listeners now - if ( !attachLater ) attachSwipe(); + // Prevent triggering parent hammer listeners + ev.srcEvent.stopPropagation(); - // auto-disconnect during destroy - scope.$on('$destroy', function() { - hammertime.destroy(); + if ( angular.isFunction(onSwipeCallback) ) { + scope.$apply(function() { + onSwipeCallback(ev); }); + } + } - 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; - } + /** + * Enable listeners and return detach() fn + */ + function attachSwipe() { + hammertime.on(eventTypes, swipeHandler ); - }; - }; - }) - - /** - * @ngdoc directive - * @module material.components.swipe - * @name mdSwipeLeft - * - * @restrict A - * - * @description - * The `
` 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=} noPan Use of attribute indicates flag to disable detection of `panleft` activity - * - * @usage - * - * - *
- * - *
- *
- * - */ - .directive("mdSwipeLeft", ['$parse', '$mdSwipe', - function MdSwipeLeft($parse, $mdSwipe) { - return { - restrict: 'A', - link : swipePostLink( $parse, $mdSwipe, "SwipeLeft" ) - }; - }]) - - /** - * @ngdoc directive - * @module material.components.swipe - * @name mdSwipeRight - * - * @restrict A - * - * @description - * The `
` 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=} noPan Use of attribute indicates flag to disable detection of `panright` activity - * - * @usage - * - * - *
- * - *
- *
- * - */ - .directive( "mdSwipeRight", ['$parse', '$mdSwipe', - function MdSwipeRight($parse, $mdSwipe) { - return { - restrict: 'A', - link: swipePostLink( $parse, $mdSwipe, "SwipeRight" ) + return function detachSwipe() { + hammertime.off( eventTypes ); }; } - ]); - - /** - * 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(); - } - }); + /** + * 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 `
` 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=} noPan Use of attribute indicates flag to disable detection of `panleft` activity + * + * @usage + * + * + *
+ * + *
+ *
+ * + */ +function MdSwipeLeftDirective($parse, $mdSwipe) { + return { + restrict: 'A', + link : swipePostLink( $parse, $mdSwipe, "SwipeLeft" ) + }; +} + +/** + * @ngdoc directive + * @module material.components.swipe + * @name mdSwipeRight + * + * @restrict A + * + * @description + * The `
` 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=} noPan Use of attribute indicates flag to disable detection of `panright` activity + * + * @usage + * + * + *
+ * + *
+ *
+ * + */ +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(); + } + }); + }; +} +})(); diff --git a/src/components/switch/switch.js b/src/components/switch/switch.js index fb62d7a89b2..43f7353b0d3 100644 --- a/src/components/switch/switch.js +++ b/src/components/switch/switch.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @private * @ngdoc module @@ -5,17 +8,11 @@ */ angular.module('material.components.switch', [ + 'material.core', 'material.components.checkbox', - 'material.components.radioButton', - 'material.services.theming' + 'material.components.radioButton' ]) - -.directive('mdSwitch', [ - 'mdCheckboxDirective', - 'mdRadioButtonDirective', - '$mdTheming', - MdSwitch -]); + .directive('mdSwitch', MdSwitch); /** * @private @@ -51,9 +48,9 @@ angular.module('material.components.switch', [ * * */ -function MdSwitch(checkboxDirectives, radioButtonDirectives, $mdTheming) { - var checkboxDirective = checkboxDirectives[0]; - var radioButtonDirective = radioButtonDirectives[0]; +function MdSwitch(mdCheckboxDirective, mdRadioButtonDirective, $mdTheming) { + var checkboxDirective = mdCheckboxDirective[0]; + var radioButtonDirective = mdRadioButtonDirective[0]; return { restrict: 'E', @@ -83,3 +80,4 @@ function MdSwitch(checkboxDirectives, radioButtonDirectives, $mdTheming) { }; } } +})(); diff --git a/src/components/tabs/js/inkBarDirective.js b/src/components/tabs/js/inkBarDirective.js index c25d85395a3..a44cb8d9c2a 100644 --- a/src/components/tabs/js/inkBarDirective.js +++ b/src/components/tabs/js/inkBarDirective.js @@ -1,19 +1,15 @@ +(function() { +'use strict'; + /** * Conditionally configure ink bar animations when the * tab selection changes. If `nobar` then do not show the * bar nor animate. */ angular.module('material.components.tabs') + .directive('mdTabsInkBar', MdTabInkDirective); -.directive('mdTabsInkBar', [ - '$mdEffects', - '$window', - '$$rAF', - '$timeout', - MdTabInkDirective -]); - -function MdTabInkDirective($mdEffects, $window, $$rAF, $timeout) { +function MdTabInkDirective($mdConstant, $window, $$rAF, $timeout) { return { restrict: 'E', @@ -59,10 +55,11 @@ function MdTabInkDirective($mdEffects, $window, $$rAF, $timeout) { display : width > 0 ? 'block' : 'none', width: width + 'px' }); - element.css($mdEffects.TRANSFORM, 'translate3d(' + left + 'px,0,0)'); + element.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + left + 'px,0,0)'); } } } } +})(); diff --git a/src/components/tabs/js/paginationDirective.js b/src/components/tabs/js/paginationDirective.js index daf4e526fd4..89de64b5cef 100644 --- a/src/components/tabs/js/paginationDirective.js +++ b/src/components/tabs/js/paginationDirective.js @@ -1,16 +1,11 @@ +(function() { +'use strict'; -angular.module('material.components.tabs') -.directive('mdTabsPagination', [ - '$mdEffects', - '$window', - '$$rAF', - '$$q', - '$timeout', - TabPaginationDirective -]); +angular.module('material.components.tabs') + .directive('mdTabsPagination', TabPaginationDirective); -function TabPaginationDirective($mdEffects, $window, $$rAF, $$q, $timeout) { +function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) { // TODO allow configuration of TAB_MIN_WIDTH // Must match tab min-width rule in _tabs.scss @@ -151,15 +146,15 @@ function TabPaginationDirective($mdEffects, $window, $$rAF, $$q, $timeout) { var deferred = $$q.defer(); tabsCtrl.$$pagingOffset = x; - tabsParent.css($mdEffects.TRANSFORM, 'translate3d(' + x + 'px,0,0)'); - tabsParent.on($mdEffects.TRANSITIONEND_EVENT, onTabsParentTransitionEnd); + 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($mdEffects.TRANSITIONEND_EVENT, onTabsParentTransitionEnd); + tabsParent.off($mdConstant.CSS.TRANSITIONEND, onTabsParentTransitionEnd); deferred.resolve(); } } @@ -194,3 +189,4 @@ function TabPaginationDirective($mdEffects, $window, $$rAF, $$q, $timeout) { } } +})(); diff --git a/src/components/tabs/js/tabItemController.js b/src/components/tabs/js/tabItemController.js index 44858fa6a0a..14f0a6ea35a 100644 --- a/src/components/tabs/js/tabItemController.js +++ b/src/components/tabs/js/tabItemController.js @@ -1,22 +1,17 @@ +(function() { +'use strict'; -angular.module('material.components.tabs') -.controller('$mdTab', [ - '$scope', - '$element', - '$compile', - '$animate', - '$mdUtil', - TabItemController -]); +angular.module('material.components.tabs') + .controller('$mdTab', TabItemController); -function TabItemController(scope, element, $compile, $animate, $mdUtil) { +function TabItemController($scope, $element, $compile, $animate, $mdUtil) { var self = this; // Properties self.contentContainer = angular.element('
'); - self.hammertime = Hammer(self.contentContainer[0]); - self.element = element; + self.hammertime = new Hammer(self.contentContainer[0]); + self.element = $element; // Methods self.isDisabled = isDisabled; @@ -26,7 +21,7 @@ function TabItemController(scope, element, $compile, $animate, $mdUtil) { self.onDeselect = onDeselect; function isDisabled() { - return element[0].hasAttribute('disabled'); + return $element[0].hasAttribute('disabled'); } /** @@ -36,7 +31,7 @@ function TabItemController(scope, element, $compile, $animate, $mdUtil) { function onAdd(contentArea) { if (self.content.length) { self.contentContainer.append(self.content); - self.contentScope = scope.$parent.$new(); + self.contentScope = $scope.$parent.$new(); contentArea.append(self.contentContainer); $compile(self.contentContainer)(self.contentScope); @@ -55,29 +50,30 @@ function TabItemController(scope, element, $compile, $animate, $mdUtil) { function onSelect() { // Resume watchers and events firing when tab is selected $mdUtil.reconnectScope(self.contentScope); - self.hammertime.on('swipeleft swiperight', scope.onSwipe); + self.hammertime.on('swipeleft swiperight', $scope.onSwipe); - element.addClass('active'); - element.attr('aria-selected', true); - element.attr('tabIndex', 0); + $element.addClass('active'); + $element.attr('aria-selected', true); + $element.attr('tabIndex', 0); $animate.removeClass(self.contentContainer, 'ng-hide'); - scope.onSelect(); + $scope.onSelect(); } function onDeselect() { // Stop watchers & events from firing while tab is deselected $mdUtil.disconnectScope(self.contentScope); - self.hammertime.off('swipeleft swiperight', scope.onSwipe); + self.hammertime.off('swipeleft swiperight', $scope.onSwipe); - element.removeClass('active'); - element.attr('aria-selected', false); + $element.removeClass('active'); + $element.attr('aria-selected', false); // Only allow tabbing to the active tab - element.attr('tabIndex', -1); + $element.attr('tabIndex', -1); $animate.addClass(self.contentContainer, 'ng-hide'); - scope.onDeselect(); + $scope.onDeselect(); } } +})(); diff --git a/src/components/tabs/js/tabItemDirective.js b/src/components/tabs/js/tabItemDirective.js index 280454355c9..3966a2c31c1 100644 --- a/src/components/tabs/js/tabItemDirective.js +++ b/src/components/tabs/js/tabItemDirective.js @@ -1,13 +1,8 @@ -angular.module('material.components.tabs') +(function() { +'use strict'; -.directive('mdTab', [ - '$mdInkRipple', - '$compile', - '$mdAria', - '$mdUtil', - '$mdConstant', - MdTabDirective -]); +angular.module('material.components.tabs') + .directive('mdTab', MdTabDirective); /** * @ngdoc directive @@ -233,3 +228,4 @@ function MdTabDirective($mdInkRipple, $compile, $mdAria, $mdUtil, $mdConstant) { } +})(); diff --git a/src/components/tabs/js/tabsController.js b/src/components/tabs/js/tabsController.js index 2bd4c984275..6c8b0385cea 100644 --- a/src/components/tabs/js/tabsController.js +++ b/src/components/tabs/js/tabsController.js @@ -1,21 +1,18 @@ -angular.module('material.components.tabs') +(function() { +'use strict'; -.controller('$mdTabs', [ - '$scope', - '$element', - '$mdUtil', - MdTabsController -]); +angular.module('material.components.tabs') + .controller('$mdTabs', MdTabsController); -function MdTabsController(scope, element, $mdUtil) { +function MdTabsController($scope, $element, $mdUtil) { var tabsList = $mdUtil.iterator([], false); var self = this; // Properties - self.element = element; - // The section containing the tab content elements - self.contentArea = angular.element(element[0].querySelector('.md-tabs-content')); + self.$element = $element; + // The section containing the tab content $elements + self.contentArea = angular.element($element[0].querySelector('.md-tabs-content')); // Methods from iterator self.inRange = tabsList.inRange; @@ -33,7 +30,7 @@ function MdTabsController(scope, element, $mdUtil) { self.next = next; self.previous = previous; - scope.$on('$destroy', function() { + $scope.$on('$destroy', function() { self.deselect(self.selected()); for (var i = tabsList.count() - 1; i >= 0; i--) { self.remove(tabsList[i], true); @@ -42,7 +39,7 @@ function MdTabsController(scope, element, $mdUtil) { // Get the selected tab function selected() { - return self.itemAt(scope.selectedIndex); + return self.itemAt($scope.selectedIndex); } // Add a new tab. @@ -54,11 +51,11 @@ function MdTabsController(scope, element, $mdUtil) { // 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)) { + if ($scope.selectedIndex === -1 || !angular.isNumber($scope.selectedIndex) || + $scope.selectedIndex === self.indexOf(tab)) { self.select(tab); } - scope.$broadcast('$mdTabsChanged'); + $scope.$broadcast('$mdTabsChanged'); } function remove(tab, noReselect) { @@ -77,7 +74,7 @@ function MdTabsController(scope, element, $mdUtil) { tabsList.remove(tab); tab.onRemove(); - scope.$broadcast('$mdTabsChanged'); + $scope.$broadcast('$mdTabsChanged'); } // Move a tab (used when ng-repeat order changes) @@ -88,7 +85,7 @@ function MdTabsController(scope, element, $mdUtil) { tabsList.add(tab, toIndex); if (isSelected) self.select(tab); - scope.$broadcast('$mdTabsChanged'); + $scope.$broadcast('$mdTabsChanged'); } function select(tab) { @@ -97,7 +94,7 @@ function MdTabsController(scope, element, $mdUtil) { self.deselect(self.selected()); - scope.selectedIndex = self.indexOf(tab); + $scope.selectedIndex = self.indexOf(tab); tab.isSelected = true; tab.onSelect(); } @@ -106,7 +103,7 @@ function MdTabsController(scope, element, $mdUtil) { if (!tab || !tab.isSelected) return; if (!tabsList.contains(tab)) return; - scope.selectedIndex = -1; + $scope.selectedIndex = -1; tab.isSelected = false; tab.onDeselect(); } @@ -123,3 +120,4 @@ function MdTabsController(scope, element, $mdUtil) { } } +})(); diff --git a/src/components/tabs/js/tabsDirective.js b/src/components/tabs/js/tabsDirective.js index c03b55d512f..34baf7fddae 100644 --- a/src/components/tabs/js/tabsDirective.js +++ b/src/components/tabs/js/tabsDirective.js @@ -1,4 +1,8 @@ +(function() { +'use strict'; + angular.module('material.components.tabs') + .directive('mdTabs', TabsDirective); /** * @ngdoc directive @@ -72,12 +76,6 @@ angular.module('material.components.tabs') * * */ -.directive('mdTabs', [ - '$parse', - '$mdTheming', - TabsDirective -]); - function TabsDirective($parse, $mdTheming) { return { restrict: 'E', @@ -149,3 +147,4 @@ function TabsDirective($parse, $mdTheming) { } } +})(); diff --git a/src/components/tabs/tabs.js b/src/components/tabs/tabs.js index 4044083e126..18b8ed71275 100644 --- a/src/components/tabs/tabs.js +++ b/src/components/tabs/tabs.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.tabs @@ -5,9 +8,10 @@ * * Tabs */ +/* + * @see js folder for tabs implementation + */ angular.module('material.components.tabs', [ - 'material.core', - 'material.animations', - 'material.services.theming', - 'material.services.aria' + 'material.core' ]); +})(); diff --git a/src/components/textField/README.md b/src/components/textField/README.md deleted file mode 100644 index 74650736160..00000000000 --- a/src/components/textField/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Text fields allow the user to input text. They can be single line, with or without scrolling, or multi-line, and can have an icon. Touching a text field places the cursor and automatically displays the keyboard. In addition to typing, text fields allow for a variety of other tasks, such as text selection (cut, copy, paste) and data lookup via auto-completion. Text fields can have different types. - -[Floating Label](https://www.google.com/design/spec/components/text-fields.html#text-fields-floating-labels) Text fields are show below: - diff --git a/src/components/textField/module.json b/src/components/textField/module.json deleted file mode 100644 index 8cfd9dafef3..00000000000 --- a/src/components/textField/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.textField", - "name": "Text field", - "demos": { - "demo1": { - "name": "Using Floating-Label Text fields", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/textField/textField.js b/src/components/textField/textField.js index 51abfeae97b..540186ea1c9 100644 --- a/src/components/textField/textField.js +++ b/src/components/textField/textField.js @@ -1,13 +1,18 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.textField * @description * Form */ -angular.module('material.components.textField', ['material.core', 'material.services.theming']) - .directive('mdInputGroup', [ mdInputGroupDirective ]) - .directive('mdInput', ['$mdUtil', mdInputDirective ]) - .directive('mdTextFloat', [ '$mdTheming', '$mdUtil', mdTextFloatDirective ]); +angular.module('material.components.textField', [ + 'material.core' +]) + .directive('mdInputGroup', mdInputGroupDirective) + .directive('mdInput', mdInputDirective) + .directive('mdTextFloat', mdTextFloatDirective); @@ -144,7 +149,8 @@ function mdInputDirective($mdUtil) { var ngModelCtrl = ctrls[1]; // scan for disabled and transpose the `type` value to the element - var isDisabled = $mdUtil.isParentDisabled(element); + var parent = element[0].parentNode; + var isDisabled = parent && parent.hasAttribute('disabled'); element.attr({ 'tabindex': isDisabled ? -1 : 0, @@ -191,6 +197,4 @@ function mdInputDirective($mdUtil) { }; } - - - +})(); diff --git a/src/components/toast/README.md b/src/components/toast/README.md deleted file mode 100644 index 96fccc87faf..00000000000 --- a/src/components/toast/README.md +++ /dev/null @@ -1 +0,0 @@ -Toasts are notifications that can be created on any part of the screen using the `$mdToast` service. diff --git a/src/components/toast/_toast.scss b/src/components/toast/_toast.scss index ee90c6e8ff2..1437d59a0fe 100644 --- a/src/components/toast/_toast.scss +++ b/src/components/toast/_toast.scss @@ -1,6 +1,6 @@ // See height set globally, depended on by buttons md-toast { - display: block; + display: flex; position:absolute; box-sizing: border-box; align-items: center; diff --git a/src/components/toast/demoBasicUsage/index.html b/src/components/toast/demoBasicUsage/index.html index 61cac3d8e4b..774d50c1aac 100644 --- a/src/components/toast/demoBasicUsage/index.html +++ b/src/components/toast/demoBasicUsage/index.html @@ -2,13 +2,17 @@

Toast can be dismissed with a swipe, a timer, or a button.

- + + Show Custom + +
+ Show Simple
- Show Advanced + ng-click="showActionToast()"> + Show With Action
diff --git a/src/components/toast/demoBasicUsage/script.js b/src/components/toast/demoBasicUsage/script.js index 1622477367e..099065af829 100644 --- a/src/components/toast/demoBasicUsage/script.js +++ b/src/components/toast/demoBasicUsage/script.js @@ -2,7 +2,7 @@ angular.module('toastDemo1', ['ngMaterial']) .controller('AppCtrl', function($scope, $mdToast, $animate) { - + $scope.toastPosition = { bottom: false, top: true, @@ -16,7 +16,7 @@ angular.module('toastDemo1', ['ngMaterial']) .join(' '); }; - $scope.complexToastIt = function() { + $scope.showCustomToast = function() { $mdToast.show({ controller: 'ToastCtrl', templateUrl: 'toast-template.html', @@ -25,11 +25,24 @@ angular.module('toastDemo1', ['ngMaterial']) }); }; - $scope.toastIt = function() { - $mdToast.show({ - template: 'Hello, ' + Math.random() + '', - hideDelay: 2000, - position: $scope.getToastPosition() + $scope.showSimpleToast = function() { + $mdToast.show( + $mdToast.simple() + .content('Hello World') + .position($scope.getToastPosition()) + .hideDelay(0) + ); + }; + + $scope.showActionToast = function() { + var toast = $mdToast.simple() + .content('Hello world') + .action('Click me') + .highlightAction(false) + .position($scope.getToastPosition()); + + $mdToast.show(toast).then(function() { + alert('Action clicked!'); }); }; diff --git a/src/components/toast/module.json b/src/components/toast/module.json deleted file mode 100644 index 48c5a12feda..00000000000 --- a/src/components/toast/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.toast", - "name": "Toast", - "demos": { - "demo1": { - "name": "Toast Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js index 29b8443aad7..6220c0b7957 100644 --- a/src/components/toast/toast.js +++ b/src/components/toast/toast.js @@ -1,3 +1,6 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.toast @@ -5,19 +8,12 @@ * Toast */ angular.module('material.components.toast', [ - 'material.services.interimElement', - 'material.components.swipe' + 'material.core', + 'material.components.swipe', + 'material.components.button' ]) - .directive('mdToast', [ - MdToastDirective - ]) - .factory('$mdToast', [ - '$timeout', - '$$interimElement', - '$animate', - '$mdSwipe', - MdToastService - ]); + .directive('mdToast', MdToastDirective) + .provider('$mdToast', MdToastProvider); function MdToastDirective() { return { @@ -31,11 +27,11 @@ function MdToastDirective() { * @module material.components.toast * * @description - * `$mdToast` opens a toast nofication on any position on the screen with an optional - * duration, and provides a simple promise API. + * `$mdToast` is a service to butild a toast nofication on any position + * on the screen with an optional duration, and provides a simple promise API. * * - * ### Restrictions + * ### Restrictions on custom toasts * - The toast's template must have an outer `` element. * - For a toast action, use element with class `md-action`. * - Add the class `md-capsule` for curved corners. @@ -53,10 +49,7 @@ function MdToastDirective() { * var app = angular.module('app', ['ngMaterial']); * app.controller('MyController', function($scope, $mdToast) { * $scope.openToast = function($event) { - * $mdToast.show({ - * template: 'Hello!', - * hideDelay: 3000 - * }); + * $mdToast.show($mdToast.simple().content('Hello!')); * }; * }); * @@ -64,12 +57,37 @@ function MdToastDirective() { /** * @ngdoc method - * @name $mdToast#show + * @name $mdToast#simple * * @description - * Show a toast dialog with the specified options. + * Builds a preconfigured toast. + * + * @returns {obj} a `$mdToastPreset` with the chainable configuration methods: + * + * - $mdToastPreset#content(string) - sets toast content to string + * - $mdToastPreset#action(string) - adds an action button, which resolves the promise returned from `show()` if clicked. + * - $mdToastPreset#highlightAction(boolean) - sets action button to be highlighted + * - $mdToastPreset#capsule(boolean) - adds 'md-capsule' class to the toast (curved corners) + */ + + /** + * @ngdoc method + * @name $mdToast#build + * + * @description + * Creates a custom `$mdToastPreset` that you can configure. + * + * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below). + */ + + /** + * @ngdoc method + * @name $mdToast#show + * + * @description Shows the toast. * - * @param {object} options An options object, with the following properties: + * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()` + * and `build()`, or 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 toast. Restrictions: the template must @@ -77,7 +95,7 @@ function MdToastDirective() { * - `template` - `{string=}`: Same as templateUrl, except this is an actual * template string. * - `hideDelay` - `{number=}`: How many milliseconds the toast should stay - * active before automatically closing. Set to 0 or false to have the toast stay open until + * active before automatically closing. Set to 0 or false to have the toast stay open until * closed manually. Default: 3000. * - `position` - `{string=}`: Where to place the toast. Available: any combination * of 'bottom', 'left', 'top', 'right', 'fit'. Default: 'bottom left'. @@ -93,7 +111,7 @@ function MdToastDirective() { * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope. * * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or - * rejected with `$mdBottomSheet.cancel()`. + * rejected with `$mdToast.cancel()`. */ /** @@ -101,7 +119,7 @@ function MdToastDirective() { * @name $mdToast#hide * * @description - * Hide the existing toast and resolve the promise returned from `$mdToast.show()`. + * Hide an existing toast and resolve the promise returned from `$mdToast.show()`. * * @param {*=} response An argument for the resolved promise. * @@ -112,51 +130,82 @@ function MdToastDirective() { * @name $mdToast#cancel * * @description - * Hide the existing toast and reject the promise returned from + * Hide the existing toast and reject the promise returned from * `$mdToast.show()`. * * @param {*=} response An argument for the rejected promise. * */ -function MdToastService($timeout, $$interimElement, $animate, $mdSwipe, $mdTheming) { +function MdToastProvider($$interimElementProvider) { - var factoryDef = { - onShow: onShow, - onRemove: onRemove, - position: 'bottom left', - themable: true, - hideDelay: 3000 - }; - - var $mdToast = $$interimElement(factoryDef); - return $mdToast; - - function onShow(scope, element, options) { - // 'top left' -> 'md-top md-left' - element.addClass(options.position.split(' ').map(function(pos) { - return 'md-' + pos; - }).join(' ')); - options.parent.addClass(toastOpenClass(options.position)); - - var configureSwipe = $mdSwipe(scope, 'swipeleft swiperight'); - options.detachSwipe = configureSwipe(element, function(ev) { - //Add swipeleft/swiperight class to element so it can animate correctly - element.addClass('md-' + ev.type); - $timeout($mdToast.hide); + return $$interimElementProvider('$mdToast') + .setDefaults({ + methods: ['position', 'hideDelay', 'capsule'], + options: toastDefaultOptions + }) + .addPreset('simple', { + methods: ['content', 'action', 'highlightAction'], + options: /* @ngInject */ function($mdToast) { + return { + template: [ + '', + '{{ toast.content }}', + '', + '{{toast.action}}', + '', + '' + ].join(''), + controller: function mdToastCtrl() { + this.resolve = function() { + $mdToast.hide(); + }; + }, + controllerAs: 'toast', + bindToController: true + }; + } }); - return $animate.enter(element, options.parent); - } + /* @ngInject */ + function toastDefaultOptions($timeout, $animate, $mdSwipe, $mdTheming, $mdToast) { + return { + onShow: onShow, + onRemove: onRemove, + position: 'bottom left', + themable: true, + hideDelay: 3000 + }; - function onRemove(scope, element, options) { - options.detachSwipe(); - options.parent.removeClass(toastOpenClass(options.position)); - return $animate.leave(element); - } + function onShow(scope, element, options) { + // 'top left' -> 'md-top md-left' + element.addClass(options.position.split(' ').map(function(pos) { + return 'md-' + pos; + }).join(' ')); + options.parent.addClass(toastOpenClass(options.position)); + + var configureSwipe = $mdSwipe(scope, 'swipeleft swiperight'); + options.detachSwipe = configureSwipe(element, function(ev) { + //Add swipeleft/swiperight class to element so it can animate correctly + element.addClass('md-' + ev.type); + $timeout($mdToast.cancel); + }); + + return $animate.enter(element, options.parent); + } - function toastOpenClass(position) { - return 'md-toast-open-' + - (position.indexOf('top') > -1 ? 'top' : 'bottom'); + function onRemove(scope, element, options) { + options.detachSwipe(); + options.parent.removeClass(toastOpenClass(options.position)); + return $animate.leave(element); + } + + function toastOpenClass(position) { + return 'md-toast-open-' + + (position.indexOf('top') > -1 ? 'top' : 'bottom'); + } } + } + +})(); diff --git a/src/components/toast/toast.spec.js b/src/components/toast/toast.spec.js index b4127b371d3..3bc005c9751 100644 --- a/src/components/toast/toast.spec.js +++ b/src/components/toast/toast.spec.js @@ -11,88 +11,168 @@ describe('$mdToast service', function() { }); } - describe('options', function() { + describe('simple()', function() { + hasConfigMethods(['content', 'action', 'capsule', 'highlightAction']); - it('should hide after duration', inject(function($timeout, $animate, $rootElement) { + it('supports a basic toast', inject(function($mdToast, $rootScope, $timeout, $animate) { + var rejected = false; var parent = angular.element('
'); - setup({ - template: '', - hideTimeout: 1234 + $mdToast.show( + $mdToast.simple({ + parent: parent, + content: 'Do something', + capsule: true + }) + ).catch(function() { + rejected = true; }); - expect($rootElement.find('md-toast').length).toBe(1); + $rootScope.$digest(); + expect(parent.find('span').text()).toBe('Do something'); + expect(parent.find('md-toast')).toHaveClass('md-capsule'); + $animate.triggerCallbacks(); $timeout.flush(); - expect($rootElement.find('md-toast').length).toBe(0); + $animate.triggerCallbacks(); + expect(rejected).toBe(true); })); - it('should have template', inject(function($timeout, $rootScope, $rootElement) { + it('supports an action toast', inject(function($mdToast, $rootScope, $timeout, $animate) { + var resolved = false; var parent = angular.element('
'); - setup({ - template: '{{1}}234', - appendTo: parent - }); - var toast = $rootElement.find('md-toast'); - $timeout.flush(); - expect(toast.text()).toBe('1234'); - })); - - it('should have templateUrl', inject(function($timeout, $rootScope, $templateCache, $rootElement) { - $templateCache.put('template.html', 'hello, {{1}}'); - setup({ - templateUrl: 'template.html', + $mdToast.show( + $mdToast.simple({ + content: 'Do something', + parent: parent + }) + .action('Click me') + .highlightAction(true) + ).then(function() { + resolved = true; }); - var toast = $rootElement.find('md-toast'); - expect(toast.text()).toBe('hello, 1'); + $rootScope.$digest(); + $animate.triggerCallbacks(); + var button = parent.find('button'); + expect(button.text()).toBe('Click me'); + button.triggerHandler('click'); + $rootScope.$digest(); + $animate.triggerCallbacks(); + expect(resolved).toBe(true); })); - it('should add position class to tast', inject(function($rootElement, $timeout) { - setup({ - template: '', - position: 'top left' + function hasConfigMethods(methods) { + angular.forEach(methods, function(method) { + return it('supports config method #' + method, inject(function($mdToast) { + var basic = $mdToast.simple(); + expect(typeof basic[method]).toBe('function'); + expect(basic[method]()).toBe(basic); + })); }); - var toast = $rootElement.find('md-toast'); - $timeout.flush(); - expect(toast.hasClass('md-top')).toBe(true); - expect(toast.hasClass('md-left')).toBe(true); - })); + } }); - describe('lifecycle', function() { + describe('build()', function() { + describe('options', function() { + it('should hide current toast when showing new one', inject(function($rootElement) { + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeTruthy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); - it('should hide current toast when showing new one', inject(function($rootElement) { - setup({ - template: '' - }); - expect($rootElement[0].querySelector('md-toast.one')).toBeTruthy(); - expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); - expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeTruthy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); - setup({ - template: '' - }); - expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); - expect($rootElement[0].querySelector('md-toast.two')).toBeTruthy(); - expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeTruthy(); + })); - setup({ - template: '' - }); - expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); - expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); - expect($rootElement[0].querySelector('md-toast.three')).toBeTruthy(); - })); + it('should hide after duration', inject(function($timeout, $animate, $rootElement) { + var parent = angular.element('
'); + setup({ + template: '', + hideTimeout: 1234 + }); + expect($rootElement.find('md-toast').length).toBe(1); + $timeout.flush(); + expect($rootElement.find('md-toast').length).toBe(0); + })); - it('should add class to toastParent', inject(function($rootElement) { - setup({ - template: '' - }); - expect($rootElement.hasClass('md-toast-open-bottom')).toBe(true); + it('should have template', inject(function($timeout, $rootScope, $rootElement) { + var parent = angular.element('
'); + setup({ + template: '{{1}}234', + appendTo: parent + }); + var toast = $rootElement.find('md-toast'); + $timeout.flush(); + expect(toast.text()).toBe('1234'); + })); - setup({ - template: '', - position: 'top' - }); - expect($rootElement.hasClass('md-toast-open-top')).toBe(true); - })); + it('should have templateUrl', inject(function($timeout, $rootScope, $templateCache, $rootElement) { + $templateCache.put('template.html', 'hello, {{1}}'); + setup({ + templateUrl: 'template.html', + }); + var toast = $rootElement.find('md-toast'); + expect(toast.text()).toBe('hello, 1'); + })); + + it('should add position class to tast', inject(function($rootElement, $timeout) { + setup({ + template: '', + position: 'top left' + }); + var toast = $rootElement.find('md-toast'); + $timeout.flush(); + expect(toast.hasClass('md-top')).toBe(true); + expect(toast.hasClass('md-left')).toBe(true); + })); + }); + + describe('lifecycle', function() { + it('should hide current toast when showing new one', inject(function($rootElement) { + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeTruthy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); + + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeTruthy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeFalsy(); + setup({ + template: '' + }); + expect($rootElement[0].querySelector('md-toast.one')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.two')).toBeFalsy(); + expect($rootElement[0].querySelector('md-toast.three')).toBeTruthy(); + })); + + it('should add class to toastParent', inject(function($rootElement) { + setup({ + template: '' + }); + expect($rootElement.hasClass('md-toast-open-bottom')).toBe(true); + + setup({ + template: '', + position: 'top' + }); + expect($rootElement.hasClass('md-toast-open-top')).toBe(true); + })); + }); }); }); diff --git a/src/components/toolbar/README.md b/src/components/toolbar/README.md deleted file mode 100644 index 4b3947064f2..00000000000 --- a/src/components/toolbar/README.md +++ /dev/null @@ -1 +0,0 @@ -Toolbars, created using the `` directive. diff --git a/src/components/toolbar/module.json b/src/components/toolbar/module.json deleted file mode 100644 index 2551990f2a9..00000000000 --- a/src/components/toolbar/module.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "module": "material.components.toolbar", - "name": "Toolbar", - "demos": { - "demo1": { - "name": "Toolbar Basic Usage", - "files": ["demo1/*"] - }, - "demo2": { - "name": "Scroll Shrinking Toolbar", - "files": ["demo2/*"] - } - } -} diff --git a/src/components/toolbar/toolbar.js b/src/components/toolbar/toolbar.js index aa2188f4e2a..aa3753817d7 100644 --- a/src/components/toolbar/toolbar.js +++ b/src/components/toolbar/toolbar.js @@ -1,20 +1,15 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.toolbar */ angular.module('material.components.toolbar', [ 'material.core', - 'material.components.content', - 'material.services.theming', - 'material.animations' + 'material.components.content' ]) - .directive('mdToolbar', [ - '$$rAF', - '$mdEffects', - '$mdUtil', - '$mdTheming', - mdToolbarDirective - ]); + .directive('mdToolbar', mdToolbarDirective); /** * @ngdoc directive @@ -63,7 +58,7 @@ angular.module('material.components.toolbar', [ * shrinking by. For example, if 0.25 is given then the toolbar will shrink * at one fourth the rate at which the user scrolls down. Default 0.5. */ -function mdToolbarDirective($$rAF, $mdEffects, $mdUtil, $mdTheming) { +function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming) { return { restrict: 'E', @@ -95,7 +90,8 @@ function mdToolbarDirective($$rAF, $mdEffects, $mdUtil, $mdTheming) { scope.$on('$mdContentLoaded', onMdContentLoad); function onMdContentLoad($event, newContentEl) { - if ($mdUtil.elementIsSibling(element, newContentEl)) { + // Toolbar and content must be siblings + if (element.parent()[0] === newContentEl.parent()[0]) { // unhook old content event listener if exists if (contentElement) { contentElement.off('scroll', debouncedContentScroll); @@ -135,11 +131,11 @@ function mdToolbarDirective($$rAF, $mdEffects, $mdUtil, $mdTheming) { ); element.css( - $mdEffects.TRANSFORM, + $mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (-y * shrinkSpeedFactor) + 'px,0)' ); contentElement.css( - $mdEffects.TRANSFORM, + $mdConstant.CSS.TRANSFORM, 'translate3d(0,' + ((toolbarHeight - y) * shrinkSpeedFactor) + 'px,0)' ); @@ -152,3 +148,4 @@ function mdToolbarDirective($$rAF, $mdEffects, $mdUtil, $mdTheming) { }; } +})(); diff --git a/src/components/toolbar/toolbar.spec.js b/src/components/toolbar/toolbar.spec.js index e45b31f0aaf..c624c3820c0 100644 --- a/src/components/toolbar/toolbar.spec.js +++ b/src/components/toolbar/toolbar.spec.js @@ -13,7 +13,7 @@ describe('', function() { $provide.value('$$rAF', raf); })); - it('with scrollShrink, it should shrink scrollbar when going to bottom', inject(function($compile, $rootScope, $mdEffects, mdToolbarDirective) { + it('with scrollShrink, it should shrink scrollbar when going to bottom', inject(function($compile, $rootScope, $mdConstant, mdToolbarDirective) { var parent = angular.element('
'); var toolbar = angular.element(''); @@ -47,9 +47,9 @@ describe('', function() { $rootScope.$broadcast('$mdContentLoaded', contentEl); //Expect everything to be in its proper initial state. - expect(toolbarCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,0px,0)'); + expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)'); expect(contentCss['margin-top']).toEqual('-100px'); - expect(contentCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,100px,0)'); + expect(contentCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,100px,0)'); // Fake scroll to the bottom contentEl.triggerHandler({ @@ -57,8 +57,8 @@ describe('', function() { target: { scrollTop: 500 } }); - expect(toolbarCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,-100px,0)'); - expect(contentCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,0px,0)'); + expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,-100px,0)'); + expect(contentCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)'); // Fake scroll back to the top contentEl.triggerHandler({ @@ -66,8 +66,8 @@ describe('', function() { target: { scrollTop: 0 } }); - expect(toolbarCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,0px,0)'); - expect(contentCss[$mdEffects.TRANSFORM]).toEqual('translate3d(0,100px,0)'); + expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)'); + expect(contentCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,100px,0)'); })); }); diff --git a/src/components/tooltip/README.md b/src/components/tooltip/README.md deleted file mode 100644 index b216d118544..00000000000 --- a/src/components/tooltip/README.md +++ /dev/null @@ -1 +0,0 @@ -Use [Tooltips](https://www.google.com/design/spec/components/tooltips.html#tooltips-usage) for elements that are 1) interactive and 2) primarily graphical (not textual): diff --git a/src/components/tooltip/module.json b/src/components/tooltip/module.json deleted file mode 100644 index fad57db45f5..00000000000 --- a/src/components/tooltip/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.tooltip", - "name": "Tooltip", - "demos": { - "demo1": { - "name": "Tooltip Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js index 465727b18a3..cd024a8e8cf 100644 --- a/src/components/tooltip/tooltip.js +++ b/src/components/tooltip/tooltip.js @@ -1,21 +1,14 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.tooltip */ angular.module('material.components.tooltip', [ - 'material.core', - 'material.services.theming' + 'material.core' ]) - -.directive('mdTooltip', [ - '$timeout', - '$window', - '$$rAF', - '$document', - '$mdUtil', - '$mdTheming', - MdTooltipDirective -]); + .directive('mdTooltip', MdTooltipDirective); /** * @ngdoc directive @@ -190,3 +183,4 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe } } +})(); diff --git a/src/components/whiteframe/README.md b/src/components/whiteframe/README.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/components/whiteframe/module.json b/src/components/whiteframe/module.json deleted file mode 100644 index b460b07b0dd..00000000000 --- a/src/components/whiteframe/module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module": "material.components.whiteframe", - "name": "Whiteframe", - "demos": { - "demo1": { - "name": "Whiteframe Basic Usage", - "files": ["demo1/*"] - } - } -} diff --git a/src/components/whiteframe/whiteframe.js b/src/components/whiteframe/whiteframe.js index b90ac304009..b56e3913ac4 100644 --- a/src/components/whiteframe/whiteframe.js +++ b/src/components/whiteframe/whiteframe.js @@ -1,5 +1,9 @@ +(function() { +'use strict'; + /** * @ngdoc module * @name material.components.whiteframe */ angular.module('material.components.whiteframe', []); +})(); diff --git a/src/core/core.js b/src/core/core.js index a0542bd43f9..f0a809b3a6c 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -2,7 +2,7 @@ * Initialization function that validates environment * requirements. */ -angular.module('material.core', [] ) +angular.module('material.core', []) .run(function validateEnvironment() { @@ -13,6 +13,7 @@ angular.module('material.core', [] ) } }) + .config(['$provide', function($provide) { $provide.decorator('$$rAF', ['$delegate', '$rootScope', rAFDecorator]); diff --git a/src/services/aria/aria.js b/src/core/services/aria/aria.js similarity index 92% rename from src/services/aria/aria.js rename to src/core/services/aria/aria.js index 631b021c153..40e96ae016c 100644 --- a/src/services/aria/aria.js +++ b/src/core/services/aria/aria.js @@ -1,10 +1,8 @@ -angular.module('material.services.aria', []) +(function() { +'use strict'; -.service('$mdAria', [ - '$$rAF', - '$log', - AriaService -]); +angular.module('material.core') + .service('$mdAria', AriaService); function AriaService($$rAF, $log) { @@ -50,3 +48,4 @@ function AriaService($$rAF, $log) { } } +})(); diff --git a/src/services/compiler/compiler.js b/src/core/services/compiler/compiler.js similarity index 91% rename from src/services/compiler/compiler.js rename to src/core/services/compiler/compiler.js index 40efc735af3..4f24867f49f 100644 --- a/src/services/compiler/compiler.js +++ b/src/core/services/compiler/compiler.js @@ -1,26 +1,15 @@ -/* - * @ngdoc module - * @name material.services.compiler - * @description compiler service - */ -angular.module('material.services.compiler', [ -]) - .service('$mdCompiler', [ - '$q', - '$http', - '$injector', - '$compile', - '$controller', - '$templateCache', - mdCompilerService - ]); +(function() { +'use strict'; + +angular.module('material.core') + .service('$mdCompiler', mdCompilerService); function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) { /* * @ngdoc service * @name $mdCompiler - * @module material.services.compiler + * @module material.core * @description * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer * to easily compile an element with a templateUrl, controller, and locals. @@ -73,7 +62,8 @@ function mdCompilerService($q, $http, $injector, $compile, $controller, $templat * - `link` - `{function(scope)}`: A link function, which, when called, will compile * the element and instantiate the provided controller (if given). * - `locals` - `{object}`: The locals which will be passed into the controller once `link` is - * called. + * called. If `bindToController` is true, they will be coppied to the ctrl instead + * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in */ this.compile = function(options) { var templateUrl = options.templateUrl; @@ -83,6 +73,7 @@ function mdCompilerService($q, $http, $injector, $compile, $controller, $templat var resolve = options.resolve || {}; var locals = options.locals || {}; var transformTemplate = options.transformTemplate || angular.identity; + var bindToController = options.bindToController; // Take resolve values and invoke them. // Resolves can either be a string (value: 'MyRegisteredAngularConst'), @@ -124,6 +115,9 @@ function mdCompilerService($q, $http, $injector, $compile, $controller, $templat //Instantiate controller if it exists, because we have scope if (controller) { var ctrl = $controller(controller, locals); + if (bindToController) { + angular.extend(ctrl, locals); + } //See angular-route source for this logic element.data('$ngControllerController', ctrl); element.children().data('$ngControllerController', ctrl); @@ -140,3 +134,4 @@ function mdCompilerService($q, $http, $injector, $compile, $controller, $templat }; } +})(); diff --git a/src/services/compiler/compiler.spec.js b/src/core/services/compiler/compiler.spec.js similarity index 86% rename from src/services/compiler/compiler.spec.js rename to src/core/services/compiler/compiler.spec.js index e08d5f4e8c5..cc36fa5283a 100644 --- a/src/services/compiler/compiler.spec.js +++ b/src/core/services/compiler/compiler.spec.js @@ -1,5 +1,5 @@ describe('$mdCompiler service', function() { - beforeEach(module('material.services.compiler')); + beforeEach(module('material.core')); function compile(options) { var compileData; @@ -99,6 +99,19 @@ describe('$mdCompiler service', function() { data.link(scope); expect(scope.myControllerAs).toBe(data.element.controller()); })); + + it('should work with bindToController', inject(function($rootScope) { + var data = compile({ + template: 'hello', + controller: function() { }, + controllerAs: 'ctrl', + bindToController: true, + locals: { name: 'Bob' } + }); + var scope = $rootScope.$new(); + data.link(scope); + expect(scope.ctrl.name).toBe('Bob'); + })); }); }); }); diff --git a/src/core/services/interimElement/interimElement.js b/src/core/services/interimElement/interimElement.js new file mode 100644 index 00000000000..3cd9bfbcb49 --- /dev/null +++ b/src/core/services/interimElement/interimElement.js @@ -0,0 +1,308 @@ +(function() { +'use strict'; + +angular.module('material.core') + .provider('$$interimElement', InterimElementProvider); + +/* + * @ngdoc service + * @name $$interimElement + * @module material.core + * + * @description + * + * Factory that contructs `$$interimElement.$service` services. + * Used internally in material design for elements that appear on screen temporarily. + * The service provides a promise-like API for interacting with the temporary + * elements. + * + * ```js + * app.service('$mdToast', function($$interimElement) { + * var $mdToast = $$interimElement(toastDefaultOptions); + * return $mdToast; + * }); + * ``` + * @param {object=} defaultOptions Options used by default for the `show` method on the service. + * + * @returns {$$interimElement.$service} + * + */ + +function InterimElementProvider() { + createInterimElementProvider.$get = InterimElementFactory; + return createInterimElementProvider; + + /** + * Returns a new provider which allows configuration of a new interimElement + * service. Allows configuration of default options & methods for options, + * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method) + */ + function createInterimElementProvider(interimFactoryName) { + var providerConfig = { + presets: {} + }; + var provider = { + setDefaults: setDefaults, + addPreset: addPreset, + $get: factory + }; + + /** + * all interim elements will come with the 'build' preset + */ + provider.addPreset('build', { + methods: ['controller', 'controllerAs', 'onRemove', 'onShow', 'resolve', + 'template', 'templateUrl', 'themable', 'transformTemplate'] + }); + + return provider; + + /** + * Save the configured defaults to be used when the factory is instantiated + */ + function setDefaults(definition) { + providerConfig.optionsFactory = definition.options; + providerConfig.methods = definition.methods; + return provider; + } + /** + * Save the configured preset to be used when the factory is instantiated + */ + function addPreset(name, definition) { + definition = definition || {}; + definition.methods = definition.methods || []; + definition.options = definition.options || function() { return {}; }; + + if (/^cancel|hide|show$/.test(name)) { + throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!"); + } + if (definition.methods.indexOf('_options') > -1) { + throw new Error("Method '_options' in " + interimFactoryName + " is reserved!"); + } + providerConfig.presets[name] = { + methods: definition.methods, + optionsFactory: definition.options + }; + return provider; + } + + /** + * Create a factory that has the given methods & defaults implementing interimElement + */ + /* @ngInject */ + function factory($$interimElement, $animate, $injector) { + var defaultMethods; + var defaultOptions; + var interimElementService = $$interimElement(); + + /* + * publicService is what the developer will be using. + * It has methods hide(), cancel(), show(), build(), and any other + * presets which were set during the config phase. + */ + var publicService = {}; + publicService.hide = interimElementService.hide; + publicService.cancel = interimElementService.cancel; + publicService.show = showInterimElement; + + defaultMethods = providerConfig.methods || []; + // This must be invoked after the publicService is initialized + defaultOptions = invokeFactory(providerConfig.optionsFactory, {}); + + angular.forEach(providerConfig.presets, function(definition, name) { + var presetDefaults = invokeFactory(definition.optionsFactory, {}); + var presetMethods = (definition.methods || []).concat(defaultMethods); + + // Every interimElement built with a preset has a field called `$type`, + // which matches the name of the preset. + // Eg in preset 'confirm', options.$type === 'confirm' + angular.extend(presetDefaults, { $type: name }); + + function Preset(opts) { + this._options = angular.extend({}, presetDefaults, opts); + } + angular.forEach(presetMethods, function(name) { + Preset.prototype[name] = function(value) { + this._options[name] = value; + return this; + }; + }); + + publicService[name] = function(options) { + return new Preset(options); + }; + }); + + return publicService; + + function showInterimElement(opts) { + // opts is either a preset which stores its options on an _options field, + // or just an object made up of options + return interimElementService.show( + angular.extend({}, defaultOptions, (opts || {})._options || opts) + ); + } + + /** + * Helper to call $injector.invoke with a local of the factory name for + * this provider. + * If an $mdDialog is providing options for a dialog and tries to inject + * $mdDialog, a circular dependency error will happen. + * We get around that by manually injecting $mdDialog as a local. + */ + function invokeFactory(fn, defaultVal) { + var locals = {}; + locals[interimFactoryName] = publicService; + return $injector.invoke(fn || function() { return defaultVal; }, {}, locals); + } + + } + + } + + /* @ngInject */ + function InterimElementFactory($q, $rootScope, $timeout, $rootElement, $animate, $mdCompiler, $mdTheming) { + + return function createInterimElementService() { + /* + * @ngdoc service + * @name $$interimElement.$service + * + * @description + * A service used to control inserting and removing an element into the DOM. + * + */ + var stack = []; + var service; + return service = { + show: show, + hide: hide, + cancel: cancel, + }; + + function show(options) { + if (stack.length) { + service.cancel(); + } + + var interimElement = new InterimElement(options); + + stack.push(interimElement); + return interimElement.show().then(function() { + return interimElement.deferred.promise; + }); + } + + /* + * @ngdoc method + * @name $$interimElement.$service#hide + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and resolves the promise returned from `show` + * + * @param {*} resolveParam Data to resolve the promise with + * + * @returns undefined data that resolves after the element has been removed. + * + */ + function hide(response) { + var interimElement = stack.shift(); + interimElement && interimElement.remove().then(function() { + interimElement.deferred.resolve(response); + }); + } + + /* + * @ngdoc method + * @name $$interimElement.$service#cancel + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and rejects the promise returned from `show` + * + * @param {*} reason Data to reject the promise with + * + * @returns undefined + * + */ + function cancel(reason) { + var interimElement = stack.shift(); + interimElement && interimElement.remove().then(function() { + interimElement.deferred.reject(reason); + }); + } + + + /* + * Internal Interim Element Object + * Used internally to manage the DOM element and related data + */ + function InterimElement(options) { + var self; + var hideTimeout, element; + + options = options || {}; + options = angular.extend({ + scope: options.scope || $rootScope.$new(options.isolateScope), + onShow: function(scope, element, options) { + return $animate.enter(element, options.parent); + }, + onRemove: function(scope, element, options) { + // Element could be undefined if a new element is shown before + // the old one finishes compiling. + return element && $animate.leave(element) || $q.when(); + } + }, options); + + return self = { + options: options, + deferred: $q.defer(), + show: function() { + return $mdCompiler.compile(options).then(function(compileData) { + angular.extend(compileData.locals, self.options); + + // Search for parent at insertion time, if not specified + if (!options.parent) { + options.parent = $rootElement.find('body'); + if (!options.parent.length) options.parent = $rootElement; + } + element = compileData.link(options.scope); + if (options.themable) $mdTheming(element); + var ret = options.onShow(options.scope, element, options); + return $q.when(ret) + .then(function(){ + // Issue onComplete callback when the `show()` finishes + var notify = options.onComplete || angular.noop; + notify.apply(null, [options.scope, element, options]); + }) + .then(startHideTimeout); + + function startHideTimeout() { + if (options.hideDelay) { + hideTimeout = $timeout(service.cancel, options.hideDelay) ; + } + } + }); + }, + cancelTimeout: function() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = undefined; + } + }, + remove: function() { + self.cancelTimeout(); + var ret = options.onRemove(options.scope, element, options); + return $q.when(ret).then(function() { + options.scope.$destroy(); + }); + } + }; + } + }; + } + +} + +})(); diff --git a/src/core/services/interimElement/interimElement.spec.js b/src/core/services/interimElement/interimElement.spec.js new file mode 100644 index 00000000000..11745a6aede --- /dev/null +++ b/src/core/services/interimElement/interimElement.spec.js @@ -0,0 +1,328 @@ +describe('$$interimElement service', function() { + beforeEach(module('material.core')); + var $compilerSpy, $themingSpy, resolvingPromise; + + function setup() { + module('material.core', 'ngAnimateMock', function($provide) { + var $mdCompiler = { compile: angular.noop }; + $compilerSpy = spyOn($mdCompiler, 'compile'); + $themingSpy = jasmine.createSpy('$mdTheming'); + + $provide.value('$mdCompiler', $mdCompiler); + $provide.value('$mdTheming', $themingSpy); + }); + inject(function($q, $compile, $rootScope) { + $compilerSpy.andCallFake(function(opts) { + var el = $compile(opts.template); + var deferred = $q.defer(); + deferred.resolve({ + link: el, + locals: {} + }); + $rootScope.$apply(); + return deferred.promise; + }); + }); + } + + function createInterimProvider(providerName) { + var interimProvider; + module(function($$interimElementProvider, $provide) { + interimProvider = $$interimElementProvider(providerName); + $provide.provider(providerName, interimProvider); + }); + + setup(); + + return interimProvider; + } + + describe('provider', function() { + + it('by default create a factory with default methods', function() { + createInterimProvider('interimTest'); + inject(function(interimTest) { + expect(interimTest.hide).toBeOfType('function'); + expect(interimTest.build).toBeOfType('function'); + expect(interimTest.cancel).toBeOfType('function'); + expect(interimTest.show).toBeOfType('function'); + + var builder = interimTest.build(); + [ 'controller', 'controllerAs', 'onRemove', 'onShow', 'resolve', + 'template', 'templateUrl', 'themable', 'transformTemplate' + ].forEach(function(methodName) { + expect(builder[methodName]).toBeOfType('function'); + }); + }); + }); + + it('should show with provided builder', function() { + createInterimProvider('interimTest'); + inject(function(interimTest, $rootScope) { + var shown = false; + interimTest.show( + interimTest.build({ + controller: 'test ctrl', + }) + .onShow(function(scope, element, options) { + shown = true; + expect(options.controller).toBe('test ctrl'); + }) + ); + + $rootScope.$apply(); + expect(shown).toBe(true); + }); + }); + + it('should add specified defaults', function() { + createInterimProvider('interimTest').setDefaults({ + options: function($rootScope) { + return { + id: $rootScope.$id + }; + }, + methods: ['foo', 'bar'] + }); + inject(function(interimTest, $rootScope) { + var builder = interimTest.build({ + onShow: function(scope, element, options) { + shown = true; + expect(options.id).toBe($rootScope.$id); + } + }); + expect(builder.foo).toBeOfType('function'); + expect(builder.bar).toBeOfType('function'); + + var shown = false; + interimTest.show(builder); + $rootScope.$apply(); + shown = true; + }); + }); + + it('should add specified builder with defaults', function() { + createInterimProvider('interimTest') + .setDefaults({ + options: function() { + return { + pizza: 'pepperoni' + }; + }, + methods: ['banana'] + }) + .addPreset('bob', { + options: function() { + return { + nut: 'almond' + }; + }, + methods: ['mango'] + }); + inject(function(interimTest, $rootScope) { + var shown = false; + var builder = interimTest.bob({ + onShow: function(scope, element, options) { + expect(options.pizza).toBe('pepperoni'); + expect(options.nut).toBe('almond'); + expect(options.banana).toBe(1); + expect(options.mango).toBe(2); + shown = true; + } + }); + builder.banana(1); + builder.mango(2); + interimTest.show(builder); + $rootScope.$apply(); + expect(shown).toBe(true); + + }); + }); + + it('should show with proper options', function() { + createInterimProvider('interimTest') + .setDefaults({ + options: function() { + return { key: 'defaultValue' }; + } + }) + .addPreset('preset', { + options: function() { + return { key2: 'defaultValue2' }; + }, + methods: ['key2'] + }); + inject(function(interimTest, $rootScope) { + interimTest.show(); + expect($compilerSpy.mostRecentCall.args[0].key).toBe('defaultValue'); + + $compilerSpy.reset(); + interimTest.show({ + key: 'newValue' + }); + expect($compilerSpy.mostRecentCall.args[0].key).toBe('newValue'); + + $compilerSpy.reset(); + interimTest.show(interimTest.preset()); + expect($compilerSpy.mostRecentCall.args[0].key).toBe('defaultValue'); + expect($compilerSpy.mostRecentCall.args[0].key2).toBe('defaultValue2'); + + $compilerSpy.reset(); + interimTest.show( + interimTest.preset({ + key: 'newValue', + key2: 'newValue2' + }) + ); + expect($compilerSpy.mostRecentCall.args[0].key).toBe('newValue'); + expect($compilerSpy.mostRecentCall.args[0].key2).toBe('newValue2'); + + $compilerSpy.reset(); + interimTest.show( + interimTest.preset({ + key2: 'newValue2' + }).key2('superNewValue2') + ); + expect($compilerSpy.mostRecentCall.args[0].key).toBe('defaultValue'); + expect($compilerSpy.mostRecentCall.args[0].key2).toBe('superNewValue2'); + }); + }); + + }); + + describe('a service', function() { + var Service; + beforeEach(function() { + setup(); + inject(function($$interimElement) { + Service = $$interimElement(); + }); + }); + + describe('instance#show', function() { + it('inherits default options', inject(function($$interimElement) { + var defaults = { templateUrl: 'testing.html' }; + Service.show(defaults); + expect($compilerSpy.mostRecentCall.args[0].templateUrl).toBe('testing.html'); + })); + + it('forwards options to $mdCompiler', inject(function($$interimElement) { + var options = {template: ''}; + Service.show(options); + expect($compilerSpy.mostRecentCall.args[0].template).toBe(''); + })); + + it('supports theming', inject(function($$interimElement, $rootScope) { + Service.show({themable: true}); + $rootScope.$digest(); + expect($themingSpy).toHaveBeenCalled(); + })); + + it('calls hide after hideDelay', inject(function($animate, $timeout, $rootScope) { + var hideSpy = spyOn(Service, 'cancel').andCallThrough(); + Service.show({hideDelay: 1000}); + $rootScope.$digest(); + $animate.triggerCallbacks(); + $timeout.flush(); + expect(hideSpy).toHaveBeenCalled(); + })); + + it('calls onRemove', inject(function($rootScope) { + var onRemoveCalled = false; + Service.show({ + template: '', + isPassingOptions: true, + onRemove: onRemove + }); + $rootScope.$digest(); + Service.hide(); + $rootScope.$digest(); + expect(onRemoveCalled).toBe(true); + + function onRemove(scope, el, options) { + onRemoveCalled = true; + expect(options.isPassingOptions).toBe(true); + expect(el[0]).toBeTruthy(); + } + })); + + it('returns a promise', inject(function($$interimElement) { + expect(typeof Service.show().then).toBe('function'); + })); + }); + + + describe('#hide', function() { + it('calls onRemove', inject(function($rootScope) { + var onRemoveCalled = false; + Service.show({ + template: '', + passingOptions: true, + onRemove: onRemove + }); + $rootScope.$digest(); + Service.hide(); + $rootScope.$digest(); + expect(onRemoveCalled).toBe(true); + + function onRemove(scope, el, options) { + onRemoveCalled = true; + expect(options.passingOptions).toBe(true); + expect(el[0]).toBeTruthy(); + } + })); + + it('resolves the show promise', inject(function($animate, $rootScope) { + var resolved = false; + + Service.show().then(function(arg) { + expect(arg).toBe('test'); + resolved = true; + }); + $rootScope.$digest(); + $animate.triggerCallbacks(); + Service.hide('test'); + $rootScope.$digest(); + $animate.triggerCallbacks(); + expect(resolved).toBe(true); + })); + }); + + describe('#cancel', function() { + it('calls onRemove', inject(function($rootScope) { + var onRemoveCalled = false; + Service.show({ + template: '', + passingOptions: true, + onRemove: onRemove + }); + $rootScope.$digest(); + Service.cancel(); + $rootScope.$digest(); + expect(onRemoveCalled).toBe(true); + + function onRemove(scope, el, options) { + onRemoveCalled = true; + expect(options.passingOptions).toBe(true); + expect(el[0]).toBeTruthy(); + } + })); + + it('rejects the show promise', inject(function($animate, $rootScope) { + var rejected = false; + + Service.show().catch(function(arg) { + expect(arg).toBe('test'); + rejected = true; + }); + $rootScope.$digest(); + $animate.triggerCallbacks(); + Service.cancel('test'); + $rootScope.$digest(); + $animate.triggerCallbacks(); + expect(rejected).toBe(true); + })); + }); + }); +}); + diff --git a/src/components/animate/inkCssRipple.js b/src/core/services/ripple/ripple.js similarity index 64% rename from src/components/animate/inkCssRipple.js rename to src/core/services/ripple/ripple.js index 2a3ca4c345d..faa111b2eba 100644 --- a/src/components/animate/inkCssRipple.js +++ b/src/core/services/ripple/ripple.js @@ -1,19 +1,13 @@ +(function() { +'use strict'; -angular.module('material.animations') -.directive('inkRipple', [ - '$mdInkRipple', - InkRippleDirective -]) - -.factory('$mdInkRipple', [ - '$window', - '$$rAF', - '$mdEffects', - '$timeout', - '$mdUtil', - InkRippleService -]); +angular.module('material.core') + .factory('$mdInkRipple', InkRippleService) + .directive('inkRipple', InkRippleDirective) + .directive('noink', attrNoDirective()) + .directive('nobar', attrNoDirective()) + .directive('nostretch', attrNoDirective()); function InkRippleDirective($mdInkRipple) { return function(scope, element, attr) { @@ -25,7 +19,7 @@ function InkRippleDirective($mdInkRipple) { }; } -function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { +function InkRippleService($window, $$rAF, $mdUtil, $timeout, $mdConstant) { return { attachButtonBehavior: attachButtonBehavior, @@ -88,16 +82,17 @@ function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { }; function rippleIsAllowed() { - return !$mdUtil.isParentDisabled(element, 2); + return !element[0].hasAttribute('disabled') && + !(element[0].parentNode && element[0].parentNode.hasAttribute('disabled')); } function createRipple(left, top, positionsAreAbsolute) { var rippleEl = angular.element('
') - .css($mdEffects.ANIMATION_DURATION, options.animationDuration + 'ms') - .css($mdEffects.ANIMATION_NAME, options.animationName) - .css($mdEffects.ANIMATION_TIMING, options.animationTimingFunction) - .on($mdEffects.ANIMATIONEND_EVENT, function() { + .css($mdConstant.CSS.ANIMATION_DURATION, options.animationDuration + 'ms') + .css($mdConstant.CSS.ANIMATION_NAME, options.animationName) + .css($mdConstant.CSS.ANIMATION_TIMING, options.animationTimingFunction) + .on($mdConstant.CSS.ANIMATIONEND, function() { rippleEl.remove(); }); @@ -133,7 +128,7 @@ function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { top: (top - containerWidth / 2) + 'px', height: containerWidth + 'px' }; - css[$mdEffects.ANIMATION_DURATION] = options.fadeoutDuration + 'ms'; + css[$mdConstant.CSS.ANIMATION_DURATION] = options.fadeoutDuration + 'ms'; rippleEl.css(css); return rippleEl; @@ -146,7 +141,7 @@ function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { rippleEl = createRipple(ev.center.x, ev.center.y, true); pauseTimeout = $timeout(function() { - rippleEl && rippleEl.css($mdEffects.ANIMATION_PLAY_STATE, 'paused'); + rippleEl && rippleEl.css($mdConstant.CSS.ANIMATION_PLAY_STATE, 'paused'); }, options.mousedownPauseTime, false); rippleEl.on('$destroy', function() { @@ -155,10 +150,50 @@ function InkRippleService($window, $$rAF, $mdEffects, $timeout, $mdUtil) { } else if (ev.eventType === Hammer.INPUT_END && ev.isFinal) { $timeout.cancel(pauseTimeout); - rippleEl && rippleEl.css($mdEffects.ANIMATION_PLAY_STATE, ''); + rippleEl && rippleEl.css($mdConstant.CSS.ANIMATION_PLAY_STATE, ''); } } } } + +/** + * noink/nobar/nostretch directive: make any element that has one of + * these attributes be given a controller, so that other directives can + * `require:` these and see if there is a `no` parent attribute. + * + * @usage + * + * + * + * + * + * + * + * + * myApp.directive('detectNo', function() { + * return { + * require: ['^?noink', ^?nobar'], + * link: function(scope, element, attr, ctrls) { + * var noinkCtrl = ctrls[0]; + * var nobarCtrl = ctrls[1]; + * if (noInkCtrl) { + * alert("the noink flag has been specified on an ancestor!"); + * } + * if (nobarCtrl) { + * alert("the nobar flag has been specified on an ancestor!"); + * } + * } + * }; + * }); + * + */ +function attrNoDirective() { + return function() { + return { + controller: angular.noop + }; + }; +} +})(); diff --git a/src/services/theming/theming.js b/src/core/services/theming/theming.js similarity index 89% rename from src/services/theming/theming.js rename to src/core/services/theming/theming.js index dc847c89b60..7a8c5257500 100644 --- a/src/services/theming/theming.js +++ b/src/core/services/theming/theming.js @@ -1,26 +1,16 @@ -/* - * @ngdoc module - * @name material.services.theming - * @description Used to provide theming to angular-material directives - */ +(function() { +'use strict'; + -angular.module('material.services.theming', [ -]) -.directive('mdTheme', [ - '$interpolate', - ThemingDirective -]) -.directive('mdThemable', [ - '$mdTheming', - ThemableDirective -]) -.provider('$mdTheming', [ - Theming -]); +angular.module('material.core') + .directive('mdTheme', ThemingDirective) + .directive('mdThemable', ThemableDirective) + .provider('$mdTheming', Theming); /** * @ngdoc provider * @name $mdThemingProvider + * @module material.core * * @description Provider to configure the `$mdTheming` service. */ @@ -132,3 +122,4 @@ function ThemingDirective($interpolate) { function ThemableDirective($mdTheming) { return $mdTheming; } +})(); diff --git a/src/services/theming/theming.spec.js b/src/core/services/theming/theming.spec.js similarity index 94% rename from src/services/theming/theming.spec.js rename to src/core/services/theming/theming.spec.js index 408cd69c3df..7181241f74c 100644 --- a/src/services/theming/theming.spec.js +++ b/src/core/services/theming/theming.spec.js @@ -1,6 +1,6 @@ describe('$mdTheming service', function() { var $mdThemingProvider; - beforeEach(module('material.services.theming', function(_$mdThemingProvider_) { + beforeEach(module('material.core', function(_$mdThemingProvider_) { $mdThemingProvider = _$mdThemingProvider_; })); @@ -63,7 +63,7 @@ describe('$mdTheming service', function() { }); describe('md-theme directive', function() { - beforeEach(module('material.services.theming')); + beforeEach(module('material.core')); it('should observe and set mdTheme controller', inject(function($compile, $rootScope) { $rootScope.themey = 'red'; @@ -77,10 +77,8 @@ describe('md-theme directive', function() { }); describe('md-themable directive', function() { - beforeEach(module('material.services.theming')); - var $mdThemingProvider; - beforeEach(module('material.services.theming', function(_$mdThemingProvider_) { + beforeEach(module('material.core', function(_$mdThemingProvider_) { $mdThemingProvider = _$mdThemingProvider_; })); diff --git a/src/core/style/structure.scss b/src/core/style/structure.scss index a9216314c07..50d06514409 100644 --- a/src/core/style/structure.scss +++ b/src/core/style/structure.scss @@ -217,3 +217,54 @@ input { .md-shadow-animated.md-shadow { transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); } + +// Button ripple: keep same opacity, but expand to 0.75 scale. +// Then, fade out and expand to 2.0 scale. +@keyframes inkRippleButton { + 0% { + transform: scale(0); + opacity: 0.15; + } + 50% { + transform: scale(0.75); + opacity: 0.15; + } + 100% { + transform: scale(2.0); + opacity: 0; + } +} + +// Checkbox ripple: fully expand, then fade out. +@keyframes inkRippleCheckbox { + 0% { + transform: scale(0); + opacity: 0.4; + } + 50% { + transform: scale(1.0); + opacity: 0.4; + } + 100% { + transform: scale(1.0); + opacity: 0; + } +} + +/* + * A container inside of a rippling element (eg a button), + * which contains all of the individual ripples + */ +.md-ripple-container { + pointer-events: none; + position: absolute; + overflow: hidden; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.md-ripple { + position: absolute; +} diff --git a/src/core/util/constant.js b/src/core/util/constant.js index 6bdd1ac4604..c15ccb527f7 100644 --- a/src/core/util/constant.js +++ b/src/core/util/constant.js @@ -1,12 +1,38 @@ +(function() { + angular.module('material.core') -.constant('$mdConstant', { - KEY_CODE: { - ENTER: 13, - ESCAPE: 27, - SPACE: 32, - LEFT_ARROW : 37, - UP_ARROW : 38, - RIGHT_ARROW : 39, - DOWN_ARROW : 40 +.factory('$mdConstant', function($$rAF, $sniffer) { + + var webkit = /webkit/i.test($sniffer.vendorPrefix); + function vendorProperty(name) { + return webkit ? ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name; } + + return { + KEY_CODE: { + ENTER: 13, + ESCAPE: 27, + SPACE: 32, + LEFT_ARROW : 37, + UP_ARROW : 38, + RIGHT_ARROW : 39, + DOWN_ARROW : 40 + }, + CSS: { + /* Constants */ + TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''), + ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''), + + TRANSFORM: vendorProperty('transform'), + TRANSITION: vendorProperty('transition'), + TRANSITION_DURATION: vendorProperty('transitionDuration'), + ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'), + ANIMATION_DURATION: vendorProperty('animationDuration'), + ANIMATION_NAME: vendorProperty('animationName'), + ANIMATION_TIMING: vendorProperty('animationTimingFunction'), + ANIMATION_DIRECTION: vendorProperty('animationDirection') + } + }; }); + +})(); diff --git a/src/core/util/iterator.spec.js b/src/core/util/iterator.spec.js index e85c31c6de7..5c469d57035 100644 --- a/src/core/util/iterator.spec.js +++ b/src/core/util/iterator.spec.js @@ -289,11 +289,6 @@ describe('iterator', function() { }); - - - }); - - }); diff --git a/src/core/util/util.js b/src/core/util/util.js index a5286629489..9867bec204d 100644 --- a/src/core/util/util.js +++ b/src/core/util/util.js @@ -8,72 +8,10 @@ var nextUniqueId = ['0','0','0']; angular.module('material.core') .factory('$mdUtil', ['$cacheFactory', function($cacheFactory) { - var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; - var Util; return Util = { now: window.performance ? angular.bind(window.performance, window.performance.now) : Date.now, - /** - * Checks if the specified element has an ancestor (ancestor being parent, grandparent, etc) - * with the given attribute defined. - * - * Also pass in an optional `limit` (levels of ancestry to scan), default 4. - */ - ancestorHasAttribute: function ancestorHasAttribute(element, attrName, limit) { - limit = limit || 4; - var current = element; - while (limit-- && current.length) { - if (current[0].hasAttribute && current[0].hasAttribute(attrName)) { - return true; - } - current = current.parent(); - } - return false; - }, - - /** - * Checks to see if the element or its parents are disabled. - * @param element DOM element to start scanning for `disabled` attribute - * @param limit Number of parent levels that should be scanned; defaults to 4 - * @returns {*} Boolean - */ - isParentDisabled: function isParentDisabled(element, limit) { - return Util.ancestorHasAttribute(element, 'disabled', limit); - }, - - /** - * Checks if two elements have the same parent - */ - elementIsSibling: function elementIsSibling(element, otherElement) { - return element.parent().length && - (element.parent()[0] === otherElement.parent()[0]); - }, - - /** - * Converts snake_case to camelCase. - * @param name Name to normalize - */ - camelCase: function camelCase(name) { - return name - .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { - return offset ? letter.toUpperCase() : letter; - }); - }, - - /** - * Selects 'n' words from a string - * for use in an HTML attribute - */ - stringFromTextBody: function stringFromTextBody(textBody, numWords) { - var string = textBody.trim(); - - if(string.split(/\s+/).length > numWords){ - string = textBody.split(/\s+/).slice(1, (numWords + 1)).join(" ") + '...'; - } - return string; - }, - /** * Publish the iterator facade to easily support iteration and accessors * @see iterator below @@ -119,23 +57,6 @@ angular.module('material.core') }; }, - /** - * Wraps an element with a tag - * - * @param el element to wrap - * @param tag tag to wrap it with - * @param [className] optional class to apply to the wrapper - * @returns new element - * - */ - wrap: function(el, tag, className) { - if(el.hasOwnProperty(0)) { el = el[0]; } - var wrapper = document.createElement(tag); - wrapper.className += className; - wrapper.appendChild(el.parentNode.replaceChild(wrapper, el)); - return angular.element(wrapper); - }, - /** * nextUid, from angular.js. * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric diff --git a/src/services/attrBind/attrBind.js b/src/services/attrBind/attrBind.js deleted file mode 100644 index 6ce5dcabfa2..00000000000 --- a/src/services/attrBind/attrBind.js +++ /dev/null @@ -1,91 +0,0 @@ -angular.module('material.services.attrBind', [ -]) - .factory('$attrBind', [ - '$parse', - '$interpolate', - MdAttrBind - ]); - -/** - * This service allows directives to easily databind attributes to private scope properties. - * - * @private - */ -function MdAttrBind($parse, $interpolate) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - - return function (scope, attrs, bindDefinition, bindDefaults) { - angular.forEach(bindDefinition || {}, function (definition, scopeName) { - //Adapted from angular.js $compile - var match = definition.match(LOCAL_REGEXP) || [], - attrName = match[3] || scopeName, - mode = match[1], // @, =, or & - parentGet, - unWatchFn; - - switch (mode) { - case '@': // One-way binding from attribute into scope - - attrs.$observe(attrName, function (value) { - scope[scopeName] = value; - }); - attrs.$$observers[attrName].$$scope = scope; - - if (!bypassWithDefaults(attrName, scopeName)) { - // we trigger an interpolation to ensure - // the value is there for use immediately - scope[scopeName] = $interpolate(attrs[attrName])(scope); - } - break; - - case '=': // Two-way binding... - - if (!bypassWithDefaults(attrName, scopeName)) { - // Immediate evaluation - scope[scopeName] = (attrs[attrName] === "") ? true : scope.$eval(attrs[attrName]); - - // Data-bind attribute to scope (incoming) and - // auto-release watcher when scope is destroyed - - unWatchFn = scope.$watch(attrs[attrName], function (value) { - scope[scopeName] = value; - }); - scope.$on('$destroy', unWatchFn); - } - - break; - - case '&': // execute an attribute-defined expression in the context of the parent scope - - if (!bypassWithDefaults(attrName, scopeName, angular.noop)) { - /* jshint -W044 */ - if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) { - throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' + - attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.'); - } - - parentGet = $parse(attrs[attrName]); - scope[scopeName] = function (locals) { - return parentGet(scope, locals); - }; - } - - break; - } - }); - - /** - * Optional fallback value if attribute is not specified on element - * @param scopeName - */ - function bypassWithDefaults(attrName, scopeName, defaultVal) { - if (!angular.isDefined(attrs[attrName])) { - var hasDefault = bindDefaults && bindDefaults.hasOwnProperty(scopeName); - scope[scopeName] = hasDefault ? bindDefaults[scopeName] : defaultVal; - return true; - } - return false; - } - - }; -} diff --git a/src/services/interimElement/interimElement.js b/src/services/interimElement/interimElement.js deleted file mode 100644 index 15b91a1faed..00000000000 --- a/src/services/interimElement/interimElement.js +++ /dev/null @@ -1,203 +0,0 @@ -/* - * @ngdoc module - * @name material.services.interimElement - * @description InterimElement - */ - -angular.module('material.services.interimElement', [ - 'material.services.compiler', - 'material.services.theming' -]) -.factory('$$interimElement', [ - '$q', - '$rootScope', - '$timeout', - '$rootElement', - '$animate', - '$mdCompiler', - '$mdTheming', - InterimElementFactory -]); - -/* - * @ngdoc service - * @name $$interimElement - * - * @description - * - * Factory that contructs `$$interimElement.$service` services. - * Used internally in material design for elements that appear on screen temporarily. - * The service provides a promise-like API for interacting with the temporary - * elements. - * - * ```js - * app.service('$mdToast', function($$interimElement) { - * var $mdToast = $$interimElement(toastDefaultOptions); - * return $mdToast; - * }); - * ``` - * @param {object=} defaultOptions Options used by default for the `show` method on the service. - * - * @returns {$$interimElement.$service} - * - */ - -function InterimElementFactory($q, $rootScope, $timeout, $rootElement, $animate, $mdCompiler, $mdTheming) { - - return function createInterimElementService(defaults) { - - /* - * @ngdoc service - * @name $$interimElement.$service - * - * @description - * A service used to control inserting and removing an element into the DOM. - * - */ - - - var stack = []; - - defaults = angular.extend({ - onShow: function(scope, $el, options) { - return $animate.enter($el, options.parent); - }, - onRemove: function(scope, $el, options) { - return $animate.leave($el); - }, - }, defaults || {}); - - var service; - return service = { - show: show, - hide: hide, - cancel: cancel - }; - - /* - * @ngdoc method - * @name $$interimElement.$service#show - * @kind function - * - * @description - * Compiles and inserts an element into the DOM. - * - * @param {Object} options Options object to compile with. - * - * @returns {Promise} Promise that will resolve when the service - * has `#close()` or `#cancel()` called. - * - */ - function show(options) { - if (stack.length) { - service.hide(); - } - - var interimElement = new InterimElement(options); - stack.push(interimElement); - return interimElement.show().then(function() { - return interimElement.deferred.promise; - }); - } - - /* - * @ngdoc method - * @name $$interimElement.$service#hide - * @kind function - * - * @description - * Removes the `$interimElement` from the DOM and resolves the promise returned from `show` - * - * @param {*} resolveParam Data to resolve the promise with - * - * @returns undefined data that resolves after the element has been removed. - * - */ - function hide(success) { - var interimElement = stack.shift(); - interimElement && interimElement.remove().then(function() { - interimElement.deferred.resolve(success); - }); - } - - /* - * @ngdoc method - * @name $$interimElement.$service#cancel - * @kind function - * - * @description - * Removes the `$interimElement` from the DOM and rejects the promise returned from `show` - * - * @param {*} reason Data to reject the promise with - * - * @returns undefined - * - */ - function cancel(reason) { - var interimElement = stack.shift(); - interimElement && interimElement.remove().then(function() { - interimElement.deferred.reject(reason); - }); - } - - - /* - * Internal Interim Element Object - * Used internally to manage the DOM element and related data - */ - function InterimElement(options) { - var self; - var hideTimeout, element; - - options = options || {}; - - options = angular.extend({ - scope: options.scope || $rootScope.$new(options.isolateScope) - }, defaults, options); - - return self = { - options: options, - deferred: $q.defer(), - show: function() { - return $mdCompiler.compile(options).then(function(compiledData) { - // Search for parent at insertion time, if not specified - if (!options.parent) { - options.parent = $rootElement.find('body'); - if (!options.parent.length) options.parent = $rootElement; - } - element = compiledData.link(options.scope); - if (options.themable) $mdTheming(element); - var ret = options.onShow(options.scope, element, options); - return $q.when(ret) - .then(function(){ - // Issue onComplete callback when the `show()` finishes - var notify = options.onComplete || angular.noop; - notify.apply(null, [options.scope, element, options]); - }) - .then(startHideTimeout); - - function startHideTimeout() { - if (options.hideDelay) { - hideTimeout = $timeout(service.hide, options.hideDelay) ; - } - } - }); - }, - cancelTimeout: function() { - if (hideTimeout) { - $timeout.cancel(hideTimeout); - hideTimeout = undefined; - } - }, - remove: function() { - self.cancelTimeout(); - var ret = options.onRemove(options.scope, element, options); - return $q.when(ret).then(function() { - options.scope.$destroy(); - }); - } - }; - } - }; -} - diff --git a/src/services/interimElement/interimElement.spec.js b/src/services/interimElement/interimElement.spec.js deleted file mode 100644 index 8c4f919eb33..00000000000 --- a/src/services/interimElement/interimElement.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -describe('$$interimElement service', function() { - var $compilerSpy, $themingSpy, resolvingPromise; - - beforeEach(module('material.services.interimElement', 'ngAnimateMock', function($provide) { - var $mdCompiler = { compile: angular.noop }; - $compilerSpy = spyOn($mdCompiler, 'compile'); - $themingSpy = jasmine.createSpy('$mdTheming'); - - $provide.value('$mdCompiler', $mdCompiler); - $provide.value('$mdTheming', $themingSpy); - })); - - beforeEach(inject(function($q, $compile) { - $compilerSpy.andCallFake(function(opts) { - var el = $compile(opts.template); - var deferred = $q.defer(); - deferred.resolve({ - link: el - }); - return deferred.promise; - }); - })); - - describe('instance', function() { - var Service; - beforeEach(inject(function($$interimElement) { - Service = $$interimElement(); - })); - - describe('#show', function() { - it('inherits default options', inject(function($$interimElement) { - var defaults = { templateUrl: 'testing.html' }; - Service = $$interimElement(defaults); - Service.show(); - expect($compilerSpy.mostRecentCall.args[0].templateUrl).toBe('testing.html'); - })); - - it('forwards options to $mdCompiler', inject(function($$interimElement) { - var options = {template: ''}; - Service.show(options); - expect($compilerSpy.mostRecentCall.args[0].template).toBe(''); - })); - - it('supports theming', inject(function($$interimElement, $rootScope) { - Service.show({themable: true}); - $rootScope.$digest(); - expect($themingSpy).toHaveBeenCalled(); - })); - - it('calls hide after hideDelay', inject(function($animate, $timeout, $rootScope) { - var hideSpy = spyOn(Service, 'hide').andCallThrough(); - Service.show({hideDelay: 1000}); - $rootScope.$digest(); - $animate.triggerCallbacks(); - $timeout.flush(); - expect(hideSpy).toHaveBeenCalled(); - })); - - it('calls onRemove', inject(function($rootScope) { - var onRemoveCalled = false; - Service.show({ - template: '', - isPassingOptions: true, - onRemove: onRemove - }); - $rootScope.$digest(); - Service.hide(); - $rootScope.$digest(); - expect(onRemoveCalled).toBe(true); - - function onRemove(scope, el, options) { - onRemoveCalled = true; - expect(options.isPassingOptions).toBe(true); - expect(el[0]).toBeTruthy(); - } - })); - - it('returns a promise', inject(function($$interimElement) { - expect(typeof Service.show().then).toBe('function'); - })); - }); - - describe('#hide', function() { - it('calls onRemove', inject(function($rootScope) { - var onRemoveCalled = false; - Service.show({ - template: '', - passingOptions: true, - onRemove: onRemove - }); - $rootScope.$digest(); - Service.hide(); - $rootScope.$digest(); - expect(onRemoveCalled).toBe(true); - - function onRemove(scope, el, options) { - onRemoveCalled = true; - expect(options.passingOptions).toBe(true); - expect(el[0]).toBeTruthy(); - } - })); - - it('resolves the show promise', inject(function($animate, $rootScope) { - var resolved = false; - - Service.show().then(function(arg) { - expect(arg).toBe('test'); - resolved = true; - }); - $rootScope.$digest(); - $animate.triggerCallbacks(); - Service.hide('test'); - $rootScope.$digest(); - $animate.triggerCallbacks(); - expect(resolved).toBe(true); - })); - }); - - describe('#cancel', function() { - it('calls onRemove', inject(function($rootScope) { - var onRemoveCalled = false; - Service.show({ - template: '', - passingOptions: true, - onRemove: onRemove - }); - $rootScope.$digest(); - Service.cancel(); - $rootScope.$digest(); - expect(onRemoveCalled).toBe(true); - - function onRemove(scope, el, options) { - onRemoveCalled = true; - expect(options.passingOptions).toBe(true); - expect(el[0]).toBeTruthy(); - } - })); - - it('rejects the show promise', inject(function($animate, $rootScope) { - var rejected = false; - - Service.show().then(undefined, function(arg) { - expect(arg).toBe('test'); - rejected = true; - }); - $rootScope.$digest(); - $animate.triggerCallbacks(); - Service.cancel('test'); - $rootScope.$digest(); - $animate.triggerCallbacks(); - expect(rejected).toBe(true); - })); - }); - - }); -}); - diff --git a/src/services/media/media.js b/src/services/media/media.js deleted file mode 100644 index 79e0faa038b..00000000000 --- a/src/services/media/media.js +++ /dev/null @@ -1,54 +0,0 @@ -angular.module('material.services.media', [ - 'material.core' -]) - -.factory('$mdMedia', [ - '$window', - '$mdUtil', - '$timeout', - mdMediaFactory -]); - -function mdMediaFactory($window, $mdUtil, $timeout) { - var cache = $mdUtil.cacheFactory('$mdMedia', { capacity: 15 }); - var presets = { - sm: '(min-width: 600px)', - md: '(min-width: 960px)', - lg: '(min-width: 1200px)' - }; - - angular.element($window).on('resize', updateAll); - - return $mdMedia; - - function $mdMedia(query) { - query = validate(query); - var result; - if ( !angular.isDefined(result = cache.get(query)) ) { - return add(query); - } - return result; - } - - function validate(query) { - return presets[query] || ( - query.charAt(0) != '(' ? ('(' + query + ')') : query - ); - } - - function add(query) { - return cache.put(query, !!$window.matchMedia(query).matches); - } - - function updateAll() { - var keys = cache.keys(); - if (keys.length) { - for (var i = 0, ii = keys.length; i < ii; i++) { - cache.put(keys[i], !!$window.matchMedia(keys[i]).matches); - } - // trigger a $digest() - $timeout(angular.noop); - } - } - -} diff --git a/src/services/registry/registry.js b/src/services/registry/registry.js deleted file mode 100644 index 89e8092b60d..00000000000 --- a/src/services/registry/registry.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * @ngdoc module - * @name material.services.registry - * - * @description - * A component registry system for accessing various component instances in an app. - */ -angular.module('material.services.registry', [ -]) - .factory('$mdComponentRegistry', [ - '$log', - mdComponentRegistry - ]); - -/* - * @ngdoc service - * @name $mdComponentRegistry - * @module material.services.registry - * - * @description - * $mdComponentRegistry enables the user to interact with multiple instances of - * certain complex components in a running app. - */ -function mdComponentRegistry($log) { - var instances = []; - - return { - /** - * Used to print an error when an instance for a handle isn't found. - */ - notFoundError: function(handle) { - $log.error('No instance found for handle', handle); - }, - /** - * Return all registered instances as an array. - */ - getInstances: function() { - return instances; - }, - - /** - * Get a registered instance. - * @param handle the String handle to look up for a registered instance. - */ - get: function(handle) { - var i, j, instance; - for(i = 0, j = instances.length; i < j; i++) { - instance = instances[i]; - if(instance.$$mdHandle === handle) { - return instance; - } - } - return null; - }, - - /** - * Register an instance. - * @param instance the instance to register - * @param handle the handle to identify the instance under. - */ - register: function(instance, handle) { - instance.$$mdHandle = handle; - instances.push(instance); - - return function deregister() { - var index = instances.indexOf(instance); - if (index !== -1) { - instances.splice(index, 1); - } - }; - } - } -} -