55 Licensed under the MIT license
66 */
77import { escapeRegex } from "../escapeRegex" ;
8+ import { applyInputValue } from "../inputHandling" ;
89import Inputmask from "../inputmask" ;
910import { keys } from "../keycode" ;
10- import { seekNext } from "../positioning" ;
11+ import { caret , seekNext } from "../positioning" ;
1112
1213const $ = Inputmask . dependencyLib ;
1314
@@ -57,6 +58,30 @@ function alignDigits(buffer, digits, opts, force) {
5758 return buffer ;
5859}
5960
61+ function unmaskAsNumber ( str , opts ) {
62+ return opts . onUnMask (
63+ str ,
64+ undefined ,
65+ $ . extend ( { } , opts , { unmaskAsNumber : true } )
66+ ) ;
67+ }
68+
69+ function boundaryBuffer ( bound , opts ) {
70+ return alignDigits (
71+ bound . toString ( ) . replace ( "." , opts . radixPoint ) . split ( "" ) ,
72+ opts . digits ,
73+ opts
74+ ) . reverse ( ) ;
75+ }
76+
77+ // Internal numeric rewrites (negation-delete, radix-dance) — push an
78+ // already-clean masked buffer back into the input without re-running the
79+ // alias parser.
80+ function setBufferAndCaret ( input , value , caretBegin ) {
81+ applyInputValue ( input , value , new $ . Event ( "input" ) , true ) ;
82+ caret . call ( input . inputmask , input , caretBegin ) ;
83+ }
84+
6085function findValidator ( symbol , maskset ) {
6186 let posNdx = 0 ;
6287 if ( symbol === "+" ) {
@@ -387,14 +412,31 @@ Inputmask.extendAliases({
387412 pos = handleRadixDance ( pos , c , radixPos , maskset , opts ) ;
388413 if ( c === "-" || c === opts . negationSymbol . front ) {
389414 if ( opts . allowMinus !== true ) return false ;
390- let isNegative = false ,
391- front = findValid ( "+" , maskset ) ,
415+ let isNegative = false ;
416+ const front = findValid ( "+" , maskset ) ,
392417 back = findValid ( "-" , maskset ) ;
393418 if ( front !== - 1 ) {
394419 isNegative = [ front ] ;
395420 if ( back !== - 1 ) isNegative . push ( back ) ;
396421 }
397422
423+ const checkMax = isNegative !== false && opts . max !== null ,
424+ checkMin = isNegative === false && opts . min !== null ;
425+ // Reject typing "-" against a non-negative min — alignDigits would
426+ // pad the orphan sign to "-0". SetMaxOnOverflow=true has its own
427+ // boundary refresh in postValidation.
428+ if ( ! opts . SetMaxOnOverflow && checkMin && opts . min >= 0 ) return false ;
429+ // Reject sign flips that would push the buffer out of range.
430+ // postValidation's range check doesn't fire after the validator's
431+ // `{remove: ...}` return (toggle-off path), so overflow on that
432+ // path must be caught here.
433+ if ( ( checkMax || checkMin ) && this . maskset . validPositions . length > 0 ) {
434+ const absVal = Math . abs (
435+ unmaskAsNumber ( buffer . slice ( ) . reverse ( ) . join ( "" ) , opts )
436+ ) ;
437+ if ( checkMax && absVal > opts . max ) return false ;
438+ if ( checkMin && - absVal < opts . min ) return false ;
439+ }
398440 return isNegative !== false
399441 ? {
400442 remove : isNegative ,
@@ -509,37 +551,39 @@ Inputmask.extendAliases({
509551 if ( currentResult === false ) return currentResult ;
510552 if ( strict ) return true ;
511553 if ( opts . min !== null || opts . max !== null ) {
512- const unmasked = opts . onUnMask (
554+ const unmasked = unmaskAsNumber (
513555 buffer . slice ( ) . reverse ( ) . join ( "" ) ,
514- undefined ,
515- $ . extend ( { } , opts , {
516- unmaskAsNumber : true
517- } )
556+ opts
518557 ) ;
519558 if (
520559 opts . min !== null &&
521560 unmasked < opts . min &&
522- fromAlternate !== true &&
561+ ( fromAlternate !== true || unmasked < 0 ) &&
523562 ( unmasked . toString ( ) . length > opts . min . toString ( ) . length || // > instead of >= because we want to allow to type a bigger number
524563 buffer [ 0 ] === opts . radixPoint || // disallow radixpoint when value is smaller than min
525564 unmasked < 0 )
526565 ) {
527- return false ;
528- // return {
529- // refreshFromBuffer: true,
530- // buffer: alignDigits(opts.min.toString().replace(".", opts.radixPoint).split(""), opts.digits, opts).reverse()
531- // };
566+ return unmasked < 0 && opts . SetMaxOnOverflow
567+ ? {
568+ refreshFromBuffer : true ,
569+ buffer : boundaryBuffer ( opts . min , opts )
570+ }
571+ : false ;
532572 }
533573
534574 if ( opts . max !== null && opts . max >= 0 && unmasked > opts . max ) {
575+ // #2846: revalidateMask may strip the negation sign, so an
576+ // overflowing positive here can actually be a valid negative.
577+ // Consult the DOM (stale during checkval) or, element-less,
578+ // trust min < 0.
579+ const isNegativeContext = this . el
580+ ? ! fromCheckval && unmaskAsNumber ( this . _valueGet ( true ) , opts ) < 0
581+ : opts . min !== null && opts . min < 0 ;
582+ if ( isNegativeContext ) return currentResult ;
535583 return opts . SetMaxOnOverflow
536584 ? {
537585 refreshFromBuffer : true ,
538- buffer : alignDigits (
539- opts . max . toString ( ) . replace ( "." , opts . radixPoint ) . split ( "" ) ,
540- opts . digits ,
541- opts
542- ) . reverse ( )
586+ buffer : boundaryBuffer ( opts . max , opts )
543587 }
544588 : false ;
545589 }
@@ -606,6 +650,10 @@ Inputmask.extendAliases({
606650 maskedValue = maskedValue . replace ( escapeRegex ( opts . radixPoint ) , "." ) ;
607651 return isFinite ( maskedValue ) ;
608652 } ,
653+ // Numeric alias onBeforeMask hook — parses/normalizes a value into the
654+ // alias buffer format and clamps to min/max.
655+ // #2715: opts.__skipRounding suppresses the parseFloat round-trip so bignum
656+ // precision survives the setvalue path; clamping and other transforms remain.
609657 onBeforeMask : function ( initialValue , opts ) {
610658 initialValue = initialValue ?? "" ;
611659 const radixPoint = opts . radixPoint || "," ;
@@ -637,7 +685,10 @@ Inputmask.extendAliases({
637685 : opts . digits < decimalPart . length
638686 ? opts . digits
639687 : decimalPart . length ;
640- if ( decimalPart !== "" || ! opts . digitsOptional ) {
688+ if (
689+ ! opts . __skipRounding &&
690+ ( decimalPart !== "" || ! opts . digitsOptional )
691+ ) {
641692 const digitsFactor = Math . pow ( 10 , digits || 1 ) ;
642693
643694 // make the initialValue a valid javascript number for the parsefloat
@@ -659,16 +710,22 @@ Inputmask.extendAliases({
659710 ) ;
660711 }
661712
713+ let clamped = false ;
662714 if ( initialValue !== "" && ( opts . min !== null || opts . max !== null ) ) {
663715 const numberValue = initialValue . toString ( ) . replace ( radixPoint , "." ) ;
664716 if ( opts . min !== null && numberValue < opts . min ) {
665717 initialValue = opts . min . toString ( ) . replace ( "." , radixPoint ) ;
718+ clamped = true ;
666719 } else if ( opts . max !== null && numberValue > opts . max ) {
667720 initialValue = opts . max . toString ( ) . replace ( "." , radixPoint ) ;
721+ clamped = true ;
668722 }
669723 }
670724
671- if ( isNegative && initialValue . charAt ( 0 ) !== "-" ) {
725+ // After a clamp the boundary's own sign already lives in initialValue —
726+ // re-prepending the original input's "-" would invert nonneg boundaries
727+ // (setvalue("-5") with min:10 → "-10") or strand "-0" (min:0).
728+ if ( isNegative && ! clamped && initialValue . charAt ( 0 ) !== "-" ) {
672729 initialValue = "-" + initialValue ;
673730 }
674731 return alignDigits (
@@ -716,35 +773,23 @@ Inputmask.extendAliases({
716773 switch ( e . type ) {
717774 case "blur" :
718775 case "checkval" :
719- if ( opts . min !== null || opts . max !== null ) {
720- const unmasked = opts . onUnMask (
776+ if (
777+ ( opts . min !== null || opts . max !== null ) &&
778+ this . maskset . validPositions . length > 0
779+ ) {
780+ const unmasked = unmaskAsNumber (
721781 buffer . slice ( ) . reverse ( ) . join ( "" ) ,
722- undefined ,
723- $ . extend ( { } , opts , {
724- unmaskAsNumber : true
725- } )
782+ opts
726783 ) ;
727- if (
728- opts . min !== null &&
729- unmasked < opts . min &&
730- buffer . join ( ) !== ""
731- ) {
784+ if ( opts . min !== null && unmasked < opts . min ) {
732785 return {
733786 refreshFromBuffer : true ,
734- buffer : alignDigits (
735- opts . min . toString ( ) . replace ( "." , opts . radixPoint ) . split ( "" ) ,
736- opts . digits ,
737- opts
738- ) . reverse ( )
787+ buffer : boundaryBuffer ( opts . min , opts )
739788 } ;
740789 } else if ( opts . max !== null && unmasked > opts . max ) {
741790 return {
742791 refreshFromBuffer : true ,
743- buffer : alignDigits (
744- opts . max . toString ( ) . replace ( "." , opts . radixPoint ) . split ( "" ) ,
745- opts . digits ,
746- opts
747- ) . reverse ( )
792+ buffer : boundaryBuffer ( opts . max , opts )
748793 } ;
749794 }
750795 }
@@ -850,7 +895,7 @@ Inputmask.extendAliases({
850895 bffr = buffer . slice ( ) . reverse ( ) ;
851896 if ( opts . negationSymbol . front !== "" ) bffr . shift ( ) ;
852897 if ( opts . negationSymbol . back !== "" ) bffr . pop ( ) ;
853- $input . trigger ( "setvalue" , [ bffr . join ( "" ) , caretPos . begin ] ) ;
898+ setBufferAndCaret ( this , bffr . join ( "" ) , caretPos . begin ) ;
854899 return false ;
855900 } else if ( opts . _radixDance === true ) {
856901 const radixPos = buffer . indexOf ( opts . radixPoint ) ;
@@ -889,19 +934,21 @@ Inputmask.extendAliases({
889934 if ( restoreCaretPos ) {
890935 caretPos = restoreCaretPos ;
891936 }
892- $input . trigger ( "setvalue" , [
937+ setBufferAndCaret (
938+ this ,
893939 bffr ,
894940 caretPos . begin >= bffr . length ? radixPos + 1 : caretPos . begin
895- ] ) ;
941+ ) ;
896942 return false ;
897943 }
898944 } else if ( radixPos === 0 ) {
899945 bffr = buffer . slice ( ) . reverse ( ) ;
900946 bffr . pop ( ) ;
901- $input . trigger ( "setvalue" , [
947+ setBufferAndCaret (
948+ this ,
902949 bffr . join ( "" ) ,
903950 caretPos . begin >= bffr . length ? bffr . length : caretPos . begin
904- ] ) ;
951+ ) ;
905952 return false ;
906953 }
907954 }
0 commit comments