Skip to content

Commit d409666

Browse files
committed
Sync antd and mui components' last states
1 parent 9c86c38 commit d409666

File tree

16 files changed

+165
-46
lines changed

16 files changed

+165
-46
lines changed

development/src/ant-phone/index.tsx

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {
24
ChangeEvent,
35
forwardRef,
@@ -10,7 +12,9 @@ import {
1012
useState
1113
} from "react";
1214
import useFormInstance from "antd/es/form/hooks/useFormInstance";
15+
import {ConfigContext} from "antd/es/config-provider";
1316
import {FormContext} from "antd/es/form/context";
17+
import {useWatch} from "antd/es/form/Form";
1418
import Select from "antd/es/select";
1519
import Input from "antd/es/input";
1620

@@ -31,18 +35,19 @@ import {
3135
import {injectMergedStyles} from "./styles";
3236
import {PhoneInputProps, PhoneNumber} from "./types";
3337

34-
injectMergedStyles();
35-
3638
const PhoneInput = forwardRef(({
3739
value: initialValue = "",
3840
country = getDefaultISO2Code(),
41+
disabled = false,
3942
enableSearch = false,
4043
disableDropdown = false,
44+
disableParentheses = false,
4145
onlyCountries = [],
4246
excludeCountries = [],
4347
preferredCountries = [],
4448
searchNotFound = "No country found",
4549
searchPlaceholder = "Search country",
50+
dropdownRender = (node) => node,
4651
onMount: handleMount = () => null,
4752
onInput: handleInput = () => null,
4853
onChange: handleChange = () => null,
@@ -51,13 +56,18 @@ const PhoneInput = forwardRef(({
5156
}: PhoneInputProps, forwardedRef: any) => {
5257
const formInstance = useFormInstance();
5358
const formContext = useContext(FormContext);
59+
const {getPrefixCls} = useContext(ConfigContext);
5460
const inputRef = useRef<any>(null);
61+
const searchRef = useRef<any>(null);
5562
const selectedRef = useRef<boolean>(false);
5663
const initiatedRef = useRef<boolean>(false);
5764
const [query, setQuery] = useState<string>("");
5865
const [minWidth, setMinWidth] = useState<number>(0);
5966
const [countryCode, setCountryCode] = useState<string>(country);
6067

68+
const prefixCls = getPrefixCls();
69+
injectMergedStyles(prefixCls);
70+
6171
const {
6272
value,
6373
pattern,
@@ -72,6 +82,7 @@ const PhoneInput = forwardRef(({
7282
onlyCountries,
7383
excludeCountries,
7484
preferredCountries,
85+
disableParentheses,
7586
});
7687

7788
const {
@@ -85,18 +96,22 @@ const PhoneInput = forwardRef(({
8596
return ({...metadata})?.[0] + ({...metadata})?.[2];
8697
}, [countriesList, countryCode, value])
8798

88-
const setFieldValue = useCallback((value: PhoneNumber) => {
89-
if (formInstance) {
90-
let namePath = [];
91-
let formName = (formContext as any)?.name || "";
92-
let fieldName = (antInputProps as any)?.id || "";
93-
if (formName) {
94-
namePath.push(formName);
95-
fieldName = fieldName.slice(formName.length + 1);
96-
}
97-
formInstance.setFieldValue(namePath.concat(fieldName.split("_")), value);
99+
const namePath = useMemo(() => {
100+
let path = [];
101+
let formName = (formContext as any)?.name || "";
102+
let fieldName = (antInputProps as any)?.id || "";
103+
if (formName) {
104+
path.push(formName);
105+
fieldName = fieldName.slice(formName.length + 1);
98106
}
99-
}, [antInputProps, formContext, formInstance])
107+
return path.concat(fieldName.split("_"));
108+
}, [antInputProps, formContext])
109+
110+
const phoneValue = useWatch(namePath, formInstance);
111+
112+
const setFieldValue = useCallback((value: PhoneNumber) => {
113+
if (formInstance) formInstance.setFieldValue(namePath, value);
114+
}, [formInstance, namePath])
100115

101116
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
102117
onKeyDownMaskHandler(event);
@@ -122,13 +137,29 @@ const PhoneInput = forwardRef(({
122137
handleMount(value);
123138
}, [handleMount, setFieldValue])
124139

140+
const onDropdownVisibleChange = useCallback((open: boolean) => {
141+
if (open && enableSearch) setTimeout(() => searchRef.current.focus(), 100);
142+
}, [enableSearch])
143+
125144
const ref = useCallback((node: any) => {
126145
[forwardedRef, inputRef].forEach((ref) => {
127146
if (typeof ref === "function") ref(node);
128147
else if (ref != null) ref.current = node;
129148
})
130149
}, [forwardedRef])
131150

151+
useEffect(() => {
152+
const rawValue = getRawValue(phoneValue);
153+
const metadata = getMetadata(rawValue);
154+
// Skip if value has not been updated by `setFieldValue`.
155+
if (!metadata?.[3] || rawValue === getRawValue(value)) return;
156+
const formattedNumber = getFormattedNumber(rawValue, metadata?.[3] as string);
157+
const phoneMetadata = parsePhoneNumber(formattedNumber);
158+
setFieldValue({...phoneMetadata, valid: (strict: boolean) => checkValidity(phoneMetadata, strict)});
159+
setCountryCode(metadata?.[0] as string);
160+
setValue(formattedNumber);
161+
}, [phoneValue, value, setFieldValue, setValue])
162+
132163
useEffect(() => {
133164
if (initiatedRef.current) return;
134165
initiatedRef.current = true;
@@ -147,28 +178,33 @@ const PhoneInput = forwardRef(({
147178
<Select
148179
suffixIcon={null}
149180
value={selectValue}
181+
disabled={disabled}
150182
open={disableDropdown ? false : undefined}
151183
onSelect={(selectedOption, {key}) => {
152184
const [_, mask] = key.split("_");
153-
if (selectValue === selectedOption) return;
154185
const selectedCountryCode = selectedOption.slice(0, 2);
155186
const formattedNumber = displayFormat(cleanInput(mask, mask).join(""));
156187
const phoneMetadata = parsePhoneNumber(formattedNumber, countriesList, selectedCountryCode);
157188
setFieldValue({...phoneMetadata, valid: (strict: boolean) => checkValidity(phoneMetadata, strict)});
158189
setCountryCode(selectedCountryCode);
159190
setValue(formattedNumber);
191+
setQuery("");
160192
selectedRef.current = true;
161193
const nativeInputValueSetter = (Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value") as any).set;
162194
nativeInputValueSetter.call(inputRef.current.input, formattedNumber);
163195
inputRef.current.input.dispatchEvent(new Event("change", {bubbles: true}));
196+
inputRef.current.input.focus();
164197
}}
165198
optionLabelProp="label"
166199
dropdownStyle={{minWidth}}
167200
notFoundContent={searchNotFound}
201+
onDropdownVisibleChange={onDropdownVisibleChange}
168202
dropdownRender={(menu) => (
169-
<div className="ant-phone-input-search-wrapper">
203+
<div className={`${prefixCls}-phone-input-search-wrapper`}>
170204
{enableSearch && (
171205
<Input
206+
value={query}
207+
ref={searchRef}
172208
placeholder={searchPlaceholder}
173209
onInput={({target}: any) => setQuery(target.value)}
174210
/>
@@ -177,22 +213,25 @@ const PhoneInput = forwardRef(({
177213
</div>
178214
)}
179215
>
180-
{countriesList.map(([iso, name, dial, mask]) => (
181-
<Select.Option
182-
value={iso + dial}
183-
key={`${iso}_${mask}`}
184-
label={<div className={`flag ${iso}`}/>}
185-
children={<div className="ant-phone-input-select-item">
186-
<div className={`flag ${iso}`}/>
187-
{name}&nbsp;{displayFormat(mask)}
188-
</div>}
189-
/>
190-
))}
216+
{countriesList.map(([iso, name, dial, pattern]) => {
217+
const mask = disableParentheses ? pattern.replace(/[()]/g, "") : pattern;
218+
return (
219+
<Select.Option
220+
value={iso + dial}
221+
key={`${iso}_${mask}`}
222+
label={<div className={`flag ${iso}`}/>}
223+
children={<div className={`${prefixCls}-phone-input-select-item`}>
224+
<div className={`flag ${iso}`}/>
225+
{name}&nbsp;{displayFormat(mask)}
226+
</div>}
227+
/>
228+
)
229+
})}
191230
</Select>
192-
), [selectValue, disableDropdown, minWidth, searchNotFound, countriesList, setFieldValue, setValue, enableSearch, searchPlaceholder])
231+
), [selectValue, query, disabled, disableParentheses, disableDropdown, onDropdownVisibleChange, minWidth, searchNotFound, countriesList, setFieldValue, setValue, prefixCls, enableSearch, searchPlaceholder])
193232

194233
return (
195-
<div className="ant-phone-input-wrapper"
234+
<div className={`${prefixCls}-phone-input-wrapper`}
196235
ref={node => setMinWidth(node?.offsetWidth || 0)}>
197236
<Input
198237
ref={ref}
@@ -201,7 +240,8 @@ const PhoneInput = forwardRef(({
201240
onInput={onInput}
202241
onChange={onChange}
203242
onKeyDown={onKeyDown}
204-
addonBefore={countriesSelect}
243+
addonBefore={dropdownRender(countriesSelect)}
244+
disabled={disabled}
205245
{...antInputProps}
206246
/>
207247
</div>

development/src/ant-phone/resources/stylesheet.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"margin": "0 6px 6px 6px"
1313
},
1414
".ant-phone-input-wrapper .ant-select-selector": {
15+
"padding": "0 11px !important",
16+
"height": "unset !important",
1517
"border": "none !important"
1618
},
1719
".ant-phone-input-wrapper .ant-select-selection-item": {

development/src/ant-phone/styles.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
1+
"use client";
2+
13
import {injectStyles, jsonToCss} from "react-phone-hooks/styles";
24
import commonStyles from "react-phone-hooks/stylesheet.json";
5+
import {defaultPrefixCls} from "antd/es/config-provider";
36

47
import customStyles from "./resources/stylesheet.json";
58

6-
export const injectMergedStyles = () => injectStyles(jsonToCss(Object.assign(commonStyles, customStyles)));
9+
let prefix: any = null;
10+
11+
export const injectMergedStyles = (prefixCls: any = null) => {
12+
const stylesheet = customStyles as { [key: string]: any };
13+
if (prefixCls && prefixCls !== defaultPrefixCls) {
14+
if (prefix === prefixCls) return;
15+
Object.entries(stylesheet).forEach(([k, value]) => {
16+
const key = k.replace(/ant(?=-)/g, prefixCls);
17+
stylesheet[key] = value;
18+
delete stylesheet[k];
19+
})
20+
prefix = prefixCls;
21+
}
22+
return injectStyles(jsonToCss(Object.assign(commonStyles, stylesheet)));
23+
}

development/src/ant-phone/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {ChangeEvent, KeyboardEvent} from "react";
1+
"use client";
2+
3+
import {ChangeEvent, KeyboardEvent, ReactNode} from "react";
24
import types from "react-phone-hooks/types";
35
import {InputProps} from "antd/es/input";
46

@@ -17,12 +19,16 @@ export interface PhoneInputProps extends Omit<InputProps, "value" | "onChange">
1719

1820
disableDropdown?: boolean;
1921

22+
disableParentheses?: boolean;
23+
2024
onlyCountries?: string[];
2125

2226
excludeCountries?: string[];
2327

2428
preferredCountries?: string[];
2529

30+
dropdownRender?: (menu: ReactNode) => ReactNode;
31+
2632
onMount?(value: PhoneNumber): void;
2733

2834
onInput?(event: ChangeEvent<HTMLInputElement>): void;

development/src/mui-phone/base/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {ChangeEvent, forwardRef, KeyboardEvent, useCallback, useEffect, useRef, useState} from "react";
24
import {Input as BaseInput, InputProps} from "@mui/base/Input";
35

development/src/mui-phone/base/styles.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {injectStyles, jsonToCss} from "react-phone-hooks/styles";
24
import commonStyles from "react-phone-hooks/stylesheet.json";
35

development/src/mui-phone/base/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {ChangeEvent, KeyboardEvent} from "react";
24
import types from "react-phone-hooks/types";
35
import {InputProps} from "@mui/base/Input";

development/src/mui-phone/index.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {ChangeEvent, forwardRef, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState} from "react";
24
import {InputAdornment, MenuItem, Select, TextField} from "@mui/material";
35

@@ -24,6 +26,7 @@ const PhoneInput = forwardRef(({
2426
variant = undefined,
2527
searchVariant = undefined,
2628
country = getDefaultISO2Code(),
29+
disabled = false,
2730
enableSearch = false,
2831
disableDropdown = false,
2932
onlyCountries = [],
@@ -36,8 +39,9 @@ const PhoneInput = forwardRef(({
3639
onChange: handleChange = () => null,
3740
onKeyDown: handleKeyDown = () => null,
3841
...muiInputProps
39-
}: PhoneInputProps, ref: any) => {
42+
}: PhoneInputProps, forwardedRef: any) => {
4043
searchVariant = searchVariant || variant;
44+
const inputRef = useRef<any>(null);
4145
const searchRef = useRef<boolean>(false);
4246
const initiatedRef = useRef<boolean>(false);
4347
const [query, setQuery] = useState<string>("");
@@ -94,6 +98,13 @@ const PhoneInput = forwardRef(({
9498
handleMount(value);
9599
}, [handleMount])
96100

101+
const ref = useCallback((node: any) => {
102+
[forwardedRef, inputRef].forEach((ref) => {
103+
if (typeof ref === "function") ref(node);
104+
else if (ref != null) ref.current = node;
105+
})
106+
}, [forwardedRef])
107+
97108
useEffect(() => {
98109
if (initiatedRef.current) return;
99110
initiatedRef.current = true;
@@ -111,7 +122,7 @@ const PhoneInput = forwardRef(({
111122
return (
112123
<div className="mui-phone-input-wrapper"
113124
ref={node => setMaxWidth(node?.offsetWidth || 0)}>
114-
{!disableDropdown && (
125+
{(!disableDropdown && !disabled) && (
115126
<Select
116127
open={open}
117128
variant={variant}
@@ -122,6 +133,7 @@ const PhoneInput = forwardRef(({
122133
<div className="mui-phone-input-search-wrapper" onKeyDown={(e: any) => e.stopPropagation()}>
123134
{enableSearch && (
124135
<TextField
136+
autoFocus
125137
type="search"
126138
value={query}
127139
variant={searchVariant}
@@ -141,10 +153,16 @@ const PhoneInput = forwardRef(({
141153
style={{maxWidth}}
142154
selected={selectValue === iso + dial}
143155
onClick={() => {
144-
const selectedOption = iso + dial;
145-
if (selectValue === selectedOption) return;
146-
setCountryCode(selectedOption.slice(0, 2));
147-
setValue(getFormattedNumber(mask, mask));
156+
const formattedNumber = getFormattedNumber(mask, mask);
157+
const input = inputRef.current.querySelector("input");
158+
input.value = formattedNumber;
159+
setValue(formattedNumber);
160+
setCountryCode(iso);
161+
setQuery("");
162+
const nativeInputValueSetter = (Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value") as any).set;
163+
nativeInputValueSetter.call(input, formattedNumber);
164+
input.dispatchEvent(new Event("change", {bubbles: true}));
165+
setTimeout(() => input.focus(), 100);
148166
}}
149167
children={<div className="mui-phone-input-select-item">
150168
<div className={`flag ${iso}`}/>
@@ -164,6 +182,7 @@ const PhoneInput = forwardRef(({
164182
value={value}
165183
variant={variant}
166184
onInput={onInput}
185+
disabled={disabled}
167186
onChange={onChange}
168187
onKeyDown={onKeyDown}
169188
InputProps={{

0 commit comments

Comments
 (0)