UNPKG

react-phone-number-input

Version:

Telephone number input React component

268 lines (222 loc) 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = usePhoneDigits; var _react = require("react"); var _core = require("libphonenumber-js/core"); var _getInternationalPhoneNumberPrefix = _interopRequireDefault(require("./helpers/getInternationalPhoneNumberPrefix")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } /** * Returns `[phoneDigits, setPhoneDigits]`. * "Phone digits" includes not only "digits" but also a `+` sign. */ function usePhoneDigits(_ref) { var value = _ref.value, onChange = _ref.onChange, country = _ref.country, defaultCountry = _ref.defaultCountry, international = _ref.international, withCountryCallingCode = _ref.withCountryCallingCode, useNationalFormatForDefaultCountryValue = _ref.useNationalFormatForDefaultCountryValue, metadata = _ref.metadata; var countryMismatchDetected = (0, _react.useRef)(); var onCountryMismatch = function onCountryMismatch(value, country, actualCountry) { console.error("[react-phone-number-input] Expected phone number ".concat(value, " to correspond to country ").concat(country, " but ").concat(actualCountry ? 'in reality it corresponds to country ' + actualCountry : 'it doesn\'t', ".")); countryMismatchDetected.current = true; }; var getInitialPhoneDigits = function getInitialPhoneDigits() { return getPhoneDigitsForValue(value, country, international, withCountryCallingCode, defaultCountry, useNationalFormatForDefaultCountryValue, metadata, onCountryMismatch); }; // This is only used to detect `country` property change. var _useState = (0, _react.useState)(country), _useState2 = _slicedToArray(_useState, 2), prevCountry = _useState2[0], setPrevCountry = _useState2[1]; // This is only used to detect `defaultCountry` property change. var _useState3 = (0, _react.useState)(defaultCountry), _useState4 = _slicedToArray(_useState3, 2), prevDefaultCountry = _useState4[0], setPrevDefaultCountry = _useState4[1]; // `phoneDigits` is the `value` passed to the `<input/>`. var _useState5 = (0, _react.useState)(getInitialPhoneDigits()), _useState6 = _slicedToArray(_useState5, 2), phoneDigits = _useState6[0], setPhoneDigits = _useState6[1]; // This is only used to detect `value` property changes. var _useState7 = (0, _react.useState)(value), _useState8 = _slicedToArray(_useState7, 2), valueForPhoneDigits = _useState8[0], setValueForPhoneDigits = _useState8[1]; // Rerender hack. var _useState9 = (0, _react.useState)(), _useState10 = _slicedToArray(_useState9, 2), rerenderTrigger = _useState10[0], setRerenderTrigger = _useState10[1]; var rerender = (0, _react.useCallback)(function () { return setRerenderTrigger({}); }, [setRerenderTrigger]); // If `value` property has been changed externally // then re-initialize the component. (0, _react.useEffect)(function () { if (value !== valueForPhoneDigits) { setValueForPhoneDigits(value); setPhoneDigits(getInitialPhoneDigits()); } }, [value]); // If the `country` has been changed then re-initialize the component. (0, _react.useEffect)(function () { if (country !== prevCountry) { setPrevCountry(country); setPhoneDigits(getInitialPhoneDigits()); } }, [country]); // If the `defaultCountry` has been changed then re-initialize the component. (0, _react.useEffect)(function () { if (defaultCountry !== prevDefaultCountry) { setPrevDefaultCountry(defaultCountry); setPhoneDigits(getInitialPhoneDigits()); } }, [defaultCountry]); // Update the `value` after `valueForPhoneDigits` has been updated. (0, _react.useEffect)(function () { if (valueForPhoneDigits !== value) { onChange(valueForPhoneDigits); } }, [valueForPhoneDigits]); var onSetPhoneDigits = (0, _react.useCallback)(function (phoneDigits) { var value; if (country) { if (international && withCountryCallingCode) { // The `<input/>` value must start with the country calling code. var prefix = (0, _getInternationalPhoneNumberPrefix["default"])(country, metadata); if (phoneDigits.indexOf(prefix) !== 0) { // If a user tabs into a phone number input field // that is `international` and `withCountryCallingCode`, // and then starts inputting local phone number digits, // the first digit would get "swallowed" if the fix below wasn't implemented. // https://gitlab.com/catamphetamine/react-phone-number-input/-/issues/43 if (phoneDigits && phoneDigits[0] !== '+') { phoneDigits = prefix + phoneDigits; } else { // // Reset phone digits if they don't start with the correct prefix. // // Undo the `<input/>` value change if it doesn't. if (countryMismatchDetected.current) {// In case of a `country`/`value` mismatch, // if it performed an "undo" here, then // it wouldn't let a user edit their phone number at all, // so this special case at least allows phone number editing // when `value` already doesn't match the `country`. } else { // If it simply did `phoneDigits = prefix` here, // then it could have no effect when erasing phone number // via Backspace, because `phoneDigits` in `state` wouldn't change // as a result, because it was `prefix` and it became `prefix`, // so the component wouldn't rerender, and the user would be able // to erase the country calling code part, and that part is // assumed to be non-eraseable. That's why the component is // forcefully rerendered here. setPhoneDigits(prefix); setValueForPhoneDigits(undefined); // Force a re-render of the `<input/>` with previous `phoneDigits` value. return rerender(); } } } } else { // Entering phone number either in "national" format // when `country` has been specified, or in "international" format // when `country` has been specified but `withCountryCallingCode` hasn't. // Therefore, `+` is not allowed. if (phoneDigits && phoneDigits[0] === '+') { // Remove the `+`. phoneDigits = phoneDigits.slice(1); } } } else if (!defaultCountry) { // Force a `+` in the beginning of a `value` // when no `country` and `defaultCountry` have been specified. if (phoneDigits && phoneDigits[0] !== '+') { // Prepend a `+`. phoneDigits = '+' + phoneDigits; } } // Convert `phoneDigits` to `value`. if (phoneDigits) { var asYouType = new _core.AsYouType(country || defaultCountry, metadata); asYouType.input(country && international && !withCountryCallingCode ? "+".concat((0, _core.getCountryCallingCode)(country, metadata)).concat(phoneDigits) : phoneDigits); var phoneNumber = asYouType.getNumber(); // If it's a "possible" incomplete phone number. if (phoneNumber) { value = phoneNumber.number; } } setPhoneDigits(phoneDigits); setValueForPhoneDigits(value); }, [country, international, withCountryCallingCode, defaultCountry, metadata, setPhoneDigits, setValueForPhoneDigits, rerender, countryMismatchDetected]); return [phoneDigits, onSetPhoneDigits]; } /** * Returns phone number input field value for a E.164 phone number `value`. * @param {string} [value] * @param {string} [country] * @param {boolean} [international] * @param {boolean} [withCountryCallingCode] * @param {string} [defaultCountry] * @param {boolean} [useNationalFormatForDefaultCountryValue] * @param {object} metadata * @return {string} */ function getPhoneDigitsForValue(value, country, international, withCountryCallingCode, defaultCountry, useNationalFormatForDefaultCountryValue, metadata, onCountryMismatch) { if (country && international && withCountryCallingCode) { var prefix = (0, _getInternationalPhoneNumberPrefix["default"])(country, metadata); if (value) { if (value.indexOf(prefix) !== 0) { onCountryMismatch(value, country); } return value; } return prefix; } if (!value) { return ''; } if (!country && !defaultCountry) { return value; } var asYouType = new _core.AsYouType(undefined, metadata); asYouType.input(value); var phoneNumber = asYouType.getNumber(); if (phoneNumber) { if (country) { if (phoneNumber.country && phoneNumber.country !== country) { onCountryMismatch(value, country, phoneNumber.country); } else if (phoneNumber.countryCallingCode !== (0, _core.getCountryCallingCode)(country, metadata)) { onCountryMismatch(value, country); } if (international) { return phoneNumber.nationalNumber; } return (0, _core.parseDigits)(phoneNumber.formatNational()); } else { // `phoneNumber.countryCallingCode` is compared here instead of // `phoneNumber.country`, because, for example, a person could have // previously input a phone number (in "national" format) that isn't // 100% valid for the `defaultCountry`, and if `phoneNumber.country` // was compared, then it wouldn't match, and such phone number // wouldn't be formatted as a "national" one, and instead would be // formatted as an "international" one, confusing the user. // Comparing `phoneNumber.countryCallingCode` works around such issues. // // Example: `defaultCountry="US"` and the `<input/>` is empty. // The user inputs: "222 333 4444", which gets formatted to "(222) 333-4444". // The user then clicks "Save", the page is refreshed, and the user sees // that the `<input/>` value is now "+1 222 333 4444" which confuses the user: // the user expected the `<input/>` value to be "(222) 333-4444", same as it // was when they've just typed it in. The cause of the issue is that "222 333 4444" // is not a valid national number for US, and `phoneNumber.country` is compared // instead of `phoneNumber.countryCallingCode`. After the `phoneNumber.country` // comparison is replaced with `phoneNumber.countryCallingCode` one, the issue // is no longer the case. // if (phoneNumber.countryCallingCode && phoneNumber.countryCallingCode === (0, _core.getCountryCallingCode)(defaultCountry, metadata) && useNationalFormatForDefaultCountryValue) { return (0, _core.parseDigits)(phoneNumber.formatNational()); } return value; } } else { return ''; } } //# sourceMappingURL=usePhoneDigits.js.map