react-phone-number-input
Version:
Telephone input for React
1,331 lines (1,076 loc) • 44.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _libphonenumberJs = require('libphonenumber-js');
var _inputFormat = require('input-format');
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _Select = require('react-responsive-ui/commonjs/Select');
var _Select2 = _interopRequireDefault(_Select);
var _countries = require('./countries');
var _countries2 = _interopRequireDefault(_countries);
var _InternationalIcon = require('./InternationalIcon');
var _InternationalIcon2 = _interopRequireDefault(_InternationalIcon);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// A list of all country codes
var all_countries = [];
// Country code to country name map
// Could have been `import { Select } from 'react-responsive-ui'`
// but in that case Webpack bundles the whole `react-responsive-ui` package.
var default_dictionary = {
International: 'International'
// Populate `all_countries` and `default_dictionary`
};var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(_countries2.default), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var item = _step.value;
var _item = (0, _slicedToArray3.default)(item, 2),
code = _item[0],
name = _item[1];
all_countries.push(code.toUpperCase());
default_dictionary[code.toUpperCase()] = name;
}
// Default country flag icon.
// `<img/>` is wrapped to prevent the SVG
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var FlagComponent = function FlagComponent(_ref) {
var countryCode = _ref.countryCode,
flagsPath = _ref.flagsPath;
return _react2.default.createElement(
'div',
{ className: 'react-phone-number-input__icon' },
_react2.default.createElement('img', {
alt: countryCode,
className: 'react-phone-number-input__icon-image',
src: '' + flagsPath + countryCode.toLowerCase() + '.svg' })
);
};
// Allows passing custom `libphonenumber-js` metadata
// to reduce the overall bundle size.
var Input = function (_Component) {
(0, _inherits3.default)(Input, _Component);
function Input(props) {
(0, _classCallCheck3.default)(this, Input);
var _this = (0, _possibleConstructorReturn3.default)(this, (Input.__proto__ || (0, _getPrototypeOf2.default)(Input)).call(this, props));
_initialiseProps.call(_this);
var _this$props = _this.props,
countries = _this$props.countries,
value = _this$props.value,
dictionary = _this$props.dictionary,
international = _this$props.international,
internationalIcon = _this$props.internationalIcon,
flags = _this$props.flags;
var country = _this.props.country;
// Normalize `country` code
country = normalize_country_code(country, dictionary);
// Autodetect country if value is set
// and is international (which it should be)
if (value && value[0] === '+') {
// `country` will be left `undefined` in case of non-detection
country = (0, _libphonenumberJs.parse)(value).country;
}
// If there will be no "International" option
// then a `country` must be selected.
if (!should_add_international_option(_this.props) && !country) {
country = countries[0];
}
// Set the currently selected country
_this.state.country_code = country;
// If a phone number `value` is passed then format it
if (value) {
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
_this.state.value_property = value;
// Set the currently entered `value`.
// State `value` is either in international plaintext or just plaintext format.
// (e.g. `+78005553535`, `1234567`)
_this.state.value = _this.get_input_value_depending_on_the_country_selected(value, country);
}
// `<Select/>` options
_this.select_options = [];
// Whether custom country names are supplied
var using_custom_country_names = false;
// Add a `<Select/>` option for each country
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)(countries), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var country_code = _step2.value;
if (dictionary[country_code]) {
using_custom_country_names = true;
}
_this.select_options.push({
value: country_code,
label: dictionary[country_code] || default_dictionary[country_code],
icon: get_country_option_icon(country_code, _this.props)
});
}
// Sort the list of countries alphabetically
// (if `String.localeCompare` is available).
// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
// (Which means: IE >= 11, and does not work in Safari as of May 2017)
//
// This is only done when custom country names
// are supplied via `dictionary` property
// because by default all country names are already sorted.
//
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
if (using_custom_country_names && String.prototype.localeCompare) {
_this.select_options.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
}
// Add the "International" option to the country list (if suitable)
if (should_add_international_option(_this.props)) {
_this.select_options.unshift({
label: dictionary['International'] || default_dictionary['International'],
icon: flags === false ? undefined : internationalIcon
});
}
return _this;
}
// Determines the text `<input/>` `value`
// depending on `this.props.value` and the country selected.
//
// E.g. when a country is selected and `this.props.value`
// is in international format for this country
// then it can be converted to national format
// (if `convertToNational` is `true`).
//
(0, _createClass3.default)(Input, [{
key: 'get_input_value_depending_on_the_country_selected',
value: function get_input_value_depending_on_the_country_selected(value, country_code) {
var _props = this.props,
metadata = _props.metadata,
convertToNational = _props.convertToNational;
if (!value) {
return;
}
// If the country code is specified
if (country_code) {
// and the phone is in international format
// and should convert to national phone number
if (value[0] === '+' && convertToNational) {
// If it's a fully-entered phone number
// that converts into a valid national number for this country
// then the value is set to be that national number.
var parsed = (0, _libphonenumberJs.parse)(value, metadata);
if (parsed.country === country_code) {
var input_value = (0, _libphonenumberJs.format)(parsed.phone, country_code, 'National', metadata);
return this.format(input_value, country_code).text;
}
}
}
// The country is not set.
// Must be an international phone number then.
else if (value[0] !== '+') {
// The following causes the caret to move the end of the input field
// but it's unlikely any sane person would like to erase the `+` sign
// while inputting an international phone number without any country selected.
return '+' + value;
}
return value;
}
}, {
key: 'set_country_code_value',
value: function set_country_code_value(country_code) {
var onCountryChange = this.props.onCountryChange;
if (onCountryChange) {
onCountryChange(country_code);
}
this.setState({ country_code: country_code });
}
// `<select/>` `onChange` handler
// `input-format` `parse` character function
// https://github.com/catamphetamine/input-format
// `input-format` `format` function
// https://github.com/catamphetamine/input-format
// Returns `true` if the country is available in the list
// Can be called externally
// `<input/>` `onKeyDown` handler
// `<input/>` `onChange` handler.
// Updates `this.props.value` (in e.164 phone number format)
// according to the new `this.state.value`.
// (keeps them in sync)
// This `onBlur` interceptor is a workaround for `redux-form`,
// so that it gets the parsed `value` in its `onBlur` handler,
// not the formatted one.
// A developer is not supposed to pass this `onBlur` property manually.
// Instead, `redux-form` passes `onBlur` to this component automatically
// and this component passes this `onBlur` property further to
// `input-format`'s `<ReactInput/>` which then modifies this `onBlur` handler
// to return the correct parsed `value` so that it all works with `redux-form`.
// When country `<select/>` is toggled
// Focuses the `<input/>` field
// on tab out of the country `<select/>`
}, {
key: 'can_change_country',
// Can a user change the default country or not.
value: function can_change_country() {
var countries = this.props.countries;
// If `countries` is empty,
// then only "International" option is available,
// so can't switch it.
//
// If `countries` is a single allowed country,
// then cant's switch it.
//
return countries.length > 1;
}
// Listen for default country property:
// if it is set after the page loads
// and the user hasn't selected a country yet
// then select the default country.
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(new_props) {
var _props2 = this.props,
countries = _props2.countries,
value = _props2.value,
dictionary = _props2.dictionary;
// Normalize `country` codes
var country = normalize_country_code(this.props.country, dictionary);
var new_country = normalize_country_code(new_props.country, dictionary);
// If the default country changed
// (e.g. in case of IP detection)
if (new_country !== country) {
// If the phone number input field is currently empty
// (e.g. not touched yet) then change the selected `country`
// to the newly passed one (e.g. as a result of a GeoIP query)
if (!value) {
// If the passed `country` allowed then update it
if (countries.indexOf(new_country) !== -1) {
// Set the new `country`
this.set_country(new_country, false);
}
}
}
// This code is executed:
// * after `this.props.onChange(value)` is called
// * if the `value` was externally set (e.g. cleared)
if (new_props.value !== value) {
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
// This is an optimization, it's like `shouldComponentUpdate()`.
// This is supposed to save some CPU cycles, maybe not much, I didn't check.
// Or maybe there was some other reason for this I don't remember now.
if (new_props.value !== this.state.value_property) {
// Update the `value` because it was externally set
// Country code gets updated too
var country_code = this.state.country_code;
// Autodetect country if `value` is set
// and is international (which it should be)
if (new_props.value && new_props.value[0] === '+') {
// `parse().country` will be `undefined` in case of non-detection
country_code = (0, _libphonenumberJs.parse)(new_props.value).country || country_code;
}
this.setState({
country_code: country_code,
value: this.get_input_value_depending_on_the_country_selected(new_props.value, country_code),
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
value_property: new_props.value
});
}
}
}
}, {
key: 'render',
value: function render() {
var _props3 = this.props,
saveOnIcons = _props3.saveOnIcons,
showCountrySelect = _props3.showCountrySelect,
nativeExpanded = _props3.nativeExpanded,
disabled = _props3.disabled,
autoComplete = _props3.autoComplete,
selectTabIndex = _props3.selectTabIndex,
selectMaxItems = _props3.selectMaxItems,
selectAriaLabel = _props3.selectAriaLabel,
selectCloseAriaLabel = _props3.selectCloseAriaLabel,
inputTabIndex = _props3.inputTabIndex,
style = _props3.style,
selectStyle = _props3.selectStyle,
inputStyle = _props3.inputStyle,
className = _props3.className,
inputClassName = _props3.inputClassName,
error = _props3.error,
indicateInvalid = _props3.indicateInvalid,
SelectComponent = _props3.selectComponent,
InputComponent = _props3.inputComponent,
dictionary = _props3.dictionary,
countries = _props3.countries,
country = _props3.country,
onCountryChange = _props3.onCountryChange,
flags = _props3.flags,
flagComponent = _props3.flagComponent,
flagsPath = _props3.flagsPath,
international = _props3.international,
internationalIcon = _props3.internationalIcon,
convertToNational = _props3.convertToNational,
metadata = _props3.metadata,
input_props = (0, _objectWithoutProperties3.default)(_props3, ['saveOnIcons', 'showCountrySelect', 'nativeExpanded', 'disabled', 'autoComplete', 'selectTabIndex', 'selectMaxItems', 'selectAriaLabel', 'selectCloseAriaLabel', 'inputTabIndex', 'style', 'selectStyle', 'inputStyle', 'className', 'inputClassName', 'error', 'indicateInvalid', 'selectComponent', 'inputComponent', 'dictionary', 'countries', 'country', 'onCountryChange', 'flags', 'flagComponent', 'flagsPath', 'international', 'internationalIcon', 'convertToNational', 'metadata']);
// `inputTabIndex` is deprecated, use just `tabIndex` instead
if (inputTabIndex) {
input_props.tabIndex = inputTabIndex;
}
var _state = this.state,
value = _state.value,
country_code = _state.country_code,
country_select_is_shown = _state.country_select_is_shown;
// `type="tel"` was reported to have issues with
// Samsung keyboards caret position on Android OS.
// https://github.com/catamphetamine/react-phone-number-input/issues/59
// One may choose to pass `type="text"` in those cases
// but this will result in a non-digital input keyboard.
return _react2.default.createElement(
'div',
{
style: style,
className: (0, _classnames2.default)('react-phone-number-input', {
'react-phone-number-input--invalid': error && indicateInvalid
}, className) },
_react2.default.createElement(
'div',
{ className: 'react-phone-number-input__row' },
showCountrySelect && this.can_change_country() && _react2.default.createElement(SelectComponent, {
ref: this.store_select_instance,
value: country_code,
options: this.select_options,
onChange: this.set_country,
disabled: disabled,
onToggle: this.country_select_toggled,
onTabOut: this.on_country_select_tab_out,
nativeExpanded: nativeExpanded,
autocomplete: true,
autocompleteShowAll: true,
maxItems: selectMaxItems,
concise: true,
tabIndex: selectTabIndex,
focusUponSelection: false,
saveOnIcons: saveOnIcons,
name: input_props.name ? input_props.name + '__country' : undefined,
ariaLabel: selectAriaLabel,
closeAriaLabel: selectCloseAriaLabel,
style: selectStyle,
className: (0, _classnames2.default)('react-phone-number-input__country', {
'react-phone-number-input__country--native-expanded': nativeExpanded
}),
inputClassName: inputClassName }),
!country_select_is_shown && _react2.default.createElement(InputComponent, (0, _extends3.default)({
type: 'tel'
}, input_props, {
ref: this.store_input_instance,
value: value,
onChange: this.on_change,
onBlur: this.on_blur,
disabled: disabled,
autoComplete: autoComplete,
parse: this.parse_character,
format: this.format,
onKeyDown: this.on_key_down,
style: inputStyle,
className: (0, _classnames2.default)('rrui__input', 'rrui__input-element', 'rrui__input-field', {
'rrui__input-field--invalid': error && indicateInvalid,
'rrui__input-field--disabled': disabled
}, 'react-phone-number-input__phone', inputClassName) }))
),
error && indicateInvalid && _react2.default.createElement(
'div',
{ className: (0, _classnames2.default)('rrui__input-error', 'react-phone-number-input__error') },
error
)
);
}
}]);
return Input;
}(_react.Component);
// Parses a partially entered phone number
// and returns the national number so far.
// Not using `libphonenumber-js`'s `parse`
// function here because `parse` only works
// when the number is fully entered,
// and this one is for partially entered number.
Input.propTypes = {
// Phone number `value`.
// Is a plaintext international phone number
// (e.g. "+12223333333" for USA)
value: _propTypes2.default.string,
// This handler is called each time
// the phone number <input/> changes its textual value.
onChange: _propTypes2.default.func.isRequired,
// This `onBlur` interceptor is a workaround for `redux-form`,
// so that it gets the parsed `value` in its `onBlur` handler,
// not the formatted one.
// A developer is not supposed to pass this `onBlur` property manually.
// Instead, `redux-form` passes `onBlur` to this component automatically
// and this component passes this `onBlur` property further to
// `input-format`'s `<ReactInput/>` which then modifies this `onBlur` handler
// to return the correct parsed `value` so that it all works with `redux-form`.
onBlur: _propTypes2.default.func,
// Set `onKeyDown` handler.
// Can be used in special cases to handle e.g. enter pressed
onKeyDown: _propTypes2.default.func,
// Disables both the <input/> and the <select/>
// (is `false` by default)
disabled: _propTypes2.default.bool.isRequired,
// An error message below the `<input/>`
error: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.bool]),
// If this flag is `true` then the `error` is shown.
// If this flag is `false` then the `error` is not shown (even if passed).
indicateInvalid: _propTypes2.default.bool,
// Remembers the input and also autofills it
// with a previously remembered phone number.
// Default value: "tel".
//
// https://developers.google.com/web/updates/2015/06/checkout-faster-with-autofill
//
// "So when should you use autocomplete="off"?
// One example is when you've implemented your own version
// of autocomplete for search. Another example is any form field
// where users will input and submit different kinds of information
// where it would not be useful to have the browser remember
// what was submitted previously".
//
autoComplete: _propTypes2.default.string.isRequired,
// Two-letter country code
// to be used as the default country
// for local (non-international) phone numbers.
country: _propTypes2.default.string,
// Is called when the selected country changes
// (either by a user manually, or by autoparsing
// an international phone number being input).
// This handler does not need to update the `country` property.
// It's simply a listener for those who might need that for whatever purpose.
onCountryChange: _propTypes2.default.func,
// Localization dictionary:
// `{ International: 'Международный', RU: 'Россия', US: 'США', ... }`
dictionary: _propTypes2.default.objectOf(_propTypes2.default.string).isRequired,
// An optional list of allowed countries
countries: _propTypes2.default.arrayOf(_propTypes2.default.string).isRequired,
// Custom national flag icons
flags: _propTypes2.default.oneOfType([_propTypes2.default.bool,
// Legacy behaviour, will be removed
// in some future major version upgrade.
_propTypes2.default.objectOf(_propTypes2.default.element)]),
// Flag icon component
flagComponent: _propTypes2.default.func.isRequired,
// A base URL path for national flag SVG icons.
// By default it uses the ones from `flag-icon-css` github repo.
flagsPath: _propTypes2.default.string.isRequired,
// Whether to use native `<select/>` when expanded
nativeExpanded: _propTypes2.default.bool.isRequired,
// If set to `false`, then country flags will be shown
// for all countries in the options list
// (not just for selected country).
saveOnIcons: _propTypes2.default.bool.isRequired,
// Whether to show country `<Select/>`
// (is `true` by default)
showCountrySelect: _propTypes2.default.bool.isRequired,
// Whether to add the "International" option
// to the list of countries.
international: _propTypes2.default.bool,
// Custom "International" phone number type icon.
internationalIcon: _propTypes2.default.element.isRequired,
// Should the initially passed phone number `value`
// be converted to a national phone number for its country.
// (is `false` by default)
convertToNational: _propTypes2.default.bool.isRequired,
// HTML `tabindex` attribute for the country select
selectTabIndex: _propTypes2.default.number,
// Defines the height of the dropdown country select list
selectMaxItems: _propTypes2.default.number,
// (deprecated, use just `tabIndex` instead)
// HTML `tabindex` attribute for the phone number `<input/>`
inputTabIndex: _propTypes2.default.number,
// `aria-label` for the `<Select/>`'s `<button/>`
selectAriaLabel: _propTypes2.default.string,
// `aria-label` for the `<Select/>`'s "Close" button
// (which is an "x" visible in fullscreen mode).
// (not yet implemented but is likely to be)
selectCloseAriaLabel: _propTypes2.default.string,
// CSS style object
style: _propTypes2.default.object,
// Inline CSS styles for country `<select/>`
selectStyle: _propTypes2.default.object,
// Inline CSS styles for phone number `<input/>`
inputStyle: _propTypes2.default.object,
// Component CSS class
className: _propTypes2.default.string,
// `<input/>` CSS class
// (both for the phone number `<input/>` and the autocomplete `<input/>`)
inputClassName: _propTypes2.default.string,
// `<Select/>` from `react-responsive-ui` is used by default
selectComponent: _propTypes2.default.func.isRequired,
// `<ReactInput/>` from `input-format` is used by default
inputComponent: _propTypes2.default.func.isRequired,
// `libphonenumber-js` metadata
metadata: _propTypes2.default.shape({
country_calling_codes: _propTypes2.default.object.isRequired,
countries: _propTypes2.default.object.isRequired
}).isRequired
};
Input.defaultProps = {
// Is enabled
disabled: false,
// Remember (and autofill) as a phone number
autoComplete: 'tel',
// Include all countries by default
countries: all_countries,
// Flag icon component
flagComponent: FlagComponent,
// By default use the ones from `flag-icon-css` github repo.
flagsPath: 'https://lipis.github.io/flag-icon-css/flags/4x3/',
// Default international icon (globe)
internationalIcon: _react2.default.createElement(
'div',
{ className: (0, _classnames2.default)('react-phone-number-input__icon', 'react-phone-number-input__icon--international') },
_react2.default.createElement(_InternationalIcon2.default, null)
),
// Custom country names
dictionary: {},
// Whether to use native `<select/>` when expanded
nativeExpanded: false,
// Don't show flags for all countries in the options list
// (show it just for selected country).
// (to save user's traffic because all flags are about 3 MegaBytes)
saveOnIcons: true,
// Show country `<Select/>` by default
showCountrySelect: true,
// Don't convert the initially passed phone number `value`
// to a national phone number for its country.
// The reason is that the newer generation grows up when
// there are no stationary phones and therefore everyone inputs
// phone numbers with a `+` in their smartphones so local phone numbers
// should now be considered obsolete.
convertToNational: false,
// `<Select/>` from `react-responsive-ui` is used by default
selectComponent: _Select2.default,
// `<ReactInput/>` from `input-format` is used by default
inputComponent: _inputFormat.ReactInput
};
var _initialiseProps = function _initialiseProps() {
var _this2 = this;
this.state = {};
this.set_country = function (country_code, focus) {
var _props4 = _this2.props,
metadata = _props4.metadata,
convertToNational = _props4.convertToNational;
// Previously selected country
var previous_country_code = _this2.state.country_code;
_this2.set_country_code_value(country_code);
// Adjust the phone number (`value`)
// according to the selected `country_code`
var value = _this2.state.value;
// If the `value` property holds any digits already
if (value) {
// If switching to a country from International or another country
if (country_code) {
// If the phone number was entered in international format.
// The phone number may be incomplete.
// The phone number entered not necessarily starts with
// the previously selected country phone prefix.
if (value[0] === '+') {
// If the international phone number already contains
// any country phone code then trim the country phone code part.
// (that also could be the newly selected country phone code prefix)
value = strip_country_phone_code(value, metadata);
// Else just trim the + sign
if (value[0] === '+') {
value = value.slice('+'.length);
}
// Prepend country phone code part if `convertToNational` is not set
if (!convertToNational) {
value = '+' + (0, _libphonenumberJs.getPhoneCode)(country_code) + value;
}
}
}
// If switching to International from a country
if (previous_country_code && !country_code) {
// If no leading `+` sign
if (value[0] !== '+') {
// Format the local phone number as an international one.
// The phone number entered not necessarily even starts with
// the previously selected country phone prefix.
// Even if the phone number belongs to whole another country
// it will still be parsed into some national phone number.
var national_number = parse_partial_number(value, previous_country_code, metadata);
value = (0, _libphonenumberJs.format)(national_number, previous_country_code, 'E.164', metadata);
}
}
// Update the adjusted `<input/>` `value`
// and update `this.props.value` (in e.164 phone number format)
// according to the new `this.state.value`.
// (keep them in sync)
_this2.on_change(value, country_code, true);
}
// Disabling this feature because if a user selects a country
// then it means he doesn't know how to input his phone number
// in international format therefore not forcing it
// by prepending `+${getPhoneCode(country_code)}`.
//
// else
// {
// // If the `value` property is `undefined`
// // (which means the `<input/>` is either empty
// // or just the country phone code part is entered)
// // and `convertToNational` wasn't set to `true`
// // then populate `<input/>` with the selected country
// // phone code prefix.
// if (!convertToNational && country_code)
// {
// // Update the adjusted `<input/>` `value`
// // and update `this.props.value` (in e.164 phone number format)
// // according to the new `this.state.value`.
// // (keep them in sync)
// this.on_change(`+${getPhoneCode(country_code)}`, country_code, true)
// }
// }
// Focus the phone number input upon country selection
// (do it in a timeout because the `<input/>`
// is hidden while selecting a country)
if (focus !== false) {
setTimeout(_this2.focus, 0);
}
};
this.parse_character = function (character, value) {
var countries = _this2.props.countries;
if (character === '+') {
// Only allow a leading `+`
if (!value) {
// If the "International" option is available
// then allow the leading `+` because it's meant to be this way.
//
// Otherwise, the leading `+` will either erase all subsequent digits
// (if they're not appropriate for the selected country)
// or the subsequent digits (if any) will join the `+`
// forming an international phone number. Because a user
// might be comfortable with entering an international phone number
// (i.e. with country code) rather than the local one.
// Therefore such possibility is given.
//
return character;
}
}
// For digits.
// Converts wide-ascii and arabic-indic numerals to European numerals.
// E.g. in Iraq they don't write `+442323234` but rather `+٤٤٢٣٢٣٢٣٤`.
else if (_libphonenumberJs.DIGITS[character]) {
var metadata = _this2.props.metadata;
var country_code = _this2.state.country_code;
// If the "International" option is not available
// and if the value has a leading `+`
// then it means that the phone number being entered
// is an international one, so only allow the country phone code
// for the selected country to be entered.
if (!should_add_international_option(_this2.props) && value && value[0] === '+') {
if (!could_phone_number_belong_to_country(value + _libphonenumberJs.DIGITS[character], country_code, metadata)) {
return;
}
}
return _libphonenumberJs.DIGITS[character];
}
};
this.format = function (input_text) {
var country_code = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this2.state.country_code;
var metadata = _this2.props.metadata;
// "As you type" formatter
var formatter = new _libphonenumberJs.AsYouType(country_code, metadata);
// Is used to check if a country code can already be derived
_this2.formatter = formatter;
// Format phone number
var text = formatter.input(input_text);
return { text: text, template: formatter.template };
};
this.is_selectable_country = function (country_code) {
var countries = _this2.props.countries;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = (0, _getIterator3.default)(countries), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var available_country_code = _step3.value;
if (available_country_code === country_code) {
return true;
}
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
};
this.focus = function () {
_reactDom2.default.findDOMNode(_this2.input).focus();
};
this.on_key_down = function (event) {
var onKeyDown = _this2.props.onKeyDown;
// Expand country `<select/>`` on "Down arrow" key press
if (event.keyCode === 40) {
_this2.select.toggle();
}
if (onKeyDown) {
onKeyDown(event);
}
};
this.on_change = function (value) {
var country_code = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this2.state.country_code;
var changed_country = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var _props5 = _this2.props,
metadata = _props5.metadata,
onChange = _props5.onChange;
// If the `<input/>` is empty then just exit
if (!value) {
return _this2.setState({
// State `value` is the parsed input value
// (e.g. `+78005553535`, `1234567`).
// This is not `this.props.value`
// i.e. it's not neccessarily an international plaintext phone number,
// it's just the `value` parsed by `input-format`.
value: value,
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
value_property: value
},
// Write the new `this.props.value`.
function () {
return onChange(value);
});
}
// For international phone numbers
if (value[0] === '+') {
// If an international phone number is being erased up to the first `+` sign
// or if an international phone number is just starting (with a `+` sign)
// then unset the current country because it's clear that a user intends to change it.
if (value.length === 1) {
// If "International" country option has not been disabled
// then reset the currently selected country.
if (!changed_country && should_add_international_option(_this2.props)) {
country_code = undefined;
_this2.set_country_code_value(country_code);
}
} else {
// If a phone number is being input as an international one
// and the country code can already be derived,
// then switch the country.
// (`001` is a special "non-geograpical entity" code in `libphonenumber` library)
if (!changed_country && _this2.formatter.country && _this2.formatter.country !== '001' && _this2.is_selectable_country(_this2.formatter.country)) {
country_code = _this2.formatter.country;
_this2.set_country_code_value(country_code);
}
// If "International" country option has not been disabled
// and the international phone number entered doesn't correspond
// to the currently selected country then reset the currently selected country.
else if (!changed_country && should_add_international_option(_this2.props) && country_code && value.indexOf((0, _libphonenumberJs.getPhoneCode)(country_code) !== '+'.length)) {
country_code = undefined;
_this2.set_country_code_value(country_code);
}
}
}
// If "International" mode is selected
// and the `value` doesn't start with a + sign,
// then prepend it to the `value`.
else if (!country_code) {
value = '+' + value;
}
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
var value_property = void 0;
// `value` equal to `+` makes no sense
if (value === '+') {
value_property = undefined;
}
// If a phone number is in international format then check
// that the phone number entered belongs to the selected country.
else if (country_code && value[0] === '+' && !(value.indexOf('+' + (0, _libphonenumberJs.getPhoneCode)(country_code)) === 0 && value.length > ('+' + (0, _libphonenumberJs.getPhoneCode)(country_code)).length)) {
value_property = undefined;
}
// Should be a most-probably-valid phone number
else {
// Convert `value` to E.164 phone number format
value_property = e164(value, country_code, metadata);
}
_this2.setState({
// State `value` is the parsed input value
// (e.g. `+78005553535`, `1234567`).
// This is not `this.props.value`
// i.e. it's not neccessarily an international plaintext phone number,
// it's just the `value` parsed by `input-format`.
value: value,
// `this.state.value_property` is the `this.props.value`
// which corresponding to `this.state.value`.
// It is being compared in `componentWillReceiveProps()`
// against `newProps.value` to find out if the new `value` property
// needs `this.state.value` recalculation.
value_property: value_property
},
// Write the new `this.props.value`.
function () {
return onChange(value_property);
});
};
this.on_blur = function (event) {
var onBlur = _this2.props.onBlur;
var value_property = _this2.state.value_property;
if (!onBlur) {
return;
}
var _event = (0, _extends3.default)({}, event, {
target: (0, _extends3.default)({}, event.target, {
value: value_property
})
// For `redux-form` event detection.
// https://github.com/erikras/redux-form/blob/v5/src/events/isEvent.js
});_event.stopPropagation = event.stopPropagation;
_event.preventDefault = event.preventDefault;
return onBlur(_event);
};
this.country_select_toggled = function (is_shown) {
_this2.setState({ country_select_is_shown: is_shown });
};
this.on_country_select_tab_out = function (event) {
event.preventDefault();
// Focus the phone number input upon country selection
// (do it in a timeout because the `<input/>`
// is hidden while selecting a country)
setTimeout(_this2.focus, 0);
};
this.store_select_instance = function (instance) {
_this2.select = instance;
};
this.store_input_instance = function (instance) {
_this2.input = instance;
};
};
exports.default = Input;
function parse_partial_number(value, country_code, metadata) {
// "As you type" formatter
var formatter = new _libphonenumberJs.AsYouType(country_code, metadata);
// Input partially entered phone number
formatter.input(value);
// Return the parsed partial national phone number
return formatter.national_number;
}
// Converts `value` to E.164 phone number format
function e164(value, country_code, metadata) {
if (!value) {
return undefined;
}
// If the phone number is being input in an international format
if (value[0] === '+') {
// If it's just the `+` sign
if (value.length === 1) {
return undefined;
}
// If there are some digits, the `value` is returned as is
return value;
}
// For non-international phone number a country code is required
if (!country_code) {
return undefined;
}
// The phone number is being input in a country-specific format
var partial_national_number = parse_partial_number(value, country_code);
if (!partial_national_number) {
return undefined;
}
// The value is converted to international plaintext
return (0, _libphonenumberJs.format)(partial_national_number, country_code, 'E.164', metadata);
}
// Gets country flag element by country code
function get_country_option_icon(countryCode, _ref2) {
var flags = _ref2.flags,
flagsPath = _ref2.flagsPath,
flagComponent = _ref2.flagComponent;
if (flags === false) {
return undefined;
}
if (flags && flags[countryCode]) {
return flags[countryCode];
}
return _react2.default.createElement(flagComponent, { countryCode: countryCode, flagsPath: flagsPath });
}
// Whether to add the "International" option to the list of countries
function should_add_international_option(properties) {
var countries = properties.countries,
international = properties.international;
// If this behaviour is explicitly set, then do as it says.
if (international !== undefined) {
return international;
}
// If `countries` is empty,
// then only "International" option is available, so add it.
if (countries.length === 0) {
return true;
}
// If `countries` is a single allowed country,
// then don't add the "International" option
// because it would make no sense.
if (countries.length === 1) {
return false;
}
// Show the "International" option by default
return true;
}
// Is it possible that the partially entered phone number belongs to the given country
function could_phone_number_belong_to_country(phone_number, country_code, metadata) {
// Strip the leading `+`
var phone_number_digits = phone_number.slice('+'.length);
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = (0, _getIterator3.default)((0, _keys2.default)(metadata.country_calling_codes)), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var country_phone_code = _step4.value;
var possible_country_phone_code = phone_number_digits.substring(0, country_phone_code.length);
if (country_phone_code.indexOf(possible_country_phone_code) === 0) {
// This country phone code is possible.
// Does the given country correspond to this country phone code.
if (metadata.country_calling_codes[country_phone_code].indexOf(country_code) >= 0) {
return true;
}
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
}
// If a formatted phone number is an international one
// then it strips the `+${country_phone_code}` prefix from the formatted number.
function strip_country_phone_code(formatted_number, metadata) {
if (!formatted_number || formatted_number[0] !== '+' || formatted_number === '+') {
return formatted_number;
}
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = (0, _getIterator3.default)((0, _keys2.default)(metadata.country_calling_codes)), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var country_phone_code = _step5.value;
if (formatted_number.indexOf(country_phone_code) === '+'.length) {
return formatted_number.slice('+'.length + country_phone_code.length).trim();
}
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
return formatted_number;
}
// Validates country code
function normalize_country_code(country, dictionary) {
// Normalize `country` if it's an empty string
if (country === '') {
country = undefined;
}
// No country is selected ("International")
if (country === undefined || country === null) {
return country;
}
// Check that `country` code exists
if (dictionary[country] || default_dictionary[country]) {
return country;
}
throw new Error('Unknown country: "' + country + '"');
}
//# sourceMappingURL=Input.js.map