55 useRef ,
66 useState ,
77} from 'react' ;
8+ import type { ReactNode } from 'react' ;
89import type {
910 StyleProp ,
1011 TextStyle ,
@@ -72,11 +73,24 @@ interface GooglePlacesTextInputStyles {
7273 secondary ?: StyleProp < TextStyle > ;
7374 } ;
7475 loadingIndicator ?: {
75- color ?: string ; // ✅ Keep as string, not StyleProp
76+ color ?: string ;
7677 } ;
7778 placeholder ?: {
78- color ?: string ; // ✅ Keep as string, not StyleProp
79+ color ?: string ;
7980 } ;
81+ clearButtonText ?: StyleProp < ViewStyle > ;
82+ }
83+
84+ interface GooglePlacesAccessibilityLabels {
85+ input ?: string ;
86+ clearButton ?: string ;
87+ loadingIndicator ?: string ;
88+ /**
89+ * A function that receives a place prediction and returns a descriptive string
90+ * for the suggestion item.
91+ * @example (prediction) => `Select ${prediction.structuredFormat.mainText.text}, ${prediction.structuredFormat.secondaryText?.text}`
92+ */
93+ suggestionItem ?: ( prediction : PlacePrediction ) => string ;
8094}
8195
8296type TextInputInheritedProps = Pick < TextInputProps , 'onFocus' | 'onBlur' > ;
@@ -98,6 +112,7 @@ interface GooglePlacesTextInputProps extends TextInputInheritedProps {
98112 showClearButton ?: boolean ;
99113 forceRTL ?: boolean ;
100114 style ?: GooglePlacesTextInputStyles ;
115+ clearElement ?: ReactNode ;
101116 hideOnKeyboardDismiss ?: boolean ;
102117 scrollEnabled ?: boolean ;
103118 nestedScrollEnabled ?: boolean ;
@@ -106,10 +121,12 @@ interface GooglePlacesTextInputProps extends TextInputInheritedProps {
106121 detailsFields ?: string [ ] ;
107122 onError ?: ( error : any ) => void ;
108123 enableDebug ?: boolean ;
124+ accessibilityLabels ?: GooglePlacesAccessibilityLabels ;
109125}
110126
111127interface GooglePlacesTextInputRef {
112128 clear : ( ) => void ;
129+ blur : ( ) => void ;
113130 focus : ( ) => void ;
114131 getSessionToken : ( ) => string | null ;
115132}
@@ -140,6 +157,7 @@ const GooglePlacesTextInput = forwardRef<
140157 showClearButton = true ,
141158 forceRTL = undefined ,
142159 style = { } ,
160+ clearElement,
143161 hideOnKeyboardDismiss = false ,
144162 scrollEnabled = true ,
145163 nestedScrollEnabled = true ,
@@ -150,6 +168,7 @@ const GooglePlacesTextInput = forwardRef<
150168 enableDebug = false ,
151169 onFocus,
152170 onBlur,
171+ accessibilityLabels = { } ,
153172 } ,
154173 ref
155174 ) => {
@@ -210,6 +229,9 @@ const GooglePlacesTextInput = forwardRef<
210229 setShowSuggestions ( false ) ;
211230 setSessionToken ( generateSessionToken ( ) ) ;
212231 } ,
232+ blur : ( ) => {
233+ inputRef . current ?. blur ( ) ;
234+ } ,
213235 focus : ( ) => {
214236 inputRef . current ?. focus ( ) ;
215237 } ,
@@ -452,8 +474,18 @@ const GooglePlacesTextInput = forwardRef<
452474 const backgroundColor =
453475 suggestionsContainerStyle ?. backgroundColor || '#efeff1' ;
454476
477+ const defaultAccessibilityLabel = `${ mainText . text } ${
478+ secondaryText ? `, ${ secondaryText . text } ` : ''
479+ } `;
480+ const accessibilityLabel =
481+ accessibilityLabels . suggestionItem ?.( item . placePrediction ) ||
482+ defaultAccessibilityLabel ;
483+
455484 return (
456485 < TouchableOpacity
486+ accessibilityRole = "button"
487+ accessibilityLabel = { accessibilityLabel }
488+ accessibilityHint = "Double tap to select this place"
457489 style = { [
458490 styles . suggestionItem ,
459491 { backgroundColor } ,
@@ -541,7 +573,7 @@ const GooglePlacesTextInput = forwardRef<
541573 } ) ;
542574 }
543575 // eslint-disable-next-line react-hooks/exhaustive-deps
544- } , [ ] ) ; // ✅ Only run on mount
576+ } , [ ] ) ;
545577
546578 return (
547579 < View style = { [ styles . container , style . container ] } >
@@ -556,6 +588,8 @@ const GooglePlacesTextInput = forwardRef<
556588 onFocus = { handleFocus }
557589 onBlur = { handleBlur }
558590 clearButtonMode = "never" // Disable iOS native clear button
591+ accessibilityRole = "search"
592+ accessibilityLabel = { accessibilityLabels . input || placeHolderText }
559593 />
560594
561595 { /* Clear button - shown only if showClearButton is true */ }
@@ -574,15 +608,28 @@ const GooglePlacesTextInput = forwardRef<
574608 setSessionToken ( generateSessionToken ( ) ) ;
575609 inputRef . current ?. focus ( ) ;
576610 } }
611+ accessibilityRole = "button"
612+ accessibilityLabel = {
613+ accessibilityLabels . clearButton || 'Clear input text'
614+ }
577615 >
578- < Text
579- style = { Platform . select ( {
580- ios : styles . iOSclearButton ,
581- android : styles . androidClearButton ,
582- } ) }
583- >
584- { '×' }
585- </ Text >
616+ { clearElement || (
617+ < View style = { styles . clearTextWrapper } >
618+ < Text
619+ style = { [
620+ Platform . select ( {
621+ ios : styles . iOSclearText ,
622+ android : styles . androidClearText ,
623+ } ) ,
624+ style . clearButtonText ,
625+ ] }
626+ accessibilityElementsHidden = { true }
627+ importantForAccessibility = "no-hide-descendants"
628+ >
629+ { '×' }
630+ </ Text >
631+ </ View >
632+ ) }
586633 </ TouchableOpacity >
587634 ) }
588635
@@ -592,6 +639,10 @@ const GooglePlacesTextInput = forwardRef<
592639 style = { [ styles . loadingIndicator , getIconPosition ( 45 ) ] }
593640 size = { 'small' }
594641 color = { style . loadingIndicator ?. color || '#000000' }
642+ accessibilityLiveRegion = "polite"
643+ accessibilityLabel = {
644+ accessibilityLabels . loadingIndicator || 'Loading suggestions'
645+ }
595646 />
596647 ) }
597648 </ View >
@@ -610,6 +661,8 @@ const GooglePlacesTextInput = forwardRef<
610661 nestedScrollEnabled = { nestedScrollEnabled }
611662 bounces = { false }
612663 style = { style . suggestionsList }
664+ accessibilityRole = "list"
665+ accessibilityLabel = { `${ predictions . length } place suggestion resuts` }
613666 />
614667 </ View >
615668 ) }
@@ -662,30 +715,27 @@ const styles = StyleSheet.create({
662715 top : '50%' ,
663716 transform : [ { translateY : - 10 } ] ,
664717 } ,
665- iOSclearButton : {
666- fontSize : 18 ,
718+ clearTextWrapper : {
719+ backgroundColor : '#999' ,
720+ borderRadius : 12 ,
721+ width : 24 ,
722+ height : 24 ,
723+ alignItems : 'center' ,
724+ justifyContent : 'center' ,
725+ } ,
726+ //this is never going to be consistent between different phone fonts and sizes
727+ iOSclearText : {
728+ fontSize : 22 ,
667729 fontWeight : '400' ,
668730 color : 'white' ,
669- backgroundColor : '#999' ,
670- width : 25 ,
671- height : 25 ,
672- borderRadius : 12.5 ,
673- textAlign : 'center' ,
674- textAlignVertical : 'center' ,
675- lineHeight : 19 ,
731+ lineHeight : 24 ,
676732 includeFontPadding : false ,
677733 } ,
678- androidClearButton : {
734+ androidClearText : {
679735 fontSize : 24 ,
680736 fontWeight : '400' ,
681737 color : 'white' ,
682- backgroundColor : '#999' ,
683- width : 24 ,
684- height : 24 ,
685- borderRadius : 12 ,
686- textAlign : 'center' ,
687- textAlignVertical : 'center' ,
688- lineHeight : 20 ,
738+ lineHeight : 25.5 ,
689739 includeFontPadding : false ,
690740 } ,
691741} ) ;
@@ -698,6 +748,7 @@ export type {
698748 PlaceDetailsFields ,
699749 PlacePrediction ,
700750 PlaceStructuredFormat ,
751+ GooglePlacesAccessibilityLabels ,
701752} ;
702753
703754export default GooglePlacesTextInput ;
0 commit comments