cleave.js
Version:
JavaScript library for formatting input text content when you are typing
1,499 lines (1,278 loc) • 94.2 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("react"));
else if(typeof define === 'function' && define.amd)
define(["react"], factory);
else if(typeof exports === 'object')
exports["Cleave"] = factory(require("react"));
else
root["Cleave"] = factory(root["React"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
'use strict';
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
var React = __webpack_require__(1); // eslint-disable-line no-unused-vars
var CreateReactClass = __webpack_require__(2);
var NumeralFormatter = __webpack_require__(9);
var DateFormatter = __webpack_require__(10);
var TimeFormatter = __webpack_require__(11);
var PhoneFormatter = __webpack_require__(12);
var CreditCardDetector = __webpack_require__(13);
var Util = __webpack_require__(14);
var DefaultProperties = __webpack_require__(15);
var cleaveReactClass = CreateReactClass({
componentDidMount: function componentDidMount() {
this.init();
},
componentDidUpdate: function componentDidUpdate(prevProps) {
var owner = this,
phoneRegionCode = (owner.props.options || {}).phoneRegionCode,
newValue = owner.props.value,
pps = owner.properties;
owner.updateRegisteredEvents(owner.props);
if (prevProps.value !== newValue && newValue !== undefined && newValue !== null) {
newValue = newValue.toString();
if (newValue !== owner.properties.result) {
owner.properties.initValue = newValue;
owner.onInput(newValue, true);
}
}
// update phone region code
var prevPhoneRegionCode = (prevProps.options || {}).phoneRegionCode;
if (prevPhoneRegionCode !== phoneRegionCode && phoneRegionCode && phoneRegionCode !== owner.properties.phoneRegionCode) {
owner.properties.phoneRegionCode = phoneRegionCode;
owner.initPhoneFormatter();
owner.onInput(owner.properties.result);
}
Util.setSelection(owner.element, owner.state.cursorPosition, pps.document);
},
updateRegisteredEvents: function updateRegisteredEvents(props) {
var owner = this,
_owner$registeredEven = owner.registeredEvents,
onKeyDown = _owner$registeredEven.onKeyDown,
onChange = _owner$registeredEven.onChange,
onFocus = _owner$registeredEven.onFocus,
onBlur = _owner$registeredEven.onBlur,
onInit = _owner$registeredEven.onInit;
if (props.onInit && props.onInit !== onInit) owner.registeredEvents.onInit = props.onInit;
if (props.onChange && props.onChange !== onChange) owner.registeredEvents.onChange = props.onChange;
if (props.onFocus && props.onFocus !== onFocus) owner.registeredEvents.onFocus = props.onFocus;
if (props.onBlur && props.onBlur !== onBlur) owner.registeredEvents.onBlur = props.onBlur;
if (props.onKeyDown && props.onKeyDown !== onKeyDown) owner.registeredEvents.onKeyDown = props.onKeyDown;
},
getInitialState: function getInitialState() {
var owner = this,
_owner$props = owner.props,
value = _owner$props.value,
options = _owner$props.options,
onKeyDown = _owner$props.onKeyDown,
onChange = _owner$props.onChange,
onFocus = _owner$props.onFocus,
onBlur = _owner$props.onBlur,
onInit = _owner$props.onInit;
owner.registeredEvents = {
onInit: onInit || Util.noop,
onChange: onChange || Util.noop,
onFocus: onFocus || Util.noop,
onBlur: onBlur || Util.noop,
onKeyDown: onKeyDown || Util.noop
};
if (!options) {
options = {};
}
options.initValue = value;
owner.properties = DefaultProperties.assign({}, options);
return {
value: owner.properties.result,
cursorPosition: 0
};
},
init: function init() {
var owner = this,
pps = owner.properties;
// so no need for this lib at all
if (!pps.numeral && !pps.phone && !pps.creditCard && !pps.time && !pps.date && pps.blocksLength === 0 && !pps.prefix) {
owner.onInput(pps.initValue);
owner.registeredEvents.onInit(owner);
return;
}
pps.maxLength = Util.getMaxLength(pps.blocks);
owner.isAndroid = Util.isAndroid();
owner.initPhoneFormatter();
owner.initDateFormatter();
owner.initTimeFormatter();
owner.initNumeralFormatter();
// avoid touch input field if value is null
// otherwise Firefox will add red box-shadow for <input required />
if (pps.initValue || pps.prefix && !pps.noImmediatePrefix) {
owner.onInput(pps.initValue);
}
owner.registeredEvents.onInit(owner);
},
initNumeralFormatter: function initNumeralFormatter() {
var owner = this,
pps = owner.properties;
if (!pps.numeral) {
return;
}
pps.numeralFormatter = new NumeralFormatter(pps.numeralDecimalMark, pps.numeralIntegerScale, pps.numeralDecimalScale, pps.numeralThousandsGroupStyle, pps.numeralPositiveOnly, pps.stripLeadingZeroes, pps.prefix, pps.signBeforePrefix, pps.tailPrefix, pps.delimiter);
},
initTimeFormatter: function initTimeFormatter() {
var owner = this,
pps = owner.properties;
if (!pps.time) {
return;
}
pps.timeFormatter = new TimeFormatter(pps.timePattern, pps.timeFormat);
pps.blocks = pps.timeFormatter.getBlocks();
pps.blocksLength = pps.blocks.length;
pps.maxLength = Util.getMaxLength(pps.blocks);
},
initDateFormatter: function initDateFormatter() {
var owner = this,
pps = owner.properties;
if (!pps.date) {
return;
}
pps.dateFormatter = new DateFormatter(pps.datePattern, pps.dateMin, pps.dateMax);
pps.blocks = pps.dateFormatter.getBlocks();
pps.blocksLength = pps.blocks.length;
pps.maxLength = Util.getMaxLength(pps.blocks);
},
initPhoneFormatter: function initPhoneFormatter() {
var owner = this,
pps = owner.properties;
if (!pps.phone) {
return;
}
// Cleave.AsYouTypeFormatter should be provided by
// external google closure lib
try {
pps.phoneFormatter = new PhoneFormatter(new pps.root.Cleave.AsYouTypeFormatter(pps.phoneRegionCode), pps.delimiter);
} catch (ex) {
throw new Error('Please include phone-type-formatter.{country}.js lib');
}
},
setRawValue: function setRawValue(value) {
var owner = this,
pps = owner.properties;
value = value !== undefined && value !== null ? value.toString() : '';
if (pps.numeral) {
value = value.replace('.', pps.numeralDecimalMark);
}
pps.postDelimiterBackspace = false;
owner.onChange({
target: { value: value },
// Methods to better resemble a SyntheticEvent
stopPropagation: Util.noop,
preventDefault: Util.noop,
persist: Util.noop
});
},
getRawValue: function getRawValue() {
var owner = this,
pps = owner.properties,
rawValue = pps.result;
if (pps.rawValueTrimPrefix) {
rawValue = Util.getPrefixStrippedValue(rawValue, pps.prefix, pps.prefixLength, pps.result, pps.delimiter, pps.delimiters, pps.noImmediatePrefix, pps.tailPrefix, pps.signBeforePrefix);
}
if (pps.numeral) {
rawValue = pps.numeralFormatter ? pps.numeralFormatter.getRawValue(rawValue) : '';
} else {
rawValue = Util.stripDelimiters(rawValue, pps.delimiter, pps.delimiters);
}
return rawValue;
},
getISOFormatDate: function getISOFormatDate() {
var owner = this,
pps = owner.properties;
return pps.date ? pps.dateFormatter.getISOFormatDate() : '';
},
getISOFormatTime: function getISOFormatTime() {
var owner = this,
pps = owner.properties;
return pps.time ? pps.timeFormatter.getISOFormatTime() : '';
},
onInit: function onInit(owner) {
return owner;
},
onKeyDown: function onKeyDown(event) {
var owner = this,
pps = owner.properties,
charCode = event.which || event.keyCode;
owner.lastInputValue = pps.result;
owner.isBackward = charCode === 8;
owner.registeredEvents.onKeyDown(event);
},
onFocus: function onFocus(event) {
var owner = this,
pps = owner.properties;
if (pps.prefix && pps.noImmediatePrefix && !event.target.value) {
owner.onInput(pps.prefix);
}
event.target.rawValue = owner.getRawValue();
event.target.value = pps.result;
owner.registeredEvents.onFocus(event);
Util.fixPrefixCursor(owner.element, pps.prefix, pps.delimiter, pps.delimiters);
},
onBlur: function onBlur(event) {
var owner = this,
pps = owner.properties;
event.target.rawValue = owner.getRawValue();
event.target.value = pps.result;
owner.registeredEvents.onBlur(event);
},
onChange: function onChange(event) {
var owner = this,
pps = owner.properties;
owner.isBackward = owner.isBackward || event.inputType === 'deleteContentBackward';
// hit backspace when last character is delimiter
var postDelimiter = Util.getPostDelimiter(owner.lastInputValue, pps.delimiter, pps.delimiters);
if (owner.isBackward && postDelimiter) {
pps.postDelimiterBackspace = postDelimiter;
} else {
pps.postDelimiterBackspace = false;
}
owner.onInput(event.target.value);
event.target.rawValue = owner.getRawValue();
event.target.value = pps.result;
owner.registeredEvents.onChange(event);
},
onInput: function onInput(value, fromProps) {
var owner = this,
pps = owner.properties;
// case 1: delete one more character "4"
// 1234*| -> hit backspace -> 123|
// case 2: last character is not delimiter which is:
// 12|34* -> hit backspace -> 1|34*
var postDelimiterAfter = Util.getPostDelimiter(value, pps.delimiter, pps.delimiters);
if (!fromProps && !pps.numeral && pps.postDelimiterBackspace && !postDelimiterAfter) {
value = Util.headStr(value, value.length - pps.postDelimiterBackspace.length);
}
// phone formatter
if (pps.phone) {
if (pps.prefix && (!pps.noImmediatePrefix || value.length)) {
pps.result = pps.prefix + pps.phoneFormatter.format(value).slice(pps.prefix.length);
} else {
pps.result = pps.phoneFormatter.format(value);
}
owner.updateValueState();
return;
}
// numeral formatter
if (pps.numeral) {
// Do not show prefix when noImmediatePrefix is specified
// This mostly because we need to show user the native input placeholder
if (pps.prefix && pps.noImmediatePrefix && value.length === 0) {
pps.result = '';
} else {
pps.result = pps.numeralFormatter.format(value);
}
owner.updateValueState();
return;
}
// date
if (pps.date) {
value = pps.dateFormatter.getValidatedDate(value);
}
// time
if (pps.time) {
value = pps.timeFormatter.getValidatedTime(value);
}
// strip delimiters
value = Util.stripDelimiters(value, pps.delimiter, pps.delimiters);
// strip prefix
value = Util.getPrefixStrippedValue(value, pps.prefix, pps.prefixLength, pps.result, pps.delimiter, pps.delimiters, pps.noImmediatePrefix, pps.tailPrefix, pps.signBeforePrefix);
// strip non-numeric characters
value = pps.numericOnly ? Util.strip(value, /[^\d]/g) : value;
// convert case
value = pps.uppercase ? value.toUpperCase() : value;
value = pps.lowercase ? value.toLowerCase() : value;
// prevent from showing prefix when no immediate option enabled with empty input value
if (pps.prefix) {
if (pps.tailPrefix) {
value = value + pps.prefix;
} else {
value = pps.prefix + value;
}
// no blocks specified, no need to do formatting
if (pps.blocksLength === 0) {
pps.result = value;
owner.updateValueState();
return;
}
}
// update credit card props
if (pps.creditCard) {
owner.updateCreditCardPropsByValue(value);
}
// strip over length characters
value = pps.maxLength > 0 ? Util.headStr(value, pps.maxLength) : value;
// apply blocks
pps.result = Util.getFormattedValue(value, pps.blocks, pps.blocksLength, pps.delimiter, pps.delimiters, pps.delimiterLazyShow);
owner.updateValueState();
},
updateCreditCardPropsByValue: function updateCreditCardPropsByValue(value) {
var owner = this,
pps = owner.properties,
creditCardInfo;
// At least one of the first 4 characters has changed
if (Util.headStr(pps.result, 4) === Util.headStr(value, 4)) {
return;
}
creditCardInfo = CreditCardDetector.getInfo(value, pps.creditCardStrictMode);
pps.blocks = creditCardInfo.blocks;
pps.blocksLength = pps.blocks.length;
pps.maxLength = Util.getMaxLength(pps.blocks);
// credit card type changed
if (pps.creditCardType !== creditCardInfo.type) {
pps.creditCardType = creditCardInfo.type;
pps.onCreditCardTypeChanged.call(owner, pps.creditCardType);
}
},
updateValueState: function updateValueState() {
var owner = this,
pps = owner.properties;
if (!owner.element) {
owner.setState({ value: pps.result });
return;
}
var endPos = owner.element.selectionEnd;
var oldValue = owner.element.value;
var newValue = pps.result;
owner.lastInputValue = newValue;
endPos = Util.getNextCursorPosition(endPos, oldValue, newValue, pps.delimiter, pps.delimiters);
if (owner.isAndroid) {
window.setTimeout(function () {
owner.setState({ value: newValue, cursorPosition: endPos });
}, 1);
return;
}
owner.setState({ value: newValue, cursorPosition: endPos });
},
render: function render() {
var owner = this;
// eslint-disable-next-line
var _owner$props2 = owner.props,
value = _owner$props2.value,
options = _owner$props2.options,
onKeyDown = _owner$props2.onKeyDown,
onFocus = _owner$props2.onFocus,
onBlur = _owner$props2.onBlur,
onChange = _owner$props2.onChange,
onInit = _owner$props2.onInit,
htmlRef = _owner$props2.htmlRef,
propsToTransfer = _objectWithoutProperties(_owner$props2, ['value', 'options', 'onKeyDown', 'onFocus', 'onBlur', 'onChange', 'onInit', 'htmlRef']);
return React.createElement('input', _extends({
type: 'text',
ref: function ref(_ref) {
owner.element = _ref;
if (!htmlRef) {
return;
}
htmlRef.apply(this, arguments);
},
value: owner.state.value,
onKeyDown: owner.onKeyDown,
onChange: owner.onChange,
onFocus: owner.onFocus,
onBlur: owner.onBlur
}, propsToTransfer));
}
});
module.exports = cleaveReactClass;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
var React = __webpack_require__(1);
var factory = __webpack_require__(3);
if (typeof React === 'undefined') {
throw Error(
'create-react-class could not find the React object. If you are using script tags, ' +
'make sure that React is being loaded before create-react-class.'
);
}
// Hack to grab NoopUpdateQueue from isomorphic React
var ReactNoopUpdateQueue = new React.Component().updater;
module.exports = factory(
React.Component,
React.isValidElement,
ReactNoopUpdateQueue
);
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
var _assign = __webpack_require__(4);
var emptyObject = __webpack_require__(5);
var _invariant = __webpack_require__(6);
if (process.env.NODE_ENV !== 'production') {
var warning = __webpack_require__(7);
}
var MIXINS_KEY = 'mixins';
// Helper function to allow the creation of anonymous functions which do not
// have .name set to the name of the variable being assigned to.
function identity(fn) {
return fn;
}
var ReactPropTypeLocationNames;
if (process.env.NODE_ENV !== 'production') {
ReactPropTypeLocationNames = {
prop: 'prop',
context: 'context',
childContext: 'child context'
};
} else {
ReactPropTypeLocationNames = {};
}
function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) {
/**
* Policies that describe methods in `ReactClassInterface`.
*/
var injectedMixins = [];
/**
* Composite components are higher-level components that compose other composite
* or host components.
*
* To create a new type of `ReactClass`, pass a specification of
* your new class to `React.createClass`. The only requirement of your class
* specification is that you implement a `render` method.
*
* var MyComponent = React.createClass({
* render: function() {
* return <div>Hello World</div>;
* }
* });
*
* The class specification supports a specific protocol of methods that have
* special meaning (e.g. `render`). See `ReactClassInterface` for
* more the comprehensive protocol. Any other properties and methods in the
* class specification will be available on the prototype.
*
* @interface ReactClassInterface
* @internal
*/
var ReactClassInterface = {
/**
* An array of Mixin objects to include when defining your component.
*
* @type {array}
* @optional
*/
mixins: 'DEFINE_MANY',
/**
* An object containing properties and methods that should be defined on
* the component's constructor instead of its prototype (static methods).
*
* @type {object}
* @optional
*/
statics: 'DEFINE_MANY',
/**
* Definition of prop types for this component.
*
* @type {object}
* @optional
*/
propTypes: 'DEFINE_MANY',
/**
* Definition of context types for this component.
*
* @type {object}
* @optional
*/
contextTypes: 'DEFINE_MANY',
/**
* Definition of context types this component sets for its children.
*
* @type {object}
* @optional
*/
childContextTypes: 'DEFINE_MANY',
// ==== Definition methods ====
/**
* Invoked when the component is mounted. Values in the mapping will be set on
* `this.props` if that prop is not specified (i.e. using an `in` check).
*
* This method is invoked before `getInitialState` and therefore cannot rely
* on `this.state` or use `this.setState`.
*
* @return {object}
* @optional
*/
getDefaultProps: 'DEFINE_MANY_MERGED',
/**
* Invoked once before the component is mounted. The return value will be used
* as the initial value of `this.state`.
*
* getInitialState: function() {
* return {
* isOn: false,
* fooBaz: new BazFoo()
* }
* }
*
* @return {object}
* @optional
*/
getInitialState: 'DEFINE_MANY_MERGED',
/**
* @return {object}
* @optional
*/
getChildContext: 'DEFINE_MANY_MERGED',
/**
* Uses props from `this.props` and state from `this.state` to render the
* structure of the component.
*
* No guarantees are made about when or how often this method is invoked, so
* it must not have side effects.
*
* render: function() {
* var name = this.props.name;
* return <div>Hello, {name}!</div>;
* }
*
* @return {ReactComponent}
* @required
*/
render: 'DEFINE_ONCE',
// ==== Delegate methods ====
/**
* Invoked when the component is initially created and about to be mounted.
* This may have side effects, but any external subscriptions or data created
* by this method must be cleaned up in `componentWillUnmount`.
*
* @optional
*/
componentWillMount: 'DEFINE_MANY',
/**
* Invoked when the component has been mounted and has a DOM representation.
* However, there is no guarantee that the DOM node is in the document.
*
* Use this as an opportunity to operate on the DOM when the component has
* been mounted (initialized and rendered) for the first time.
*
* @param {DOMElement} rootNode DOM element representing the component.
* @optional
*/
componentDidMount: 'DEFINE_MANY',
/**
* Invoked before the component receives new props.
*
* Use this as an opportunity to react to a prop transition by updating the
* state using `this.setState`. Current props are accessed via `this.props`.
*
* componentWillReceiveProps: function(nextProps, nextContext) {
* this.setState({
* likesIncreasing: nextProps.likeCount > this.props.likeCount
* });
* }
*
* NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
* transition may cause a state change, but the opposite is not true. If you
* need it, you are probably looking for `componentWillUpdate`.
*
* @param {object} nextProps
* @optional
*/
componentWillReceiveProps: 'DEFINE_MANY',
/**
* Invoked while deciding if the component should be updated as a result of
* receiving new props, state and/or context.
*
* Use this as an opportunity to `return false` when you're certain that the
* transition to the new props/state/context will not require a component
* update.
*
* shouldComponentUpdate: function(nextProps, nextState, nextContext) {
* return !equal(nextProps, this.props) ||
* !equal(nextState, this.state) ||
* !equal(nextContext, this.context);
* }
*
* @param {object} nextProps
* @param {?object} nextState
* @param {?object} nextContext
* @return {boolean} True if the component should update.
* @optional
*/
shouldComponentUpdate: 'DEFINE_ONCE',
/**
* Invoked when the component is about to update due to a transition from
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
* and `nextContext`.
*
* Use this as an opportunity to perform preparation before an update occurs.
*
* NOTE: You **cannot** use `this.setState()` in this method.
*
* @param {object} nextProps
* @param {?object} nextState
* @param {?object} nextContext
* @param {ReactReconcileTransaction} transaction
* @optional
*/
componentWillUpdate: 'DEFINE_MANY',
/**
* Invoked when the component's DOM representation has been updated.
*
* Use this as an opportunity to operate on the DOM when the component has
* been updated.
*
* @param {object} prevProps
* @param {?object} prevState
* @param {?object} prevContext
* @param {DOMElement} rootNode DOM element representing the component.
* @optional
*/
componentDidUpdate: 'DEFINE_MANY',
/**
* Invoked when the component is about to be removed from its parent and have
* its DOM representation destroyed.
*
* Use this as an opportunity to deallocate any external resources.
*
* NOTE: There is no `componentDidUnmount` since your component will have been
* destroyed by that point.
*
* @optional
*/
componentWillUnmount: 'DEFINE_MANY',
/**
* Replacement for (deprecated) `componentWillMount`.
*
* @optional
*/
UNSAFE_componentWillMount: 'DEFINE_MANY',
/**
* Replacement for (deprecated) `componentWillReceiveProps`.
*
* @optional
*/
UNSAFE_componentWillReceiveProps: 'DEFINE_MANY',
/**
* Replacement for (deprecated) `componentWillUpdate`.
*
* @optional
*/
UNSAFE_componentWillUpdate: 'DEFINE_MANY',
// ==== Advanced methods ====
/**
* Updates the component's currently mounted DOM representation.
*
* By default, this implements React's rendering and reconciliation algorithm.
* Sophisticated clients may wish to override this.
*
* @param {ReactReconcileTransaction} transaction
* @internal
* @overridable
*/
updateComponent: 'OVERRIDE_BASE'
};
/**
* Similar to ReactClassInterface but for static methods.
*/
var ReactClassStaticInterface = {
/**
* This method is invoked after a component is instantiated and when it
* receives new props. Return an object to update state in response to
* prop changes. Return null to indicate no change to state.
*
* If an object is returned, its keys will be merged into the existing state.
*
* @return {object || null}
* @optional
*/
getDerivedStateFromProps: 'DEFINE_MANY_MERGED'
};
/**
* Mapping from class specification keys to special processing functions.
*
* Although these are declared like instance properties in the specification
* when defining classes using `React.createClass`, they are actually static
* and are accessible on the constructor instead of the prototype. Despite
* being static, they must be defined outside of the "statics" key under
* which all other static methods are defined.
*/
var RESERVED_SPEC_KEYS = {
displayName: function(Constructor, displayName) {
Constructor.displayName = displayName;
},
mixins: function(Constructor, mixins) {
if (mixins) {
for (var i = 0; i < mixins.length; i++) {
mixSpecIntoComponent(Constructor, mixins[i]);
}
}
},
childContextTypes: function(Constructor, childContextTypes) {
if (process.env.NODE_ENV !== 'production') {
validateTypeDef(Constructor, childContextTypes, 'childContext');
}
Constructor.childContextTypes = _assign(
{},
Constructor.childContextTypes,
childContextTypes
);
},
contextTypes: function(Constructor, contextTypes) {
if (process.env.NODE_ENV !== 'production') {
validateTypeDef(Constructor, contextTypes, 'context');
}
Constructor.contextTypes = _assign(
{},
Constructor.contextTypes,
contextTypes
);
},
/**
* Special case getDefaultProps which should move into statics but requires
* automatic merging.
*/
getDefaultProps: function(Constructor, getDefaultProps) {
if (Constructor.getDefaultProps) {
Constructor.getDefaultProps = createMergedResultFunction(
Constructor.getDefaultProps,
getDefaultProps
);
} else {
Constructor.getDefaultProps = getDefaultProps;
}
},
propTypes: function(Constructor, propTypes) {
if (process.env.NODE_ENV !== 'production') {
validateTypeDef(Constructor, propTypes, 'prop');
}
Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes);
},
statics: function(Constructor, statics) {
mixStaticSpecIntoComponent(Constructor, statics);
},
autobind: function() {}
};
function validateTypeDef(Constructor, typeDef, location) {
for (var propName in typeDef) {
if (typeDef.hasOwnProperty(propName)) {
// use a warning instead of an _invariant so components
// don't show up in prod but only in __DEV__
if (process.env.NODE_ENV !== 'production') {
warning(
typeof typeDef[propName] === 'function',
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
'React.PropTypes.',
Constructor.displayName || 'ReactClass',
ReactPropTypeLocationNames[location],
propName
);
}
}
}
}
function validateMethodOverride(isAlreadyDefined, name) {
var specPolicy = ReactClassInterface.hasOwnProperty(name)
? ReactClassInterface[name]
: null;
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactClassMixin.hasOwnProperty(name)) {
_invariant(
specPolicy === 'OVERRIDE_BASE',
'ReactClassInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
);
}
// Disallow defining methods more than once unless explicitly allowed.
if (isAlreadyDefined) {
_invariant(
specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED',
'ReactClassInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
);
}
}
/**
* Mixin helper which handles policy validation and reserved
* specification keys when building React classes.
*/
function mixSpecIntoComponent(Constructor, spec) {
if (!spec) {
if (process.env.NODE_ENV !== 'production') {
var typeofSpec = typeof spec;
var isMixinValid = typeofSpec === 'object' && spec !== null;
if (process.env.NODE_ENV !== 'production') {
warning(
isMixinValid,
"%s: You're attempting to include a mixin that is either null " +
'or not an object. Check the mixins included by the component, ' +
'as well as any mixins they include themselves. ' +
'Expected object but got %s.',
Constructor.displayName || 'ReactClass',
spec === null ? null : typeofSpec
);
}
}
return;
}
_invariant(
typeof spec !== 'function',
"ReactClass: You're attempting to " +
'use a component class or function as a mixin. Instead, just use a ' +
'regular object.'
);
_invariant(
!isValidElement(spec),
"ReactClass: You're attempting to " +
'use a component as a mixin. Instead, just use a regular object.'
);
var proto = Constructor.prototype;
var autoBindPairs = proto.__reactAutoBindPairs;
// By handling mixins before any other properties, we ensure the same
// chaining order is applied to methods with DEFINE_MANY policy, whether
// mixins are listed before or after these methods in the spec.
if (spec.hasOwnProperty(MIXINS_KEY)) {
RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
}
for (var name in spec) {
if (!spec.hasOwnProperty(name)) {
continue;
}
if (name === MIXINS_KEY) {
// We have already handled mixins in a special case above.
continue;
}
var property = spec[name];
var isAlreadyDefined = proto.hasOwnProperty(name);
validateMethodOverride(isAlreadyDefined, name);
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else {
// Setup methods on prototype:
// The following member methods should not be automatically bound:
// 1. Expected ReactClass methods (in the "interface").
// 2. Overridden methods (that were mixed in).
var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
var isFunction = typeof property === 'function';
var shouldAutoBind =
isFunction &&
!isReactClassMethod &&
!isAlreadyDefined &&
spec.autobind !== false;
if (shouldAutoBind) {
autoBindPairs.push(name, property);
proto[name] = property;
} else {
if (isAlreadyDefined) {
var specPolicy = ReactClassInterface[name];
// These cases should already be caught by validateMethodOverride.
_invariant(
isReactClassMethod &&
(specPolicy === 'DEFINE_MANY_MERGED' ||
specPolicy === 'DEFINE_MANY'),
'ReactClass: Unexpected spec policy %s for key %s ' +
'when mixing in component specs.',
specPolicy,
name
);
// For methods which are defined more than once, call the existing
// methods before calling the new property, merging if appropriate.
if (specPolicy === 'DEFINE_MANY_MERGED') {
proto[name] = createMergedResultFunction(proto[name], property);
} else if (specPolicy === 'DEFINE_MANY') {
proto[name] = createChainedFunction(proto[name], property);
}
} else {
proto[name] = property;
if (process.env.NODE_ENV !== 'production') {
// Add verbose displayName to the function, which helps when looking
// at profiling tools.
if (typeof property === 'function' && spec.displayName) {
proto[name].displayName = spec.displayName + '_' + name;
}
}
}
}
}
}
}
function mixStaticSpecIntoComponent(Constructor, statics) {
if (!statics) {
return;
}
for (var name in statics) {
var property = statics[name];
if (!statics.hasOwnProperty(name)) {
continue;
}
var isReserved = name in RESERVED_SPEC_KEYS;
_invariant(
!isReserved,
'ReactClass: You are attempting to define a reserved ' +
'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
'as an instance property instead; it will still be accessible on the ' +
'constructor.',
name
);
var isAlreadyDefined = name in Constructor;
if (isAlreadyDefined) {
var specPolicy = ReactClassStaticInterface.hasOwnProperty(name)
? ReactClassStaticInterface[name]
: null;
_invariant(
specPolicy === 'DEFINE_MANY_MERGED',
'ReactClass: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be ' +
'due to a mixin.',
name
);
Constructor[name] = createMergedResultFunction(Constructor[name], property);
return;
}
Constructor[name] = property;
}
}
/**
* Merge two objects, but throw if both contain the same key.
*
* @param {object} one The first object, which is mutated.
* @param {object} two The second object
* @return {object} one after it has been mutated to contain everything in two.
*/
function mergeIntoWithNoDuplicateKeys(one, two) {
_invariant(
one && two && typeof one === 'object' && typeof two === 'object',
'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'
);
for (var key in two) {
if (two.hasOwnProperty(key)) {
_invariant(
one[key] === undefined,
'mergeIntoWithNoDuplicateKeys(): ' +
'Tried to merge two objects with the same key: `%s`. This conflict ' +
'may be due to a mixin; in particular, this may be caused by two ' +
'getInitialState() or getDefaultProps() methods returning objects ' +
'with clashing keys.',
key
);
one[key] = two[key];
}
}
return one;
}
/**
* Creates a function that invokes two functions and merges their return values.
*
* @param {function} one Function to invoke first.
* @param {function} two Function to invoke second.
* @return {function} Function that invokes the two argument functions.
* @private
*/
function createMergedResultFunction(one, two) {
return function mergedResult() {
var a = one.apply(this, arguments);
var b = two.apply(this, arguments);
if (a == null) {
return b;
} else if (b == null) {
return a;
}
var c = {};
mergeIntoWithNoDuplicateKeys(c, a);
mergeIntoWithNoDuplicateKeys(c, b);
return c;
};
}
/**
* Creates a function that invokes two functions and ignores their return vales.
*
* @param {function} one Function to invoke first.
* @param {function} two Function to invoke second.
* @return {function} Function that invokes the two argument functions.
* @private
*/
function createChainedFunction(one, two) {
return function chainedFunction() {
one.apply(this, arguments);
two.apply(this, arguments);
};
}
/**
* Binds a method to the component.
*
* @param {object} component Component whose method is going to be bound.
* @param {function} method Method to be bound.
* @return {function} The bound method.
*/
function bindAutoBindMethod(component, method) {
var boundMethod = method.bind(component);
if (process.env.NODE_ENV !== 'production') {
boundMethod.__reactBoundContext = component;
boundMethod.__reactBoundMethod = method;
boundMethod.__reactBoundArguments = null;
var componentName = component.constructor.displayName;
var _bind = boundMethod.bind;
boundMethod.bind = function(newThis) {
for (
var _len = arguments.length,
args = Array(_len > 1 ? _len - 1 : 0),
_key = 1;
_key < _len;
_key++
) {
args[_key - 1] = arguments[_key];
}
// User is trying to bind() an autobound method; we effectively will
// ignore the value of "this" that the user is trying to use, so
// let's warn.
if (newThis !== component && newThis !== null) {
if (process.env.NODE_ENV !== 'production') {
warning(
false,
'bind(): React component methods may only be bound to the ' +
'component instance. See %s',
componentName
);
}
} else if (!args.length) {
if (process.env.NODE_ENV !== 'production') {
warning(
false,
'bind(): You are binding a component method to the component. ' +
'React does this for you automatically in a high-performance ' +
'way, so you can safely remove this call. See %s',
componentName
);
}
return boundMethod;
}
var reboundMethod = _bind.apply(boundMethod, arguments);
reboundMethod.__reactBoundContext = component;
reboundMethod.__reactBoundMethod = method;
reboundMethod.__reactBoundArguments = args;
return reboundMethod;
};
}
return boundMethod;
}
/**
* Binds all auto-bound methods in a component.
*
* @param {object} component Component whose method is going to be bound.
*/
function bindAutoBindMethods(component) {
var pairs = component.__reactAutoBindPairs;
for (var i = 0; i < pairs.length; i += 2) {
var autoBindKey = pairs[i];
var method = pairs[i + 1];
component[autoBindKey] = bindAutoBindMethod(component, method);
}
}
var IsMountedPreMixin = {
componentDidMount: function() {
this.__isMounted = true;
}
};
var IsMountedPostMixin = {
componentWillUnmount: function() {
this.__isMounted = false;
}
};
/**
* Add more to the ReactClass base class. These are all legacy features and
* therefore not already part of the modern ReactComponent.
*/
var ReactClassMixin = {
/**
* TODO: This will be deprecated because state should always keep a consistent
* type signature and the only use case for this, is to avoid that.
*/
replaceState: function(newState, callback) {
this.updater.enqueueReplaceState(this, newState, callback);
},
/**
* Checks whether or not this composite component is mounted.
* @return {boolean} True if mounted, false otherwise.
* @protected
* @final
*/
isMounted: function() {
if (process.env.NODE_ENV !== 'production') {
warning(
this.__didWarnIsMounted,
'%s: isMounted is deprecated. Instead, make sure to clean up ' +
'subscriptions and pending requests in componentWillUnmount to ' +
'prevent memory leaks.',
(this.constructor && this.constructor.displayName) ||
this.name ||
'Component'
);
this.__didWarnIsMounted = true;
}
return !!this.__isMounted;
}
};
var ReactClassComponent = function() {};
_assign(
ReactClassComponent.prototype,
ReactComponent.prototype,
ReactClassMixin
);
/**
* Creates a composite component class given a class specification.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
*
* @param {object} spec Class specification (which must define `render`).
* @return {function} Component constructor function.
* @public
*/
function createClass(spec) {
// To keep our warnings more understandable, we'll use a little hack here to
// ensure that Constructor.name !== 'Constructor'. This makes sure we don't
// unnecessarily identify a class without displayName as 'Constructor'.
var Constructor = identity(function(props, context, updater) {
// This constructor gets overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
if (process.env.NODE_ENV !== 'production') {
warning(
this instanceof Constructor,
'Something is calling a React component directly. Use a factory or ' +
'JSX instead. See: https://fb.me/react-legacyfactory'
);
}
// Wire up auto-binding
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
this.state = null;
// ReactClasses doesn't have constructors. Instead, they use the
// getInitialState and componentWillMount methods for initialization.
var initialState = this.getInitialState ? this.getInitialState() : null;
if (process.env.NODE_ENV !== 'production') {
// We allow auto-mocks to proceed as if they're returning null.
if (
initialState === undefined &&
this.getInitialState._isMockFunction
) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
initialState = null;
}
}
_invariant(
typeof initialState === 'object' && !Array.isArray(initialState),
'%s.getInitialState(): must return an object or null',
Constructor.displayName || 'ReactCompositeComponent'
);
this.state = initialState;
});
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
mixSpecIntoComponent(Constructor, IsMountedPreMixin);
mixSpecIntoComponent(Constructor, spec);
mixSpecIntoComponent(Constructor, IsMountedPostMixin);
// Initialize the defaultProps property after all mixins have been merged.
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
if (process.env.NODE_ENV !== 'production') {
// This is a tag to indicate that the use of these method names is ok,
// since it's used with createClass. If it's not, then it's likely a
// mistake so we'll warn you to use the static property, property
// initializer or constructor respectively.
if (Constructor.getDefaultProps) {
Constructor.getDefaultProps.isReactClassApproved = {};
}
if (Constructor.prototype.getInitialState) {
Constructor.prototype.getInitialState.isReactClassApproved = {};
}
}
_invariant(
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
if (process.env.NODE_ENV !== 'production') {
warning(
!Constructor.prototype.componentShouldUpdate,
'%s has a method called ' +
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
'The name is phrased as a question because the function is ' +
'expected to return a value.',
spec.displayName || 'A component'
);
warning(
!Constructor.prototype.componentWillRecieveProps,
'%s has a method called ' +
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
spec.displayName || 'A component'
);
warning(
!Constructor.prototype.UNSAFE_componentWillRecieveProps,
'%s has a method called UNSAFE_componentWillRecieveProps(). ' +
'Did you mean UNSAFE_componentWillReceiveProps()?',
spec.displayName || 'A component'
);
}
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in