From e85e1b950a60403a7287854ae28d687bdbcbd119 Mon Sep 17 00:00:00 2001 From: soooooot Date: Mon, 21 Mar 2016 22:53:43 +0800 Subject: [PATCH] feat(slider): md-invert md-invert: make min value to top/right and max value to bottom/left Closes #7666 Closes #7667 --- .../slider/demoBasicUsage/index.html | 30 +++ .../slider/demoBasicUsage/script.js | 2 + src/components/slider/slider.js | 24 +- src/components/slider/slider.scss | 12 + src/components/slider/slider.spec.js | 226 ++++++++++++++++++ 5 files changed, 288 insertions(+), 6 deletions(-) diff --git a/src/components/slider/demoBasicUsage/index.html b/src/components/slider/demoBasicUsage/index.html index 20b98853d7c..171542cc9ef 100644 --- a/src/components/slider/demoBasicUsage/index.html +++ b/src/components/slider/demoBasicUsage/index.html @@ -33,6 +33,8 @@

+
+

Rating: {{rating}}/5 - demo of theming classes

@@ -56,6 +58,8 @@

Rating: {{rating}}/5 - demo of theming classes

+
+

Disabled

@@ -68,9 +72,35 @@

Disabled

Is disabled +
+

Disabled, Discrete, Read Only

Read only + +
+

Invert

+ +
+ Regular +
+ + + + + +
+ +
+ Invert +
+ + + + + +
+
diff --git a/src/components/slider/demoBasicUsage/script.js b/src/components/slider/demoBasicUsage/script.js index 24a5836493a..0d4dcfdf9c7 100644 --- a/src/components/slider/demoBasicUsage/script.js +++ b/src/components/slider/demoBasicUsage/script.js @@ -20,5 +20,7 @@ angular.module('sliderDemo1', ['ngMaterial']) $scope.disabled2 = 0; $scope.disabled3 = 70; + $scope.invert = Math.floor(Math.random() * 100); + $scope.isDisabled = true; }); diff --git a/src/components/slider/slider.js b/src/components/slider/slider.js index 381ac3c4b99..d6a2690258f 100644 --- a/src/components/slider/slider.js +++ b/src/components/slider/slider.js @@ -120,8 +120,14 @@ function SliderContainerDirective() { * * * + *

Invert Mode

+ * + * + * + * * * @param {boolean=} md-discrete Whether to enable discrete mode. + * @param {boolean=} md-invert Whether to enable invert mode. * @param {number=} step The distance between values the user is allowed to pick. Default 1. * @param {number=} min The minimum value the user is allowed to pick. Default 0. * @param {number=} max The maximum value the user is allowed to pick. Default 100. @@ -206,6 +212,7 @@ function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdThemi var DEFAULT_ROUND = 3; var vertical = angular.isDefined(attr.mdVertical); var discrete = angular.isDefined(attr.mdDiscrete); + var invert = angular.isDefined(attr.mdInvert); angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0); angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100); angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1); @@ -360,6 +367,7 @@ function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdThemi } else if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.UP_ARROW : ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) { changeAmount = step; } + changeAmount = invert ? -changeAmount : changeAmount; if (changeAmount) { if (ev.metaKey || ev.ctrlKey || ev.altKey) { changeAmount *= 4; @@ -408,7 +416,7 @@ function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdThemi ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue); - var percent = (ngModelCtrl.$viewValue - min) / (max - min); + var percent = valueToPercent(ngModelCtrl.$viewValue); scope.modelValue = ngModelCtrl.$viewValue; element.attr('aria-valuenow', ngModelCtrl.$viewValue); setSliderPercent(percent); @@ -447,12 +455,14 @@ function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdThemi percent = clamp(percent); var thumbPosition = (percent * 100) + '%'; + var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition; thumbContainer.css(vertical ? 'bottom' : 'left', thumbPosition); - activeTrack.css(vertical ? 'height' : 'width', thumbPosition); + + activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent); - element.toggleClass('_md-min', percent === 0); - element.toggleClass('_md-max', percent === 1); + element.toggleClass((invert ? '_md-max' : '_md-min'), percent === 0); + element.toggleClass((invert ? '_md-min' : '_md-max'), percent === 1); } /** @@ -562,11 +572,13 @@ function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdThemi * @returns {*} */ function percentToValue( percent ) { - return (min + percent * (max - min)); + var adjustedPercent = invert ? (1 - percent) : percent; + return (min + adjustedPercent * (max - min)); } function valueToPercent( val ) { - return (val - min)/(max - min); + var percent = (val - min) / (max - min); + return invert ? (1 - percent) : percent; } } } diff --git a/src/components/slider/slider.scss b/src/components/slider/slider.scss index 0635329dcf1..bedef63414d 100644 --- a/src/components/slider/slider.scss +++ b/src/components/slider/slider.scss @@ -425,6 +425,18 @@ md-slider { } } } + &[md-invert] { + &:not([md-vertical]) ._md-track-fill { + left: auto; + right: 0; + } + &[md-vertical] { + ._md-track-fill { + bottom: auto; + top: 0; + } + } + } } md-slider-container { diff --git a/src/components/slider/slider.spec.js b/src/components/slider/slider.spec.js index 878d73461e1..58dec70a2a9 100644 --- a/src/components/slider/slider.spec.js +++ b/src/components/slider/slider.spec.js @@ -553,6 +553,232 @@ describe('md-slider', function() { })); }); + + describe('invert', function () { + it('should set model on press', function() { + var slider = setup('md-vertical md-invert ng-model="value" min="0" max="100"'); + pageScope.$apply('value = 50'); + + var wrapper = getWrapper(slider); + + wrapper.triggerHandler({type: '$md.pressdown', pointer: { y: 70 }}); + wrapper.triggerHandler({type: '$md.dragstart', pointer: { y: 70 }}); + $timeout.flush(); + expect(pageScope.value).toBe(70); + + // When going past max, it should clamp to max. + wrapper.triggerHandler({type: '$md.drag', pointer: { y: 0 }}); + $timeout.flush(); + expect(pageScope.value).toBe(0); + + wrapper.triggerHandler({type: '$md.drag', pointer: { y: 50 }}); + $timeout.flush(); + expect(pageScope.value).toBe(50); + }); + + it('should decrement model on up arrow', function() { + var slider = setup('md-vertical md-invert min="100" max="104" step="2" ng-model="model"'); + pageScope.$apply('model = 104'); + + var wrapper = getWrapper(slider); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.UP_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(102); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.UP_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(100); + + // Stays at min. + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.UP_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(100); + + }); + + it('should increment model on down arrow', function() { + var slider = setup('md-vertical md-invert min="100" max="104" step="2" ng-model="model"'); + pageScope.$apply('model = 100'); + + var wrapper = getWrapper(slider); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.DOWN_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(102); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.DOWN_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(104); + + // Stays at max. + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.DOWN_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(104); + }); + + it('should update the thumb text', function() { + var slider = setup('md-vertical md-invert ng-model="value" md-discrete min="0" max="100" step="1"'); + var wrapper = getWrapper(slider); + + pageScope.$apply('value = 30'); + expect(slider[0].querySelector('._md-thumb-text').textContent).toBe('30'); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.DOWN_ARROW + }); + $timeout.flush(); + expect(slider[0].querySelector('._md-thumb-text').textContent).toBe('31'); + + wrapper.triggerHandler({type: '$md.pressdown', pointer: { y: 70 }}); + expect(slider[0].querySelector('._md-thumb-text').textContent).toBe('70'); + + wrapper.triggerHandler({type: '$md.dragstart', pointer: { y: 93 }}); + wrapper.triggerHandler({type: '$md.drag', pointer: { y: 93 }}); + expect(slider[0].querySelector('._md-thumb-text').textContent).toBe('93'); + }); + + it('should add _md-min class only when at min value', function() { + var slider = setup('md-vertical md-invert ng-model="model" min="0" max="30"'); + var wrapper = getWrapper(slider); + + pageScope.$apply('model = 0'); + expect(slider).toHaveClass('_md-min'); + + wrapper.triggerHandler({type: '$md.dragstart', pointer: {y: 0}}); + wrapper.triggerHandler({type: '$md.drag', pointer: {y: 10}}); + $timeout.flush(); + expect(slider).not.toHaveClass('_md-min'); + }); + + it('should add _md-max class only when at max value', function() { + var slider = setup('md-vertical md-invert ng-model="model" min="0" max="30"'); + var wrapper = getWrapper(slider); + + pageScope.$apply('model = 30'); + expect(slider).toHaveClass('_md-max'); + + wrapper.triggerHandler({type: '$md.dragstart', pointer: {y: 30}}); + wrapper.triggerHandler({type: '$md.drag', pointer: {y: 10}}); + $timeout.flush(); + expect(slider).not.toHaveClass('_md-max'); + }); + + it('should increment at a predictable step', function() { + + buildSlider(0.1, 0, 1).drag({y:30}); + expect(pageScope.value).toBe(0.3); + + buildSlider(0.25, 0, 1).drag({y:45}); + expect(pageScope.value).toBe(0.5); + + buildSlider(0.25, 0, 1).drag({y:75}); + expect(pageScope.value).toBe(0.75); + + buildSlider(1, 0, 100).drag({y:10}); + expect(pageScope.value).toBe(10); + + buildSlider(20, 5, 45).drag({y:50}); + expect(pageScope.value).toBe(25); + + function buildSlider(step, min, max) { + var slider = setup('md-vertical md-invert ng-model="value" min="' + min + '" max="' + max + '" step="' + step + '"'); + pageScope.$apply('value = 0.5'); + + var wrapper = getWrapper(slider); + + return { + drag : function simulateDrag(drag) { + + wrapper.triggerHandler({type: '$md.pressdown', pointer: drag }); + wrapper.triggerHandler({type: '$md.dragstart', pointer: drag }); + + $timeout.flush(); + } + }; + } + + }); + + it('should increment model on left arrow', function() { + var slider = setup('md-invert min="100" max="104" step="2" ng-model="model"'); + pageScope.$apply('model = 100'); + + var wrapper = getWrapper(slider); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.LEFT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(102); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.LEFT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(104); + + // Stays at max. + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.LEFT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(104); + }); + + it('should decrement model on right arrow', function() { + var slider = setup('md-invert min="100" max="104" step="2" ng-model="model"'); + pageScope.$apply('model = 104'); + + var wrapper = getWrapper(slider); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(102); + + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(100); + + // Stays at min. + wrapper.triggerHandler({ + type: 'keydown', + keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW + }); + $timeout.flush(); + expect(pageScope.model).toBe(100); + }); + + }); + it('should set a default tabindex', function() { var slider = setup();