Browse Source

Merge branch '2.x' into dev

Robin Herbots 12 years ago
parent
commit
21860ce94a

+ 16 - 5
README.md

@@ -19,6 +19,7 @@ Highlights:
 - supports readonly/disabled/dir="rtl" attributes
 - support data-inputmask attribute  
 - multi-mask support  
+- regex-mask support
 
 
 ## Usage:
@@ -544,9 +545,7 @@ In Layout
 
 # jquery.inputmask extensions
 
-## Alias definitions
-
-### date & datetime aliases
+## date & datetime extensions
 
 ```javascript
 $(document).ready(function(){
@@ -572,7 +571,7 @@ $(document).ready(function(){
 });
 ```
 
-### numeric aliases
+## numeric extensions
 
 ```javascript
 $(document).ready(function(){
@@ -618,7 +617,19 @@ $(document).ready(function(){
 });
 ```
 
-### other aliases
+## Regex extensions
+
+With the regex extension you can use any regular expression as a mask.  Currently this does only input restriction.  
+There is no further masking visualization.
+
+Example simple email regex:
+```javascript
+$(document).ready(function(){
+   $("#numeric").inputmask('Regex', { regex: "[a-zA-Z0-9._%-]+@[a-zA-Z0-9-]+\\.[a-zA-Z]{2,4}" });
+});
+```
+
+## other extensions
 
 An ip adress alias for entering valid ip-addresses.
 

+ 1 - 1
build.properties

@@ -7,7 +7,7 @@ distdir = dist
 
 build.major = 2
 build.minor = 3
-build.revision = 4
+build.revision = 11
 
 target = jquery.inputmask.bundle.js
 target.min = jquery.inputmask.bundle.min.js

+ 1 - 1
component.json

@@ -1,6 +1,6 @@
 {
     "name": "jquery.inputmask",
-    "version": "2.3.4",
+    "version": "2.3.11",
     "main": "./dist/jquery.inputmask.bundle.js",
     "dependencies": {
         "jquery": ">=1.5"

BIN
dist/jQuery.InputMask.2.3.11.nupkg


BIN
dist/jQuery.InputMask.2.3.4.nupkg


+ 182 - 74
dist/jquery.inputmask.bundle.js

@@ -3,7 +3,7 @@
 * http://github.com/RobinHerbots/jquery.inputmask
 * Copyright (c) 2010 - 2013 Robin Herbots
 * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
-* Version: 2.3.4
+* Version: 2.3.11
 */
 
 (function ($) {
@@ -117,39 +117,43 @@
                     case "remove":
                         return this.each(function () {
                             var $input = $(this), input = this;
-                            setTimeout(function () {
-                                if ($input.data('_inputmask')) {
-                                    masksets = $input.data('_inputmask')['masksets'];
-                                    activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex'];
-                                    opts = $input.data('_inputmask')['opts'];
-                                    //writeout the unmaskedvalue
-                                    input._valueSet(maskScope(masksets, activeMasksetIndex).unmaskedvalue($input, true));
-                                    //clear data
-                                    $input.removeData('_inputmask');
-                                    //unbind all events
-                                    $input.unbind(".inputmask");
-                                    $input.removeClass('focus.inputmask');
-                                    //restore the value property
-                                    var valueProperty;
-                                    if (Object.getOwnPropertyDescriptor)
-                                        valueProperty = Object.getOwnPropertyDescriptor(input, "value");
-                                    if (valueProperty && valueProperty.get) {
-                                        if (input._valueGet) {
-                                            Object.defineProperty(input, "value", {
-                                                get: input._valueGet,
-                                                set: input._valueSet
-                                            });
-                                        }
-                                    } else if (document.__lookupGetter__ && input.__lookupGetter__("value")) {
-                                        if (input._valueGet) {
-                                            input.__defineGetter__("value", input._valueGet);
-                                            input.__defineSetter__("value", input._valueSet);
-                                        }
+                            if ($input.data('_inputmask')) {
+                                masksets = $input.data('_inputmask')['masksets'];
+                                activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex'];
+                                opts = $input.data('_inputmask')['opts'];
+                                //writeout the unmaskedvalue
+                                input._valueSet(maskScope(masksets, activeMasksetIndex).unmaskedvalue($input, true));
+                                //clear data
+                                $input.removeData('_inputmask');
+                                //unbind all events
+                                $input.unbind(".inputmask");
+                                $input.removeClass('focus.inputmask');
+                                //restore the value property
+                                var valueProperty;
+                                if (Object.getOwnPropertyDescriptor)
+                                    valueProperty = Object.getOwnPropertyDescriptor(input, "value");
+                                if (valueProperty && valueProperty.get) {
+                                    if (input._valueGet) {
+                                        Object.defineProperty(input, "value", {
+                                            get: input._valueGet,
+                                            set: input._valueSet
+                                        });
+                                    }
+                                } else if (document.__lookupGetter__ && input.__lookupGetter__("value")) {
+                                    if (input._valueGet) {
+                                        input.__defineGetter__("value", input._valueGet);
+                                        input.__defineSetter__("value", input._valueSet);
                                     }
+                                }
+                                try { //try catch needed for IE7 as it does not supports deleting fns
                                     delete input._valueGet;
                                     delete input._valueSet;
+                                } catch (e) {
+                                    input._valueGet = undefined;
+                                    input._valueSet = undefined;
+
                                 }
-                            }, 0);
+                            }
                         });
                         break;
                     case "getemptymask": //return the default (empty) mask value, usefull for setting the default value in validation
@@ -486,9 +490,12 @@
                                     }
                                     var newValidPosition = result.pos || maskPos;
                                     if (activeMaskset['lastValidPosition'] == undefined ||
-                                        (isRTL ? (opts.greedy ? activeMaskset['lastValidPosition'] > newValidPosition : newValidPosition == getActiveBuffer().length - 1)
-                                            : activeMaskset['lastValidPosition'] < newValidPosition))
-                                        activeMaskset['lastValidPosition'] = newValidPosition; //set new position from isValid
+                                        (isRTL ?
+                                            (opts.greedy ?
+                                                activeMaskset['lastValidPosition'] > newValidPosition :
+                                                newValidPosition == getActiveBuffer().length - 1) :
+                                             activeMaskset['lastValidPosition'] < newValidPosition))
+                                        activeMaskset['lastValidPosition'] = opts.numericInput ? 0 : newValidPosition; //set new position from isValid
                                 }
                                 results.push({ "activeMasksetIndex": index, "result": result });
                             }
@@ -640,7 +647,7 @@
                     $.each(masksets, function (ndx, ms) {
                         ms["buffer"] = ms["_buffer"].slice();
                         ms["lastValidPosition"] = undefined;
-                        ms["p"] = isRTL ? getMaskLength() : 0;
+                        ms["p"] = (isRTL && ms["greedy"] == true) ? getMaskLength() : -1;
                     });
                     if (strict !== true) activeMasksetIndex = 0;
                     if (writeOut) input._valueSet(""); //initial clear
@@ -650,7 +657,7 @@
 
                     var ml = getMaskLength();
                     $.each(inputValue, function (ndx, charCode) {
-                        var index = isRTL ? (opts.numericInput ? ml : ml - ndx) : ndx,
+                        var index = (isRTL && getActiveMaskSet()["greedy"] == true) ? (opts.numericInput ? ml : ml - ndx) : ndx,
                             lvp = getActiveMaskSet()["lastValidPosition"],
                             pos = getActiveMaskSet()["p"];
 
@@ -665,7 +672,7 @@
                         }
                     });
                     if (strict === true) {
-                        getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(getActiveMaskSet()["p"]) : seekPrevious(getActiveMaskSet()["p"]);
+                        getActiveMaskSet()["lastValidPosition"] = isRTL ? (opts.numericInput ? 0 : seekNext(getActiveMaskSet()["p"])) : seekPrevious(getActiveMaskSet()["p"]);
                     }
                 }
 
@@ -798,13 +805,23 @@
                     getActiveMaskSet()['greedy'] = getActiveMaskSet()['greedy'] ? getActiveMaskSet()['greedy'] : getActiveMaskSet()['repeat'] == 0;
 
                     //handle maxlength attribute
-                    var maxLength = $input.prop('maxLength');
-                    if (getMaskLength() > maxLength && maxLength > -1) { //FF sets no defined max length to -1 
-                        if (maxLength < getActiveBufferTemplate().length) getActiveBufferTemplate().length = maxLength;
-                        if (getActiveMaskSet()['greedy'] == false) {
-                            getActiveMaskSet()['repeat'] = Math.round(maxLength / getActiveBufferTemplate().length);
+                    if ($input.attr("maxLength") != null) //only when the attribute is set
+                    {
+                        var maxLength = $input.prop('maxLength');
+                        if (maxLength > -1) { //handle *-repeat
+                            $.each(masksets, function (ndx, ms) {
+                                if (ms["repeat"] == "*") {
+                                    ms["repeat"] = maxLength;
+                                }
+                            });
+                        }
+                        if (getMaskLength() > maxLength && maxLength > -1) { //FF sets no defined max length to -1 
+                            if (maxLength < getActiveBufferTemplate().length) getActiveBufferTemplate().length = maxLength;
+                            if (getActiveMaskSet()['greedy'] == false) {
+                                getActiveMaskSet()['repeat'] = Math.round(maxLength / getActiveBufferTemplate().length);
+                            }
+                            $input.prop('maxLength', getMaskLength() * 2);
                         }
-                        $input.prop('maxLength', getMaskLength() * 2);
                     }
 
                     patchValueProperty(el);
@@ -1309,7 +1326,7 @@
                             caret(input, seekNext(radixPosition != -1 ? radixPosition : getMaskLength()));
                         }
 
-                        if ((e.ctrlKey || e.metaKey || ignorable) && checkval !== true) {
+                        if ((!(e.ctrlKey && e.altKey) && (e.ctrlKey || e.metaKey || ignorable)) && checkval !== true) {
                             return true;
                         } else {
                             if (k) {
@@ -1329,6 +1346,11 @@
                                         activeMasksetIndex = ndx;
                                         getActiveMaskSet()["undoBuffer"] = getActiveBuffer().join(''); //init undobuffer for recovery when not valid
                                         var posend = pos.end < getMaskLength() ? pos.end : getMaskLength();
+                                        if (getActiveMaskSet()["lastValidPosition"] > pos.begin && getActiveMaskSet()["lastValidPosition"] < posend) {
+                                            getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(posend) : seekPrevious(pos.begin);
+                                        } else {
+                                            redetermineLVP = true;
+                                        }
                                         clearBuffer(getActiveBuffer(), pos.begin, posend);
                                         var ml = getMaskLength();
                                         if (opts.greedy == false) {
@@ -1339,11 +1361,6 @@
                                                     isRTL ? shiftR(0, posend - 1, getPlaceHolder(posend - 1), true) : shiftL(pos.begin, ml);
                                             }
                                         }
-                                        if (getActiveMaskSet()["lastValidPosition"] > pos.begin && getActiveMaskSet()["lastValidPosition"] < posend) {
-                                            getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(posend) : seekPrevious(pos.begin);
-                                        } else {
-                                            redetermineLVP = true;
-                                        }
                                     });
                                     if (redetermineLVP === true) {
                                         activeMasksetIndex = initialIndex;
@@ -1360,7 +1377,7 @@
                                 }
 
                                 if (isRTL) {
-                                    var p = seekPrevious(pos.end);
+                                    var p = seekPrevious(isSelection ? pos.begin : pos.end);
                                     results = isValid(p, c, strict, isRTL);
                                     if (strict === true) results = [{ "activeMasksetIndex": activeMasksetIndex, "result": results }];
                                     $.each(results, function (index, result) {
@@ -1495,7 +1512,7 @@ Input Mask plugin extensions
 http://github.com/RobinHerbots/jquery.inputmask
 Copyright (c) 2010 - 2013 Robin Herbots
 Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
-Version: 2.3.4
+Version: 2.3.11
 
 Optional extensions on the jquery.inputmask base
 */
@@ -1597,7 +1614,7 @@ Input Mask plugin extensions
 http://github.com/RobinHerbots/jquery.inputmask
 Copyright (c) 2010 - 2012 Robin Herbots
 Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
-Version: 2.3.4
+Version: 2.3.11
 
 Optional extensions on the jquery.inputmask base
 */
@@ -1653,10 +1670,18 @@ Optional extensions on the jquery.inputmask base
                 return (enteredyear != NaN ? minyear <= enteredyear && enteredyear <= maxyear : false) ||
             		   (enteredyear2 != NaN ? minyear <= enteredyear2 && enteredyear2 <= maxyear : false);
             },
-            determinebaseyear: function (minyear, maxyear) {
+            determinebaseyear: function (minyear, maxyear, hint) {
                 var currentyear = (new Date()).getFullYear();
                 if (minyear > currentyear) return minyear;
-                if (maxyear < currentyear) return maxyear;
+                if (maxyear < currentyear) {
+                    var maxYearPrefix = maxyear.toString().slice(0, 2);
+                    var maxYearPostfix = maxyear.toString().slice(2, 4);
+                    while (maxyear < maxYearPrefix + hint) {
+                        maxYearPrefix--;
+                    }
+                    var maxxYear = maxYearPrefix + maxYearPostfix;
+                    return minyear > maxxYear ? minyear : maxxYear;
+                }
 
                 return currentyear;
             },
@@ -1754,14 +1779,14 @@ Optional extensions on the jquery.inputmask base
                     validator: function (chrs, buffer, pos, strict, opts) {
                         var isValid = opts.isInYearRange(chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                         if (!strict && !isValid) {
-                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 1);
+                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs + "0").toString().slice(0, 1);
 
                             isValid = opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
                                 buffer[pos++] = yearPrefix[0];
                                 return { "pos": pos };
                             }
-                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs + "0").toString().slice(0, 2);
 
                             isValid = opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
@@ -1778,7 +1803,7 @@ Optional extensions on the jquery.inputmask base
                     validator: function (chrs, buffer, pos, strict, opts) {
                         var isValid = opts.isInYearRange(chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                         if (!strict && !isValid) {
-                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs).toString().slice(0, 2);
 
                             isValid = opts.isInYearRange(chrs[0] + yearPrefix[1] + chrs[1], opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
@@ -1786,7 +1811,7 @@ Optional extensions on the jquery.inputmask base
                                 return { "pos": pos };
                             }
 
-                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs).toString().slice(0, 2);
                             if (opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear)) {
                                 var dayMonthValue = buffer.join('').substr(0, 6);
                                 if (dayMonthValue != opts.leapday)
@@ -2066,7 +2091,7 @@ Input Mask plugin extensions
 http://github.com/RobinHerbots/jquery.inputmask
 Copyright (c) 2010 - 2013 Robin Herbots
 Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
-Version: 2.3.4
+Version: 2.3.11
 
 Optional extensions on the jquery.inputmask base
 */
@@ -2233,7 +2258,7 @@ Input Mask plugin extensions
 http://github.com/RobinHerbots/jquery.inputmask
 Copyright (c) 2010 - 2013 Robin Herbots
 Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
-Version: 2.3.4
+Version: 2.3.11
 
 Regex extensions on the jquery.inputmask base
 Allows for using regular expressions as a mask
@@ -2245,34 +2270,117 @@ Allows for using regular expressions as a mask
             greedy: false,
             repeat: "*",
             regex: null,
-            regexSplit: null,
+            regexTokens: null,
+            tokenizer: /\[\^?]?(?:[^\\\]]+|\\[\S\s]?)*]?|\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\]+|./g,
             definitions: {
                 'r': {
                     validator: function (chrs, buffer, pos, strict, opts) {
 
-                        function analyseRegex() {  //ENHANCE ME
-                            var regexSplitRegex = "\\[.*?\]\\*";
+                        function analyseRegex() {
+                            var currentToken = {
+                                "quantifier": undefined,
+                                "matches": [],
+                                "isGroup": false
+                            }, match, m, opengroups = [];
+
+                            opts.regexTokens = [];
+
+                            // The tokenizer regex does most of the tokenization grunt work
+                            while (match = opts.tokenizer.exec(opts.regex)) {
+                                m = match[0];
+                                switch (m.charAt(0)) {
+                                    // Character class
+                                    case "[":
+                                        // Escape or backreference
+                                    case "\\":
+                                        if (currentToken["isGroup"] !== true) {
+                                            currentToken = {
+                                                "matches": [],
+                                                "isGroup": false
+                                            };
+                                            opts.regexTokens.push(currentToken);
+                                        }
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(m);
+                                        } else {
+                                            currentToken["matches"].push(m);
+                                        }
+                                        break;
+                                        // Group opening
+                                    case "(":
+                                        currentToken = {
+                                            "matches": [],
+                                            "isGroup": true
+                                        };
+                                        opengroups.push(currentToken);
+                                        break;
+                                        // Group closing
+                                    case ")":
+                                        var groupToken = opengroups.pop();
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(groupToken);
+                                        } else {
+                                            currentToken = groupToken;
+                                            opts.regexTokens.push(currentToken);
+                                        }
+                                        break;
+                                        // Not a character class, group opening/closing, escape sequence, or backreference
+                                    default:
+                                        // Quantifier 
+                                        // Vertical bar (alternator) 
+                                        // ^ or $ anchor
+                                        // Dot (.)
+                                        // Literal character sequence
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(m);
+                                        } else {
+                                            currentToken["matches"].push(m);
+                                        }
+                                }
+                            }
+                        };
+
+                        function validateRegexToken(token, fromGroup) {
+                            var isvalid = false;
+                            if (fromGroup) {
+                                regexPart += "(";
+                                openGroupCount++;
+                            }
+                            for (var mndx = 0; mndx < token["matches"].length; mndx++) {
+                                var matchToken = token["matches"][mndx];
+                                if (matchToken["isGroup"] == true) {
+                                    isvalid = validateRegexToken(matchToken, true);
+                                } else {
+                                    regexPart += matchToken;
+                                    var testExp = regexPart;
+                                    for (var j = 0; j < openGroupCount; j++) {
+                                        testExp += ")";
+                                    }
+                                    var exp = new RegExp("^" + testExp + "$");
+                                    isvalid = exp.test(bufferStr);
+                                }
+                                if (isvalid) break;
+                            }
 
-                            opts.regexSplit = opts.regex.match(new RegExp(regexSplitRegex, "g"));
+                            if (fromGroup) {
+                                regexPart += ")";
+                                openGroupCount--;
+                            }
 
-                            //if (opts.regex.indexOf("*") != (opts.regex.length - 1)) {
-                            //    opts.regex += "{1}";
-                            //}
-                            //opts.regexSplit.push(opts.regex);
+                            return isvalid;
                         }
 
-                        if (opts.regexSplit == null) {
+
+                        if (opts.regexTokens == null) {
                             analyseRegex();
                         }
 
-                        var cbuffer = buffer.slice(), regexPart = "", isValid = false;
+                        var cbuffer = buffer.slice(), regexPart = "", isValid = false, openGroupCount = 0;
                         cbuffer.splice(pos, 0, chrs);
                         var bufferStr = cbuffer.join('');
-                        for (var i = 0; i < opts.regexSplit.length; i++) {
-                            regexPart += opts.regexSplit[i];
-                            var exp = new RegExp("^" + regexPart + "$");
-                            isValid = exp.test(bufferStr);
-                            console.log(bufferStr + ' ' + isValid + ' ' + regexPart);
+                        for (var i = 0; i < opts.regexTokens.length; i++) {
+                            var regexToken = opts.regexTokens[i];
+                            isValid = validateRegexToken(regexToken, regexPart, regexToken["isGroup"]);
                             if (isValid) break;
                         }
 

File diff suppressed because it is too large
+ 72 - 70
dist/jquery.inputmask.bundle.min.js


File diff suppressed because it is too large
+ 13 - 13
dist/min/jquery.inputmask.date.extensions.js


File diff suppressed because it is too large
+ 41 - 41
dist/min/jquery.inputmask.js


File diff suppressed because it is too large
+ 3 - 1
dist/min/jquery.inputmask.regex.extensions.js


+ 1 - 1
jquery.inputmask.jquery.json

@@ -8,7 +8,7 @@
 		"inputmask",
 		"mask"
     ],
-    "version": "2.3.4",
+    "version": "2.3.11",
     "author": {
         "name": "Robin Herbots",
         "url": "http://github.com/RobinHerbots/jquery.inputmask"

+ 14 - 6
js/jquery.inputmask.date.extensions.js

@@ -59,10 +59,18 @@ Optional extensions on the jquery.inputmask base
                 return (enteredyear != NaN ? minyear <= enteredyear && enteredyear <= maxyear : false) ||
             		   (enteredyear2 != NaN ? minyear <= enteredyear2 && enteredyear2 <= maxyear : false);
             },
-            determinebaseyear: function (minyear, maxyear) {
+            determinebaseyear: function (minyear, maxyear, hint) {
                 var currentyear = (new Date()).getFullYear();
                 if (minyear > currentyear) return minyear;
-                if (maxyear < currentyear) return maxyear;
+                if (maxyear < currentyear) {
+                    var maxYearPrefix = maxyear.toString().slice(0, 2);
+                    var maxYearPostfix = maxyear.toString().slice(2, 4);
+                    while (maxyear < maxYearPrefix + hint) {
+                        maxYearPrefix--;
+                    }
+                    var maxxYear = maxYearPrefix + maxYearPostfix;
+                    return minyear > maxxYear ? minyear : maxxYear;
+                }
 
                 return currentyear;
             },
@@ -160,14 +168,14 @@ Optional extensions on the jquery.inputmask base
                     validator: function (chrs, buffer, pos, strict, opts) {
                         var isValid = opts.isInYearRange(chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                         if (!strict && !isValid) {
-                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 1);
+                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs + "0").toString().slice(0, 1);
 
                             isValid = opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
                                 buffer[pos++] = yearPrefix[0];
                                 return { "pos": pos };
                             }
-                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs + "0").toString().slice(0, 2);
 
                             isValid = opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
@@ -184,7 +192,7 @@ Optional extensions on the jquery.inputmask base
                     validator: function (chrs, buffer, pos, strict, opts) {
                         var isValid = opts.isInYearRange(chrs, opts.yearrange.minyear, opts.yearrange.maxyear);
                         if (!strict && !isValid) {
-                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            var yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs).toString().slice(0, 2);
 
                             isValid = opts.isInYearRange(chrs[0] + yearPrefix[1] + chrs[1], opts.yearrange.minyear, opts.yearrange.maxyear);
                             if (isValid) {
@@ -192,7 +200,7 @@ Optional extensions on the jquery.inputmask base
                                 return { "pos": pos };
                             }
 
-                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear).toString().slice(0, 2);
+                            yearPrefix = opts.determinebaseyear(opts.yearrange.minyear, opts.yearrange.maxyear, chrs).toString().slice(0, 2);
                             if (opts.isInYearRange(yearPrefix + chrs, opts.yearrange.minyear, opts.yearrange.maxyear)) {
                                 var dayMonthValue = buffer.join('').substr(0, 6);
                                 if (dayMonthValue != opts.leapday)

+ 65 - 48
js/jquery.inputmask.js

@@ -117,39 +117,43 @@
                     case "remove":
                         return this.each(function () {
                             var $input = $(this), input = this;
-                            setTimeout(function () {
-                                if ($input.data('_inputmask')) {
-                                    masksets = $input.data('_inputmask')['masksets'];
-                                    activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex'];
-                                    opts = $input.data('_inputmask')['opts'];
-                                    //writeout the unmaskedvalue
-                                    input._valueSet(maskScope(masksets, activeMasksetIndex).unmaskedvalue($input, true));
-                                    //clear data
-                                    $input.removeData('_inputmask');
-                                    //unbind all events
-                                    $input.unbind(".inputmask");
-                                    $input.removeClass('focus.inputmask');
-                                    //restore the value property
-                                    var valueProperty;
-                                    if (Object.getOwnPropertyDescriptor)
-                                        valueProperty = Object.getOwnPropertyDescriptor(input, "value");
-                                    if (valueProperty && valueProperty.get) {
-                                        if (input._valueGet) {
-                                            Object.defineProperty(input, "value", {
-                                                get: input._valueGet,
-                                                set: input._valueSet
-                                            });
-                                        }
-                                    } else if (document.__lookupGetter__ && input.__lookupGetter__("value")) {
-                                        if (input._valueGet) {
-                                            input.__defineGetter__("value", input._valueGet);
-                                            input.__defineSetter__("value", input._valueSet);
-                                        }
+                            if ($input.data('_inputmask')) {
+                                masksets = $input.data('_inputmask')['masksets'];
+                                activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex'];
+                                opts = $input.data('_inputmask')['opts'];
+                                //writeout the unmaskedvalue
+                                input._valueSet(maskScope(masksets, activeMasksetIndex).unmaskedvalue($input, true));
+                                //clear data
+                                $input.removeData('_inputmask');
+                                //unbind all events
+                                $input.unbind(".inputmask");
+                                $input.removeClass('focus.inputmask');
+                                //restore the value property
+                                var valueProperty;
+                                if (Object.getOwnPropertyDescriptor)
+                                    valueProperty = Object.getOwnPropertyDescriptor(input, "value");
+                                if (valueProperty && valueProperty.get) {
+                                    if (input._valueGet) {
+                                        Object.defineProperty(input, "value", {
+                                            get: input._valueGet,
+                                            set: input._valueSet
+                                        });
+                                    }
+                                } else if (document.__lookupGetter__ && input.__lookupGetter__("value")) {
+                                    if (input._valueGet) {
+                                        input.__defineGetter__("value", input._valueGet);
+                                        input.__defineSetter__("value", input._valueSet);
                                     }
+                                }
+                                try { //try catch needed for IE7 as it does not supports deleting fns
                                     delete input._valueGet;
                                     delete input._valueSet;
+                                } catch (e) {
+                                    input._valueGet = undefined;
+                                    input._valueSet = undefined;
+
                                 }
-                            }, 0);
+                            }
                         });
                         break;
                     case "getemptymask": //return the default (empty) mask value, usefull for setting the default value in validation
@@ -486,9 +490,12 @@
                                     }
                                     var newValidPosition = result.pos || maskPos;
                                     if (activeMaskset['lastValidPosition'] == undefined ||
-                                        (isRTL ? (opts.greedy ? activeMaskset['lastValidPosition'] > newValidPosition : newValidPosition == getActiveBuffer().length - 1)
-                                            : activeMaskset['lastValidPosition'] < newValidPosition))
-                                        activeMaskset['lastValidPosition'] = newValidPosition; //set new position from isValid
+                                        (isRTL ?
+                                            (opts.greedy ?
+                                                activeMaskset['lastValidPosition'] > newValidPosition :
+                                                newValidPosition == getActiveBuffer().length - 1) :
+                                             activeMaskset['lastValidPosition'] < newValidPosition))
+                                        activeMaskset['lastValidPosition'] = opts.numericInput ? 0 : newValidPosition; //set new position from isValid
                                 }
                                 results.push({ "activeMasksetIndex": index, "result": result });
                             }
@@ -640,7 +647,7 @@
                     $.each(masksets, function (ndx, ms) {
                         ms["buffer"] = ms["_buffer"].slice();
                         ms["lastValidPosition"] = undefined;
-                        ms["p"] = isRTL ? getMaskLength() : 0;
+                        ms["p"] = (isRTL && ms["greedy"] == true) ? getMaskLength() : -1;
                     });
                     if (strict !== true) activeMasksetIndex = 0;
                     if (writeOut) input._valueSet(""); //initial clear
@@ -650,7 +657,7 @@
 
                     var ml = getMaskLength();
                     $.each(inputValue, function (ndx, charCode) {
-                        var index = isRTL ? (opts.numericInput ? ml : ml - ndx) : ndx,
+                        var index = (isRTL && getActiveMaskSet()["greedy"] == true) ? (opts.numericInput ? ml : ml - ndx) : ndx,
                             lvp = getActiveMaskSet()["lastValidPosition"],
                             pos = getActiveMaskSet()["p"];
 
@@ -665,7 +672,7 @@
                         }
                     });
                     if (strict === true) {
-                        getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(getActiveMaskSet()["p"]) : seekPrevious(getActiveMaskSet()["p"]);
+                        getActiveMaskSet()["lastValidPosition"] = isRTL ? (opts.numericInput ? 0 : seekNext(getActiveMaskSet()["p"])) : seekPrevious(getActiveMaskSet()["p"]);
                     }
                 }
 
@@ -798,13 +805,23 @@
                     getActiveMaskSet()['greedy'] = getActiveMaskSet()['greedy'] ? getActiveMaskSet()['greedy'] : getActiveMaskSet()['repeat'] == 0;
 
                     //handle maxlength attribute
-                    var maxLength = $input.prop('maxLength');
-                    if (getMaskLength() > maxLength && maxLength > -1) { //FF sets no defined max length to -1 
-                        if (maxLength < getActiveBufferTemplate().length) getActiveBufferTemplate().length = maxLength;
-                        if (getActiveMaskSet()['greedy'] == false) {
-                            getActiveMaskSet()['repeat'] = Math.round(maxLength / getActiveBufferTemplate().length);
+                    if ($input.attr("maxLength") != null) //only when the attribute is set
+                    {
+                        var maxLength = $input.prop('maxLength');
+                        if (maxLength > -1) { //handle *-repeat
+                            $.each(masksets, function (ndx, ms) {
+                                if (ms["repeat"] == "*") {
+                                    ms["repeat"] = maxLength;
+                                }
+                            });
+                        }
+                        if (getMaskLength() > maxLength && maxLength > -1) { //FF sets no defined max length to -1 
+                            if (maxLength < getActiveBufferTemplate().length) getActiveBufferTemplate().length = maxLength;
+                            if (getActiveMaskSet()['greedy'] == false) {
+                                getActiveMaskSet()['repeat'] = Math.round(maxLength / getActiveBufferTemplate().length);
+                            }
+                            $input.prop('maxLength', getMaskLength() * 2);
                         }
-                        $input.prop('maxLength', getMaskLength() * 2);
                     }
 
                     patchValueProperty(el);
@@ -1309,7 +1326,7 @@
                             caret(input, seekNext(radixPosition != -1 ? radixPosition : getMaskLength()));
                         }
 
-                        if ((e.ctrlKey || e.metaKey || ignorable) && checkval !== true) {
+                        if ((!(e.ctrlKey && e.altKey) && (e.ctrlKey || e.metaKey || ignorable)) && checkval !== true) {
                             return true;
                         } else {
                             if (k) {
@@ -1329,6 +1346,11 @@
                                         activeMasksetIndex = ndx;
                                         getActiveMaskSet()["undoBuffer"] = getActiveBuffer().join(''); //init undobuffer for recovery when not valid
                                         var posend = pos.end < getMaskLength() ? pos.end : getMaskLength();
+                                        if (getActiveMaskSet()["lastValidPosition"] > pos.begin && getActiveMaskSet()["lastValidPosition"] < posend) {
+                                            getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(posend) : seekPrevious(pos.begin);
+                                        } else {
+                                            redetermineLVP = true;
+                                        }
                                         clearBuffer(getActiveBuffer(), pos.begin, posend);
                                         var ml = getMaskLength();
                                         if (opts.greedy == false) {
@@ -1339,11 +1361,6 @@
                                                     isRTL ? shiftR(0, posend - 1, getPlaceHolder(posend - 1), true) : shiftL(pos.begin, ml);
                                             }
                                         }
-                                        if (getActiveMaskSet()["lastValidPosition"] > pos.begin && getActiveMaskSet()["lastValidPosition"] < posend) {
-                                            getActiveMaskSet()["lastValidPosition"] = isRTL ? seekNext(posend) : seekPrevious(pos.begin);
-                                        } else {
-                                            redetermineLVP = true;
-                                        }
                                     });
                                     if (redetermineLVP === true) {
                                         activeMasksetIndex = initialIndex;
@@ -1360,7 +1377,7 @@
                                 }
 
                                 if (isRTL) {
-                                    var p = seekPrevious(pos.end);
+                                    var p = seekPrevious(isSelection ? pos.begin : pos.end);
                                     results = isValid(p, c, strict, isRTL);
                                     if (strict === true) results = [{ "activeMasksetIndex": activeMasksetIndex, "result": results }];
                                     $.each(results, function (index, result) {

+ 98 - 15
js/jquery.inputmask.regex.extensions.js

@@ -15,34 +15,117 @@ Allows for using regular expressions as a mask
             greedy: false,
             repeat: "*",
             regex: null,
-            regexSplit: null,
+            regexTokens: null,
+            tokenizer: /\[\^?]?(?:[^\\\]]+|\\[\S\s]?)*]?|\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\]+|./g,
             definitions: {
                 'r': {
                     validator: function (chrs, buffer, pos, strict, opts) {
 
-                        function analyseRegex() {  //ENHANCE ME
-                            var regexSplitRegex = "\\[.*?\]\\*";
+                        function analyseRegex() {
+                            var currentToken = {
+                                "quantifier": undefined,
+                                "matches": [],
+                                "isGroup": false
+                            }, match, m, opengroups = [];
 
-                            opts.regexSplit = opts.regex.match(new RegExp(regexSplitRegex, "g"));
+                            opts.regexTokens = [];
 
-                            //if (opts.regex.indexOf("*") != (opts.regex.length - 1)) {
-                            //    opts.regex += "{1}";
-                            //}
-                            //opts.regexSplit.push(opts.regex);
+                            // The tokenizer regex does most of the tokenization grunt work
+                            while (match = opts.tokenizer.exec(opts.regex)) {
+                                m = match[0];
+                                switch (m.charAt(0)) {
+                                    // Character class
+                                    case "[":
+                                        // Escape or backreference
+                                    case "\\":
+                                        if (currentToken["isGroup"] !== true) {
+                                            currentToken = {
+                                                "matches": [],
+                                                "isGroup": false
+                                            };
+                                            opts.regexTokens.push(currentToken);
+                                        }
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(m);
+                                        } else {
+                                            currentToken["matches"].push(m);
+                                        }
+                                        break;
+                                        // Group opening
+                                    case "(":
+                                        currentToken = {
+                                            "matches": [],
+                                            "isGroup": true
+                                        };
+                                        opengroups.push(currentToken);
+                                        break;
+                                        // Group closing
+                                    case ")":
+                                        var groupToken = opengroups.pop();
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(groupToken);
+                                        } else {
+                                            currentToken = groupToken;
+                                            opts.regexTokens.push(currentToken);
+                                        }
+                                        break;
+                                        // Not a character class, group opening/closing, escape sequence, or backreference
+                                    default:
+                                        // Quantifier 
+                                        // Vertical bar (alternator) 
+                                        // ^ or $ anchor
+                                        // Dot (.)
+                                        // Literal character sequence
+                                        if (opengroups.length > 0) {
+                                            opengroups[opengroups.length - 1]["matches"].push(m);
+                                        } else {
+                                            currentToken["matches"].push(m);
+                                        }
+                                }
+                            }
+                        };
+
+                        function validateRegexToken(token, fromGroup) {
+                            var isvalid = false;
+                            if (fromGroup) {
+                                regexPart += "(";
+                                openGroupCount++;
+                            }
+                            for (var mndx = 0; mndx < token["matches"].length; mndx++) {
+                                var matchToken = token["matches"][mndx];
+                                if (matchToken["isGroup"] == true) {
+                                    isvalid = validateRegexToken(matchToken, true);
+                                } else {
+                                    regexPart += matchToken;
+                                    var testExp = regexPart;
+                                    for (var j = 0; j < openGroupCount; j++) {
+                                        testExp += ")";
+                                    }
+                                    var exp = new RegExp("^" + testExp + "$");
+                                    isvalid = exp.test(bufferStr);
+                                }
+                                if (isvalid) break;
+                            }
+
+                            if (fromGroup) {
+                                regexPart += ")";
+                                openGroupCount--;
+                            }
+
+                            return isvalid;
                         }
 
-                        if (opts.regexSplit == null) {
+
+                        if (opts.regexTokens == null) {
                             analyseRegex();
                         }
 
-                        var cbuffer = buffer.slice(), regexPart = "", isValid = false;
+                        var cbuffer = buffer.slice(), regexPart = "", isValid = false, openGroupCount = 0;
                         cbuffer.splice(pos, 0, chrs);
                         var bufferStr = cbuffer.join('');
-                        for (var i = 0; i < opts.regexSplit.length; i++) {
-                            regexPart += opts.regexSplit[i];
-                            var exp = new RegExp("^" + regexPart + "$");
-                            isValid = exp.test(bufferStr);
-                            console.log(bufferStr + ' ' + isValid + ' ' + regexPart);
+                        for (var i = 0; i < opts.regexTokens.length; i++) {
+                            var regexToken = opts.regexTokens[i];
+                            isValid = validateRegexToken(regexToken, regexPart, regexToken["isGroup"]);
                             if (isValid) break;
                         }
 

+ 3 - 0
nuget/jquery.inputmask.nuspec

@@ -26,6 +26,7 @@ Highlights:
 - supports readonly/disabled/dir="rtl" attributes
 - support data-inputmask attribute
 - multi-mask support
+- regex-mask support
 </description>
         <tags>jQuery, plugins, input, form, inputmask, mask</tags>
     </metadata>
@@ -33,10 +34,12 @@ Highlights:
         <file src="..\js\jquery.inputmask.date.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.date.extensions-0.0.0.js" />
         <file src="..\js\jquery.inputmask.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.extensions-0.0.0.js" />
         <file src="..\js\jquery.inputmask.numeric.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.numeric.extensions-0.0.0.js" />
+		<file src="..\js\jquery.inputmask.regex.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.regex.extensions-0.0.0.js" />
         <file src="..\js\jquery.inputmask.js" target="content\Scripts\jquery.inputmask\jquery.inputmask-0.0.0.js" />
 		<file src="..\dist\min\jquery.inputmask.date.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.date.extensions-0.0.0.min.js" />
         <file src="..\dist\min\jquery.inputmask.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.extensions-0.0.0.min.js" />
         <file src="..\dist\min\jquery.inputmask.numeric.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.numeric.extensions-0.0.0.min.js" />
+		<file src="..\dist\min\jquery.inputmask.regex.extensions.js" target="content\Scripts\jquery.inputmask\jquery.inputmask.regex.extensions-0.0.0.min.js" />
         <file src="..\dist\min\jquery.inputmask.js" target="content\Scripts\jquery.inputmask\jquery.inputmask-0.0.0.min.js" />
     </files>
 </package>

+ 289 - 37
qunit/tests.js

@@ -2,7 +2,39 @@ var keyCodes = {
     ALT: 18, BACKSPACE: 8, CAPS_LOCK: 20, COMMA: 188, COMMAND: 91, COMMAND_LEFT: 91, COMMAND_RIGHT: 93, CONTROL: 17, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, INSERT: 45, LEFT: 37, MENU: 93, NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108,
     NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SHIFT: 16, SPACE: 32, TAB: 9, UP: 38, WINDOWS: 91
 }
-
+function caret(input, begin, end) {
+    var npt = input.jquery && input.length > 0 ? input[0] : input, range;
+    if (typeof begin == 'number') {
+        if (!$(input).is(':visible')) {
+            return;
+        }
+        end = (typeof end == 'number') ? end : begin;
+        if (npt.setSelectionRange) {
+            npt.selectionStart = begin;
+            npt.selectionEnd = end;
+
+        } else if (npt.createTextRange) {
+            range = npt.createTextRange();
+            range.collapse(true);
+            range.moveEnd('character', end);
+            range.moveStart('character', begin);
+            range.select();
+        }
+    } else {
+        if (!$(input).is(':visible')) {
+            return { "begin": 0, "end": 0 };
+        }
+        if (npt.setSelectionRange) {
+            begin = npt.selectionStart;
+            end = npt.selectionEnd;
+        } else if (document.selection && document.selection.createRange) {
+            range = document.selection.createRange();
+            begin = 0 - range.duplicate().moveStart('character', -100000);
+            end = begin + range.text.length;
+        }
+        return { "begin": begin, "end": end };
+    }
+};
 $.fn.SendKey = function (keyCode, modifier) {
     var sendDummyKeydown = false;
     if (Object.prototype.toString.call(keyCode) == '[object String]') {
@@ -10,40 +42,6 @@ $.fn.SendKey = function (keyCode, modifier) {
         sendDummyKeydown = true;
     }
 
-    function caret(input, begin, end) {
-        var npt = input.jquery && input.length > 0 ? input[0] : input, range;
-        if (typeof begin == 'number') {
-            if (!$(input).is(':visible')) {
-                return;
-            }
-            end = (typeof end == 'number') ? end : begin;
-            if (npt.setSelectionRange) {
-                npt.selectionStart = begin;
-                npt.selectionEnd = end;
-
-            } else if (npt.createTextRange) {
-                range = npt.createTextRange();
-                range.collapse(true);
-                range.moveEnd('character', end);
-                range.moveStart('character', begin);
-                range.select();
-            }
-        } else {
-            if (!$(input).is(':visible')) {
-                return { "begin": 0, "end": 0 };
-            }
-            if (npt.setSelectionRange) {
-                begin = npt.selectionStart;
-                end = npt.selectionEnd;
-            } else if (document.selection && document.selection.createRange) {
-                range = document.selection.createRange();
-                begin = 0 - range.duplicate().moveStart('character', -100000);
-                end = begin + range.text.length;
-            }
-            return { "begin": begin, "end": end };
-        }
-    };
-
     switch (keyCode) {
         case keyCodes.LEFT: {
             if (modifier == undefined) {
@@ -253,6 +251,33 @@ test("inputmask(\"*****\")", function () {
     $("#testmask").remove();
 });
 
+module("Non-greedy masks");
+test("inputmask(\"*\", { greedy: false, repeat: \"*\" }) - replace cd with 1", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("*", { greedy: false, repeat: "*" });
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("abcdef");
+    caret($("#testmask"), 2, 4);
+    $("#testmask").SendKey("1");
+    equal($("#testmask").val(), "ab1ef", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"*\", { greedy: false, repeat: \"*\" }) - type abcdef", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("*", { greedy: false, repeat: "*" });
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("abcdef");
+
+    equal($("#testmask").val(), "abcdef", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
 
 module("Initial value setting");
 
@@ -369,7 +394,7 @@ test("inputmask({ mask: [\"999.999.999-99\", \"99.999.999/9999-99\"]}) - input 1
 
     $("#testmask")[0].focus();
     $("#testmask").Type("12312312312");
-    
+
     equal($("#testmask").val(), "123.123.123-12", "Result " + $("#testmask").val());
 
     $("#testmask").remove();
@@ -380,7 +405,7 @@ test("inputmask({ mask: [\"999.999.999-99\", \"99.999.999/9999-99\"]}) - input 1
 
     $("#testmask")[0].focus();
     $("#testmask").Type("12.123123123412");
- 
+
     equal($("#testmask").val(), "12.123.123/1234-12", "Result " + $("#testmask").val());
 
     $("#testmask").remove();
@@ -660,6 +685,45 @@ test("inputmask(\"dd/mm/yyyy\") - input 2331973 BACKSPACE x4 2013", function ()
     $("#testmask").remove();
 });
 
+test("inputmask(\"dd/mm/yyyy\") - input 23373 ", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("dd/mm/yyyy");
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("23373");
+    equal($("#testmask").val(), "23/03/2073", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"dd/mm/yyyy\", { yearrange: { minyear: 1900, maxyear: 2000 } }) - input 23373 ", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("dd/mm/yyyy", { yearrange: { minyear: 1900, maxyear: 2000 } });
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("23373");
+    equal($("#testmask").val(), "23/03/1973", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"hh:mm\") - add remove add", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $('#testmask').inputmask('hh:mm', { clearIncomplete: true });
+    $('#testmask').inputmask('remove');
+    $('#testmask').inputmask('hh:mm', { clearIncomplete: true });
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("abcdef");
+    $("#testmask").Type("23:50");
+   
+    equal($("#testmask").val(), "23:50", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
 module("Numeric.Extensions");
 test("inputmask(\"decimal\", { autoGroup: true, groupSeparator: \",\" }\") - input 12345.123", function () {
     $('body').append('<input type="text" id="testmask" />');
@@ -764,6 +828,96 @@ test("inputmask(\"decimal\", { autoGroup: false, groupSeparator: \",\" }\") - in
     $("#testmask").remove();
 });
 
+test("inputmask(\"decimal\") - maxlength 10", function () {
+    $('body').append('<input type="text" id="testmask" maxlength="10" />');
+    $("#testmask").inputmask("decimal");
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("123456789012345");
+
+    equal($("#testmask").val(), "1234567890", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal, { repeat: 15 }\") - maxlength 10", function () {
+    $('body').append('<input type="text" id="testmask" maxlength="10" />');
+    $("#testmask").inputmask("decimal", { repeat: 15 });
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("123456789012345");
+
+    equal($("#testmask").val(), "1234567890", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal, { repeat: 5 }\") - maxlength 10", function () {
+    $('body').append('<input type="text" id="testmask" maxlength="10" />');
+    $("#testmask").inputmask("decimal", { repeat: 5 });
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("123456789012345");
+
+    equal($("#testmask").val(), "12345", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal\")", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("decimal");
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("1234567890");
+    caret($("#testmask"), 0, 10);
+    $("#testmask").Type("12345");
+
+    equal($("#testmask").val(), "12345", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal\") - value=\"1234567890\"", function () {
+    $('body').append('<input type="text" id="testmask" value="1234567890" />');
+    $("#testmask").inputmask("decimal");
+
+    $("#testmask")[0].focus();
+
+    caret($("#testmask"), 0, 10);
+    $("#testmask").Type("12345");
+
+    equal($("#testmask").val(), "12345", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal\")", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("decimal");
+
+    $("#testmask")[0].focus();
+
+    $("#testmask").Type("1234567890");
+    caret($("#testmask"), 3, 5);
+    $("#testmask").SendKey("0");
+
+    equal($("#testmask").val(), "123067890", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
+test("inputmask(\"decimal\") - value=\"1234567890\"", function () {
+    $('body').append('<input type="text" id="testmask" value="1234567890" />');
+    $("#testmask").inputmask("decimal");
+
+    $("#testmask")[0].focus();
+
+    caret($("#testmask"), 3, 5);
+    $("#testmask").SendKey("0");
+
+    equal($("#testmask").val(), "123067890", "Result " + $("#testmask").val());
+    $("#testmask").remove();
+});
+
 module("Direction RTL");
 test("inputmask(\"999.999.999\") - delete 2nd with backspace, continue the mask", function () {
     $('body').append('<input type="text" id="testmask" dir="rtl" />');
@@ -818,4 +972,102 @@ test("inputmask(\"999-aaa-999\")", function () {
     equal($("#testmask").val(), "_21-cba-321", "Result " + $("#testmask").val());
 
     $("#testmask").remove();
+});
+
+module("Regex masks")
+
+test("inputmask(\"Regex\", { regex: \"[0-9]*\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[0-9]*"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("123abc45");
+
+    equal($("#testmask").val(), "12345", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+test("inputmask(\"Regex\", { regex: \"[A-Za-z\u0410-\u044F\u0401\u04510-9]*\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[A-Za-z\u0410-\u044F\u0401\u04510-9]*"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("123abc45");
+
+    equal($("#testmask").val(), "123abc45", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"Regex\", { regex: \"[A-Za-z\u0410-\u044F\u0401\u0451]+[A-Za-z\u0410-\u044F\u0401\u04510-9]*\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[A-Za-z\u0410-\u044F\u0401\u0451]+[A-Za-z\u0410-\u044F\u0401\u04510-9]*"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("123abc45");
+
+    equal($("#testmask").val(), "abc45", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+
+test("inputmask(\"Regex\", { regex: \"[A-Za-z\u0410-\u044F\u0401\u0451]{1}[A-Za-z\u0410-\u044F\u0401\u04510-9]*\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[A-Za-z\u0410-\u044F\u0401\u0451]{1}[A-Za-z\u0410-\u044F\u0401\u04510-9]*"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("123abc45");
+
+    equal($("#testmask").val(), "abc45", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"Regex\", { regex: \"[-]?(([1-8][0-9])|[1-9]0?)\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[-]?(([1-8][0-9])|[1-9]0?)"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("90");
+
+    equal($("#testmask").val(), "90", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"Regex\", { regex: \"[-]?(([1-8][0-9])|[1-9]0?)\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[-]?(([1-8][0-9])|[1-9]0?)"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("0");
+
+    equal($("#testmask").val(), "", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"Regex\", { regex: \"[-]?(([1-8][0-9])|[1-9]0?)\"});", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[-]?(([1-8][0-9])|[1-9]0?)"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("-78");
+
+    equal($("#testmask").val(), "-78", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
+});
+
+test("inputmask(\"Regex\", { regex: \"[a-zA-Z0-9._%-]+@[a-zA-Z0-9-]+\\.[a-zA-Z]{2,4}\" - regex simple email", function () {
+    $('body').append('<input type="text" id="testmask" />');
+    $("#testmask").inputmask("Regex", { regex: "[a-zA-Z0-9._%-]+@[a-zA-Z0-9-]+\\.[a-zA-Z]{2,4}"});
+
+    $("#testmask")[0].focus();
+    $("#testmask").Type("some.body@mail.com");
+
+    equal($("#testmask").val(), "some.body@mail.com", "Result " + $("#testmask").val());
+
+    $("#testmask").remove();
 });