Skip to content

Fix numeric min/max validation across input, blur, isValid, and setvalue#2883

Open
thorn0 wants to merge 1 commit intoRobinHerbots:5.xfrom
thorn0:fix/numeric-min-max-validation-v2
Open

Fix numeric min/max validation across input, blur, isValid, and setvalue#2883
thorn0 wants to merge 1 commit intoRobinHerbots:5.xfrom
thorn0:fix/numeric-min-max-validation-v2

Conversation

@thorn0
Copy link
Copy Markdown
Contributor

@thorn0 thorn0 commented Apr 14, 2026

Summary

This PR makes numeric min / max handling predictable across typing, blur normalization, Inputmask.isValid / Inputmask.format, and setvalue.

The intended contract is:

  • interactive typing uses SetMaxOnOverflow when an out-of-range keystroke can be resolved immediately;
  • non-empty values supplied through initial masking, Inputmask.format, Inputmask.isValid, setvalue, or blur normalization are clamped to the configured range;
  • empty values stay empty;
  • the setvalue fix preserves the bignum protection from Numeric Mask is casting value to a number not supported by JavaScript #2715.

Playground link

Problem / Fix

Keystroke range behavior

Problem: The upper and lower range limits behaved differently while typing. Above-max input honored SetMaxOnOverflow, but negative input below min was rejected instead of clamped.

Fix: Resolved lower-bound overflows now use the same policy as upper-bound overflows: clamp when SetMaxOnOverflow is enabled, reject otherwise. Prefixes that can still become valid larger numbers remain typeable.

Negation handling

Problem: Typing - with a non-negative min could leave an orphan minus sign in the input, such as -0. The sign stayed visible until blur cleanup removed it.

Fix: Invalid sign flips are rejected before the minus sign is inserted, so a field with min:0 no longer displays a temporary -0.

Valid negative values inside the configured range

Problem: A range such as { min:-100, max:30 } should accept -32.123, but the mask could block that value while typing. The same valid negative value could also fail through element-less APIs such as Inputmask.isValid("-32.123", ...).

Fix: Negative values are now validated with their sign intact across DOM input, Inputmask.isValid, and Inputmask.format. Values inside the configured range pass; real positive overflow above max is still rejected or clamped according to the entry path.

Programmatically cleared optional numeric fields

Problem: Optional numeric fields with min / max could refill themselves after being cleared by code or after receiving a whitespace-only external value. For example, { alias:"integer", min:1, max:255 } could turn $(input).val("") back into 1, and { alias:"numeric", min:1, digits:0 } could clamp " " to 1 on blur.

Fix: Min/max clamping now runs only when the mask has accepted numeric input. Programmatic clears and whitespace-only external values stay empty.

setvalue

Problem: Programmatic values were not normalized through the same min/max path as initial values and formatting. For example, setvalue("10000") with max:999 could be partially accepted as "100" instead of being clamped to "999".

Fix: setvalue now treats the programmatic input as a complete value, so 10000 clamps to 999 instead of being reduced to the accepted prefix 100, without reintroducing the bignum precision bug from #2715.

Tests

Coverage was added across DOM input, static APIs (Inputmask.isValid / Inputmask.format), and the setvalue path: in-range pass-through, empty values, number arguments, formatted prefix/groupSeparator input above and below range, European locale (radixPoint=',' / groupSeparator='.'), onBeforeMask=null, and bignum preservation.


Closes #1763, closes #2715, closes #2775, closes #2846, closes #2863.

Related to #2651: this PR makes empty numeric fields stay empty consistently, including min:0, rather than implementing that issue's requested empty-to-min behavior.

Also adds regression coverage for previously closed #951, #2284, #2829, and for the element-less-validation case from #2485 (RangeError and radix point auto-insertion in #2485 are out of scope).

AI disclosure

This PR was written primarily by Claude Code. Follow-up fixes were reviewed and amended by Codex.

@thorn0 thorn0 force-pushed the fix/numeric-min-max-validation-v2 branch 10 times, most recently from 126458c to 764a5ee Compare April 16, 2026 15:43
Input / blur / isValid
- Make below-min postValidation symmetric with above-max: when
  SetMaxOnOverflow is true and the value goes negative past min,
  clamp to min; otherwise reject the keystroke.
- Reject a negation toggle when the resulting value would fall
  out of range on either side.
- After revalidateMask strips the negation sign from the buffer,
  an overflowing positive in postValidation can be a valid
  negative. The max-overflow check consults the live DOM (skipped
  during checkVal where the DOM is stale) and falls back to
  opts.min < 0 in element-less contexts (Inputmask.isValid /
  Inputmask.format), returning currentResult instead of clamping.
- Empty-field guard checks maskset.validPositions.length > 0
  rather than buffer.join() !== "", so mask-literal padding and
  whitespace-only values no longer trigger a clamp to min on blur.

setvalue
- applyInputValue invokes onBeforeMask with __skipRounding on the
  setvalue path: the alias parser clamps before checkVal while
  the parseFloat round-trip that mangles bignums (RobinHerbots#2715) is
  suppressed.
- Internal numeric rewrites (negation-delete, radix-dance) call
  applyInputValue directly via setBufferAndCaret with
  skipOnBeforeMask=true, bypassing the parser for already-clean
  masked buffers without round-tripping through the public
  setvalue trigger.

Tests added across DOM input, element-less APIs, and the setvalue
path (in-range pass-through, empty, number-arg, formatted
prefix/groupSeparator above and below range, European locale,
onBeforeMask=null, bignum, internal-rewrite bypass).

Closes RobinHerbots#1763, RobinHerbots#2715, RobinHerbots#2775, RobinHerbots#2846, RobinHerbots#2863, RobinHerbots#2651.
Adds regression coverage for previously closed RobinHerbots#951, RobinHerbots#2284, RobinHerbots#2829.
Also covers the element-less-validation case from RobinHerbots#2485
(input-path RangeError in RobinHerbots#2485 out of scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@thorn0 thorn0 force-pushed the fix/numeric-min-max-validation-v2 branch from 764a5ee to d355c8c Compare April 16, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment