@@ -1037,13 +1037,19 @@ var inputType = {
10371037   * The model for the range input must always be a `Number`. 
10381038   * 
10391039   * IE9 and other browsers that do not support the `range` type fall back 
1040-    * to a text input. Model binding, validation and number parsing are nevertheless supported. 
1040+    * to a text input without any default values for `min`, `max` and `step`. Model binding, 
1041+    * validation and number parsing are nevertheless supported. 
10411042   * 
10421043   * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` 
10431044   * in a way that never allows the input to hold an invalid value. That means: 
10441045   * - any non-numerical value is set to `(max + min) / 2`. 
10451046   * - any numerical value that is less than the current min val, or greater than the current max val 
10461047   * is set to the min / max val respectively. 
1048+    * - additionally, the current `step` is respected, so the nearest value that satisfies a step 
1049+    * is used. 
1050+    * 
1051+    * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) 
1052+    * for more info. 
10471053   * 
10481054   * This has the following consequences for Angular: 
10491055   * 
@@ -1056,23 +1062,30 @@ var inputType = {
10561062   * That means the model for range will immediately be set to `50` after `ngModel` has been 
10571063   * initialized. It also means a range input can never have the required error. 
10581064   * 
1059-    * This does not only affect changes to the model value, but also to the values of the `min` and  
1060-    * `max`  attributes. When these change in a way that will cause the browser to modify the input value,  
1061-    * Angular will also update the model value. 
1065+    * This does not only affect changes to the model value, but also to the values of the `min`,  
1066+    * `max`, and `step`  attributes. When these change in a way that will cause the browser to modify 
1067+    * the input value,  Angular will also update the model value. 
10621068   * 
10631069   * Automatic value adjustment also means that a range input element can never have the `required`, 
10641070   * `min`, or `max` errors. 
10651071   * 
1066-    * Note that `input[range]` is not compatible with`ngMax` and `ngMin`, because they do not set the 
1067-    * `min` and `max` attributes, which means that the browser won't automatically adjust the input 
1068-    * value based on their values, and will always assume min = 0 and max = 100. 
1072+    * However, `step` is currently only fully implemented by Firefox. Other browsers have problems 
1073+    * when the step value changes dynamically - they do not adjust the element value correctly, but 
1074+    * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step` 
1075+    * error on the input, and set the model to `undefined`. 
1076+    * 
1077+    * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do 
1078+    * not set the `min` and `max` attributes, which means that the browser won't automatically adjust 
1079+    * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. 
10691080   * 
10701081   * @param  {string }  ngModel Assignable angular expression to data-bind to. 
10711082   * @param  {string= } name Property name of the form under which the control is published. 
10721083   * @param  {string= } min Sets the `min` validation to ensure that the value entered is greater 
10731084   *                  than `min`. Can be interpolated. 
10741085   * @param  {string= } max Sets the `max` validation to ensure that the value entered is less than `max`. 
10751086   *                  Can be interpolated. 
1087+    * @param  {string= } step Sets the `step` validation to ensure that the value entered matches the `step` 
1088+    *                  Can be interpolated. 
10761089   * @param  {string= } ngChange Angular expression to be executed when the ngModel value changes due 
10771090   *                  to user interaction with the input element. 
10781091   * 
@@ -1499,6 +1512,13 @@ function numberFormatterParser(ctrl) {
14991512  } ) ; 
15001513} 
15011514
1515+ function  parseNumberAttrVal ( val )  { 
1516+   if  ( isDefined ( val )  &&  ! isNumber ( val ) )  { 
1517+     val  =  parseFloat ( val ) ; 
1518+   } 
1519+   return  isNumber ( val )  &&  ! isNaN ( val )  ? val  : undefined ; 
1520+ } 
1521+ 
15021522function  numberInputType ( scope ,  element ,  attr ,  ctrl ,  $sniffer ,  $browser )  { 
15031523  badInputChecker ( scope ,  element ,  attr ,  ctrl ) ; 
15041524  numberFormatterParser ( ctrl ) ; 
@@ -1511,10 +1531,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15111531    } ; 
15121532
15131533    attr . $observe ( 'min' ,  function ( val )  { 
1514-       if  ( isDefined ( val )  &&  ! isNumber ( val ) )  { 
1515-         val  =  parseFloat ( val ) ; 
1516-       } 
1517-       minVal  =  isNumber ( val )  &&  ! isNaN ( val )  ? val  : undefined ; 
1534+       minVal  =  parseNumberAttrVal ( val ) ; 
15181535      // TODO(matsko): implement validateLater to reduce number of validations 
15191536      ctrl . $validate ( ) ; 
15201537    } ) ; 
@@ -1527,10 +1544,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15271544    } ; 
15281545
15291546    attr . $observe ( 'max' ,  function ( val )  { 
1530-       if  ( isDefined ( val )  &&  ! isNumber ( val ) )  { 
1531-         val  =  parseFloat ( val ) ; 
1532-       } 
1533-       maxVal  =  isNumber ( val )  &&  ! isNaN ( val )  ? val  : undefined ; 
1547+       maxVal  =  parseNumberAttrVal ( val ) ; 
15341548      // TODO(matsko): implement validateLater to reduce number of validations 
15351549      ctrl . $validate ( ) ; 
15361550    } ) ; 
@@ -1545,9 +1559,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15451559  var  supportsRange  =  ctrl . $$hasNativeValidators  &&  element [ 0 ] . type  ===  'range' , 
15461560      minVal  =  supportsRange  ? 0  : undefined , 
15471561      maxVal  =  supportsRange  ? 100  : undefined , 
1562+       stepVal  =  supportsRange  ? 1  : undefined , 
15481563      validity  =  element [ 0 ] . validity , 
15491564      hasMinAttr  =  isDefined ( attr . min ) , 
1550-       hasMaxAttr  =  isDefined ( attr . max ) ; 
1565+       hasMaxAttr  =  isDefined ( attr . max ) , 
1566+       hasStepAttr  =  isDefined ( attr . step ) ; 
15511567
15521568  var  originalRender  =  ctrl . $render ; 
15531569
@@ -1564,7 +1580,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15641580    ctrl . $validators . min  =  supportsRange  ?
15651581      // Since all browsers set the input to a valid value, we don't need to check validity 
15661582      function  noopMinValidator ( )  {  return  true ;  }  :
1567-       // non-support browsers validate the range  
1583+       // non-support browsers validate the min val  
15681584      function  minValidator ( modelValue ,  viewValue )  { 
15691585        return  ctrl . $isEmpty ( viewValue )  ||  isUndefined ( minVal )  ||  viewValue  >=  minVal ; 
15701586      } ; 
@@ -1576,28 +1592,40 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15761592    ctrl . $validators . max  =  supportsRange  ?
15771593      // Since all browsers set the input to a valid value, we don't need to check validity 
15781594      function  noopMaxValidator ( )  {  return  true ;  }  :
1579-       // ngMax doesn't set  the max attr, so the browser doesn't adjust the input value as setting max would  
1595+       // non-support browsers validate  the max val  
15801596      function  maxValidator ( modelValue ,  viewValue )  { 
15811597        return  ctrl . $isEmpty ( viewValue )  ||  isUndefined ( maxVal )  ||  viewValue  <=  maxVal ; 
15821598      } ; 
15831599
15841600    setInitialValueAndObserver ( 'max' ,  maxChange ) ; 
15851601  } 
15861602
1603+   if  ( hasStepAttr )  { 
1604+     ctrl . $validators . step  =  supportsRange  ?
1605+       function  nativeStepValidator ( )  { 
1606+         // Currently, only FF implements the spec on step change correctly (i.e. adjusting the 
1607+         // input element value to a valid value). It's possible that other browsers set the stepMismatch 
1608+         // validity error instead, so we can at least report an error in that case. 
1609+         return  ! validity . stepMismatch ; 
1610+       }  :
1611+       // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would 
1612+       function  stepValidator ( modelValue ,  viewValue )  { 
1613+         return  ctrl . $isEmpty ( viewValue )  ||  isUndefined ( stepVal )  ||  viewValue  %  stepVal  ===  0 ; 
1614+       } ; 
1615+ 
1616+     setInitialValueAndObserver ( 'step' ,  stepChange ) ; 
1617+   } 
1618+ 
15871619  function  setInitialValueAndObserver ( htmlAttrName ,  changeFn )  { 
15881620    // interpolated attributes set the attribute value only after a digest, but we need the 
15891621    // attribute value when the input is first rendered, so that the browser can adjust the 
15901622    // input value based on the min/max value 
15911623    element . attr ( htmlAttrName ,  attr [ htmlAttrName ] ) ; 
1592- 
15931624    attr . $observe ( htmlAttrName ,  changeFn ) ; 
15941625  } 
15951626
15961627  function  minChange ( val )  { 
1597-     if  ( isDefined ( val )  &&  ! isNumber ( val ) )  { 
1598-       val  =  parseFloat ( val ) ; 
1599-     } 
1600-     minVal  =  isNumber ( val )  &&  ! isNaN ( val )  ? val  : undefined ; 
1628+     minVal  =  parseNumberAttrVal ( val ) ; 
16011629    // ignore changes before model is initialized 
16021630    if  ( isNumber ( ctrl . $modelValue )  &&  isNaN ( ctrl . $modelValue ) )  { 
16031631      return ; 
@@ -1618,10 +1646,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16181646  } 
16191647
16201648  function  maxChange ( val )  { 
1621-     if  ( isDefined ( val )  &&  ! isNumber ( val ) )  { 
1622-       val  =  parseFloat ( val ) ; 
1623-     } 
1624-     maxVal  =  isNumber ( val )  &&  ! isNaN ( val )  ? val  : undefined ; 
1649+     maxVal  =  parseNumberAttrVal ( val ) ; 
16251650    // ignore changes before model is initialized 
16261651    if  ( isNumber ( ctrl . $modelValue )  &&  isNaN ( ctrl . $modelValue ) )  { 
16271652      return ; 
@@ -1642,6 +1667,21 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16421667    } 
16431668  } 
16441669
1670+   function  stepChange ( val )  { 
1671+     stepVal  =  parseNumberAttrVal ( val ) ; 
1672+     // ignore changes before model is initialized 
1673+     if  ( isNumber ( ctrl . $modelValue )  &&  isNaN ( ctrl . $modelValue ) )  { 
1674+       return ; 
1675+     } 
1676+ 
1677+     // Some browsers don't adjust the input value correctly, but set the stepMismatch error 
1678+     if  ( supportsRange  &&  ctrl . $viewValue  !==  element . val ( ) )  { 
1679+       ctrl . $setViewValue ( element . val ( ) ) ; 
1680+     }  else  { 
1681+       // TODO(matsko): implement validateLater to reduce number of validations 
1682+       ctrl . $validate ( ) ; 
1683+     } 
1684+   } 
16451685} 
16461686
16471687function  urlInputType ( scope ,  element ,  attr ,  ctrl ,  $sniffer ,  $browser )  { 
0 commit comments