diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..800127a --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +demo +dist +src +test +.gitignore +.jshintrc +.travis.yml +gulpfile.js \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..9b38ece --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,12 @@ +## 1.0.0 / 2015-03-25 + +Initial release features: + +* Based on [inputmaask-core@1.1.0](https://github.com/insin/inputmask-core/blob/master/CHANGES.md#110--2015-03-25) +* Basic editing works: + * Typing, backspacing, pasting, cutting and deleting + * Invalid content will be ignored if typed or pasted + * Static parts of the mask can't be modfied + * Editing operations can handle text selections + * Tested in latest versions of Firefox, Chrome, Opera and IE +* Placeholder generation and display when the mask has no user input diff --git a/dist/react-maskedinput.js b/dist/react-maskedinput.js index 6644e0e..a389103 100644 --- a/dist/react-maskedinput.js +++ b/dist/react-maskedinput.js @@ -1,5 +1,5 @@ /*! - * react-maskedinput 1.0.0 (dev build at Sun, 22 Mar 2015 03:04:53 GMT) - https://github.com/insin/react-maskedinput + * react-maskedinput 1.0.0 - https://github.com/insin/react-maskedinput * MIT Licensed */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.MaskedInput = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} value + * @return {Array} + */ +Pattern.prototype.formatValue = function format(value) { + var valueBuffer = new Array(this.length) + var valueIndex = 0 + + for (var i = 0, l = this.length; i < l ; i++) { + if (this.isEditableIndex(i)) { + valueBuffer[i] = (value.length > valueIndex && this.isValidAtIndex(value[valueIndex], i) + ? value[valueIndex] + : PLACEHOLDER) + valueIndex++ + } + else { + valueBuffer[i] = this.pattern[i] + // Also allow the value to contain static values from the pattern by + // advancing its index. + if (value.length > valueIndex && value[valueIndex] === this.pattern[i]) { + valueIndex++ + } + } + } + + return valueBuffer +} + +/** + * @param {number} index + * @return {boolean} + */ +Pattern.prototype.isEditableIndex = function isEditableIndex(index) { + return !!this._editableIndices[index] +} + +/** + * @param {string} char + * @param {number} index + * @return {boolean} + */ +Pattern.prototype.isValidAtIndex = function isValidAtIndex(char, index) { + return this.formatCharacters[this.pattern[index]].validate(char) +} + +function InputMask(options) { + if (!(this instanceof InputMask)) { return new InputMask(options) } + + options = extend({ + pattern: null, + selection: {start: 0, end: 0}, + value: '' + }, options) + + if (options.pattern == null) { + throw new Error('InputMask: you must provide a pattern.') + } + + this.setPattern(options.pattern, options.value) + this.setSelection(options.selection) +} + +/** + * Applies a single character of input based on the current selection. + * @param {string} char + * @return {boolean} true if a change has been made to value or selection as a + * result of the input, false otherwise. + */ +InputMask.prototype.input = function input(char) { + // Ignore additional input if the cursor's at the end of the pattern + if (this.selection.start === this.selection.end && + this.selection.start === this.pattern.length) { + return false + } + + var inputIndex = this.selection.start + + // If a range of characters was selected and it includes the first editable + // character, make sure any input given is applied to it. + if (this.selection.start !== this.selection.end && + this.selection.start < this.pattern.firstEditableIndex && + this.selection.end > this.pattern.firstEditableIndex) { + inputIndex = this.pattern.firstEditableIndex + } + + // Bail out or add the character to input + if (this.pattern.isEditableIndex(inputIndex)) { + if (!this.pattern.isValidAtIndex(char, inputIndex)) { + return false + } + this.value[inputIndex] = char + } + + // If multiple characters were selected, blank the remainder out based on the + // pattern. + var end = this.selection.end - 1 + while (end > inputIndex) { + if (this.pattern.isEditableIndex(end)) { + this.value[end] = PLACEHOLDER + } + end-- + } + + // Advance the cursor to the next character + this.selection.start = this.selection.end = inputIndex + 1 + + // Skip over any subsequent static characters + while (this.pattern.length > this.selection.start && + !this.pattern.isEditableIndex(this.selection.start)) { + this.selection.start++ + this.selection.end++ + } + + return true +} + +/** + * Attempts to delete from the value based on the current cursor position or + * selection. + * @return {boolean} true if the value or selection changed as the result of + * backspacing, false otherwise. + */ +InputMask.prototype.backspace = function backspace() { + // If the cursor is at the start there's nothing to do + if (this.selection.start === 0 && this.selection.end === 0) { + return false + } + + var format + + // No range selected - work on the character preceding the cursor + if (this.selection.start === this.selection.end) { + if (this.pattern.isEditableIndex(this.selection.start - 1)) { + this.value[this.selection.start - 1] = PLACEHOLDER + } + this.selection.start-- + this.selection.end-- + } + // Range selected - delete characters and leave the cursor at the start of the selection + else { + var end = this.selection.end - 1 + while (end >= this.selection.start) { + if (this.pattern.isEditableIndex(end)) { + this.value[end] = PLACEHOLDER + } + end-- + } + this.selection.end = this.selection.start + } + + return true +} + +/** + * Attempts to paste a string of input at the current cursor position or over + * the top of the current selection. + * Invalid content at any position will cause the paste to be rejected, and it + * may contain static parts of the mask's pattern. + * @param {string} input + * @return {boolean} true if the paste was successful, false otherwise. + */ +InputMask.prototype.paste = function paste(input) { + var initialValue = this.value.slice() + var initialSelection = copy(this.selection) + + // If there are static characters at the start of the pattern and the cursor + // or selection is within them, the static characters must match for a valid + // paste. + if (this.selection.start < this.pattern.firstEditableIndex) { + for (var i = 0, l = this.pattern.firstEditableIndex - this.selection.start; i < l; i++) { + if (input.charAt(i) !== this.pattern.pattern[i]) { + return false + } + } + + // Continue as if the selection and input started from the editable part of + // the pattern. + input = input.substring(this.pattern.firstEditableIndex - this.selection.start) + this.selection.start = this.pattern.firstEditableIndex + } + + for (var i = 0, l = input.length; + i < l && this.selection.start <= this.pattern.lastEditableIndex; + i++) { + var valid = this.input(input.charAt(i)) + // Allow static parts of the pattern to appear in pasted input - they will + // already have been stepped over by input(), so verify that the value + // deemed invalid by input() was the expected static character. + if (!valid) { + if (this.selection.start > 0) { + // XXX This only allows for one static character to be skipped + var patternIndex = this.selection.start - 1 + if (!this.pattern.isEditableIndex(patternIndex) && + input.charAt(i) === this.pattern.pattern[patternIndex]) { + continue + } + } + this.value = initialValue + this.selection = initialSelection + return false + } + } + return true +} + +InputMask.prototype.setPattern = function setPattern(pattern, value) { + this.pattern = new Pattern(pattern) + this.setValue(value || '') + this.emptyValue = this.pattern.formatValue([]).join('') +} + +InputMask.prototype.setSelection = function setSelection(selection) { + this.selection = copy(selection) + if (this.selection.start === this.selection.end) { + if (this.selection.start < this.pattern.firstEditableIndex) { + this.selection.start = this.selection.end = this.pattern.firstEditableIndex + return true + } + if (this.selection.end > this.pattern.lastEditableIndex + 1) { + this.selection.start = this.selection.end = this.pattern.lastEditableIndex + 1 + return true + } + } + return false +} + +InputMask.prototype.setValue = function setValue(value) { + this.value = this.pattern.formatValue(value.split('')) +} + +InputMask.prototype.getValue = function getValue() { + return this.value.join('') +} + +InputMask.Pattern = Pattern + +module.exports = InputMask +},{}],3:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -163,7 +501,7 @@ var ExecutionEnvironment = { module.exports = ExecutionEnvironment; -},{}],3:[function(require,module,exports){ +},{}],4:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -376,7 +714,7 @@ var ReactDOMSelection = { module.exports = ReactDOMSelection; -},{"./ExecutionEnvironment":2,"./getNodeForCharacterOffset":8,"./getTextContentAccessor":9}],4:[function(require,module,exports){ +},{"./ExecutionEnvironment":3,"./getNodeForCharacterOffset":9,"./getTextContentAccessor":10}],5:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -511,7 +849,7 @@ var ReactInputSelection = { module.exports = ReactInputSelection; -},{"./ReactDOMSelection":3,"./containsNode":5,"./focusNode":6,"./getActiveElement":7}],5:[function(require,module,exports){ +},{"./ReactDOMSelection":4,"./containsNode":6,"./focusNode":7,"./getActiveElement":8}],6:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -555,7 +893,7 @@ function containsNode(outerNode, innerNode) { module.exports = containsNode; -},{"./isTextNode":11}],6:[function(require,module,exports){ +},{"./isTextNode":12}],7:[function(require,module,exports){ /** * Copyright 2014-2015, Facebook, Inc. * All rights reserved. @@ -584,7 +922,7 @@ function focusNode(node) { module.exports = focusNode; -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -613,7 +951,7 @@ function getActiveElement() /*?DOMElement*/ { module.exports = getActiveElement; -},{}],8:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -688,7 +1026,7 @@ function getNodeForCharacterOffset(root, offset) { module.exports = getNodeForCharacterOffset; -},{}],9:[function(require,module,exports){ +},{}],10:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -725,7 +1063,7 @@ function getTextContentAccessor() { module.exports = getTextContentAccessor; -},{"./ExecutionEnvironment":2}],10:[function(require,module,exports){ +},{"./ExecutionEnvironment":3}],11:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -752,7 +1090,7 @@ function isNode(object) { module.exports = isNode; -},{}],11:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. @@ -777,5 +1115,5 @@ function isTextNode(object) { module.exports = isTextNode; -},{"./isNode":10}]},{},[1])(1) +},{"./isNode":11}]},{},[1])(1) }); \ No newline at end of file diff --git a/dist/react-maskedinput.min.js b/dist/react-maskedinput.min.js new file mode 100644 index 0000000..2e8ae46 --- /dev/null +++ b/dist/react-maskedinput.min.js @@ -0,0 +1,5 @@ +/*! + * react-maskedinput 1.0.0 - https://github.com/insin/react-maskedinput + * MIT Licensed + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.MaskedInput=t()}}(function(){return function t(e,n,i){function s(a,o){if(!n[a]){if(!e[a]){var c="function"==typeof require&&require;if(!o&&c)return c(a,!0);if(r)return r(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return s(n?n:t)},u,u.exports,t,e,n,i)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;ai;i++)t[n[i]]=e[n[i]];return t}function i(t){return n({},t)}function s(t){return this instanceof s?(this.formatCharacters=d,this.source=t,this.pattern=[],this.length=0,this.firstEditableIndex=null,this.lastEditableIndex=null,this._editableIndices={},void this._parse()):new s(t)}function r(t){if(!(this instanceof r))return new r(t);if(t=n({pattern:null,selection:{start:0,end:0},value:""},t),null==t.pattern)throw new Error("InputMask: you must provide a pattern.");this.setPattern(t.pattern,t.value),this.setSelection(t.selection)}var a="_",o="\\",c=/^\d$/,l=/^[A-Za-z]$/,u=/^[\dA-Za-z]$/,d={"*":{validate:function(t){return u.test(t)}},1:{validate:function(t){return c.test(t)}},A:{validate:function(t){return l.test(t)}}};s.prototype._parse=function(){for(var t=this.source.split(""),e=0,n=[],i=0,s=t.length;s>i;i++){var r=t[i];if(r===o){if(i===s-1)throw new Error("InputMask: pattern ends with a raw "+o);r=t[++i]}else r in this.formatCharacters&&(null===this.firstEditableIndex&&(this.firstEditableIndex=e),this.lastEditableIndex=e,this._editableIndices[e]=!0);n.push(r),e++}if(null===this.firstEditableIndex)throw new Error('InputMask: pattern "'+this.source+'" does not contain any editable characters.');this.pattern=n,this.length=n.length},s.prototype.formatValue=function(t){for(var e=new Array(this.length),n=0,i=0,s=this.length;s>i;i++)this.isEditableIndex(i)?(e[i]=t.length>n&&this.isValidAtIndex(t[n],i)?t[n]:a,n++):(e[i]=this.pattern[i],t.length>n&&t[n]===this.pattern[i]&&n++);return e},s.prototype.isEditableIndex=function(t){return!!this._editableIndices[t]},s.prototype.isValidAtIndex=function(t,e){return this.formatCharacters[this.pattern[e]].validate(t)},r.prototype.input=function(t){if(this.selection.start===this.selection.end&&this.selection.start===this.pattern.length)return!1;var e=this.selection.start;if(this.selection.start!==this.selection.end&&this.selection.startthis.pattern.firstEditableIndex&&(e=this.pattern.firstEditableIndex),this.pattern.isEditableIndex(e)){if(!this.pattern.isValidAtIndex(t,e))return!1;this.value[e]=t}for(var n=this.selection.end-1;n>e;)this.pattern.isEditableIndex(n)&&(this.value[n]=a),n--;for(this.selection.start=this.selection.end=e+1;this.pattern.length>this.selection.start&&!this.pattern.isEditableIndex(this.selection.start);)this.selection.start++,this.selection.end++;return!0},r.prototype.backspace=function(){if(0===this.selection.start&&0===this.selection.end)return!1;if(this.selection.start===this.selection.end)this.pattern.isEditableIndex(this.selection.start-1)&&(this.value[this.selection.start-1]=a),this.selection.start--,this.selection.end--;else{for(var t=this.selection.end-1;t>=this.selection.start;)this.pattern.isEditableIndex(t)&&(this.value[t]=a),t--;this.selection.end=this.selection.start}return!0},r.prototype.paste=function(t){var e=this.value.slice(),n=i(this.selection);if(this.selection.starts;s++)if(t.charAt(s)!==this.pattern.pattern[s])return!1;t=t.substring(this.pattern.firstEditableIndex-this.selection.start),this.selection.start=this.pattern.firstEditableIndex}for(var s=0,r=t.length;r>s&&this.selection.start<=this.pattern.lastEditableIndex;s++){var a=this.input(t.charAt(s));if(!a){if(this.selection.start>0){var o=this.selection.start-1;if(!this.pattern.isEditableIndex(o)&&t.charAt(s)===this.pattern.pattern[o])continue}return this.value=e,this.selection=n,!1}}return!0},r.prototype.setPattern=function(t,e){this.pattern=new s(t),this.setValue(e||""),this.emptyValue=this.pattern.formatValue([]).join("")},r.prototype.setSelection=function(t){if(this.selection=i(t),this.selection.start===this.selection.end){if(this.selection.startthis.pattern.lastEditableIndex+1)return this.selection.start=this.selection.end=this.pattern.lastEditableIndex+1,!0}return!1},r.prototype.setValue=function(t){this.value=this.pattern.formatValue(t.split(""))},r.prototype.getValue=function(){return this.value.join("")},r.Pattern=s,e.exports=r},{}],3:[function(t,e){"use strict";var n=!("undefined"==typeof window||!window.document||!window.document.createElement),i={canUseDOM:n,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:n&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:n&&!!window.screen,isInWorker:!n};e.exports=i},{}],4:[function(t,e){"use strict";function n(t,e,n,i){return t===n&&e===i}function i(t){var e=document.selection,n=e.createRange(),i=n.text.length,s=n.duplicate();s.moveToElementText(t),s.setEndPoint("EndToStart",n);var r=s.text.length,a=r+i;return{start:r,end:a}}function s(t){var e=window.getSelection&&window.getSelection();if(!e||0===e.rangeCount)return null;var i=e.anchorNode,s=e.anchorOffset,r=e.focusNode,a=e.focusOffset,o=e.getRangeAt(0),c=n(e.anchorNode,e.anchorOffset,e.focusNode,e.focusOffset),l=c?0:o.toString().length,u=o.cloneRange();u.selectNodeContents(t),u.setEnd(o.startContainer,o.startOffset);var d=n(u.startContainer,u.startOffset,u.endContainer,u.endOffset),h=d?0:u.toString().length,f=h+l,p=document.createRange();p.setStart(i,s),p.setEnd(r,a);var v=p.collapsed;return{start:v?f:h,end:v?h:f}}function r(t,e){var n,i,s=document.selection.createRange().duplicate();"undefined"==typeof e.end?(n=e.start,i=n):e.start>e.end?(n=e.end,i=e.start):(n=e.start,i=e.end),s.moveToElementText(t),s.moveStart("character",n),s.setEndPoint("EndToStart",s),s.moveEnd("character",i-n),s.select()}function a(t,e){if(window.getSelection){var n=window.getSelection(),i=t[l()].length,s=Math.min(e.start,i),r="undefined"==typeof e.end?s:Math.min(e.end,i);if(!n.extend&&s>r){var a=r;r=s,s=a}var o=c(t,s),u=c(t,r);if(o&&u){var d=document.createRange();d.setStart(o.node,o.offset),n.removeAllRanges(),s>r?(n.addRange(d),n.extend(u.node,u.offset)):(d.setEnd(u.node,u.offset),n.addRange(d))}}}var o=t("./ExecutionEnvironment"),c=t("./getNodeForCharacterOffset"),l=t("./getTextContentAccessor"),u=o.canUseDOM&&"selection"in document&&!("getSelection"in window),d={getOffsets:u?i:s,setOffsets:u?r:a};e.exports=d},{"./ExecutionEnvironment":3,"./getNodeForCharacterOffset":9,"./getTextContentAccessor":10}],5:[function(t,e){"use strict";function n(t){return s(document.documentElement,t)}var i=t("./ReactDOMSelection"),s=t("./containsNode"),r=t("./focusNode"),a=t("./getActiveElement"),o={hasSelectionCapabilities:function(t){return t&&("INPUT"===t.nodeName&&"text"===t.type||"TEXTAREA"===t.nodeName||"true"===t.contentEditable)},getSelectionInformation:function(){var t=a();return{focusedElem:t,selectionRange:o.hasSelectionCapabilities(t)?o.getSelection(t):null}},restoreSelection:function(t){var e=a(),i=t.focusedElem,s=t.selectionRange;e!==i&&n(i)&&(o.hasSelectionCapabilities(i)&&o.setSelection(i,s),r(i))},getSelection:function(t){var e;if("selectionStart"in t)e={start:t.selectionStart,end:t.selectionEnd};else if(document.selection&&"INPUT"===t.nodeName){var n=document.selection.createRange();n.parentElement()===t&&(e={start:-n.moveStart("character",-t.value.length),end:-n.moveEnd("character",-t.value.length)})}else e=i.getOffsets(t);return e||{start:0,end:0}},setSelection:function(t,e){var n=e.start,s=e.end;if("undefined"==typeof s&&(s=n),"selectionStart"in t)t.selectionStart=n,t.selectionEnd=Math.min(s,t.value.length);else if(document.selection&&"INPUT"===t.nodeName){var r=t.createTextRange();r.collapse(!0),r.moveStart("character",n),r.moveEnd("character",s-n),r.select()}else i.setOffsets(t,e)}};e.exports=o},{"./ReactDOMSelection":4,"./containsNode":6,"./focusNode":7,"./getActiveElement":8}],6:[function(t,e){function n(t,e){return t&&e?t===e?!0:i(t)?!1:i(e)?n(t,e.parentNode):t.contains?t.contains(e):t.compareDocumentPosition?!!(16&t.compareDocumentPosition(e)):!1:!1}var i=t("./isTextNode");e.exports=n},{"./isTextNode":12}],7:[function(t,e){"use strict";function n(t){try{t.focus()}catch(e){}}e.exports=n},{}],8:[function(t,e){function n(){try{return document.activeElement||document.body}catch(t){return document.body}}e.exports=n},{}],9:[function(t,e){"use strict";function n(t){for(;t&&t.firstChild;)t=t.firstChild;return t}function i(t){for(;t;){if(t.nextSibling)return t.nextSibling;t=t.parentNode}}function s(t,e){for(var s=n(t),r=0,a=0;s;){if(3===s.nodeType){if(a=r+s.textContent.length,e>=r&&a>=e)return{node:s,offset:e-r};r=a}s=n(i(s))}}e.exports=s},{}],10:[function(t,e){"use strict";function n(){return!s&&i.canUseDOM&&(s="textContent"in document.documentElement?"textContent":"innerText"),s}var i=t("./ExecutionEnvironment"),s=null;e.exports=n},{"./ExecutionEnvironment":3}],11:[function(t,e){function n(t){return!(!t||!("function"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName))}e.exports=n},{}],12:[function(t,e){function n(t){return i(t)&&3==t.nodeType}var i=t("./isNode");e.exports=n},{"./isNode":11}]},{},[1])(1)}); \ No newline at end of file diff --git a/package.json b/package.json index 9ee67a4..17d6467 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "inputmask-core": "^1.1.0" }, "peerDependencies": { - "react": ">=0.13.0" + "react": ">=0.12.0" }, "devDependencies": { "browserify": "^9.0.3",