1+ "use client" ;
2+
13import {
24 ChangeEvent ,
35 forwardRef ,
@@ -10,7 +12,9 @@ import {
1012 useState
1113} from "react" ;
1214import useFormInstance from "antd/es/form/hooks/useFormInstance" ;
15+ import { ConfigContext } from "antd/es/config-provider" ;
1316import { FormContext } from "antd/es/form/context" ;
17+ import { useWatch } from "antd/es/form/Form" ;
1418import Select from "antd/es/select" ;
1519import Input from "antd/es/input" ;
1620
@@ -31,18 +35,19 @@ import {
3135import { injectMergedStyles } from "./styles" ;
3236import { PhoneInputProps , PhoneNumber } from "./types" ;
3337
34- injectMergedStyles ( ) ;
35-
3638const 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 } { 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 } { 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 >
0 commit comments