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). +![This project has never been used by its author, other than while making it.](https://img.shields.io/badge/author--usage-never-red.png "This project has never been used by its author, other than while making it") + ## [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) + }) +})