+
diff --git a/src/components/slider/demo1/script.js b/src/components/slider/demo1/script.js
index 371773d60f6..c7dd535d929 100644
--- a/src/components/slider/demo1/script.js
+++ b/src/components/slider/demo1/script.js
@@ -3,10 +3,14 @@ angular.module('app', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
- $scope.data = {
- slider1: 0,
- slider2: 50,
- slider3: 8,
- }
+ $scope.color = {
+ red: Math.floor(Math.random() * 255),
+ green: Math.floor(Math.random() * 255),
+ blue: Math.floor(Math.random() * 255)
+ };
+
+ $scope.rating = 3;
+ $scope.disabled1 = 0;
+ $scope.disabled2 = 70;
});
diff --git a/src/components/slider/slider.js b/src/components/slider/slider.js
index 5c273c96530..4f6e2e6d3f3 100644
--- a/src/components/slider/slider.js
+++ b/src/components/slider/slider.js
@@ -1,112 +1,315 @@
/**
* @ngdoc module
* @name material.components.slider
- * @description Slider module!
*/
-angular.module('material.components.slider', [])
- .directive('materialSlider', [
- '$window',
- materialSliderDirective
- ]);
+angular.module('material.components.slider', [
+ 'material.animations',
+ 'material.services.aria'
+])
+.directive('materialSlider', [
+ SliderDirective
+]);
/**
* @ngdoc directive
* @name materialSlider
* @module material.components.slider
* @restrict E
- *
* @description
- * The `material-slider` directive creates a slider bar that you can use.
+ * The `` component allows the user to choose from a range of
+ * values.
*
- * Simply put a native `` element inside of a
- * `` container.
+ * It has two modes: 'normal' mode, where the user slides between a wide range
+ * of values, and 'discrete' mode, where the user slides between only a few
+ * select values.
*
- * On the range input, all HTML5 range attributes are supported.
+ * To enable discrete mode, add the `discrete` attribute to a slider,
+ * and use the `step` attribute to change the distance between
+ * values the user is allowed to pick.
*
* @usage
+ *
Normal Mode
+ *
+ *
+ *
+ *
+ *
Discrete Mode
*
- *
- *
+ *
*
*
+ *
+ * @param {boolean=} discrete Whether to enable discrete mode.
+ * @param {number=} step The distance between values the user is allowed to pick. Default 1.
+ * @param {number=} min The minimum value the user is allowed to pick. Default 0.
+ * @param {number=} max The maximum value the user is allowed to pick. Default 100.
*/
-function materialSliderDirective($window) {
-
- var MIN_VALUE_CSS = 'material-slider-min';
- var ACTIVE_CSS = 'material-active';
+function SliderDirective() {
+ return {
+ scope: {},
+ require: ['?ngModel', 'materialSlider'],
+ controller: [
+ '$scope',
+ '$element',
+ '$attrs',
+ '$$rAF',
+ '$timeout',
+ '$window',
+ '$materialEffects',
+ '$aria',
+ SliderController
+ ],
+ template:
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ link: postLink
+ };
- function rangeSettings(rangeEle) {
- return {
- min: parseInt( rangeEle.min !== "" ? rangeEle.min : 0, 10 ),
- max: parseInt( rangeEle.max !== "" ? rangeEle.max : 100, 10 ),
- step: parseInt( rangeEle.step !== "" ? rangeEle.step : 1, 10 )
+ function postLink(scope, element, attr, ctrls) {
+ var ngModelCtrl = ctrls[0] || {
+ // Mock ngModelController if it doesn't exist to give us
+ // the minimum functionality needed
+ $setViewValue: function(val) {
+ this.$viewValue = val;
+ this.$viewChangeListeners.forEach(function(cb) { cb(); });
+ },
+ $parsers: [],
+ $formatters: [],
+ $viewChangeListeners: []
};
+
+ var sliderCtrl = ctrls[1];
+ sliderCtrl.init(ngModelCtrl);
}
+}
- return {
- restrict: 'E',
- scope: true,
- transclude: true,
- template: '',
- link: link
- };
+/**
+ * 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, $timeout, $window, $materialEffects, $aria) {
+
+ this.init = function init(ngModelCtrl) {
+ var thumb = angular.element(element[0].querySelector('.slider-thumb'));
+ var thumbContainer = thumb.parent();
+ var trackContainer = angular.element(element[0].querySelector('.slider-track-container'));
+ var activeTrack = angular.element(element[0].querySelector('.slider-track-fill'));
+ var tickContainer = angular.element(element[0].querySelector('.slider-track-ticks'));
- // **********************************************************
- // Private Methods
- // **********************************************************
+ // 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);
- function link(scope, element, attr) {
- var input = element.find('input');
- var ngModelCtrl = angular.element(input).controller('ngModel');
+ attr.ngDisabled ?
+ scope.$watch(attr.ngDisabled, updateAriaDisabled) :
+ updateAriaDisabled(!!attr.disabled);
- if(!input || !ngModelCtrl || input[0].type !== 'range') return;
+ $aria.expect(element, 'aria-label');
+ element.attr('tabIndex', 0);
+ element.attr('role', Constant.ARIA.ROLE.SLIDER);
+ element.on('keydown', keydownListener);
- var rangeEle = input[0];
- var trackEle = angular.element( element[0].querySelector('.material-track') );
+ var hammertime = new Hammer(element[0], {
+ recognizers: [
+ [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
+ ]
+ });
+ hammertime.on('hammer.input', onInput);
+ hammertime.on('panstart', onPanStart);
+ hammertime.on('pan', onPan);
- trackEle.append('
');
- var fillEle = trackEle[0].querySelector('.material-fill');
+ // On resize, recalculate the slider's dimensions and re-render
+ var onWindowResize = $$rAF.debounce(function() {
+ refreshSliderDimensions();
+ ngModelRender();
+ redrawTicks();
+ }, false);
+ angular.element($window).on('resize', onWindowResize);
- if(input.attr('step')) {
- var settings = rangeSettings(rangeEle);
- var tickCount = (settings.max - settings.min) / settings.step;
- var tickMarkersEle = angular.element('');
- for(var i=0; i');
+ scope.$on('$destroy', function() {
+ angular.element($window).off('resize', onWindowResize);
+ hammertime.destroy();
+ });
+
+ ngModelCtrl.$render = ngModelRender;
+ ngModelCtrl.$viewChangeListeners.push(ngModelRender);
+ ngModelCtrl.$formatters.push(minMaxValidator);
+ ngModelCtrl.$formatters.push(stepValidator);
+
+ /**
+ * Attributes
+ */
+ var min;
+ var max;
+ var step;
+ function updateMin(value) {
+ min = parseFloat(value);
+ element.attr('aria-valuemin', value);
+ }
+ function updateMax(value) {
+ max = parseFloat(value);
+ element.attr('aria-valuemax', value);
+ }
+ function updateStep(value) {
+ step = parseFloat(value);
+ redrawTicks();
+ }
+ function updateAriaDisabled(isDisabled) {
+ element.attr('aria-disabled', !!isDisabled);
+ }
+
+ // Draw the ticks with canvas.
+ // The alternative to drawing ticks with canvas is to draw one element for each tick,
+ // which could quickly become a performance bottleneck.
+ var tickCanvas, tickCtx;
+ function redrawTicks() {
+ if (!angular.isDefined(attr.discrete)) return;
+
+ var numSteps = Math.floor( (max - min) / step );
+ if (!tickCanvas) {
+ tickCanvas = angular.element('