@@ -1037,13 +1037,19 @@ var inputType = {
1037
1037
* The model for the range input must always be a `Number`.
1038
1038
*
1039
1039
* 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.
1041
1042
*
1042
1043
* Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
1043
1044
* in a way that never allows the input to hold an invalid value. That means:
1044
1045
* - any non-numerical value is set to `(max + min) / 2`.
1045
1046
* - any numerical value that is less than the current min val, or greater than the current max val
1046
1047
* 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.
1047
1053
*
1048
1054
* This has the following consequences for Angular:
1049
1055
*
@@ -1056,23 +1062,30 @@ var inputType = {
1056
1062
* That means the model for range will immediately be set to `50` after `ngModel` has been
1057
1063
* initialized. It also means a range input can never have the required error.
1058
1064
*
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.
1062
1068
*
1063
1069
* Automatic value adjustment also means that a range input element can never have the `required`,
1064
1070
* `min`, or `max` errors.
1065
1071
*
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.
1069
1080
*
1070
1081
* @param {string } ngModel Assignable angular expression to data-bind to.
1071
1082
* @param {string= } name Property name of the form under which the control is published.
1072
1083
* @param {string= } min Sets the `min` validation to ensure that the value entered is greater
1073
1084
* than `min`. Can be interpolated.
1074
1085
* @param {string= } max Sets the `max` validation to ensure that the value entered is less than `max`.
1075
1086
* Can be interpolated.
1087
+ * @param {string= } step Sets the `step` validation to ensure that the value entered matches the `step`
1088
+ * Can be interpolated.
1076
1089
* @param {string= } ngChange Angular expression to be executed when the ngModel value changes due
1077
1090
* to user interaction with the input element.
1078
1091
*
@@ -1499,6 +1512,13 @@ function numberFormatterParser(ctrl) {
1499
1512
} ) ;
1500
1513
}
1501
1514
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
+
1502
1522
function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1503
1523
badInputChecker ( scope , element , attr , ctrl ) ;
1504
1524
numberFormatterParser ( ctrl ) ;
@@ -1511,10 +1531,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1511
1531
} ;
1512
1532
1513
1533
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 ) ;
1518
1535
// TODO(matsko): implement validateLater to reduce number of validations
1519
1536
ctrl . $validate ( ) ;
1520
1537
} ) ;
@@ -1527,10 +1544,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1527
1544
} ;
1528
1545
1529
1546
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 ) ;
1534
1548
// TODO(matsko): implement validateLater to reduce number of validations
1535
1549
ctrl . $validate ( ) ;
1536
1550
} ) ;
@@ -1545,9 +1559,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1545
1559
var supportsRange = ctrl . $$hasNativeValidators && element [ 0 ] . type === 'range' ,
1546
1560
minVal = supportsRange ? 0 : undefined ,
1547
1561
maxVal = supportsRange ? 100 : undefined ,
1562
+ stepVal = supportsRange ? 1 : undefined ,
1548
1563
validity = element [ 0 ] . validity ,
1549
1564
hasMinAttr = isDefined ( attr . min ) ,
1550
- hasMaxAttr = isDefined ( attr . max ) ;
1565
+ hasMaxAttr = isDefined ( attr . max ) ,
1566
+ hasStepAttr = isDefined ( attr . step ) ;
1551
1567
1552
1568
var originalRender = ctrl . $render ;
1553
1569
@@ -1564,7 +1580,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1564
1580
ctrl . $validators . min = supportsRange ?
1565
1581
// Since all browsers set the input to a valid value, we don't need to check validity
1566
1582
function noopMinValidator ( ) { return true ; } :
1567
- // non-support browsers validate the range
1583
+ // non-support browsers validate the min val
1568
1584
function minValidator ( modelValue , viewValue ) {
1569
1585
return ctrl . $isEmpty ( viewValue ) || isUndefined ( minVal ) || viewValue >= minVal ;
1570
1586
} ;
@@ -1576,28 +1592,40 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1576
1592
ctrl . $validators . max = supportsRange ?
1577
1593
// Since all browsers set the input to a valid value, we don't need to check validity
1578
1594
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
1580
1596
function maxValidator ( modelValue , viewValue ) {
1581
1597
return ctrl . $isEmpty ( viewValue ) || isUndefined ( maxVal ) || viewValue <= maxVal ;
1582
1598
} ;
1583
1599
1584
1600
setInitialValueAndObserver ( 'max' , maxChange ) ;
1585
1601
}
1586
1602
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
+
1587
1619
function setInitialValueAndObserver ( htmlAttrName , changeFn ) {
1588
1620
// interpolated attributes set the attribute value only after a digest, but we need the
1589
1621
// attribute value when the input is first rendered, so that the browser can adjust the
1590
1622
// input value based on the min/max value
1591
1623
element . attr ( htmlAttrName , attr [ htmlAttrName ] ) ;
1592
-
1593
1624
attr . $observe ( htmlAttrName , changeFn ) ;
1594
1625
}
1595
1626
1596
1627
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 ) ;
1601
1629
// ignore changes before model is initialized
1602
1630
if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
1603
1631
return ;
@@ -1618,10 +1646,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1618
1646
}
1619
1647
1620
1648
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 ) ;
1625
1650
// ignore changes before model is initialized
1626
1651
if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
1627
1652
return ;
@@ -1642,6 +1667,21 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1642
1667
}
1643
1668
}
1644
1669
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
+ }
1645
1685
}
1646
1686
1647
1687
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
0 commit comments