antd-phone-input
Version:
Advanced, highly customizable phone input component for Ant Design.
163 lines (162 loc) • 12.8 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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.locale = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const useFormInstance_1 = __importDefault(require("antd/es/form/hooks/useFormInstance"));
const config_provider_1 = require("antd/es/config-provider");
const context_1 = require("antd/es/form/context");
const Form_1 = require("antd/es/form/Form");
const select_1 = __importDefault(require("antd/es/select"));
const input_1 = __importDefault(require("antd/es/input"));
const react_phone_hooks_1 = require("react-phone-hooks");
const locale_1 = __importDefault(require("./locale"));
exports.locale = locale_1.default;
const styles_1 = require("./styles");
const PhoneInput = (0, react_1.forwardRef)((_a, forwardedRef) => {
var { value: initialValue = "", country = (0, react_phone_hooks_1.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", dropdownRender = (node) => node, onMount: handleMount = () => null, onInput: handleInput = () => null, onChange: handleChange = () => null, onKeyDown: handleKeyDown = () => null } = _a, antInputProps = __rest(_a, ["value", "country", "distinct", "disabled", "enableArrow", "enableSearch", "disableDropdown", "disableParentheses", "onlyCountries", "excludeCountries", "preferredCountries", "searchNotFound", "searchPlaceholder", "dropdownRender", "onMount", "onInput", "onChange", "onKeyDown"]);
const formInstance = (0, useFormInstance_1.default)();
const { locale = {}, getPrefixCls } = (0, react_1.useContext)(config_provider_1.ConfigContext);
const formContext = (0, react_1.useContext)(context_1.FormContext);
const inputRef = (0, react_1.useRef)(null);
const searchRef = (0, react_1.useRef)(null);
const selectedRef = (0, react_1.useRef)(false);
const initiatedRef = (0, react_1.useRef)(false);
const [query, setQuery] = (0, react_1.useState)("");
const [minWidth, setMinWidth] = (0, react_1.useState)(0);
const [countryCode, setCountryCode] = (0, react_1.useState)(country);
const { locale: localeIdentifier, searchNotFound = defaultSearchNotFound, searchPlaceholder = defaultSearchPlaceholder, countries = new Proxy({}, ({ get: (_, prop) => prop })), } = locale.PhoneInput || {};
const prefixCls = getPrefixCls();
(0, styles_1.injectMergedStyles)(prefixCls);
const { value, pattern, metadata, setValue, countriesList, } = (0, react_phone_hooks_1.usePhone)({
query,
country,
distinct,
countryCode,
initialValue,
onlyCountries,
excludeCountries,
preferredCountries,
disableParentheses,
locale: localeIdentifier,
});
const { onInput: onInputMaskHandler, onKeyDown: onKeyDownMaskHandler, } = (0, react_phone_hooks_1.useMask)(pattern);
const selectValue = (0, react_1.useMemo)(() => {
var _a, _b;
let metadata = (0, react_phone_hooks_1.getMetadata)((0, react_phone_hooks_1.getRawValue)(value), countriesList);
metadata = metadata || (0, react_phone_hooks_1.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 namePath = (0, react_1.useMemo)(() => {
let path = [];
let formName = (formContext === null || formContext === void 0 ? void 0 : formContext.name) || "";
let fieldName = (antInputProps === null || antInputProps === void 0 ? void 0 : antInputProps.id) || "";
if (formName) {
path.push(formName);
fieldName = fieldName.slice(formName.length + 1);
}
return path.concat(fieldName.split("_"));
}, [antInputProps, formContext]);
const phoneValue = (0, Form_1.useWatch)(namePath, formInstance);
const setFieldValue = (0, react_1.useCallback)((value) => {
if (formInstance)
formInstance.setFieldValue(namePath, value);
}, [formInstance, namePath]);
const onKeyDown = (0, react_1.useCallback)((event) => {
onKeyDownMaskHandler(event);
handleKeyDown(event);
}, [handleKeyDown, onKeyDownMaskHandler]);
const onChange = (0, react_1.useCallback)((event) => {
const formattedNumber = selectedRef.current ? event.target.value : (0, react_phone_hooks_1.getFormattedNumber)(event.target.value, pattern);
selectedRef.current = false;
const phoneMetadata = (0, react_phone_hooks_1.parsePhoneNumber)(formattedNumber, countriesList);
setCountryCode(phoneMetadata.isoCode);
setValue(formattedNumber);
setQuery("");
handleChange(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => (0, react_phone_hooks_1.checkValidity)(phoneMetadata, strict) }), event);
}, [countriesList, handleChange, pattern, setValue]);
const onInput = (0, react_1.useCallback)((event) => {
onInputMaskHandler(event);
handleInput(event);
}, [onInputMaskHandler, handleInput]);
const onMount = (0, react_1.useCallback)((value) => {
setFieldValue(value);
handleMount(value);
}, [handleMount, setFieldValue]);
const onDropdownVisibleChange = (0, react_1.useCallback)((open) => {
if (open && enableSearch)
setTimeout(() => searchRef.current.focus(), 100);
}, [enableSearch]);
const ref = (0, react_1.useCallback)((node) => {
[forwardedRef, inputRef].forEach((ref) => {
if (typeof ref === "function")
ref(node);
else if (ref != null)
ref.current = node;
});
}, [forwardedRef]);
(0, react_1.useEffect)(() => {
const rawValue = (0, react_phone_hooks_1.getRawValue)(phoneValue);
const metadata = (0, react_phone_hooks_1.getMetadata)(rawValue);
// Skip if value has not been updated by `setFieldValue`.
if (!(metadata === null || metadata === void 0 ? void 0 : metadata[3]) || rawValue === (0, react_phone_hooks_1.getRawValue)(value))
return;
const formattedNumber = (0, react_phone_hooks_1.getFormattedNumber)(rawValue, metadata === null || metadata === void 0 ? void 0 : metadata[3]);
const phoneMetadata = (0, react_phone_hooks_1.parsePhoneNumber)(formattedNumber);
setFieldValue(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => (0, react_phone_hooks_1.checkValidity)(phoneMetadata, strict) }));
setCountryCode(metadata === null || metadata === void 0 ? void 0 : metadata[0]);
setValue(formattedNumber);
}, [phoneValue, value, setFieldValue, setValue]);
(0, react_1.useEffect)(() => {
if (initiatedRef.current)
return;
initiatedRef.current = true;
let initialValue = (0, react_phone_hooks_1.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 = (0, react_phone_hooks_1.getFormattedNumber)(initialValue, pattern);
const phoneMetadata = (0, react_phone_hooks_1.parsePhoneNumber)(formattedNumber, countriesList);
onMount(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => (0, react_phone_hooks_1.checkValidity)(phoneMetadata, strict) }));
setCountryCode(phoneMetadata.isoCode);
setValue(formattedNumber);
}, [countriesList, metadata, onMount, pattern, setValue, value]);
const suffixIcon = (0, react_1.useMemo)(() => {
return enableArrow && ((0, jsx_runtime_1.jsx)("span", { role: "img", className: "anticon", style: { paddingLeft: 8 }, children: (0, jsx_runtime_1.jsx)("svg", { className: "icon", viewBox: "0 0 1024 1024", focusable: "false", fill: "currentColor", width: "16", height: "18", children: (0, jsx_runtime_1.jsx)("path", { d: "M848 368a48 48 0 0 0-81.312-34.544l-0.016-0.016-254.784 254.784-251.488-251.488a48 48 0 1 0-71.04 64.464l-0.128 0.128 288 288 0.016-0.016a47.84 47.84 0 0 0 34.544 14.688h0.224a47.84 47.84 0 0 0 34.544-14.688l0.016 0.016 288-288-0.016-0.016c8.32-8.624 13.44-20.368 13.44-33.312z" }) }) }));
}, [enableArrow]);
const countriesSelect = (0, react_1.useMemo)(() => ((0, jsx_runtime_1.jsxs)(select_1.default, { suffixIcon: null, value: selectValue, disabled: disabled, open: disableDropdown ? false : undefined, onSelect: (selectedOption, { key }) => {
const [_, mask] = key.split("_");
const selectedCountryCode = selectedOption.slice(0, 2);
const formattedNumber = (0, react_phone_hooks_1.displayFormat)((0, react_phone_hooks_1.cleanInput)(mask, mask).join(""));
const phoneMetadata = (0, react_phone_hooks_1.parsePhoneNumber)(formattedNumber, countriesList, selectedCountryCode);
setFieldValue(Object.assign(Object.assign({}, phoneMetadata), { valid: (strict) => (0, react_phone_hooks_1.checkValidity)(phoneMetadata, strict) }));
setCountryCode(selectedCountryCode);
setValue(formattedNumber);
setQuery("");
selectedRef.current = true;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(inputRef.current.input, formattedNumber);
inputRef.current.input.dispatchEvent(new Event("change", { bubbles: true }));
inputRef.current.input.focus();
}, optionLabelProp: "label", dropdownStyle: { minWidth }, onDropdownVisibleChange: onDropdownVisibleChange, dropdownRender: (menu) => ((0, jsx_runtime_1.jsxs)("div", { className: `${prefixCls}-phone-input-search-wrapper`, children: [enableSearch && ((0, jsx_runtime_1.jsx)(input_1.default, { value: query, ref: searchRef, placeholder: searchPlaceholder, onInput: ({ target }) => setQuery(target.value) })), countriesList.length ? menu : ((0, jsx_runtime_1.jsx)("div", { className: "ant-select-item-empty", children: searchNotFound }))] })), children: [(0, jsx_runtime_1.jsx)(select_1.default.Option, { children: null, value: selectValue, style: { display: "none" }, label: (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex" }, children: [(0, jsx_runtime_1.jsx)("div", { className: `flag ${countryCode}` }), suffixIcon] }) }, `${countryCode}_default`), countriesList.map(([iso, name, dial, pattern]) => {
const mask = disableParentheses ? pattern.replace(/[()]/g, "") : pattern;
return ((0, jsx_runtime_1.jsx)(select_1.default.Option, { value: iso + dial, label: (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex" }, children: [(0, jsx_runtime_1.jsx)("div", { className: `flag ${iso}` }), suffixIcon] }), children: (0, jsx_runtime_1.jsxs)("div", { className: `${prefixCls}-phone-input-select-item`, children: [(0, jsx_runtime_1.jsx)("div", { className: `flag ${iso}` }), countries[name], "\u00A0", (0, react_phone_hooks_1.displayFormat)(mask)] }) }, `${iso}_${mask}`));
})] })), [selectValue, suffixIcon, countryCode, query, disabled, disableParentheses, disableDropdown, onDropdownVisibleChange, minWidth, searchNotFound, countries, countriesList, setFieldValue, setValue, prefixCls, enableSearch, searchPlaceholder]);
return ((0, jsx_runtime_1.jsx)("div", { className: `${prefixCls}-phone-input-wrapper`, ref: node => setMinWidth((node === null || node === void 0 ? void 0 : node.offsetWidth) || 0), children: (0, jsx_runtime_1.jsx)(input_1.default, Object.assign({ ref: ref, inputMode: "tel", value: value, onInput: onInput, onChange: onChange, onKeyDown: onKeyDown, addonBefore: dropdownRender(countriesSelect), disabled: disabled }, antInputProps)) }));
});
exports.default = PhoneInput;