diff --git a/config/build.config.js b/config/build.config.js index 8c404d2adb6..53d86cf3195 100644 --- a/config/build.config.js +++ b/config/build.config.js @@ -85,6 +85,7 @@ module.exports = { 'src/components/radioButton/radioButton.js', 'src/components/sidenav/sidenav.js', 'src/components/slider/slider.js', + 'src/components/switch/switch.js', 'src/components/tabs/tabs.js', 'src/components/tabs/util/*.js', 'src/components/toast/toast.js', diff --git a/src/base/utils.js b/src/base/utils.js index feccb5a8594..fb90f0242e5 100644 --- a/src/base/utils.js +++ b/src/base/utils.js @@ -38,7 +38,6 @@ var Util = { /** * Converts snake_case to camelCase. - * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ camelCase: function camelCase(name) { diff --git a/src/components/animate/_effects.scss b/src/components/animate/_effects.scss index 8b879c20d58..95b60853bb9 100644 --- a/src/components/animate/_effects.scss +++ b/src/components/animate/_effects.scss @@ -5,8 +5,6 @@ material-ink-bar { margin-top: -2px; } - - canvas.material-ink-ripple { pointer-events: none; position: absolute; diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js index cdbb59ea40d..a011b3f8028 100644 --- a/src/components/checkbox/checkbox.js +++ b/src/components/checkbox/checkbox.js @@ -59,7 +59,7 @@ function materialCheckboxDirective(inputDirectives, $expectAria) { require: '?ngModel', template: '
' + - '' + + '' + '
' + '
' + '
', @@ -109,7 +109,7 @@ function materialCheckboxDirective(inputDirectives, $expectAria) { } } function listener(ev) { - if ( Util.isDisabled(element) ) return; + if (element[0].hasAttribute('disabled')) return; scope.$apply(function() { checked = !checked; diff --git a/src/components/radioButton/_radio-button.scss b/src/components/radioButton/_radio-button.scss index 612793efe45..14d28b588a3 100644 --- a/src/components/radioButton/_radio-button.scss +++ b/src/components/radioButton/_radio-button.scss @@ -1,5 +1,6 @@ -material-radio-button { +material-radio-button, +.material-switch-thumb { // Used in switch display: block; margin: 15px; white-space: nowrap; diff --git a/src/components/radioButton/radioButton.js b/src/components/radioButton/radioButton.js index 595891f872a..dc6e69805b8 100644 --- a/src/components/radioButton/radioButton.js +++ b/src/components/radioButton/radioButton.js @@ -235,7 +235,7 @@ function materialRadioButtonDirective($expectAria) { $expectAria(element, Constant.ARIA.PROPERTY.LABEL, element.text()); function listener(ev) { - if ( Util.isDisabled(element) ) return; + if (element[0].hasAttribute('disabled')) return; scope.$apply(function() { rgCtrl.setViewValue(attr.value, ev && ev.type); diff --git a/src/components/switch/_switch.scss b/src/components/switch/_switch.scss new file mode 100644 index 00000000000..15ac8f57551 --- /dev/null +++ b/src/components/switch/_switch.scss @@ -0,0 +1,32 @@ +material-switch { + position: relative; + width: $switch-width; + height: $baseline-grid * 3; + + @include flex-display(); + @include flex-justify-content(center); + @include flex-align-items(center); + + .material-switch-bar { + width: 32px; + height: 1px; + background-color: #5a5a5a; + pointer-events: none; + } + + /* check _radio-button.scss */ + .material-switch-thumb { + position: absolute; + margin: 0; + left: 0; + top: 0; + + -webkit-transition: -webkit-transform 0.2s linear; + transition: transform 0.2s linear; + @include transform-translate3d(0,0,0); + + &.material-checked { + @include transform-translate3d($switch-width - 16,0,0); + } + } +} diff --git a/src/components/switch/demo1/index.html b/src/components/switch/demo1/index.html new file mode 100644 index 00000000000..fa2021685db --- /dev/null +++ b/src/components/switch/demo1/index.html @@ -0,0 +1,5 @@ +
+ +
+ Value: {{val}} +
diff --git a/src/components/switch/demo1/script.js b/src/components/switch/demo1/script.js new file mode 100644 index 00000000000..0bf309404de --- /dev/null +++ b/src/components/switch/demo1/script.js @@ -0,0 +1 @@ +angular.module('myApp', ['ngMaterial']); diff --git a/src/components/switch/module.json b/src/components/switch/module.json new file mode 100644 index 00000000000..48c8e376cde --- /dev/null +++ b/src/components/switch/module.json @@ -0,0 +1,10 @@ +{ + "module": "material.components.switch", + "name": "Switch", + "demos": { + "demo1": { + "name": "Switch Basic Usage", + "files": ["demo1/*"] + } + } +} diff --git a/src/components/switch/switch.js b/src/components/switch/switch.js new file mode 100644 index 00000000000..f5c3b896a43 --- /dev/null +++ b/src/components/switch/switch.js @@ -0,0 +1,39 @@ +angular.module('material.components.switch', [ + 'material.components.checkbox', + 'material.components.radioButton', +]) + +.directive('materialSwitch', [ + 'materialCheckboxDirective', + 'materialRadioButtonDirective', + MaterialSwitch +]); + +function MaterialSwitch(checkboxDirectives, radioButtonDirectives) { + var checkboxDirective = checkboxDirectives[0]; + var radioButtonDirective = radioButtonDirectives[0]; + + return { + restrict: 'E', + transclude: true, + template: + '
' + + '
' + + radioButtonDirective.template + + '
', + require: '?ngModel', + compile: compile + }; + + function compile(element, attr) { + + var thumb = angular.element(element[0].querySelector('.material-switch-thumb')); + //Copy down disabled attributes for checkboxDirective to use + thumb.attr('disabled', attr.disabled); + thumb.attr('ngDisabled', attr.ngDisabled); + + return function postLink(scope, element, attr, ngModelCtrl) { + checkboxDirective.link(scope, thumb, attr, ngModelCtrl); + }; + } +} diff --git a/src/components/switch/switch.spec.js b/src/components/switch/switch.spec.js new file mode 100644 index 00000000000..31e267a4dac --- /dev/null +++ b/src/components/switch/switch.spec.js @@ -0,0 +1,47 @@ +describe('', function() { + var CHECKED_CSS = 'material-checked'; + + beforeEach(module('material.components.switch')); + + it('should set checked css class and aria-checked attributes', inject(function($compile, $rootScope) { + var element = $compile('
' + + '' + + '' + + '' + + '' + + '
')($rootScope); + + $rootScope.$apply(function(){ + $rootScope.blue = false; + $rootScope.green = true; + }); + + var cbElements = element.find('.material-switch-thumb'); + + expect(cbElements.eq(0).hasClass(CHECKED_CSS)).toEqual(false); + expect(cbElements.eq(1).hasClass(CHECKED_CSS)).toEqual(true); + expect(cbElements.eq(0).attr('aria-checked')).toEqual('false'); + expect(cbElements.eq(1).attr('aria-checked')).toEqual('true'); + expect(cbElements.eq(0).attr('role')).toEqual('checkbox'); + })); + + it('should be disabled with disabled attr', inject(function($compile, $rootScope) { + var element = $compile('
' + + '' + + '' + + '
')($rootScope); + + var switchThumb = element.find('.material-switch-thumb'); + + $rootScope.$apply('blue = false'); + + switchThumb.triggerHandler('click'); + expect($rootScope.blue).toBe(false); + + switchThumb.removeAttr('disabled'); + + switchThumb.triggerHandler('click'); + expect($rootScope.blue).toBe(true); + })); + +}); diff --git a/src/main.scss b/src/main.scss index 23163f25796..8e9502ec407 100644 --- a/src/main.scss +++ b/src/main.scss @@ -24,6 +24,7 @@ "components/sidenav/sidenav", "components/form/form", "components/slider/slider", +"components/switch/switch", "components/toast/toast", "components/toolbar/toolbar", "components/tabs/tabs", diff --git a/src/theme/_variables.scss b/src/theme/_variables.scss index de47416b473..6189f6b8d5a 100644 --- a/src/theme/_variables.scss +++ b/src/theme/_variables.scss @@ -155,6 +155,11 @@ $slider-track-height: 2px; $slider-thumb-width: 10px; $slider-thumb-height: $slider-thumb-width; +// Switch +//-------------------------------------------- + +$switch-width: $baseline-grid * 8; + // Checkbox //--------------------------------------------