Skip to content
Open
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
20 changes: 9 additions & 11 deletions lib/eventhandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,23 +523,21 @@ const EventHandlers = {
},
setValueEvent: function (e) {
const inputmask = this.inputmask,
$ = inputmask.dependencyLib;
let input = this,
value = e && e.detail ? e.detail[0] : arguments[1];

if (value === undefined) {
value = input.inputmask._valueGet(true);
}
$ = inputmask.dependencyLib,
input = this,
explicitValue = e && e.detail ? e.detail[0] : arguments[1],
caretPos = e && e.detail ? e.detail[1] : arguments[2];

applyInputValue(
input,
value,
explicitValue !== undefined ? explicitValue : input.inputmask._valueGet(true),
new $.Event("input"),
(e && e.detail ? e.detail[0] : arguments[1]) !== undefined
false,
explicitValue !== undefined
);

if ((e.detail && e.detail[1] !== undefined) || arguments[2] !== undefined) {
caret.call(inputmask, input, e.detail ? e.detail[1] : arguments[2]);
if (caretPos !== undefined) {
caret.call(inputmask, input, caretPos);
}
},
focusEvent: function (e) {
Expand Down
141 changes: 94 additions & 47 deletions lib/extensions/inputmask.numeric.extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
Licensed under the MIT license
*/
import { escapeRegex } from "../escapeRegex";
import { applyInputValue } from "../inputHandling";
import Inputmask from "../inputmask";
import { keys } from "../keycode";
import { seekNext } from "../positioning";
import { caret, seekNext } from "../positioning";

const $ = Inputmask.dependencyLib;

Expand Down Expand Up @@ -57,6 +58,30 @@ function alignDigits(buffer, digits, opts, force) {
return buffer;
}

function unmaskAsNumber(str, opts) {
return opts.onUnMask(
str,
undefined,
$.extend({}, opts, { unmaskAsNumber: true })
);
}

function boundaryBuffer(bound, opts) {
return alignDigits(
bound.toString().replace(".", opts.radixPoint).split(""),
opts.digits,
opts
).reverse();
}

// Internal numeric rewrites (negation-delete, radix-dance) — push an
// already-clean masked buffer back into the input without re-running the
// alias parser.
function setBufferAndCaret(input, value, caretBegin) {
applyInputValue(input, value, new $.Event("input"), true);
caret.call(input.inputmask, input, caretBegin);
}

function findValidator(symbol, maskset) {
let posNdx = 0;
if (symbol === "+") {
Expand Down Expand Up @@ -387,14 +412,31 @@ Inputmask.extendAliases({
pos = handleRadixDance(pos, c, radixPos, maskset, opts);
if (c === "-" || c === opts.negationSymbol.front) {
if (opts.allowMinus !== true) return false;
let isNegative = false,
front = findValid("+", maskset),
let isNegative = false;
const front = findValid("+", maskset),
back = findValid("-", maskset);
if (front !== -1) {
isNegative = [front];
if (back !== -1) isNegative.push(back);
}

const checkMax = isNegative !== false && opts.max !== null,
checkMin = isNegative === false && opts.min !== null;
// Reject typing "-" against a non-negative min — alignDigits would
// pad the orphan sign to "-0". SetMaxOnOverflow=true has its own
// boundary refresh in postValidation.
if (!opts.SetMaxOnOverflow && checkMin && opts.min >= 0) return false;
// Reject sign flips that would push the buffer out of range.
// postValidation's range check doesn't fire after the validator's
// `{remove: ...}` return (toggle-off path), so overflow on that
// path must be caught here.
if ((checkMax || checkMin) && this.maskset.validPositions.length > 0) {
const absVal = Math.abs(
unmaskAsNumber(buffer.slice().reverse().join(""), opts)
);
if (checkMax && absVal > opts.max) return false;
if (checkMin && -absVal < opts.min) return false;
}
return isNegative !== false
? {
remove: isNegative,
Expand Down Expand Up @@ -489,37 +531,39 @@ Inputmask.extendAliases({
if (currentResult === false) return currentResult;
if (strict) return true;
if (opts.min !== null || opts.max !== null) {
const unmasked = opts.onUnMask(
const unmasked = unmaskAsNumber(
buffer.slice().reverse().join(""),
undefined,
$.extend({}, opts, {
unmaskAsNumber: true
})
opts
);
if (
opts.min !== null &&
unmasked < opts.min &&
fromAlternate !== true &&
(fromAlternate !== true || unmasked < 0) &&
(unmasked.toString().length > opts.min.toString().length || // > instead of >= because we want to allow to type a bigger number
buffer[0] === opts.radixPoint || // disallow radixpoint when value is smaller than min
unmasked < 0)
) {
return false;
// return {
// refreshFromBuffer: true,
// buffer: alignDigits(opts.min.toString().replace(".", opts.radixPoint).split(""), opts.digits, opts).reverse()
// };
return unmasked < 0 && opts.SetMaxOnOverflow
? {
refreshFromBuffer: true,
buffer: boundaryBuffer(opts.min, opts)
}
: false;
}

if (opts.max !== null && opts.max >= 0 && unmasked > opts.max) {
// #2846: revalidateMask may strip the negation sign, so an
// overflowing positive here can actually be a valid negative.
// Consult the DOM (stale during checkval) or, element-less,
// trust min < 0.
const isNegativeContext = this.el
? !fromCheckval && unmaskAsNumber(this._valueGet(true), opts) < 0
: opts.min !== null && opts.min < 0;
if (isNegativeContext) return currentResult;
return opts.SetMaxOnOverflow
? {
refreshFromBuffer: true,
buffer: alignDigits(
opts.max.toString().replace(".", opts.radixPoint).split(""),
opts.digits,
opts
).reverse()
buffer: boundaryBuffer(opts.max, opts)
}
: false;
}
Expand Down Expand Up @@ -586,6 +630,10 @@ Inputmask.extendAliases({
maskedValue = maskedValue.replace(escapeRegex(opts.radixPoint), ".");
return isFinite(maskedValue);
},
// Numeric alias onBeforeMask hook — parses/normalizes a value into the
// alias buffer format and clamps to min/max.
// #2715: opts.__skipRounding suppresses the parseFloat round-trip so bignum
// precision survives the setvalue path; clamping and other transforms remain.
onBeforeMask: function (initialValue, opts) {
initialValue = initialValue ?? "";
const radixPoint = opts.radixPoint || ",";
Expand Down Expand Up @@ -617,7 +665,10 @@ Inputmask.extendAliases({
: opts.digits < decimalPart.length
? opts.digits
: decimalPart.length;
if (decimalPart !== "" || !opts.digitsOptional) {
if (
!opts.__skipRounding &&
(decimalPart !== "" || !opts.digitsOptional)
) {
const digitsFactor = Math.pow(10, digits || 1);

// make the initialValue a valid javascript number for the parsefloat
Expand All @@ -639,16 +690,22 @@ Inputmask.extendAliases({
);
}

let clamped = false;
if (initialValue !== "" && (opts.min !== null || opts.max !== null)) {
const numberValue = initialValue.toString().replace(radixPoint, ".");
if (opts.min !== null && numberValue < opts.min) {
initialValue = opts.min.toString().replace(".", radixPoint);
clamped = true;
} else if (opts.max !== null && numberValue > opts.max) {
initialValue = opts.max.toString().replace(".", radixPoint);
clamped = true;
}
}

if (isNegative && initialValue.charAt(0) !== "-") {
// After a clamp the boundary's own sign already lives in initialValue —
// re-prepending the original input's "-" would invert nonneg boundaries
// (setvalue("-5") with min:10 → "-10") or strand "-0" (min:0).
if (isNegative && !clamped && initialValue.charAt(0) !== "-") {
initialValue = "-" + initialValue;
}
return alignDigits(
Expand Down Expand Up @@ -696,35 +753,23 @@ Inputmask.extendAliases({
switch (e.type) {
case "blur":
case "checkval":
if (opts.min !== null || opts.max !== null) {
const unmasked = opts.onUnMask(
if (
(opts.min !== null || opts.max !== null) &&
this.maskset.validPositions.length > 0
) {
const unmasked = unmaskAsNumber(
buffer.slice().reverse().join(""),
undefined,
$.extend({}, opts, {
unmaskAsNumber: true
})
opts
);
if (
opts.min !== null &&
unmasked < opts.min &&
buffer.join() !== ""
) {
if (opts.min !== null && unmasked < opts.min) {
return {
refreshFromBuffer: true,
buffer: alignDigits(
opts.min.toString().replace(".", opts.radixPoint).split(""),
opts.digits,
opts
).reverse()
buffer: boundaryBuffer(opts.min, opts)
};
} else if (opts.max !== null && unmasked > opts.max) {
return {
refreshFromBuffer: true,
buffer: alignDigits(
opts.max.toString().replace(".", opts.radixPoint).split(""),
opts.digits,
opts
).reverse()
buffer: boundaryBuffer(opts.max, opts)
};
}
}
Expand Down Expand Up @@ -830,7 +875,7 @@ Inputmask.extendAliases({
bffr = buffer.slice().reverse();
if (opts.negationSymbol.front !== "") bffr.shift();
if (opts.negationSymbol.back !== "") bffr.pop();
$input.trigger("setvalue", [bffr.join(""), caretPos.begin]);
setBufferAndCaret(this, bffr.join(""), caretPos.begin);
return false;
} else if (opts._radixDance === true) {
const radixPos = buffer.indexOf(opts.radixPoint);
Expand Down Expand Up @@ -869,19 +914,21 @@ Inputmask.extendAliases({
if (restoreCaretPos) {
caretPos = restoreCaretPos;
}
$input.trigger("setvalue", [
setBufferAndCaret(
this,
bffr,
caretPos.begin >= bffr.length ? radixPos + 1 : caretPos.begin
]);
);
return false;
}
} else if (radixPos === 0) {
bffr = buffer.slice().reverse();
bffr.pop();
$input.trigger("setvalue", [
setBufferAndCaret(
this,
bffr.join(""),
caretPos.begin >= bffr.length ? bffr.length : caretPos.begin
]);
);
return false;
}
}
Expand Down
12 changes: 9 additions & 3 deletions lib/inputHandling.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ export {
writeBuffer
};

function applyInputValue(input, value, initialEvent, strict) {
function applyInputValue(input, value, initialEvent, skipOnBeforeMask, skipRounding) {
const inputmask = input ? input.inputmask : this,
opts = inputmask.opts;

input.inputmask.refreshValue = false;
if (strict !== true && typeof opts.onBeforeMask === "function")
value = opts.onBeforeMask.call(inputmask, value, opts) || value;
// skipRounding suppresses onBeforeMask's parseFloat round-trip while
// preserving its other transforms — guards bignum precision (#2715) on the
// setvalue path. skipOnBeforeMask bypasses the hook entirely for internal
// buffer rewrites that already produced a clean masked value (#2846).
if (skipOnBeforeMask !== true && typeof opts.onBeforeMask === "function") {
const callOpts = skipRounding === true ? { ...opts, __skipRounding: true } : opts;
value = opts.onBeforeMask.call(inputmask, value, callOpts) || value;
}
value = (value || "").toString().split("");
checkVal(input, true, false, value, initialEvent);
inputmask.undoValue = inputmask._valueGet(true);
Expand Down
Loading