diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..acede28
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,13 @@
+{
+ "extends": ["standard", "standard-react"],
+ "rules": {
+ "brace-style": [2, "stroustrup", {"allowSingleLine": true}],
+ "eqeqeq": [2, "smart"],
+ "jsx-quotes": [2, "prefer-double"],
+ "react/prop-types": 0,
+ "react/self-closing-comp": 0,
+ "react/wrap-multilines": 0,
+ "space-before-function-paren": 0
+ },
+ "parser": "babel-eslint"
+}
diff --git a/.gitignore b/.gitignore
index c2658d7..4994ab3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,7 @@
-node_modules/
+/coverage
+/demo/dist
+/es6
+/lib
+/node_modules
+/umd
+npm-debug.log
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 3ccf497..0000000
--- a/.jshintrc
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "browser": true,
- "node": true,
-
- "curly": true,
- "devel": true,
- "globals": {
- },
- "noempty": true,
- "newcap": false,
- "undef": true,
- "unused": "vars",
-
- "asi": true,
- "boss": true,
- "eqnull": true,
- "expr": true,
- "funcscope": true,
- "globalstrict": true,
- "laxbreak": true,
- "laxcomma": true,
- "loopfunc": true,
- "sub": true
-}
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 564d394..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-demo
-src
-test
-.gitignore
-.jshintrc
-.travis.yml
-gulpfile.js
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..5c036ab
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - "4"
+ - "5"
+script: npm test
diff --git a/CHANGES.md b/CHANGES.md
index 0b067f7..fa2ef21 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,25 @@
+## 3.2.0 / 2016-05-24
+
+* Allow dynamic pattern updating [[martyphee][martyphee]]
+
+## 3.1.3 / 2016-05-02
+
+* Don’t call `onChange` function if undefined.
+* Update nwb to 0.9.x
+s
+## 3.1.2 / 2016-04-11
+
+* Support for React 15.x.x
+
+## 3.1.1 / 2016-03-09
+
+* Convert tooling to use [nwb](https://github.com/insin/nwb/) [[bpugh]][[bpugh]]
+* Publish `dist` files
+
+## 3.1.0 / 2016-02-11
+
+* Added support for `value` behaving as a controlled component.
+
## 3.0.0 / 2015-10-23
**Breaking change:** Now uses a `mask` prop to define the input mask instead of `pattern`, to avoid preventing use of the the HTML5 `pattern` attribute in conjunction with the input mask.
@@ -42,3 +64,4 @@ Initial release features:
[jquense]: https://github.com/jquense
[muffinresearch]: https://github.com/muffinresearch
+[martyphee]: https://github.com/martyphee
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..bc86b50
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,22 @@
+## Prerequisites
+
+[Node.js](http://nodejs.org/) must be installed.
+
+## Installation
+
+* Running `npm install` in the components's root directory will install everything you need for development.
+
+## Demo Development Server
+
+* `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading.
+
+## Running Tests
+
+* `npm test` will run the tests once.
+* `npm run test:watch` will run the tests on every change.
+
+## Building
+
+* `npm run build` will build the component for publishing to npm and also bundle the demo app.
+
+* `npm run clean` will delete built resources.
diff --git a/README.md b/README.md
index dec5b9c..a11b6dc 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
A [React](http://facebook.github.io/react/) component for `` masking, built on top of [inputmask-core](https://github.com/insin/inputmask-core).
+
+
## [Live Demo](http://insin.github.io/react-maskedinput/)
## Install
@@ -18,7 +20,8 @@ npm install react-maskedinput --save
The browser bundle exposes a global `MaskedInput` variable and expects to find a global `React` (>= 0.14.0) variable to work with.
-You can find it in the [/dist directory](https://github.com/insin/react-maskedinput/tree/master/dist).
+* [react-maskedinput.js](https://npmcdn.com/react-maskedinput/umd/react-maskedinput.js) (development version)
+* [react-maskedinput.min.js](https://npmcdn.com/react-maskedinput/umd/react-maskedinput.min.js) (compressed production version)
## Usage
diff --git a/demo/index.html b/demo/src/index.js
similarity index 66%
rename from demo/index.html
rename to demo/src/index.js
index 0a9d2e8..024e4b8 100644
--- a/demo/index.html
+++ b/demo/src/index.js
@@ -1,67 +1,18 @@
-
-
-
- react-maskedinput Demo
-
-
-
-
-
-
-
-
-
-
+render(, document.getElementById('demo'))
diff --git a/demo/src/style.css b/demo/src/style.css
new file mode 100644
index 0000000..0c54f0c
--- /dev/null
+++ b/demo/src/style.css
@@ -0,0 +1,41 @@
+body {
+ box-sizing: border-box;
+ width: 550px;
+ margin: 1em auto;
+ padding: 0 1em;
+ font-family: sans-serif;
+}
+code {
+ font-size: 1.3em;
+}
+h1 {
+ font-size: 3em;
+ text-align: center;
+ margin-top: 0;
+}
+p.lead {
+ font-weight: bold;
+ text-align: center;
+}
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #222;
+}
+.form-field {
+ margin-bottom: .5em;
+}
+label {
+ display: inline-block;
+ width: 7em;
+ text-align: right;
+ margin-right: .75em;
+}
+input {
+ border: none;
+ font-size: 1.5em;
+}
+footer {
+ text-align: center;
+}
diff --git a/dist/react-maskedinput.js b/dist/react-maskedinput.js
deleted file mode 100644
index 240ee95..0000000
--- a/dist/react-maskedinput.js
+++ /dev/null
@@ -1,1340 +0,0 @@
-/*!
- * react-maskedinput 3.0.0 (dev build at Tue, 27 Oct 2015 15:14:09 GMT) - 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)
- ? this.transform(value[valueIndex], i)
- : this.placeholderChar)
- 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)
-}
-
-Pattern.prototype.transform = function transform(char, index) {
- var format = this.formatCharacters[this.pattern[index]]
- return typeof format.transform == 'function' ? format.transform(char) : char
-}
-
-function InputMask(options) {
- if (!(this instanceof InputMask)) { return new InputMask(options) }
-
- options = extend({
- formatCharacters: null,
- pattern: null,
- placeholderChar: DEFAULT_PLACEHOLDER_CHAR,
- selection: {start: 0, end: 0},
- value: ''
- }, options)
-
- if (options.pattern == null) {
- throw new Error('InputMask: you must provide a pattern.')
- }
-
- if (options.placeholderChar.length !== 1) {
- throw new Error('InputMask: placeholderChar should be a single character.')
- }
-
- this.placeholderChar = options.placeholderChar
- this.formatCharacters = mergeFormatCharacters(options.formatCharacters)
- this.setPattern(options.pattern, {
- value: options.value,
- selection: options.selection
- })
-}
-
-// Editing
-
-/**
- * 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 selectionBefore = copy(this.selection)
- var valueBefore = this.getValue()
-
- var inputIndex = this.selection.start
-
- // If the cursor or selection is prior to the first editable character, make
- // sure any input given is applied to it.
- if (inputIndex < 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] = this.pattern.transform(char, inputIndex)
- }
-
- // 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] = this.placeholderChar
- }
- 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++
- }
-
- // History
- if (this._historyIndex != null) {
- // Took more input after undoing, so blow any subsequent history away
- console.log('splice(', this._historyIndex, this._history.length - this._historyIndex, ')')
- this._history.splice(this._historyIndex, this._history.length - this._historyIndex)
- this._historyIndex = null
- }
- if (this._lastOp !== 'input' ||
- selectionBefore.start !== selectionBefore.end ||
- this._lastSelection !== null && selectionBefore.start !== this._lastSelection.start) {
- this._history.push({value: valueBefore, selection: selectionBefore, lastOp: this._lastOp})
- }
- this._lastOp = 'input'
- this._lastSelection = copy(this.selection)
-
- 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 selectionBefore = copy(this.selection)
- var valueBefore = this.getValue()
-
- // 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] = this.placeholderChar
- }
- 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] = this.placeholderChar
- }
- end--
- }
- this.selection.end = this.selection.start
- }
-
- // History
- if (this._historyIndex != null) {
- // Took more input after undoing, so blow any subsequent history away
- this._history.splice(this._historyIndex, this._history.length - this._historyIndex)
- }
- if (this._lastOp !== 'backspace' ||
- selectionBefore.start !== selectionBefore.end ||
- this._lastSelection !== null && selectionBefore.start !== this._lastSelection.start) {
- this._history.push({value: valueBefore, selection: selectionBefore, lastOp: this._lastOp})
- }
- this._lastOp = 'backspace'
- this._lastSelection = copy(this.selection)
-
- 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) {
- // This is necessary because we're just calling input() with each character
- // and rolling back if any were invalid, rather than checking up-front.
- var initialState = {
- value: this.value.slice(),
- selection: copy(this.selection),
- _lastOp: this._lastOp,
- _history: this._history.slice(),
- _historyIndex: this._historyIndex,
- _lastSelection: copy(this._lastSelection)
- }
-
- // 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 (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
- }
- }
- extend(this, initialState)
- return false
- }
- }
-
- return true
-}
-
-// History
-
-InputMask.prototype.undo = function undo() {
- // If there is no history, or nothing more on the history stack, we can't undo
- if (this._history.length === 0 || this._historyIndex === 0) {
- return false
- }
-
- var historyItem
- if (this._historyIndex == null) {
- // Not currently undoing, set up the initial history index
- this._historyIndex = this._history.length - 1
- historyItem = this._history[this._historyIndex]
- // Add a new history entry if anything has changed since the last one, so we
- // can redo back to the initial state we started undoing from.
- var value = this.getValue()
- if (historyItem.value !== value ||
- historyItem.selection.start !== this.selection.start ||
- historyItem.selection.end !== this.selection.end) {
- this._history.push({value: value, selection: copy(this.selection), lastOp: this._lastOp, startUndo: true})
- }
- }
- else {
- historyItem = this._history[--this._historyIndex]
- }
-
- this.value = historyItem.value.split('')
- this.selection = historyItem.selection
- this._lastOp = historyItem.lastOp
- return true
-}
-
-InputMask.prototype.redo = function redo() {
- if (this._history.length === 0 || this._historyIndex == null) {
- return false
- }
- var historyItem = this._history[++this._historyIndex]
- // If this is the last history item, we're done redoing
- if (this._historyIndex === this._history.length - 1) {
- this._historyIndex = null
- // If the last history item was only added to start undoing, remove it
- if (historyItem.startUndo) {
- this._history.pop()
- }
- }
- this.value = historyItem.value.split('')
- this.selection = historyItem.selection
- this._lastOp = historyItem.lastOp
- return true
-}
-
-// Getters & setters
-
-InputMask.prototype.setPattern = function setPattern(pattern, options) {
- options = extend({
- selection: {start: 0, end: 0},
- value: ''
- }, options)
- this.pattern = new Pattern(pattern, this.formatCharacters, this.placeholderChar)
- this.setValue(options.value)
- this.emptyValue = this.pattern.formatValue([]).join('')
- this.selection = options.selection
- this._resetHistory()
-}
-
-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) {
- if (value == null) {
- value = ''
- }
- this.value = this.pattern.formatValue(value.split(''))
-}
-
-InputMask.prototype.getValue = function getValue() {
- return this.value.join('')
-}
-
-InputMask.prototype.getRawValue = function getRawValue() {
- var rawValue = []
- for (var i = 0; i < this.value.length; i++) {
- if (this.pattern._editableIndices[i] === true) {
- rawValue.push(this.value[i])
- }
- }
- return rawValue.join('')
-}
-
-InputMask.prototype._resetHistory = function _resetHistory() {
- this._history = []
- this._historyIndex = null
- this._lastOp = null
- this._lastSelection = copy(this.selection)
-}
-
-InputMask.Pattern = Pattern
-
-module.exports = InputMask
-
-},{}],3:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactDOMSelection
- */
-
-'use strict';
-
-var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
-
-var getNodeForCharacterOffset = require('./getNodeForCharacterOffset');
-var getTextContentAccessor = require('./getTextContentAccessor');
-
-/**
- * While `isCollapsed` is available on the Selection object and `collapsed`
- * is available on the Range object, IE11 sometimes gets them wrong.
- * If the anchor/focus nodes and offsets are the same, the range is collapsed.
- */
-function isCollapsed(anchorNode, anchorOffset, focusNode, focusOffset) {
- return anchorNode === focusNode && anchorOffset === focusOffset;
-}
-
-/**
- * Get the appropriate anchor and focus node/offset pairs for IE.
- *
- * The catch here is that IE's selection API doesn't provide information
- * about whether the selection is forward or backward, so we have to
- * behave as though it's always forward.
- *
- * IE text differs from modern selection in that it behaves as though
- * block elements end with a new line. This means character offsets will
- * differ between the two APIs.
- *
- * @param {DOMElement} node
- * @return {object}
- */
-function getIEOffsets(node) {
- var selection = document.selection;
- var selectedRange = selection.createRange();
- var selectedLength = selectedRange.text.length;
-
- // Duplicate selection so we can move range without breaking user selection.
- var fromStart = selectedRange.duplicate();
- fromStart.moveToElementText(node);
- fromStart.setEndPoint('EndToStart', selectedRange);
-
- var startOffset = fromStart.text.length;
- var endOffset = startOffset + selectedLength;
-
- return {
- start: startOffset,
- end: endOffset
- };
-}
-
-/**
- * @param {DOMElement} node
- * @return {?object}
- */
-function getModernOffsets(node) {
- var selection = window.getSelection && window.getSelection();
-
- if (!selection || selection.rangeCount === 0) {
- return null;
- }
-
- var anchorNode = selection.anchorNode;
- var anchorOffset = selection.anchorOffset;
- var focusNode = selection.focusNode;
- var focusOffset = selection.focusOffset;
-
- var currentRange = selection.getRangeAt(0);
-
- // In Firefox, range.startContainer and range.endContainer can be "anonymous
- // divs", e.g. the up/down buttons on an . Anonymous
- // divs do not seem to expose properties, triggering a "Permission denied
- // error" if any of its properties are accessed. The only seemingly possible
- // way to avoid erroring is to access a property that typically works for
- // non-anonymous divs and catch any error that may otherwise arise. See
- // https://bugzilla.mozilla.org/show_bug.cgi?id=208427
- try {
- /* eslint-disable no-unused-expressions */
- currentRange.startContainer.nodeType;
- currentRange.endContainer.nodeType;
- /* eslint-enable no-unused-expressions */
- } catch (e) {
- return null;
- }
-
- // If the node and offset values are the same, the selection is collapsed.
- // `Selection.isCollapsed` is available natively, but IE sometimes gets
- // this value wrong.
- var isSelectionCollapsed = isCollapsed(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset);
-
- var rangeLength = isSelectionCollapsed ? 0 : currentRange.toString().length;
-
- var tempRange = currentRange.cloneRange();
- tempRange.selectNodeContents(node);
- tempRange.setEnd(currentRange.startContainer, currentRange.startOffset);
-
- var isTempRangeCollapsed = isCollapsed(tempRange.startContainer, tempRange.startOffset, tempRange.endContainer, tempRange.endOffset);
-
- var start = isTempRangeCollapsed ? 0 : tempRange.toString().length;
- var end = start + rangeLength;
-
- // Detect whether the selection is backward.
- var detectionRange = document.createRange();
- detectionRange.setStart(anchorNode, anchorOffset);
- detectionRange.setEnd(focusNode, focusOffset);
- var isBackward = detectionRange.collapsed;
-
- return {
- start: isBackward ? end : start,
- end: isBackward ? start : end
- };
-}
-
-/**
- * @param {DOMElement|DOMTextNode} node
- * @param {object} offsets
- */
-function setIEOffsets(node, offsets) {
- var range = document.selection.createRange().duplicate();
- var start, end;
-
- if (typeof offsets.end === 'undefined') {
- start = offsets.start;
- end = start;
- } else if (offsets.start > offsets.end) {
- start = offsets.end;
- end = offsets.start;
- } else {
- start = offsets.start;
- end = offsets.end;
- }
-
- range.moveToElementText(node);
- range.moveStart('character', start);
- range.setEndPoint('EndToStart', range);
- range.moveEnd('character', end - start);
- range.select();
-}
-
-/**
- * In modern non-IE browsers, we can support both forward and backward
- * selections.
- *
- * Note: IE10+ supports the Selection object, but it does not support
- * the `extend` method, which means that even in modern IE, it's not possible
- * to programatically create a backward selection. Thus, for all IE
- * versions, we use the old IE API to create our selections.
- *
- * @param {DOMElement|DOMTextNode} node
- * @param {object} offsets
- */
-function setModernOffsets(node, offsets) {
- if (!window.getSelection) {
- return;
- }
-
- var selection = window.getSelection();
- var length = node[getTextContentAccessor()].length;
- var start = Math.min(offsets.start, length);
- var end = typeof offsets.end === 'undefined' ? start : Math.min(offsets.end, length);
-
- // IE 11 uses modern selection, but doesn't support the extend method.
- // Flip backward selections, so we can set with a single range.
- if (!selection.extend && start > end) {
- var temp = end;
- end = start;
- start = temp;
- }
-
- var startMarker = getNodeForCharacterOffset(node, start);
- var endMarker = getNodeForCharacterOffset(node, end);
-
- if (startMarker && endMarker) {
- var range = document.createRange();
- range.setStart(startMarker.node, startMarker.offset);
- selection.removeAllRanges();
-
- if (start > end) {
- selection.addRange(range);
- selection.extend(endMarker.node, endMarker.offset);
- } else {
- range.setEnd(endMarker.node, endMarker.offset);
- selection.addRange(range);
- }
- }
-}
-
-var useIEOffsets = ExecutionEnvironment.canUseDOM && 'selection' in document && !('getSelection' in window);
-
-var ReactDOMSelection = {
- /**
- * @param {DOMElement} node
- */
- getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets,
-
- /**
- * @param {DOMElement|DOMTextNode} node
- * @param {object} offsets
- */
- setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets
-};
-
-module.exports = ReactDOMSelection;
-},{"./getNodeForCharacterOffset":5,"./getTextContentAccessor":6,"fbjs/lib/ExecutionEnvironment":7}],4:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactInputSelection
- */
-
-'use strict';
-
-var ReactDOMSelection = require('./ReactDOMSelection');
-
-var containsNode = require('fbjs/lib/containsNode');
-var focusNode = require('fbjs/lib/focusNode');
-var getActiveElement = require('fbjs/lib/getActiveElement');
-
-function isInDocument(node) {
- return containsNode(document.documentElement, node);
-}
-
-/**
- * @ReactInputSelection: React input selection module. Based on Selection.js,
- * but modified to be suitable for react and has a couple of bug fixes (doesn't
- * assume buttons have range selections allowed).
- * Input selection module for React.
- */
-var ReactInputSelection = {
-
- hasSelectionCapabilities: function (elem) {
- var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
- return nodeName && (nodeName === 'input' && elem.type === 'text' || nodeName === 'textarea' || elem.contentEditable === 'true');
- },
-
- getSelectionInformation: function () {
- var focusedElem = getActiveElement();
- return {
- focusedElem: focusedElem,
- selectionRange: ReactInputSelection.hasSelectionCapabilities(focusedElem) ? ReactInputSelection.getSelection(focusedElem) : null
- };
- },
-
- /**
- * @restoreSelection: If any selection information was potentially lost,
- * restore it. This is useful when performing operations that could remove dom
- * nodes and place them back in, resulting in focus being lost.
- */
- restoreSelection: function (priorSelectionInformation) {
- var curFocusedElem = getActiveElement();
- var priorFocusedElem = priorSelectionInformation.focusedElem;
- var priorSelectionRange = priorSelectionInformation.selectionRange;
- if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
- if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
- ReactInputSelection.setSelection(priorFocusedElem, priorSelectionRange);
- }
- focusNode(priorFocusedElem);
- }
- },
-
- /**
- * @getSelection: Gets the selection bounds of a focused textarea, input or
- * contentEditable node.
- * -@input: Look up selection bounds of this input
- * -@return {start: selectionStart, end: selectionEnd}
- */
- getSelection: function (input) {
- var selection;
-
- if ('selectionStart' in input) {
- // Modern browser with input or textarea.
- selection = {
- start: input.selectionStart,
- end: input.selectionEnd
- };
- } else if (document.selection && (input.nodeName && input.nodeName.toLowerCase() === 'input')) {
- // IE8 input.
- var range = document.selection.createRange();
- // There can only be one selection per document in IE, so it must
- // be in our element.
- if (range.parentElement() === input) {
- selection = {
- start: -range.moveStart('character', -input.value.length),
- end: -range.moveEnd('character', -input.value.length)
- };
- }
- } else {
- // Content editable or old IE textarea.
- selection = ReactDOMSelection.getOffsets(input);
- }
-
- return selection || { start: 0, end: 0 };
- },
-
- /**
- * @setSelection: Sets the selection bounds of a textarea or input and focuses
- * the input.
- * -@input Set selection bounds of this input or textarea
- * -@offsets Object of same form that is returned from get*
- */
- setSelection: function (input, offsets) {
- var start = offsets.start;
- var end = offsets.end;
- if (typeof end === 'undefined') {
- end = start;
- }
-
- if ('selectionStart' in input) {
- input.selectionStart = start;
- input.selectionEnd = Math.min(end, input.value.length);
- } else if (document.selection && (input.nodeName && input.nodeName.toLowerCase() === 'input')) {
- var range = input.createTextRange();
- range.collapse(true);
- range.moveStart('character', start);
- range.moveEnd('character', end - start);
- range.select();
- } else {
- ReactDOMSelection.setOffsets(input, offsets);
- }
- }
-};
-
-module.exports = ReactInputSelection;
-},{"./ReactDOMSelection":3,"fbjs/lib/containsNode":8,"fbjs/lib/focusNode":9,"fbjs/lib/getActiveElement":10}],5:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule getNodeForCharacterOffset
- */
-
-'use strict';
-
-/**
- * Given any node return the first leaf node without children.
- *
- * @param {DOMElement|DOMTextNode} node
- * @return {DOMElement|DOMTextNode}
- */
-function getLeafNode(node) {
- while (node && node.firstChild) {
- node = node.firstChild;
- }
- return node;
-}
-
-/**
- * Get the next sibling within a container. This will walk up the
- * DOM if a node's siblings have been exhausted.
- *
- * @param {DOMElement|DOMTextNode} node
- * @return {?DOMElement|DOMTextNode}
- */
-function getSiblingNode(node) {
- while (node) {
- if (node.nextSibling) {
- return node.nextSibling;
- }
- node = node.parentNode;
- }
-}
-
-/**
- * Get object describing the nodes which contain characters at offset.
- *
- * @param {DOMElement|DOMTextNode} root
- * @param {number} offset
- * @return {?object}
- */
-function getNodeForCharacterOffset(root, offset) {
- var node = getLeafNode(root);
- var nodeStart = 0;
- var nodeEnd = 0;
-
- while (node) {
- if (node.nodeType === 3) {
- nodeEnd = nodeStart + node.textContent.length;
-
- if (nodeStart <= offset && nodeEnd >= offset) {
- return {
- node: node,
- offset: offset - nodeStart
- };
- }
-
- nodeStart = nodeEnd;
- }
-
- node = getLeafNode(getSiblingNode(node));
- }
-}
-
-module.exports = getNodeForCharacterOffset;
-},{}],6:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule getTextContentAccessor
- */
-
-'use strict';
-
-var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
-
-var contentKey = null;
-
-/**
- * Gets the key used to access text content on a DOM node.
- *
- * @return {?string} Key used to access text content.
- * @internal
- */
-function getTextContentAccessor() {
- if (!contentKey && ExecutionEnvironment.canUseDOM) {
- // Prefer textContent to innerText because many browsers support both but
- // SVG elements don't support innerText even when
does.
- contentKey = 'textContent' in document.documentElement ? 'textContent' : 'innerText';
- }
- return contentKey;
-}
-
-module.exports = getTextContentAccessor;
-},{"fbjs/lib/ExecutionEnvironment":7}],7:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ExecutionEnvironment
- */
-
-'use strict';
-
-var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
-
-/**
- * Simple, lightweight module assisting with the detection and context of
- * Worker. Helps avoid circular dependencies and allows code to reason about
- * whether or not they are in a Worker, even if they never include the main
- * `ReactWorker` dependency.
- */
-var ExecutionEnvironment = {
-
- canUseDOM: canUseDOM,
-
- canUseWorkers: typeof Worker !== 'undefined',
-
- canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
-
- canUseViewport: canUseDOM && !!window.screen,
-
- isInWorker: !canUseDOM // For now, this is true - might change in the future.
-
-};
-
-module.exports = ExecutionEnvironment;
-},{}],8:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule containsNode
- * @typechecks
- */
-
-'use strict';
-
-var isTextNode = require('./isTextNode');
-
-/*eslint-disable no-bitwise */
-
-/**
- * Checks if a given DOM node contains or is another DOM node.
- *
- * @param {?DOMNode} outerNode Outer DOM node.
- * @param {?DOMNode} innerNode Inner DOM node.
- * @return {boolean} True if `outerNode` contains or is `innerNode`.
- */
-function containsNode(_x, _x2) {
- var _again = true;
-
- _function: while (_again) {
- var outerNode = _x,
- innerNode = _x2;
- _again = false;
-
- if (!outerNode || !innerNode) {
- return false;
- } else if (outerNode === innerNode) {
- return true;
- } else if (isTextNode(outerNode)) {
- return false;
- } else if (isTextNode(innerNode)) {
- _x = outerNode;
- _x2 = innerNode.parentNode;
- _again = true;
- continue _function;
- } else if (outerNode.contains) {
- return outerNode.contains(innerNode);
- } else if (outerNode.compareDocumentPosition) {
- return !!(outerNode.compareDocumentPosition(innerNode) & 16);
- } else {
- return false;
- }
- }
-}
-
-module.exports = containsNode;
-},{"./isTextNode":12}],9:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule focusNode
- */
-
-'use strict';
-
-/**
- * @param {DOMElement} node input/textarea to focus
- */
-function focusNode(node) {
- // IE8 can throw "Can't move focus to the control because it is invisible,
- // not enabled, or of a type that does not accept the focus." for all kinds of
- // reasons that are too expensive and fragile to test.
- try {
- node.focus();
- } catch (e) {}
-}
-
-module.exports = focusNode;
-},{}],10:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule getActiveElement
- * @typechecks
- */
-
-/**
- * Same as document.activeElement but wraps in a try-catch block. In IE it is
- * not safe to call document.activeElement if there is nothing focused.
- *
- * The activeElement will be null only if the document or document body is not yet defined.
- */
-'use strict';
-
-function getActiveElement() /*?DOMElement*/{
- if (typeof document === 'undefined') {
- return null;
- }
-
- try {
- return document.activeElement || document.body;
- } catch (e) {
- return document.body;
- }
-}
-
-module.exports = getActiveElement;
-},{}],11:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule isNode
- * @typechecks
- */
-
-/**
- * @param {*} object The object to check.
- * @return {boolean} Whether or not the object is a DOM node.
- */
-'use strict';
-
-function isNode(object) {
- return !!(object && (typeof Node === 'function' ? object instanceof Node : typeof object === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string'));
-}
-
-module.exports = isNode;
-},{}],12:[function(require,module,exports){
-/**
- * Copyright 2013-2015, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule isTextNode
- * @typechecks
- */
-
-'use strict';
-
-var isNode = require('./isNode');
-
-/**
- * @param {*} object The object to check.
- * @return {boolean} Whether or not the object is a DOM text node.
- */
-function isTextNode(object) {
- return isNode(object) && object.nodeType == 3;
-}
-
-module.exports = isTextNode;
-},{"./isNode":11}]},{},[1])(1)
-});
\ No newline at end of file
diff --git a/dist/react-maskedinput.min.js b/dist/react-maskedinput.min.js
deleted file mode 100644
index 20a3628..0000000
--- a/dist/react-maskedinput.min.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/*!
- * react-maskedinput 3.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 l="function"==typeof require&&require;if(!o&&l)return l(a,!0);if(r)return r(a,!0);var h=new Error("Cannot find module '"+a+"'");throw h.code="MODULE_NOT_FOUND",h}var c=n[a]={exports:{}};e[a][0].call(c.exports,function(t){var n=e[a][1][t];return s(n?n:t)},c,c.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 s(t){return i({},t)}function r(t){var e=s(p);if(t)for(var n=Object.keys(t),i=0,r=n.length;r>i;i++){var a=n[i];null==t[a]?delete e[a]:e[a]=t[a]}return e}function a(t,e,n){return this instanceof a?(this.placeholderChar=n||d,this.formatCharacters=e||p,this.source=t,this.pattern=[],this.length=0,this.firstEditableIndex=null,this.lastEditableIndex=null,this._editableIndices={},void this._parse()):new a(t,e,n)}function o(t){if(!(this instanceof o))return new o(t);if(t=i({formatCharacters:null,pattern:null,placeholderChar:d,selection:{start:0,end:0},value:""},t),null==t.pattern)throw new Error("InputMask: you must provide a pattern.");if(1!==t.placeholderChar.length)throw new Error("InputMask: placeholderChar should be a single character.");this.placeholderChar=t.placeholderChar,this.formatCharacters=r(t.formatCharacters),this.setPattern(t.pattern,{value:t.value,selection:t.selection})}var l="\\",h=/^\d$/,c=/^[A-Za-z]$/,u=/^[\dA-Za-z]$/,d="_",p={"*":{validate:function(t){return u.test(t)}},1:{validate:function(t){return h.test(t)}},a:{validate:function(t){return c.test(t)}},A:{validate:function(t){return c.test(t)},transform:function(t){return t.toUpperCase()}},"#":{validate:function(t){return u.test(t)},transform:function(t){return t.toUpperCase()}}};a.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===l){if(i===s-1)throw new Error("InputMask: pattern ends with a raw "+l);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},a.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)?this.transform(t[n],i):this.placeholderChar,n++):(e[i]=this.pattern[i],t.length>n&&t[n]===this.pattern[i]&&n++);return e},a.prototype.isEditableIndex=function(t){return!!this._editableIndices[t]},a.prototype.isValidAtIndex=function(t,e){return this.formatCharacters[this.pattern[e]].validate(t)},a.prototype.transform=function(t,e){var n=this.formatCharacters[this.pattern[e]];return"function"==typeof n.transform?n.transform(t):t},o.prototype.input=function(t){if(this.selection.start===this.selection.end&&this.selection.start===this.pattern.length)return!1;var e=s(this.selection),n=this.getValue(),i=this.selection.start;if(ii;)this.pattern.isEditableIndex(r)&&(this.value[r]=this.placeholderChar),r--;for(this.selection.start=this.selection.end=i+1;this.pattern.length>this.selection.start&&!this.pattern.isEditableIndex(this.selection.start);)this.selection.start++,this.selection.end++;return null!=this._historyIndex&&(console.log("splice(",this._historyIndex,this._history.length-this._historyIndex,")"),this._history.splice(this._historyIndex,this._history.length-this._historyIndex),this._historyIndex=null),("input"!==this._lastOp||e.start!==e.end||null!==this._lastSelection&&e.start!==this._lastSelection.start)&&this._history.push({value:n,selection:e,lastOp:this._lastOp}),this._lastOp="input",this._lastSelection=s(this.selection),!0},o.prototype.backspace=function(){if(0===this.selection.start&&0===this.selection.end)return!1;var t=s(this.selection),e=this.getValue();if(this.selection.start===this.selection.end)this.pattern.isEditableIndex(this.selection.start-1)&&(this.value[this.selection.start-1]=this.placeholderChar),this.selection.start--,this.selection.end--;else{for(var n=this.selection.end-1;n>=this.selection.start;)this.pattern.isEditableIndex(n)&&(this.value[n]=this.placeholderChar),n--;this.selection.end=this.selection.start}return null!=this._historyIndex&&this._history.splice(this._historyIndex,this._history.length-this._historyIndex),("backspace"!==this._lastOp||t.start!==t.end||null!==this._lastSelection&&t.start!==this._lastSelection.start)&&this._history.push({value:e,selection:t,lastOp:this._lastOp}),this._lastOp="backspace",this._lastSelection=s(this.selection),!0},o.prototype.paste=function(t){var e={value:this.value.slice(),selection:s(this.selection),_lastOp:this._lastOp,_history:this._history.slice(),_historyIndex:this._historyIndex,_lastSelection:s(this._lastSelection)};if(this.selection.startn;n++)if(t.charAt(n)!==this.pattern.pattern[n])return!1;t=t.substring(this.pattern.firstEditableIndex-this.selection.start),this.selection.start=this.pattern.firstEditableIndex}for(n=0,r=t.length;r>n&&this.selection.start<=this.pattern.lastEditableIndex;n++){var a=this.input(t.charAt(n));if(!a){if(this.selection.start>0){var o=this.selection.start-1;if(!this.pattern.isEditableIndex(o)&&t.charAt(n)===this.pattern.pattern[o])continue}return i(this,e),!1}}return!0},o.prototype.undo=function(){if(0===this._history.length||0===this._historyIndex)return!1;var t;if(null==this._historyIndex){this._historyIndex=this._history.length-1,t=this._history[this._historyIndex];var e=this.getValue();(t.value!==e||t.selection.start!==this.selection.start||t.selection.end!==this.selection.end)&&this._history.push({value:e,selection:s(this.selection),lastOp:this._lastOp,startUndo:!0})}else t=this._history[--this._historyIndex];return this.value=t.value.split(""),this.selection=t.selection,this._lastOp=t.lastOp,!0},o.prototype.redo=function(){if(0===this._history.length||null==this._historyIndex)return!1;var t=this._history[++this._historyIndex];return this._historyIndex===this._history.length-1&&(this._historyIndex=null,t.startUndo&&this._history.pop()),this.value=t.value.split(""),this.selection=t.selection,this._lastOp=t.lastOp,!0},o.prototype.setPattern=function(t,e){e=i({selection:{start:0,end:0},value:""},e),this.pattern=new a(t,this.formatCharacters,this.placeholderChar),this.setValue(e.value),this.emptyValue=this.pattern.formatValue([]).join(""),this.selection=e.selection,this._resetHistory()},o.prototype.setSelection=function(t){if(this.selection=s(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},o.prototype.setValue=function(t){null==t&&(t=""),this.value=this.pattern.formatValue(t.split(""))},o.prototype.getValue=function(){return this.value.join("")},o.prototype.getRawValue=function(){for(var t=[],e=0;ee.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 o(t,e){if(window.getSelection){var n=window.getSelection(),i=t[c()].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=h(t,s),l=h(t,r);if(o&&l){var u=document.createRange();u.setStart(o.node,o.offset),n.removeAllRanges(),s>r?(n.addRange(u),n.extend(l.node,l.offset)):(u.setEnd(l.node,l.offset),n.addRange(u))}}}var l=t("fbjs/lib/ExecutionEnvironment"),h=t("./getNodeForCharacterOffset"),c=t("./getTextContentAccessor"),u=l.canUseDOM&&"selection"in document&&!("getSelection"in window),d={getOffsets:u?s:r,setOffsets:u?a:o};e.exports=d},{"./getNodeForCharacterOffset":5,"./getTextContentAccessor":6,"fbjs/lib/ExecutionEnvironment":7}],4:[function(t,e,n){"use strict";function i(t){return r(document.documentElement,t)}var s=t("./ReactDOMSelection"),r=t("fbjs/lib/containsNode"),a=t("fbjs/lib/focusNode"),o=t("fbjs/lib/getActiveElement"),l={hasSelectionCapabilities:function(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&("input"===e&&"text"===t.type||"textarea"===e||"true"===t.contentEditable)},getSelectionInformation:function(){var t=o();return{focusedElem:t,selectionRange:l.hasSelectionCapabilities(t)?l.getSelection(t):null}},restoreSelection:function(t){var e=o(),n=t.focusedElem,s=t.selectionRange;e!==n&&i(n)&&(l.hasSelectionCapabilities(n)&&l.setSelection(n,s),a(n))},getSelection:function(t){var e;if("selectionStart"in t)e={start:t.selectionStart,end:t.selectionEnd};else if(document.selection&&t.nodeName&&"input"===t.nodeName.toLowerCase()){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=s.getOffsets(t);return e||{start:0,end:0}},setSelection:function(t,e){var n=e.start,i=e.end;if("undefined"==typeof i&&(i=n),"selectionStart"in t)t.selectionStart=n,t.selectionEnd=Math.min(i,t.value.length);else if(document.selection&&t.nodeName&&"input"===t.nodeName.toLowerCase()){var r=t.createTextRange();r.collapse(!0),r.moveStart("character",n),r.moveEnd("character",i-n),r.select()}else s.setOffsets(t,e)}};e.exports=l},{"./ReactDOMSelection":3,"fbjs/lib/containsNode":8,"fbjs/lib/focusNode":9,"fbjs/lib/getActiveElement":10}],5:[function(t,e,n){"use strict";function i(t){for(;t&&t.firstChild;)t=t.firstChild;return t}function s(t){for(;t;){if(t.nextSibling)return t.nextSibling;t=t.parentNode}}function r(t,e){for(var n=i(t),r=0,a=0;n;){if(3===n.nodeType){if(a=r+n.textContent.length,e>=r&&a>=e)return{node:n,offset:e-r};r=a}n=i(s(n))}}e.exports=r},{}],6:[function(t,e,n){"use strict";function i(){return!r&&s.canUseDOM&&(r="textContent"in document.documentElement?"textContent":"innerText"),r}var s=t("fbjs/lib/ExecutionEnvironment"),r=null;e.exports=i},{"fbjs/lib/ExecutionEnvironment":7}],7:[function(t,e,n){"use strict";var i=!("undefined"==typeof window||!window.document||!window.document.createElement),s={canUseDOM:i,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:i&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:i&&!!window.screen,isInWorker:!i};e.exports=s},{}],8:[function(t,e,n){"use strict";function i(t,e){var n=!0;t:for(;n;){var i=t,r=e;if(n=!1,i&&r){if(i===r)return!0;if(s(i))return!1;if(s(r)){t=i,e=r.parentNode,n=!0;continue t}return i.contains?i.contains(r):i.compareDocumentPosition?!!(16&i.compareDocumentPosition(r)):!1}return!1}}var s=t("./isTextNode");e.exports=i},{"./isTextNode":12}],9:[function(t,e,n){"use strict";function i(t){try{t.focus()}catch(e){}}e.exports=i},{}],10:[function(t,e,n){"use strict";function i(){if("undefined"==typeof document)return null;try{return document.activeElement||document.body}catch(t){return document.body}}e.exports=i},{}],11:[function(t,e,n){"use strict";function i(t){return!(!t||!("function"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName))}e.exports=i},{}],12:[function(t,e,n){"use strict";function i(t){return s(t)&&3==t.nodeType}var s=t("./isNode");e.exports=i},{"./isNode":11}]},{},[1])(1)});
\ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
deleted file mode 100644
index 3215fbb..0000000
--- a/gulpfile.js
+++ /dev/null
@@ -1,70 +0,0 @@
-var browserify = require('browserify')
-var del = require('del')
-var gulp = require('gulp')
-var source = require('vinyl-source-stream')
-
-var header = require('gulp-header')
-var jshint = require('gulp-jshint')
-var rename = require('gulp-rename')
-var plumber = require('gulp-plumber')
-var react = require('gulp-react')
-var streamify = require('gulp-streamify')
-var uglify = require('gulp-uglify')
-var gutil = require('gulp-util')
-
-var pkg = require('./package.json')
-var devBuild = gutil.env.release ? '' : ' (dev build at ' + (new Date()).toUTCString() + ')'
-var distHeader = '/*!\n\
- * <%= pkg.name %> <%= pkg.version %><%= devBuild %> - <%= pkg.homepage %>\n\
- * <%= pkg.license %> Licensed\n\
- */\n'
-
-var jsSrcPaths = './src/**/*.js*'
-var jsLibPaths = './lib/**/*.js'
-
-gulp.task('clean-lib', function(cb) {
- del(jsLibPaths, cb)
-})
-
-gulp.task('transpile-js', ['clean-lib'], function() {
- return gulp.src(jsSrcPaths)
- .pipe(plumber())
- .pipe(react({harmony: true}))
- .pipe(gulp.dest('./lib'))
-})
-
-gulp.task('lint-js', ['transpile-js'], function() {
- return gulp.src(jsLibPaths)
- .pipe(jshint('./.jshintrc'))
- .pipe(jshint.reporter('jshint-stylish'))
-})
-
-gulp.task('bundle-js', ['lint-js'], function() {
- var b = browserify(pkg.main, {
- debug: !!gutil.env.debug
- , standalone: pkg.standalone
- , detectGlobals: false
- })
- b.transform('browserify-shim')
-
- var stream = b.bundle()
- .pipe(source(pkg.name + '.js'))
- .pipe(streamify(header(distHeader, {pkg: pkg, devBuild: devBuild})))
- .pipe(gulp.dest('./dist'))
-
- if (gutil.env.production) {
- stream = stream
- .pipe(rename(pkg.name + '.min.js'))
- .pipe(streamify(uglify()))
- .pipe(streamify(header(distHeader, {pkg: pkg, devBuild: devBuild})))
- .pipe(gulp.dest('./dist'))
- }
-
- return stream
-})
-
-gulp.task('watch', function() {
- gulp.watch(jsSrcPaths, ['bundle-js'])
-})
-
-gulp.task('default', ['bundle-js', 'watch'])
\ No newline at end of file
diff --git a/nwb.config.js b/nwb.config.js
new file mode 100644
index 0000000..43398c0
--- /dev/null
+++ b/nwb.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ type: 'react-component',
+ build: {
+ externals: {
+ 'react': 'React'
+ },
+ global: 'MaskedInput',
+ jsNext: true,
+ umd: true
+ }
+}
diff --git a/package.json b/package.json
index c3b4699..9935e8f 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,9 @@
{
"name": "react-maskedinput",
"description": "Masked React component",
- "version": "3.0.0",
+ "version": "3.2.0",
"main": "./lib/index.js",
+ "jsnext:main": "es6/index.js",
"standalone": "MaskedInput",
"homepage": "https://github.com/insin/react-maskedinput",
"license": "MIT",
@@ -13,38 +14,30 @@
"input",
"react-component"
],
+ "files": [
+ "es6",
+ "lib",
+ "umd"
+ ],
+ "scripts": {
+ "build": "nwb build",
+ "clean": "nwb clean",
+ "lint": "eslint src",
+ "start": "nwb serve",
+ "test": "nwb test",
+ "test:watch": "nwb test --server"
+ },
"dependencies": {
"inputmask-core": "^2.1.1"
},
"peerDependencies": {
- "react": ">=0.14.0"
+ "react": "0.14.x || 15.x.x"
},
"devDependencies": {
- "browserify": "^10.1.3",
- "browserify-shim": "^3.8.6",
- "del": "^1.1.1",
- "gulp": "^3.8.11",
- "gulp-header": "^1.2.2",
- "gulp-jshint": "^1.10.0",
- "gulp-plumber": "^1.0.0",
- "gulp-react": "^3.0.1",
- "gulp-rename": "^1.2.2",
- "gulp-streamify": "0.0.5",
- "gulp-uglify": "^1.2.0",
- "gulp-util": "^3.0.4",
- "jshint-stylish": "^1.0.2",
- "react": ">=0.13.0",
- "tape": "^4.0.0",
- "vinyl-source-stream": "^1.1.0"
- },
- "scripts": {
- "debug": "gulp --debug",
- "dist": "gulp bundle-js --production --release && gulp bundle-js --development --release",
- "test": "gulp transpile-js && tape test/*.js",
- "watch": "gulp"
- },
- "browserify-shim": {
- "react": "global:React"
+ "eslint-config-jonnybuchanan": "2.0.3",
+ "nwb": "0.9.x",
+ "react": "15.x.x",
+ "react-dom": "15.x.x"
},
"repository": {
"type": "git",
diff --git a/src/index.jsx b/src/index.js
similarity index 76%
rename from src/index.jsx
rename to src/index.js
index 7f91bfa..4488346 100644
--- a/src/index.jsx
+++ b/src/index.js
@@ -1,5 +1,3 @@
-'use strict';
-
var React = require('react')
var {getSelection, setSelection} = require('react/lib/ReactInputSelection')
@@ -43,12 +41,34 @@ var MaskedInput = React.createClass({
},
componentWillReceiveProps(nextProps) {
+ if (this.props.value !== nextProps.value) {
+ this.mask.setValue(nextProps.value)
+ }
if (this.props.mask !== nextProps.mask) {
this.mask.setPattern(nextProps.mask, {value: this.mask.getRawValue()})
}
this.mask.setValue(nextProps.value);
},
+ componentWillUpdate(nextProps, nextState) {
+ if (nextProps.mask !== this.props.mask) {
+ this._updatePattern(nextProps)
+ }
+ },
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.mask !== this.props.mask && this.mask.selection.start) {
+ this._updateInputSelection()
+ }
+ },
+
+ _updatePattern: function(props) {
+ this.mask.setPattern(props.mask, {
+ value: this.mask.getRawValue(),
+ selection: getSelection(this.input)
+ });
+ },
+
_updateMaskSelection() {
this.mask.selection = getSelection(this.input)
},
@@ -61,7 +81,7 @@ var MaskedInput = React.createClass({
// console.log('onChange', JSON.stringify(getSelection(this.input)), e.target.value)
var maskValue = this.mask.getValue()
- if (e.target.value != maskValue) {
+ if (e.target.value !== maskValue) {
// Cut or delete operations will have shortened the value
if (e.target.value.length < maskValue.length) {
var sizeDiff = maskValue.length - e.target.value.length
@@ -89,7 +109,9 @@ var MaskedInput = React.createClass({
if (this.mask.undo()) {
e.target.value = this._getDisplayValue()
this._updateInputSelection()
- this.props.onChange(e)
+ if (this.props.onChange) {
+ this.props.onChange(e)
+ }
}
return
}
@@ -98,12 +120,14 @@ var MaskedInput = React.createClass({
if (this.mask.redo()) {
e.target.value = this._getDisplayValue()
this._updateInputSelection()
- this.props.onChange(e)
+ if (this.props.onChange) {
+ this.props.onChange(e)
+ }
}
return
}
- if (e.key == 'Backspace') {
+ if (e.key === 'Backspace') {
e.preventDefault()
this._updateMaskSelection()
if (this.mask.backspace()) {
@@ -112,7 +136,9 @@ var MaskedInput = React.createClass({
if (value) {
this._updateInputSelection()
}
- this.props.onChange(e)
+ if (this.props.onChange) {
+ this.props.onChange(e)
+ }
}
}
},
@@ -121,14 +147,17 @@ var MaskedInput = React.createClass({
// console.log('onKeyPress', JSON.stringify(getSelection(this.input)), e.key, e.target.value)
// Ignore modified key presses
- if (e.metaKey || e.altKey || e.ctrlKey) { return }
+ // Ignore enter key to allow form submission
+ if (e.metaKey || e.altKey || e.ctrlKey || e.key === 'Enter') { return }
e.preventDefault()
this._updateMaskSelection()
if (this.mask.input(e.key)) {
e.target.value = this.mask.getValue()
this._updateInputSelection()
- this.props.onChange(e)
+ if (this.props.onChange) {
+ this.props.onChange(e)
+ }
}
},
@@ -142,7 +171,17 @@ var MaskedInput = React.createClass({
e.target.value = this.mask.getValue()
// Timeout needed for IE
setTimeout(this._updateInputSelection, 0)
- this.props.onChange(e)
+ if (this.props.onChange) {
+ this.props.onChange(e)
+ }
+ }
+ else {
+ this.mask.setValue(e.clipboardData.getData('Text'))
+ var value = this._getDisplayValue()
+ e.target.value = value
+ if (value) {
+ this._updateInputSelection()
+ }
}
else {
this.mask.setValue(e.clipboardData.getData('Text'));
@@ -159,6 +198,14 @@ var MaskedInput = React.createClass({
return value === this.mask.emptyValue ? '' : value
},
+ focus() {
+ this.input.focus();
+ },
+
+ blur() {
+ this.input.blur();
+ },
+
render() {
var {mask, formatCharacters, size, placeholder, ...props} = this.props
var patternLength = this.mask.pattern.length
diff --git a/tests/index-test.js b/tests/index-test.js
new file mode 100644
index 0000000..aa684d4
--- /dev/null
+++ b/tests/index-test.js
@@ -0,0 +1,161 @@
+/* eslint-env mocha */
+import React from 'react'
+import ReactDOM from 'react-dom'
+import expect from 'expect'
+import MaskedInput from 'src'
+
+const setup = () => {
+ const element = document.createElement('div')
+ document.body.appendChild(element)
+ return element;
+};
+
+const cleanup = (element) => {
+ ReactDOM.unmountComponentAtNode(element)
+ document.body.removeChild(element)
+}
+
+describe('MaskedInput', () => {
+ it('should render (smokescreen test)', () => {
+ expect.spyOn(console, 'error')
+ expect().toExist()
+ expect(console.error.calls[0].arguments[0]).toBe(
+ 'Warning: Failed propType: Required prop `mask` was not specified in ' +
+ '`MaskedInput`.'
+ )
+ })
+
+ it('should handle a masking workflow', () => {
+ const el = setup()
+ let ref = null
+ ReactDOM.render(
+ {
+ if (r) ref = r
+ }}
+ mask="11/11"
+ />,
+ el
+ )
+ const input = ReactDOM.findDOMNode(ref)
+
+ // initial state
+ expect(input.value).toBe('')
+ expect(input.placeholder).toBe('__/__')
+ expect(input.size).toBe(5)
+
+ cleanup(el)
+ })
+
+ it('should handle updating mask masking', () => {
+ const el = setup()
+ let ref = null
+ let defaultMask = '1111 1111 1111 1111'
+ let amexMask = '1111 111111 11111'
+ let mask = defaultMask
+
+ function render() {
+ ReactDOM.render(
+ {
+ if (r) ref = r
+ }}
+ mask={mask}
+ />,
+ el
+ )
+ }
+
+ render();
+ let input = ReactDOM.findDOMNode(ref)
+
+ // initial state
+ expect(input.value).toBe('')
+ expect(input.placeholder).toBe('____ ____ ____ ____')
+ expect(input.size).toBe(19)
+ expect(input.selectionStart).toBe(0)
+
+ mask = amexMask
+ render();
+ input = ReactDOM.findDOMNode(ref)
+
+ // initial state
+ expect(input.value).toBe('')
+ expect(input.placeholder).toBe('____ ______ _____')
+ expect(input.size).toBe(17)
+ expect(input.selectionStart).toBe(0)
+
+ cleanup(el)
+ })
+
+ describe('testing full value change', () => {
+ const el = setup()
+ let ref = null
+ let mask = '(111) 111-1111'
+ let value = ''
+
+ function render() {
+ ReactDOM.render(
+ {
+ if (r) ref = r
+ }}
+ mask={mask}
+ value={value}
+ />,
+ el
+ )
+ }
+
+ // keeping the same element to simulate a user changing between different autofill options and selecting different ones.
+ render()
+ let input = ReactDOM.findDOMNode(ref)
+
+ // initial state
+ it('should have the expected initial state', () => {
+ expect(input.value).toBe('')
+ expect(input.placeholder).toBe('(___) ___-____')
+ expect(input.size).toBe(14)
+ expect(input.selectionStart).toBe(0)
+ })
+
+ it('should handle updating value with formatting', () => {
+ value = '(432) 543-9876'
+
+ render()
+ input = ReactDOM.findDOMNode(ref)
+
+ // new state
+ expect(input.value).toBe('(432) 543-9876')
+ expect(input.size).toBe(14)
+ expect(input.selectionStart).toBe(14)
+ })
+
+ it('should handle updating value without formatting', () => {
+ value = '3454521234'
+
+ render()
+ input = ReactDOM.findDOMNode(ref)
+
+ // new state
+ expect(input.value).toBe('(345) 452-1234')
+ expect(input.size).toBe(14)
+ expect(input.selectionStart).toBe(14)
+ })
+
+ it('should handle updating value with slightly different formatting', () => {
+ // please note: if 789-123-4321, the input will fail because it is taking the - as an input
+ value = '789 123-4321'
+
+ render()
+ input = ReactDOM.findDOMNode(ref)
+
+ // new state
+ expect(input.value).toBe('(789) 123-4321')
+ expect(input.size).toBe(14)
+ expect(input.selectionStart).toBe(14)
+ })
+
+ cleanup(el)
+ })
+})