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 46f1072..4994ab3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,7 @@
-lib/
-node_modules/
\ No newline at end of file
+/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 80d52b9..a327b7e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,4 +1,26 @@
-## 3.1.0 / 2015-10-23
+## 3.3.2 / 2016-12-01
+
+* Fix for both Android and MS Edge input entering
+
+## 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
+
+## 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.
@@ -46,3 +68,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 e0d1898..cbfb565 100644
--- a/README.md
+++ b/README.md
@@ -20,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://unpkg.com/react-maskedinput/umd/react-maskedinput.js) (development version)
+* [react-maskedinput.min.js](https://unpkg.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 69%
rename from demo/index.html
rename to demo/src/index.js
index e1c166d..9419c27 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 a7ad6bf..0000000
--- a/dist/react-maskedinput.js
+++ /dev/null
@@ -1,1337 +0,0 @@
-/*!
- * react-maskedinput 3.1.0 (dev build at Thu, 11 Feb 2016 15:36:55 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
-
-},{}],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 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":11,"./getTextContentAccessor":12,"fbjs/lib/ExecutionEnvironment":2}],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 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":9,"fbjs/lib/containsNode":3,"fbjs/lib/focusNode":4,"fbjs/lib/getActiveElement":5}],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 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;
-},{}],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 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