mui-phone-input
Version:
Advanced, highly customizable phone input component for Material UI.
115 lines (114 loc) • 8.81 kB
JavaScript
"use client";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { InputAdornment, MenuItem, Select, TextField, useThemeProps } from "@mui/material";
import { checkValidity, displayFormat, getCountry, getDefaultISO2Code, getFormattedNumber, getMetadata, getRawValue, parsePhoneNumber, useMask, usePhone, } from "react-phone-hooks";
import locale from "./locale";
import { injectMergedStyles } from "./styles";
injectMergedStyles();
const PhoneInput = forwardRef((_a, forwardedRef) => {
var { value: initialValue = "", variant = undefined, searchVariant = undefined, country = getDefaultISO2Code(), distinct = false, disabled = false, enableArrow = false, enableSearch = false, disableDropdown = false, disableParentheses = false, onlyCountries = [], excludeCountries = [], preferredCountries = [], searchNotFound: defaultSearchNotFound = "No country found", searchPlaceholder: defaultSearchPlaceholder = "Search country", onMount: handleMount = () => null, onInput: handleInput = () => null, onChange: handleChange = () => null, onKeyDown: handleKeyDown = () => null } = _a, muiInputProps = __rest(_a, ["value", "variant", "searchVariant", "country", "distinct", "disabled", "enableArrow", "enableSearch", "disableDropdown", "disableParentheses", "onlyCountries", "excludeCountries", "preferredCountries", "searchNotFound", "searchPlaceholder", "onMount", "onInput", "onChange", "onKeyDown"]);
searchVariant = searchVariant || variant;
const inputRef = useRef(null);
const searchRef = useRef(false);
const initiatedRef = useRef(false);
const [query, setQuery] = useState("");
const [open, setOpen] = useState(false);
const [maxWidth, setMaxWidth] = useState(0);
const [countryCode, setCountryCode] = useState(country);
const { locale, searchNotFound = defaultSearchNotFound, searchPlaceholder = defaultSearchPlaceholder, countries = new Proxy({}, ({ get: (_, prop) => prop })), } = useThemeProps({ props: {}, name: "MuiPhoneInput" });
const { value, pattern, metadata, setValue, countriesList, } = usePhone({
query,
locale,
country,
distinct,
countryCode,
initialValue,
onlyCountries,
excludeCountries,
preferredCountries,
disableParentheses,
});
const { onInput: onInputMaskHandler, onKeyDown: onKeyDownMaskHandler, } = useMask(pattern);
const selectValue = useMemo(() => {
var _a, _b;
let metadata = getMetadata(getRawValue(value), countriesList);
metadata = metadata || getCountry(countryCode);
return ((_a = (Object.assign({}, metadata))) === null || _a === void 0 ? void 0 : _a[0]) + ((_b = (Object.assign({}, metadata))) === null || _b === void 0 ? void 0 : _b[2]);
}, [countriesList, countryCode, value]);
const onKeyDown = useCallback((event) => {
onKeyDownMaskHandler(event);
handleKeyDown(event);
}, [handleKeyDown, onKeyDownMaskHandler]);
const onChange = useCallback((event) => {
const formattedNumber = getFormattedNumber(event.target.value, pattern);
const phoneMetadata = parsePhoneNumber(formattedNumber, countriesList);
setCountryCode(phoneMetadata.isoCode);
setValue(formattedNumber);
handleChange(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => checkValidity(phoneMetadata, strict) }), event);
}, [countriesList, handleChange, pattern, setValue]);
const onInput = useCallback((event) => {
onInputMaskHandler(event);
handleInput(event);
}, [handleInput, onInputMaskHandler]);
const onMount = useCallback((value) => {
handleMount(value);
}, [handleMount]);
const ref = useCallback((node) => {
[forwardedRef, inputRef].forEach((ref) => {
if (typeof ref === "function")
ref(node);
else if (ref != null)
ref.current = node;
});
}, [forwardedRef]);
useEffect(() => {
if (initiatedRef.current)
return;
initiatedRef.current = true;
let initialValue = getRawValue(value);
if (!initialValue.startsWith(metadata === null || metadata === void 0 ? void 0 : metadata[2])) {
initialValue = metadata === null || metadata === void 0 ? void 0 : metadata[2];
}
const formattedNumber = getFormattedNumber(initialValue, pattern);
const phoneMetadata = parsePhoneNumber(formattedNumber, countriesList);
onMount(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => checkValidity(phoneMetadata, strict) }));
setCountryCode(phoneMetadata.isoCode);
setValue(formattedNumber);
}, [countriesList, metadata, onMount, pattern, setValue, value]);
return (_jsxs("div", { className: "mui-phone-input-wrapper", ref: node => setMaxWidth((node === null || node === void 0 ? void 0 : node.offsetWidth) || 0), children: [(!disableDropdown && !disabled) && (_jsx(Select, { open: open, variant: variant, value: selectValue, onClose: () => setOpen(searchRef.current), style: { position: "absolute", top: 0, left: 0, visibility: "hidden", width: "100%", zIndex: -1 }, children: _jsxs("div", { className: "mui-phone-input-search-wrapper", onKeyDown: (e) => e.stopPropagation(), children: [enableSearch && (_jsx(TextField, { autoFocus: true, type: "search", value: query, variant: searchVariant, placeholder: searchPlaceholder, className: "mui-phone-input-search", onChange: (e) => setQuery(e.target.value), onBlur: () => searchRef.current = false, onFocus: () => searchRef.current = true })), _jsx("div", { className: "mui-phone-input-search-list", children: countriesList.length ? countriesList.map(([iso, name, dial, pattern]) => {
const mask = disableParentheses ? pattern.replace(/[()]/g, "") : pattern;
return (_jsx(MenuItem, { disableRipple: true, value: iso + dial, style: { maxWidth }, selected: selectValue === iso + dial, onClick: () => {
const formattedNumber = getFormattedNumber(mask, mask);
const input = inputRef.current.querySelector("input");
input.value = formattedNumber;
setValue(formattedNumber);
setCountryCode(iso);
setQuery("");
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, formattedNumber);
input.dispatchEvent(new Event("change", { bubbles: true }));
setTimeout(() => input.focus(), 100);
}, children: _jsxs("div", { className: "mui-phone-input-select-item", children: [_jsx("div", { className: `flag ${iso}` }), _jsxs("div", { className: "label", children: [countries[name], "\u00A0", displayFormat(mask)] })] }) }, iso + mask));
}) : _jsx(MenuItem, { disabled: true, children: searchNotFound }) })] }) })), _jsx(TextField, Object.assign({ ref: ref, type: "tel", value: value, variant: variant, onInput: onInput, disabled: disabled, onChange: onChange, onKeyDown: onKeyDown, InputProps: {
startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsxs("span", { style: {
display: "flex",
cursor: "pointer",
alignItems: "center",
justifyContent: "center",
}, onClick: () => setOpen(!open), children: [_jsx("div", { className: `flag ${countryCode}` }), enableArrow && (_jsx("svg", { viewBox: "0 0 24 24", focusable: "false", fill: "currentColor", style: { paddingLeft: 4 }, width: "22", height: "20", children: _jsx("path", { d: "M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6z" }) }))] }) }))
} }, muiInputProps))] }));
});
export default PhoneInput;
export { locale };