UNPKG

react-phone-number-input

Version:

Telephone number input React component

236 lines (229 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = getPhoneInputWithCountryStateUpdateFromNewProps; exports.valuesAreEqual = valuesAreEqual; var _phoneInputHelpers = require("./phoneInputHelpers.js"); var _isE164Number = require("./isE164Number.js"); var _getInternationalPhoneNumberPrefix = _interopRequireDefault(require("./getInternationalPhoneNumberPrefix.js")); var _countries = require("./countries.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function getPhoneInputWithCountryStateUpdateFromNewProps(props, prevProps, state) { var metadata = props.metadata, countries = props.countries, newDefaultCountry = props.defaultCountry, newValue = props.value, newReset = props.reset, international = props.international, displayInitialValueAsLocalNumber = props.displayInitialValueAsLocalNumber, initialValueFormat = props.initialValueFormat; var prevDefaultCountry = prevProps.defaultCountry, prevValue = prevProps.value, prevReset = prevProps.reset; var country = state.country, value = state.value, hasUserSelectedACountry = state.hasUserSelectedACountry, latestCountrySelectedByUser = state.latestCountrySelectedByUser; var _getInitialPhoneDigits = function _getInitialPhoneDigits(parameters) { return (0, _phoneInputHelpers.getInitialPhoneDigits)(_objectSpread(_objectSpread({}, parameters), {}, { international: international, useNationalFormat: displayInitialValueAsLocalNumber || initialValueFormat === 'national', metadata: metadata })); }; // Some users requested a way to reset the component // (both number `<input/>` and country `<select/>`). // Whenever `reset` property changes both number `<input/>` // and country `<select/>` are reset. // It's not implemented as some instance `.reset()` method // because `ref` is forwarded to `<input/>`. // It's also not replaced with just resetting `country` on // external `value` reset, because a user could select a country // and then not input any `value`, and so the selected country // would be "stuck", if not using this `reset` property. // https://github.com/catamphetamine/react-phone-number-input/issues/300 if (newReset !== prevReset) { return { phoneDigits: _getInitialPhoneDigits({ value: undefined, defaultCountry: newDefaultCountry }), value: undefined, country: newDefaultCountry, latestCountrySelectedByUser: undefined, hasUserSelectedACountry: undefined }; } // `value` is the value currently shown in the component: // it's stored in the component's `state`, and it's not the `value` property. // `prevValue` is "previous `value` property". // `newValue` is "new `value` property". // If the default country changed // (e.g. in case of ajax GeoIP detection after page loaded) // then select it, but only if the user hasn't already manually // selected a country, and no phone number has been manually entered so far. // Because if the user has already started inputting a phone number // then they're okay with no country being selected at all ("International") // and they don't want to be disturbed, don't want their input to be screwed, etc. if (newDefaultCountry !== prevDefaultCountry) { var isNewDefaultCountrySupported = !newDefaultCountry || (0, _countries.isCountrySupportedWithError)(newDefaultCountry, metadata); var noValueHasBeenEnteredByTheUser = // By default, "no value has been entered" means `value` is `undefined`. !value || // When `international` is `true`, and some country has been pre-selected, // then the `<input/>` contains a pre-filled value of `+${countryCallingCode}${leadingDigits}`, // so in case of `international` being `true`, "the user hasn't entered anything" situation // doesn't just mean `value` is `undefined`, but could also mean `value` is `+${countryCallingCode}`. international && value === _getInitialPhoneDigits({ value: undefined, defaultCountry: prevDefaultCountry }); // Only update the `defaultCountry` property if no phone number // has been entered by the user or pre-set by the application. var noValueHasBeenEntered = !newValue && noValueHasBeenEnteredByTheUser; if (!hasUserSelectedACountry && isNewDefaultCountrySupported && noValueHasBeenEntered) { return { country: newDefaultCountry, // If `phoneDigits` is empty, then automatically select the new `country` // and set `phoneDigits` to `+{getCountryCallingCode(newCountry)}`. // The code assumes that "no phone number has been entered by the user", // and no `value` property has been passed, so the `phoneNumber` parameter // of `_getInitialPhoneDigits({ value, phoneNumber, ... })` is `undefined`. phoneDigits: _getInitialPhoneDigits({ value: undefined, defaultCountry: newDefaultCountry }), // `value` is `undefined` and it stays so. value: undefined }; } } // If a new `value` is set externally. // (e.g. as a result of an ajax API request // to get user's phone after page loaded) // The first part — `newValue !== prevValue` — // is basically `props.value !== prevProps.value` // so it means "if value property was changed externally". // The second part — `newValue !== value` — // is for ignoring the `getDerivedStateFromProps()` call // which happens in `this.onChange()` right after `this.setState()`. // If this `getDerivedStateFromProps()` call isn't ignored // then the country flag would reset on each input. if (!valuesAreEqual(newValue, prevValue) && !valuesAreEqual(newValue, value)) { var phoneNumber; var parsedCountry; if (newValue) { // Validate that the newly-supplied `value` is in `E.164` format. // Because sometimes people attempt to supply a `value` like "+1 (879) 490-8676". // https://gitlab.com/catamphetamine/react-phone-number-input/-/issues/231#note_2016334796 if (newValue) { (0, _isE164Number.validateE164Number)(newValue); } phoneNumber = (0, _phoneInputHelpers.parsePhoneNumber)(newValue, metadata); var supportedCountries = (0, _countries.getSupportedCountries)(countries, metadata); if (phoneNumber && phoneNumber.country) { // Ignore `else` because all countries are supported in metadata. /* istanbul ignore next */ if (!supportedCountries || supportedCountries.indexOf(phoneNumber.country) >= 0) { parsedCountry = phoneNumber.country; } } else { parsedCountry = (0, _phoneInputHelpers.getCountryForPartialE164Number)(newValue, { country: undefined, countries: supportedCountries, metadata: metadata }); // In cases when multiple countries correspond to the same country calling code, // the phone number digits of `newValue` have to be matched against country-specific // regular expressions in order to determine the exact country. // Sometimes, that algorithm can't decide for sure which country does the phone number belong to, // for example when the digits of `newValue` don't match any of those regular expressions. // and the country of the phone number couldn't be determined. // In those cases, people prefer the component to show the flag of the `defaultCountry` // if the phone number could potentially belong to that `defaultCountry`. // At least that's how the component behaves when a user pastes an international // phone number into the input field: for example, when `defaultCountry` is `"US"` // and the user pastes value "+1 555 555 5555" into the input field, it keep showing "US" flag. // So when setting new `value` property externally, the component should behave the same way: // it should select the `defaultCountry` when the new `value` could potentially belong // to that country in cases when the exact country can't be determined. // https://github.com/catamphetamine/react-phone-number-input/issues/413#issuecomment-1536219404 if (!parsedCountry) { if (newDefaultCountry) { if (newValue.indexOf((0, _getInternationalPhoneNumberPrefix["default"])(newDefaultCountry, metadata)) === 0) { parsedCountry = newDefaultCountry; } } } } } var userCountrySelectionHistoryStateUpdate; if (newValue) { // If the latest country that has been manually selected by the user // no longer corresponds to the new value then reset it. if (latestCountrySelectedByUser) { var couldNewValueCorrespondToLatestCountrySelectedByUser = parsedCountry ? latestCountrySelectedByUser === parsedCountry : (0, _phoneInputHelpers.couldNumberBelongToCountry)(newValue, latestCountrySelectedByUser, metadata); if (couldNewValueCorrespondToLatestCountrySelectedByUser) { if (!parsedCountry) { parsedCountry = latestCountrySelectedByUser; } } else { userCountrySelectionHistoryStateUpdate = { latestCountrySelectedByUser: undefined }; } } } else { // When the `value` property is being reset "externally", // reset any tracking of the country that the user has previously selected. userCountrySelectionHistoryStateUpdate = { latestCountrySelectedByUser: undefined, hasUserSelectedACountry: undefined }; } return _objectSpread(_objectSpread({}, userCountrySelectionHistoryStateUpdate), {}, { phoneDigits: _getInitialPhoneDigits({ phoneNumber: phoneNumber, value: newValue, defaultCountry: newDefaultCountry }), value: newValue, country: newValue ? parsedCountry : newDefaultCountry }); } // `defaultCountry` didn't change. // `value` didn't change. // `phoneDigits` didn't change, because `value` didn't change. // // So no need to update state. } function valuesAreEqual(value1, value2) { // If `value` has been set to `null` externally then convert it to `undefined`. // // For example, `react-hook-form` sets `value` to `null` when the user clears the input. // https://gitlab.com/catamphetamine/react-phone-number-input/-/issues/164 // In that case, without this conversion of `null` to `undefined`, it would reset // the selected country to `defaultCountry` because in that case `newValue !== value` // because `null !== undefined`. // // Historically, empty `value` is encoded as `undefined`. // Perhaps empty `value` would be better encoded as `null` instead. // But because that would be a potentially breaking change for some people, // it's left as is for the current "major" version of this library. // if (value1 === null) { value1 = undefined; } if (value2 === null) { value2 = undefined; } return value1 === value2; } //# sourceMappingURL=getPhoneInputWithCountryStateUpdateFromNewProps.js.map