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

fix(input): fix step validation for input[number]/input[range] #15264

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1529,13 +1529,62 @@ function parseNumberAttrVal(val) {
return !isNumberNaN(val) ? val : undefined;
}

function isNumberInteger(num) {
// See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
// (minus the assumption that `num` is a number)

// eslint-disable-next-line no-bitwise
return (num | 0) === num;
}

function countDecimals(num) {
var numString = num.toString();
var decimalSymbolIndex = numString.indexOf('.');

if (decimalSymbolIndex === -1) {
if (-1 < num && num < 1) {
// It may be in the exponential notation format (`1e-X`)
var match = /e-(\d+)$/.exec(numString);

if (match) {
return Number(match[1]);
}
}

return 0;
}

return numString.length - decimalSymbolIndex - 1;
}

function isValidForStep(viewValue, stepBase, step) {
// At this point `stepBase` and `step` are expected to be non-NaN values
// and `viewValue` is expected to be a valid stringified number.
var value = Number(viewValue);

// Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
// `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) {
var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step));
var multiplier = Math.pow(10, decimalCount);

value = value * multiplier;
stepBase = stepBase * multiplier;
step = step * multiplier;
}

return (value - stepBase) % step === 0;
}

function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
badInputChecker(scope, element, attr, ctrl);
numberFormatterParser(ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);

var minVal;
var maxVal;

if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
};
Expand All @@ -1548,7 +1597,6 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}

if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
};
Expand All @@ -1563,7 +1611,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
if (isDefined(attr.step) || attr.ngStep) {
var stepVal;
ctrl.$validators.step = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || viewValue % stepVal === 0;
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
isValidForStep(viewValue, minVal || 0, stepVal);
};

attr.$observe('step', function(val) {
Expand Down Expand Up @@ -1633,7 +1682,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
} :
// ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
function stepValidator(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || viewValue % stepVal === 0;
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
isValidForStep(viewValue, minVal || 0, stepVal);
};

setInitialValueAndObserver('step', stepChange);
Expand Down
Loading