Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 5b633d8

Browse files
revert: feat(input[range]): support step
This reverts commit 90c08b8
1 parent cb4603b commit 5b633d8

File tree

2 files changed

+27
-212
lines changed

2 files changed

+27
-212
lines changed

src/ng/directive/input.js

+27-67
Original file line numberDiff line numberDiff line change
@@ -1037,19 +1037,13 @@ 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 without any default values for `min`, `max` and `step`. Model binding,
1041-
* validation and number parsing are nevertheless supported.
1040+
* to a text input. Model binding, validation and number parsing are nevertheless supported.
10421041
*
10431042
* Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
10441043
* in a way that never allows the input to hold an invalid value. That means:
10451044
* - any non-numerical value is set to `(max + min) / 2`.
10461045
* - any numerical value that is less than the current min val, or greater than the current max val
10471046
* 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.
10531047
*
10541048
* This has the following consequences for Angular:
10551049
*
@@ -1062,30 +1056,23 @@ var inputType = {
10621056
* That means the model for range will immediately be set to `50` after `ngModel` has been
10631057
* initialized. It also means a range input can never have the required error.
10641058
*
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.
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.
10681062
*
10691063
* Automatic value adjustment also means that a range input element can never have the `required`,
10701064
* `min`, or `max` errors.
10711065
*
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.
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.
10801069
*
10811070
* @param {string} ngModel Assignable angular expression to data-bind to.
10821071
* @param {string=} name Property name of the form under which the control is published.
10831072
* @param {string=} min Sets the `min` validation to ensure that the value entered is greater
10841073
* than `min`. Can be interpolated.
10851074
* @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
10861075
* Can be interpolated.
1087-
* @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
1088-
* Can be interpolated.
10891076
* @param {string=} ngChange Angular expression to be executed when the ngModel value changes due
10901077
* to user interaction with the input element.
10911078
*
@@ -1512,13 +1499,6 @@ function numberFormatterParser(ctrl) {
15121499
});
15131500
}
15141501

1515-
function parseNumberAttrVal(val) {
1516-
if (isDefined(val) && !isNumber(val)) {
1517-
val = parseFloat(val);
1518-
}
1519-
return !isNumberNaN(val) ? val : undefined;
1520-
}
1521-
15221502
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15231503
badInputChecker(scope, element, attr, ctrl);
15241504
numberFormatterParser(ctrl);
@@ -1531,7 +1511,10 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15311511
};
15321512

15331513
attr.$observe('min', function(val) {
1534-
minVal = parseNumberAttrVal(val);
1514+
if (isDefined(val) && !isNumber(val)) {
1515+
val = parseFloat(val);
1516+
}
1517+
minVal = isNumber(val) && !isNaN(val) ? val : undefined;
15351518
// TODO(matsko): implement validateLater to reduce number of validations
15361519
ctrl.$validate();
15371520
});
@@ -1544,7 +1527,10 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15441527
};
15451528

15461529
attr.$observe('max', function(val) {
1547-
maxVal = parseNumberAttrVal(val);
1530+
if (isDefined(val) && !isNumber(val)) {
1531+
val = parseFloat(val);
1532+
}
1533+
maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
15481534
// TODO(matsko): implement validateLater to reduce number of validations
15491535
ctrl.$validate();
15501536
});
@@ -1559,11 +1545,9 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15591545
var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
15601546
minVal = supportsRange ? 0 : undefined,
15611547
maxVal = supportsRange ? 100 : undefined,
1562-
stepVal = supportsRange ? 1 : undefined,
15631548
validity = element[0].validity,
15641549
hasMinAttr = isDefined(attr.min),
1565-
hasMaxAttr = isDefined(attr.max),
1566-
hasStepAttr = isDefined(attr.step);
1550+
hasMaxAttr = isDefined(attr.max);
15671551

15681552
var originalRender = ctrl.$render;
15691553

@@ -1580,7 +1564,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15801564
ctrl.$validators.min = supportsRange ?
15811565
// Since all browsers set the input to a valid value, we don't need to check validity
15821566
function noopMinValidator() { return true; } :
1583-
// non-support browsers validate the min val
1567+
// non-support browsers validate the range
15841568
function minValidator(modelValue, viewValue) {
15851569
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
15861570
};
@@ -1592,40 +1576,28 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15921576
ctrl.$validators.max = supportsRange ?
15931577
// Since all browsers set the input to a valid value, we don't need to check validity
15941578
function noopMaxValidator() { return true; } :
1595-
// non-support browsers validate the max val
1579+
// ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
15961580
function maxValidator(modelValue, viewValue) {
15971581
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
15981582
};
15991583

16001584
setInitialValueAndObserver('max', maxChange);
16011585
}
16021586

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-
16191587
function setInitialValueAndObserver(htmlAttrName, changeFn) {
16201588
// interpolated attributes set the attribute value only after a digest, but we need the
16211589
// attribute value when the input is first rendered, so that the browser can adjust the
16221590
// input value based on the min/max value
16231591
element.attr(htmlAttrName, attr[htmlAttrName]);
1592+
16241593
attr.$observe(htmlAttrName, changeFn);
16251594
}
16261595

16271596
function minChange(val) {
1628-
minVal = parseNumberAttrVal(val);
1597+
if (isDefined(val) && !isNumber(val)) {
1598+
val = parseFloat(val);
1599+
}
1600+
minVal = isNumber(val) && !isNaN(val) ? val : undefined;
16291601
// ignore changes before model is initialized
16301602
if (isNumberNaN(ctrl.$modelValue)) {
16311603
return;
@@ -1646,7 +1618,10 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16461618
}
16471619

16481620
function maxChange(val) {
1649-
maxVal = parseNumberAttrVal(val);
1621+
if (isDefined(val) && !isNumber(val)) {
1622+
val = parseFloat(val);
1623+
}
1624+
maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
16501625
// ignore changes before model is initialized
16511626
if (isNumberNaN(ctrl.$modelValue)) {
16521627
return;
@@ -1667,21 +1642,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16671642
}
16681643
}
16691644

1670-
function stepChange(val) {
1671-
stepVal = parseNumberAttrVal(val);
1672-
// ignore changes before model is initialized
1673-
if (isNumberNaN(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-
}
16851645
}
16861646

16871647
function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {

test/ng/directive/inputSpec.js

-145
Original file line numberDiff line numberDiff line change
@@ -3232,151 +3232,6 @@ describe('input', function() {
32323232

32333233
}
32343234

3235-
3236-
describe('step', function() {
3237-
3238-
if (supportsRange) {
3239-
// Browsers that implement range will never allow you to set a value that doesn't match the step value
3240-
// However, currently only Firefox fully inplements the spec when setting the value after the step value changes.
3241-
// Other browsers fail in various edge cases, which is why they are not tested here.
3242-
it('should round the input value to the nearest step on user input', function() {
3243-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="5" />');
3244-
3245-
helper.changeInputValueTo('5');
3246-
expect(inputElm).toBeValid();
3247-
expect(scope.value).toBe(5);
3248-
expect(scope.form.alias.$error.step).toBeFalsy();
3249-
3250-
helper.changeInputValueTo('10');
3251-
expect(inputElm).toBeValid();
3252-
expect(scope.value).toBe(10);
3253-
expect(scope.form.alias.$error.step).toBeFalsy();
3254-
3255-
helper.changeInputValueTo('9');
3256-
expect(inputElm).toBeValid();
3257-
expect(scope.value).toBe(10);
3258-
expect(scope.form.alias.$error.step).toBeFalsy();
3259-
3260-
helper.changeInputValueTo('7');
3261-
expect(inputElm).toBeValid();
3262-
expect(scope.value).toBe(5);
3263-
expect(scope.form.alias.$error.step).toBeFalsy();
3264-
3265-
helper.changeInputValueTo('7.5');
3266-
expect(inputElm).toBeValid();
3267-
expect(scope.value).toBe(10);
3268-
expect(scope.form.alias.$error.step).toBeFalsy();
3269-
});
3270-
3271-
it('should round the input value to the nearest step when setting the model', function() {
3272-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="5" />');
3273-
3274-
scope.$apply('value = 10');
3275-
expect(inputElm.val()).toBe('10');
3276-
expect(inputElm).toBeValid();
3277-
expect(scope.value).toBe(10);
3278-
expect(scope.form.alias.$error.step).toBeFalsy();
3279-
3280-
scope.$apply('value = 5');
3281-
expect(inputElm.val()).toBe('5');
3282-
expect(inputElm).toBeValid();
3283-
expect(scope.value).toBe(5);
3284-
expect(scope.form.alias.$error.step).toBeFalsy();
3285-
3286-
scope.$apply('value = 7.5');
3287-
expect(inputElm.val()).toBe('10');
3288-
expect(inputElm).toBeValid();
3289-
expect(scope.value).toBe(10);
3290-
expect(scope.form.alias.$error.step).toBeFalsy();
3291-
3292-
scope.$apply('value = 7');
3293-
expect(inputElm.val()).toBe('5');
3294-
expect(inputElm).toBeValid();
3295-
expect(scope.value).toBe(5);
3296-
expect(scope.form.alias.$error.step).toBeFalsy();
3297-
3298-
scope.$apply('value = 9');
3299-
expect(inputElm.val()).toBe('10');
3300-
expect(inputElm).toBeValid();
3301-
expect(scope.value).toBe(10);
3302-
expect(scope.form.alias.$error.step).toBeFalsy();
3303-
});
3304-
3305-
} else {
3306-
it('should validate if "range" is not implemented', function() {
3307-
scope.step = 10;
3308-
scope.value = 20;
3309-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="{{step}}" />');
3310-
3311-
expect(inputElm.val()).toBe('20');
3312-
expect(inputElm).toBeValid();
3313-
expect(scope.value).toBe(20);
3314-
expect(scope.form.alias.$error.step).toBeFalsy();
3315-
3316-
helper.changeInputValueTo('18');
3317-
expect(inputElm).toBeInvalid();
3318-
expect(inputElm.val()).toBe('18');
3319-
expect(scope.value).toBeUndefined();
3320-
expect(scope.form.alias.$error.step).toBeTruthy();
3321-
3322-
helper.changeInputValueTo('10');
3323-
expect(inputElm).toBeValid();
3324-
expect(inputElm.val()).toBe('10');
3325-
expect(scope.value).toBe(10);
3326-
expect(scope.form.alias.$error.step).toBeFalsy();
3327-
3328-
scope.$apply('value = 12');
3329-
expect(inputElm).toBeInvalid();
3330-
expect(inputElm.val()).toBe('12');
3331-
expect(scope.value).toBe(12);
3332-
expect(scope.form.alias.$error.step).toBeTruthy();
3333-
});
3334-
3335-
it('should validate even if the step value changes on-the-fly', function() {
3336-
scope.step = 10;
3337-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="{{step}}" />');
3338-
3339-
helper.changeInputValueTo('10');
3340-
expect(inputElm).toBeValid();
3341-
expect(scope.value).toBe(10);
3342-
3343-
// Step changes, but value matches
3344-
scope.$apply('step = 5');
3345-
expect(inputElm.val()).toBe('10');
3346-
expect(inputElm).toBeValid();
3347-
expect(scope.value).toBe(10);
3348-
expect(scope.form.alias.$error.step).toBeFalsy();
3349-
3350-
// Step changes, value does not match
3351-
scope.$apply('step = 6');
3352-
expect(inputElm).toBeInvalid();
3353-
expect(scope.value).toBeUndefined();
3354-
expect(inputElm.val()).toBe('10');
3355-
expect(scope.form.alias.$error.step).toBeTruthy();
3356-
3357-
// null = valid
3358-
scope.$apply('step = null');
3359-
expect(inputElm).toBeValid();
3360-
expect(scope.value).toBe(10);
3361-
expect(inputElm.val()).toBe('10');
3362-
expect(scope.form.alias.$error.step).toBeFalsy();
3363-
3364-
// Step val as string
3365-
scope.$apply('step = "7"');
3366-
expect(inputElm).toBeInvalid();
3367-
expect(scope.value).toBeUndefined();
3368-
expect(inputElm.val()).toBe('10');
3369-
expect(scope.form.alias.$error.step).toBeTruthy();
3370-
3371-
// unparsable string is ignored
3372-
scope.$apply('step = "abc"');
3373-
expect(inputElm).toBeValid();
3374-
expect(scope.value).toBe(10);
3375-
expect(inputElm.val()).toBe('10');
3376-
expect(scope.form.alias.$error.step).toBeFalsy();
3377-
});
3378-
}
3379-
});
33803235
});
33813236

33823237
describe('email', function() {

0 commit comments

Comments
 (0)