From 87cfa93e34dffa0a70f65313e01164263b05759f Mon Sep 17 00:00:00 2001 From: Jeius Date: Tue, 3 Feb 2026 03:34:04 +0800 Subject: [PATCH] fix(invisible-text): change flex direction to row and add inputContainer style --- src/ClearButtonAndSpinner.tsx | 121 +++++ src/GooglePlacesTextInput.tsx | 988 +++++++++++++++------------------- 2 files changed, 570 insertions(+), 539 deletions(-) create mode 100644 src/ClearButtonAndSpinner.tsx diff --git a/src/ClearButtonAndSpinner.tsx b/src/ClearButtonAndSpinner.tsx new file mode 100644 index 0000000..c6e4986 --- /dev/null +++ b/src/ClearButtonAndSpinner.tsx @@ -0,0 +1,121 @@ +import type { GooglePlacesTextInputProps } from './GooglePlacesTextInput'; +import { + ActivityIndicator, + Platform, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; + +interface ClearButtonAndSpinnerProps + extends Pick< + GooglePlacesTextInputProps, + | 'showClearButton' + | 'style' + | 'accessibilityLabels' + | 'clearElement' + | 'showLoadingIndicator' + > { + isInputEmpty: boolean; + isLoading: boolean; + onPress: () => void; +} + +const ClearButtonAndSpinner: React.FC = ({ + showClearButton = true, + showLoadingIndicator = true, + style = {}, + accessibilityLabels, + isLoading, + isInputEmpty, + onPress, + clearElement, +}) => { + // Loading indicator + if (isLoading) { + if (!showLoadingIndicator) return null; + + return ( + + ); + } + + // Clear button visibility check + // Not shown if showClearButton is false or input is empty + if (!showClearButton || isInputEmpty) return null; + + // Clear button + return ( + + {clearElement || ( + + + {'×'} + + + )} + + ); +}; + +const styles = StyleSheet.create({ + clearButton: { + alignSelf: 'center', + padding: 0, + }, + loadingIndicator: { + alignSelf: 'center', + }, + clearTextWrapper: { + backgroundColor: '#999', + borderRadius: 12, + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + }, + //this is never going to be consistent between different phone fonts and sizes + iOSclearText: { + fontSize: 22, + fontWeight: '400', + color: 'white', + lineHeight: 24, + includeFontPadding: false, + }, + androidClearText: { + fontSize: 24, + fontWeight: '400', + color: 'white', + lineHeight: 25.5, + includeFontPadding: false, + }, +}); + +export type { ClearButtonAndSpinnerProps }; +export default ClearButtonAndSpinner; diff --git a/src/GooglePlacesTextInput.tsx b/src/GooglePlacesTextInput.tsx index 1c93e1b..f281937 100644 --- a/src/GooglePlacesTextInput.tsx +++ b/src/GooglePlacesTextInput.tsx @@ -15,7 +15,6 @@ import type { NativeSyntheticEvent, } from 'react-native'; import { - ActivityIndicator, FlatList, I18nManager, Keyboard, @@ -35,6 +34,8 @@ import { isRTLText, } from './services/googlePlacesApi'; +import ClearButtonAndSpinner from './ClearButtonAndSpinner'; + // Type definitions interface PlaceStructuredFormat { mainText: { @@ -65,6 +66,7 @@ interface Place { interface GooglePlacesTextInputStyles { container?: StyleProp; input?: StyleProp; + inputContainer?: StyleProp; suggestionsContainer?: StyleProp; suggestionsList?: StyleProp; suggestionItem?: StyleProp; @@ -168,144 +170,166 @@ interface PredictionItem { const GooglePlacesTextInput = forwardRef< GooglePlacesTextInputRef, GooglePlacesTextInputProps ->( - ( - { - apiKey, - value, - placeHolderText, +>((props, ref) => { + const { + apiKey, + value, + placeHolderText, + proxyUrl, + proxyHeaders = null, + languageCode, + includedRegionCodes, + locationBias, + locationRestriction, + types = [], + biasPrefixText, + minCharsToFetch = 1, + onPlaceSelect, + onTextChange, + debounceDelay = 200, + forceRTL = undefined, + style = {}, + hideOnKeyboardDismiss = false, + scrollEnabled = true, + nestedScrollEnabled = true, + fetchDetails = false, + detailsProxyUrl = null, + detailsProxyHeaders = null, + detailsFields = [], + onError, + enableDebug = false, + onFocus, + onBlur, + accessibilityLabels = {}, + suggestionTextProps = {}, + ...restTextInputProps + } = props; + + const [predictions, setPredictions] = useState([]); + const [loading, setLoading] = useState(false); + const [inputText, setInputText] = useState(value || ''); + const [showSuggestions, setShowSuggestions] = useState(false); + const [sessionToken, setSessionToken] = useState(null); + const [detailsLoading, setDetailsLoading] = useState(false); + const debounceTimeout = useRef | null>(null); + const inputRef = useRef(null); + const suggestionPressing = useRef(false); + const skipNextFocusFetch = useRef(false); + + const generateSessionToken = (): string => { + return generateUUID(); + }; + + // Initialize session token on mount + useEffect(() => { + setSessionToken(generateSessionToken()); + + return () => { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + }; + }, []); + + useEffect(() => { + setInputText(value ?? ''); + }, [value]); + + // Add keyboard listener + useEffect(() => { + if (hideOnKeyboardDismiss) { + const keyboardDidHideSubscription = Keyboard.addListener( + 'keyboardDidHide', + () => setShowSuggestions(false) + ); + + return () => { + keyboardDidHideSubscription.remove(); + }; + } + return () => {}; + }, [hideOnKeyboardDismiss]); + + // Expose methods to parent through ref + useImperativeHandle(ref, () => ({ + clear: () => { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + skipNextFocusFetch.current = true; + setInputText(''); + setPredictions([]); + setShowSuggestions(false); + setSessionToken(generateSessionToken()); + }, + blur: () => { + inputRef.current?.blur(); + }, + focus: () => { + inputRef.current?.focus(); + }, + getSessionToken: () => sessionToken, + })); + + // RTL detection logic + const isDeviceRTL = I18nManager.isRTL; + const isRTL = + forceRTL !== undefined ? forceRTL : isRTLText(placeHolderText ?? ''); + + // Add missing CORS warning effect + useEffect(() => { + if (Platform.OS === 'web' && fetchDetails && !detailsProxyUrl) { + console.warn( + 'Google Places Details API does not support CORS. ' + + 'To fetch place details on web, provide a detailsProxyUrl prop that points to a CORS-enabled proxy.' + ); + } + }, [fetchDetails, detailsProxyUrl]); + + // Debug logger utility + const debugLog = (category: string, message: string, data?: any) => { + if (enableDebug) { + const timestamp = new Date().toISOString(); + console.log( + `[GooglePlacesTextInput:${category}] ${timestamp} - ${message}` + ); + if (data) { + console.log(`[GooglePlacesTextInput:${category}] Data:`, data); + } + } + }; + + const fetchPredictions = async (text: string): Promise => { + debugLog('PREDICTIONS', `Starting fetch for text: "${text}"`); + debugLog('PREDICTIONS', 'Request params', { + text, + apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix proxyUrl, - proxyHeaders = null, + proxyHeaders, + sessionToken, languageCode, includedRegionCodes, locationBias, locationRestriction, - types = [], - biasPrefixText, - minCharsToFetch = 1, - onPlaceSelect, - onTextChange, - debounceDelay = 200, - showLoadingIndicator = true, - showClearButton = true, - forceRTL = undefined, - style = {}, - clearElement, - hideOnKeyboardDismiss = false, - scrollEnabled = true, - nestedScrollEnabled = true, - fetchDetails = false, - detailsProxyUrl = null, - detailsProxyHeaders = null, - detailsFields = [], - onError, - enableDebug = false, - onFocus, - onBlur, - accessibilityLabels = {}, - suggestionTextProps = {}, - ...restTextInputProps - }, - ref - ) => { - const [predictions, setPredictions] = useState([]); - const [loading, setLoading] = useState(false); - const [inputText, setInputText] = useState(value || ''); - const [showSuggestions, setShowSuggestions] = useState(false); - const [sessionToken, setSessionToken] = useState(null); - const [detailsLoading, setDetailsLoading] = useState(false); - const debounceTimeout = useRef | null>(null); - const inputRef = useRef(null); - const suggestionPressing = useRef(false); - const skipNextFocusFetch = useRef(false); - - const generateSessionToken = (): string => { - return generateUUID(); - }; + types, + minCharsToFetch, + }); - // Initialize session token on mount - useEffect(() => { - setSessionToken(generateSessionToken()); + if (!text || text.length < minCharsToFetch) { + debugLog( + 'PREDICTIONS', + `Text too short (${text.length} < ${minCharsToFetch})` + ); + setPredictions([]); + return; + } - return () => { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } - }; - }, []); - - useEffect(() => { - setInputText(value ?? ''); - }, [value]); - - // Add keyboard listener - useEffect(() => { - if (hideOnKeyboardDismiss) { - const keyboardDidHideSubscription = Keyboard.addListener( - 'keyboardDidHide', - () => setShowSuggestions(false) - ); - - return () => { - keyboardDidHideSubscription.remove(); - }; - } - return () => {}; - }, [hideOnKeyboardDismiss]); - - // Expose methods to parent through ref - useImperativeHandle(ref, () => ({ - clear: () => { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } - skipNextFocusFetch.current = true; - setInputText(''); - setPredictions([]); - setShowSuggestions(false); - setSessionToken(generateSessionToken()); - }, - blur: () => { - inputRef.current?.blur(); - }, - focus: () => { - inputRef.current?.focus(); - }, - getSessionToken: () => sessionToken, - })); - - // RTL detection logic - const isRTL = - forceRTL !== undefined ? forceRTL : isRTLText(placeHolderText ?? ''); - - // Add missing CORS warning effect - useEffect(() => { - if (Platform.OS === 'web' && fetchDetails && !detailsProxyUrl) { - console.warn( - 'Google Places Details API does not support CORS. ' + - 'To fetch place details on web, provide a detailsProxyUrl prop that points to a CORS-enabled proxy.' - ); - } - }, [fetchDetails, detailsProxyUrl]); - - // Debug logger utility - const debugLog = (category: string, message: string, data?: any) => { - if (enableDebug) { - const timestamp = new Date().toISOString(); - console.log( - `[GooglePlacesTextInput:${category}] ${timestamp} - ${message}` - ); - if (data) { - console.log(`[GooglePlacesTextInput:${category}] Data:`, data); - } - } - }; + setLoading(true); - const fetchPredictions = async (text: string): Promise => { - debugLog('PREDICTIONS', `Starting fetch for text: "${text}"`); - debugLog('PREDICTIONS', 'Request params', { + const { error, predictions: fetchedPredictions } = + await fetchPredictionsApi({ text, - apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix + apiKey, proxyUrl, proxyHeaders, sessionToken, @@ -314,432 +338,352 @@ const GooglePlacesTextInput = forwardRef< locationBias, locationRestriction, types, - minCharsToFetch, + biasPrefixText, }); - if (!text || text.length < minCharsToFetch) { - debugLog( - 'PREDICTIONS', - `Text too short (${text.length} < ${minCharsToFetch})` - ); - setPredictions([]); - return; - } - - setLoading(true); + if (error) { + debugLog('PREDICTIONS', 'API Error occurred', { + errorType: error.constructor.name, + errorMessage: error.message, + errorStack: error.stack, + }); + onError?.(error); + setPredictions([]); + } else { + debugLog( + 'PREDICTIONS', + `Success: ${fetchedPredictions.length} predictions received` + ); + debugLog('PREDICTIONS', 'Predictions data', fetchedPredictions); + setPredictions(fetchedPredictions); + setShowSuggestions(fetchedPredictions.length > 0); + } - const { error, predictions: fetchedPredictions } = - await fetchPredictionsApi({ - text, - apiKey, - proxyUrl, - proxyHeaders, - sessionToken, - languageCode, - includedRegionCodes, - locationBias, - locationRestriction, - types, - biasPrefixText, - }); - - if (error) { - debugLog('PREDICTIONS', 'API Error occurred', { - errorType: error.constructor.name, - errorMessage: error.message, - errorStack: error.stack, - }); - onError?.(error); - setPredictions([]); - } else { - debugLog( - 'PREDICTIONS', - `Success: ${fetchedPredictions.length} predictions received` - ); - debugLog('PREDICTIONS', 'Predictions data', fetchedPredictions); - setPredictions(fetchedPredictions); - setShowSuggestions(fetchedPredictions.length > 0); - } + setLoading(false); + }; - setLoading(false); - }; + const fetchPlaceDetails = async ( + placeId: string + ): Promise => { + debugLog('DETAILS', `Starting details fetch for placeId: ${placeId}`); + debugLog('DETAILS', 'Request params', { + placeId, + apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix + detailsProxyUrl, + detailsProxyHeaders, + sessionToken, + languageCode, + detailsFields, + fetchDetails, + platform: Platform.OS, + }); - const fetchPlaceDetails = async ( - placeId: string - ): Promise => { - debugLog('DETAILS', `Starting details fetch for placeId: ${placeId}`); - debugLog('DETAILS', 'Request params', { - placeId, - apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix - detailsProxyUrl, - detailsProxyHeaders, - sessionToken, - languageCode, - detailsFields, + if (!fetchDetails || !placeId) { + debugLog('DETAILS', 'Skipping details fetch', { fetchDetails, - platform: Platform.OS, + placeId, }); + return null; + } - if (!fetchDetails || !placeId) { - debugLog('DETAILS', 'Skipping details fetch', { - fetchDetails, - placeId, - }); - return null; - } + // Web CORS warning + if (Platform.OS === 'web' && !detailsProxyUrl) { + debugLog( + 'DETAILS', + 'WARNING: Web platform detected without detailsProxyUrl - CORS issues likely' + ); + } - // Web CORS warning - if (Platform.OS === 'web' && !detailsProxyUrl) { - debugLog( - 'DETAILS', - 'WARNING: Web platform detected without detailsProxyUrl - CORS issues likely' - ); - } + setDetailsLoading(true); - setDetailsLoading(true); + const { error, details } = await fetchPlaceDetailsApi({ + placeId, + apiKey, + detailsProxyUrl, + detailsProxyHeaders, + sessionToken, + languageCode, + detailsFields, + }); - const { error, details } = await fetchPlaceDetailsApi({ - placeId, - apiKey, - detailsProxyUrl, - detailsProxyHeaders, - sessionToken, - languageCode, - detailsFields, + setDetailsLoading(false); + + if (error) { + debugLog('DETAILS', 'API Error occurred', { + errorType: error.constructor.name, + errorMessage: error.message, + errorStack: error.stack, }); + onError?.(error); + return null; + } - setDetailsLoading(false); + debugLog('DETAILS', 'Success: Details received', details); + return details; + }; - if (error) { - debugLog('DETAILS', 'API Error occurred', { - errorType: error.constructor.name, - errorMessage: error.message, - errorStack: error.stack, - }); - onError?.(error); - return null; - } + const handleTextChange = (text: string): void => { + setInputText(text); + onTextChange?.(text); - debugLog('DETAILS', 'Success: Details received', details); - return details; - }; + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } - const handleTextChange = (text: string): void => { - setInputText(text); - onTextChange?.(text); + debounceTimeout.current = setTimeout(() => { + fetchPredictions(text); + }, debounceDelay); + }; - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } + const handleSuggestionPress = async ( + suggestion: PredictionItem + ): Promise => { + const place = suggestion.placePrediction; + debugLog( + 'SELECTION', + `User selected place: ${place.structuredFormat.mainText.text}` + ); + debugLog('SELECTION', 'Selected place data', place); - debounceTimeout.current = setTimeout(() => { - fetchPredictions(text); - }, debounceDelay); - }; + setInputText(place.structuredFormat.mainText.text); + setShowSuggestions(false); + Keyboard.dismiss(); - const handleSuggestionPress = async ( - suggestion: PredictionItem - ): Promise => { - const place = suggestion.placePrediction; + if (fetchDetails) { + debugLog('SELECTION', 'Fetching place details...'); + setLoading(true); + const details = await fetchPlaceDetails(place.placeId); + const enrichedPlace: Place = details ? { ...place, details } : place; + + debugLog('SELECTION', 'Final place object being sent to onPlaceSelect', { + hasDetails: !!details, + placeKeys: Object.keys(enrichedPlace), + detailsKeys: details ? Object.keys(details) : null, + }); + + onPlaceSelect?.(enrichedPlace, sessionToken); + setLoading(false); + } else { debugLog( 'SELECTION', - `User selected place: ${place.structuredFormat.mainText.text}` + 'Sending place without details (fetchDetails=false)' ); - debugLog('SELECTION', 'Selected place data', place); + onPlaceSelect?.(place, sessionToken); + } + setSessionToken(generateSessionToken()); + }; - setInputText(place.structuredFormat.mainText.text); - setShowSuggestions(false); - Keyboard.dismiss(); - - if (fetchDetails) { - debugLog('SELECTION', 'Fetching place details...'); - setLoading(true); - const details = await fetchPlaceDetails(place.placeId); - const enrichedPlace: Place = details ? { ...place, details } : place; - - debugLog( - 'SELECTION', - 'Final place object being sent to onPlaceSelect', - { - hasDetails: !!details, - placeKeys: Object.keys(enrichedPlace), - detailsKeys: details ? Object.keys(details) : null, - } - ); - - onPlaceSelect?.(enrichedPlace, sessionToken); - setLoading(false); - } else { - debugLog( - 'SELECTION', - 'Sending place without details (fetchDetails=false)' - ); - onPlaceSelect?.(place, sessionToken); - } - setSessionToken(generateSessionToken()); - }; + const handleFocus = ( + event: NativeSyntheticEvent + ): void => { + onFocus?.(event); + + if (skipNextFocusFetch.current) { + skipNextFocusFetch.current = false; + return; + } + if (inputText.length >= minCharsToFetch) { + fetchPredictions(inputText); + setShowSuggestions(true); + } + }; - const handleFocus = ( - event: NativeSyntheticEvent - ): void => { - onFocus?.(event); + const handleBlur = ( + event: NativeSyntheticEvent + ): void => { + onBlur?.(event); - if (skipNextFocusFetch.current) { - skipNextFocusFetch.current = false; - return; - } - if (inputText.length >= minCharsToFetch) { - fetchPredictions(inputText); - setShowSuggestions(true); + setTimeout(() => { + if (suggestionPressing.current) { + suggestionPressing.current = false; + } else { + setShowSuggestions(false); } - }; + }, 10); + }; - const handleBlur = ( - event: NativeSyntheticEvent - ): void => { - onBlur?.(event); + const renderSuggestion = ({ + item, + index, + }: { + item: PredictionItem; + index: number; + }) => { + const { mainText, secondaryText } = item.placePrediction.structuredFormat; + + // Safely extract backgroundColor from style + const suggestionsContainerStyle = StyleSheet.flatten( + style.suggestionsContainer + ); + const backgroundColor = + suggestionsContainerStyle?.backgroundColor || '#efeff1'; - setTimeout(() => { - if (suggestionPressing.current) { - suggestionPressing.current = false; - } else { - setShowSuggestions(false); - } - }, 10); - }; + const defaultAccessibilityLabel = `${mainText.text}${ + secondaryText ? `, ${secondaryText.text}` : '' + }`; + const accessibilityLabel = + accessibilityLabels.suggestionItem?.(item.placePrediction) || + defaultAccessibilityLabel; - const renderSuggestion = ({ - item, - index, - }: { - item: PredictionItem; - index: number; - }) => { - const { mainText, secondaryText } = item.placePrediction.structuredFormat; - - // Safely extract backgroundColor from style - const suggestionsContainerStyle = StyleSheet.flatten( - style.suggestionsContainer - ); - const backgroundColor = - suggestionsContainerStyle?.backgroundColor || '#efeff1'; - - const defaultAccessibilityLabel = `${mainText.text}${ - secondaryText ? `, ${secondaryText.text}` : '' - }`; - const accessibilityLabel = - accessibilityLabels.suggestionItem?.(item.placePrediction) || - defaultAccessibilityLabel; - - return ( - 0 ? styles.separatorLine : {}, - styles.suggestionItem, - { backgroundColor }, - style.suggestionItem, - ]} - onPressIn={() => { - suggestionPressing.current = true; - }} - onPress={() => { - suggestionPressing.current = false; - handleSuggestionPress(item); - }} - // Fix for web: onBlur fires before onPress, hiding suggestions too early. - {...(Platform.OS === 'web' && - ({ - onMouseDown: () => { - suggestionPressing.current = true; - }, - } as any))} + return ( + 0 ? styles.separatorLine : {}, + styles.suggestionItem, + { backgroundColor }, + style.suggestionItem, + ]} + onPressIn={() => { + suggestionPressing.current = true; + }} + onPress={() => { + suggestionPressing.current = false; + handleSuggestionPress(item); + }} + // Fix for web: onBlur fires before onPress, hiding suggestions too early. + {...(Platform.OS === 'web' && + ({ + onMouseDown: () => { + suggestionPressing.current = true; + }, + } as any))} + > + + {mainText.text} + + {secondaryText && ( - {mainText.text} + {secondaryText.text} - {secondaryText && ( - - {secondaryText.text} - - )} - - ); - }; - - const getPadding = () => { - const physicalRTL = I18nManager.isRTL; - const clearButtonPadding = showClearButton ? 75 : 45; - if (isRTL !== physicalRTL) { - return { - paddingStart: clearButtonPadding, - paddingEnd: 15, - }; - } - return { - paddingStart: 15, - paddingEnd: clearButtonPadding, - }; - }; - - const getTextAlign = () => { - const isDeviceRTL = I18nManager.isRTL; - if (isDeviceRTL) { - return { textAlign: isRTL ? 'left' : ('right' as 'left' | 'right') }; - } else { - return { textAlign: isRTL ? 'right' : ('left' as 'left' | 'right') }; - } - }; + )} + + ); + }; - const getIconPosition = (paddingValue: number) => { - const physicalRTL = I18nManager.isRTL; - if (isRTL !== physicalRTL) { - return { start: paddingValue }; - } - return { end: paddingValue }; - }; + const getTextAlign = () => { + if (isDeviceRTL) { + return { textAlign: isRTL ? 'left' : ('right' as 'left' | 'right') }; + } else { + return { textAlign: isRTL ? 'right' : ('left' as 'left' | 'right') }; + } + }; - // Debug initialization - useEffect(() => { - if (enableDebug) { - debugLog('INIT', 'Component initialized with props', { - apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix - fetchDetails, - detailsProxyUrl, - detailsFields, - platform: Platform.OS, - minCharsToFetch, - debounceDelay, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const handleClearPress = () => { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + skipNextFocusFetch.current = true; + setInputText(''); + setPredictions([]); + setShowSuggestions(false); + onTextChange?.(''); + setSessionToken(generateSessionToken()); + inputRef.current?.focus(); + }; - return ( - - - { + if (enableDebug) { + debugLog('INIT', 'Component initialized with props', { + apiKey: apiKey ? '[PROVIDED]' : '[MISSING]', // ✅ Security fix + fetchDetails, + detailsProxyUrl, + detailsFields, + platform: Platform.OS, + minCharsToFetch, + debounceDelay, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + {/* Render Button and Loader here if is forced RTL */} + {isRTL !== isDeviceRTL && ( + + )} - {/* Clear button - shown only if showClearButton is true */} - {showClearButton && inputText !== '' && ( - { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } - skipNextFocusFetch.current = true; - setInputText(''); - setPredictions([]); - setShowSuggestions(false); - onTextChange?.(''); - setSessionToken(generateSessionToken()); - inputRef.current?.focus(); - }} - accessibilityRole="button" - accessibilityLabel={ - accessibilityLabels.clearButton || 'Clear input text' - } - > - {clearElement || ( - - - {'×'} - - - )} - - )} - - {/* Loading indicator */} - {(loading || detailsLoading) && showLoadingIndicator && ( - - )} - - - {/* Suggestions */} - {showSuggestions && predictions.length > 0 && ( - - item.placePrediction.placeId} - keyboardShouldPersistTaps="always" - scrollEnabled={scrollEnabled} - nestedScrollEnabled={nestedScrollEnabled} - bounces={false} - style={style.suggestionsList} - accessibilityRole="list" - accessibilityLabel={`${predictions.length} place suggestion resuts`} - /> - + + + {isRTL === isDeviceRTL && ( + )} - ); - } -); + + {/* Suggestions */} + {showSuggestions && predictions.length > 0 && ( + + item.placePrediction.placeId} + keyboardShouldPersistTaps="always" + scrollEnabled={scrollEnabled} + nestedScrollEnabled={nestedScrollEnabled} + bounces={false} + style={style.suggestionsList} + accessibilityRole="list" + accessibilityLabel={`${predictions.length} place suggestion resuts`} + /> + + )} + + ); +}); const styles = StyleSheet.create({ container: {}, - input: { - flex: 1, + inputContainer: { borderRadius: 6, borderWidth: 1, - paddingHorizontal: 10, + paddingHorizontal: 12, + paddingVertical: 0, backgroundColor: 'white', + flexDirection: 'row', + }, + input: { + flex: 1, fontSize: 16, - paddingVertical: 16, }, suggestionsContainer: { backgroundColor: '#efeff1', // default background @@ -766,40 +710,6 @@ const styles = StyleSheet.create({ marginTop: 2, textAlign: 'left', }, - clearButton: { - position: 'absolute', - top: '50%', - transform: [{ translateY: -13 }], - padding: 0, - }, - loadingIndicator: { - position: 'absolute', - top: '50%', - transform: [{ translateY: -10 }], - }, - clearTextWrapper: { - backgroundColor: '#999', - borderRadius: 12, - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - }, - //this is never going to be consistent between different phone fonts and sizes - iOSclearText: { - fontSize: 22, - fontWeight: '400', - color: 'white', - lineHeight: 24, - includeFontPadding: false, - }, - androidClearText: { - fontSize: 24, - fontWeight: '400', - color: 'white', - lineHeight: 25.5, - includeFontPadding: false, - }, }); export type {