UNPKG

expo-osm-sdk

Version:

OpenStreetMap component for React Native with Expo

285 lines 12 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchBox = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const useNominatimSearch_1 = require("../hooks/useNominatimSearch"); /** * SearchBox Component * A search input with autocomplete functionality for finding locations * @example * ``` * <SearchBox * onLocationSelected={(location) => { * console.log('Selected:', location.displayName); * mapRef.current?.animateToLocation( * location.coordinate.latitude, * location.coordinate.longitude, * 15 * ); * }} * maxResults={5} * autoComplete={true} * /> * ``` */ const SearchBox = ({ onLocationSelected, onResultsChanged, placeholder = "Search for places...", style, containerStyle, maxResults = 5, showCurrentLocation = true, autoComplete = true, debounceMs = 300, }) => { const [query, setQuery] = (0, react_1.useState)(''); const [showResults, setShowResults] = (0, react_1.useState)(false); const { search, isLoading, error, lastResults, clearResults } = (0, useNominatimSearch_1.useNominatimSearch)(); // Debug logging for state changes react_1.default.useEffect(() => { console.log('🔍 SearchBox State:', { showResults, lastResultsCount: lastResults.length, isLoading, error, query: query.substring(0, 20) + (query.length > 20 ? '...' : ''), }); if (showResults && lastResults.length > 0) { console.log('🔍 Should show results dropdown with:', lastResults.length, 'items'); } }, [showResults, lastResults, isLoading, error, query]); const debounceTimeout = (0, react_1.useRef)(null); const inputRef = (0, react_1.useRef)(null); const onResultsChangedRef = (0, react_1.useRef)(onResultsChanged); // Keep callback ref updated (0, react_1.useEffect)(() => { onResultsChangedRef.current = onResultsChanged; }, [onResultsChanged]); // Debounced search effect (0, react_1.useEffect)(() => { if (!autoComplete || !query.trim()) { clearResults(); setShowResults(false); onResultsChangedRef.current?.([]); return; } if (debounceTimeout.current) { clearTimeout(debounceTimeout.current); } debounceTimeout.current = setTimeout(async () => { try { const results = await search(query, { limit: maxResults }); console.log('🔍 SearchBox: Search completed', { query, resultsCount: results.length, showResults: results.length > 0 }); setShowResults(results.length > 0); onResultsChangedRef.current?.(results); } catch (err) { console.error('Search error:', err); setShowResults(false); onResultsChangedRef.current?.([]); } }, debounceMs); return () => { if (debounceTimeout.current) { clearTimeout(debounceTimeout.current); } }; }, [query, autoComplete, maxResults, debounceMs, search, clearResults]); const handleLocationSelect = (location) => { setQuery(location.displayName); setShowResults(false); onLocationSelected?.(location); inputRef.current?.blur(); }; const handleSearchPress = async () => { if (!query.trim()) return; try { const results = await search(query, { limit: maxResults }); setShowResults(results.length > 0); onResultsChanged?.(results); } catch (err) { console.error('Search error:', err); } }; const handleClear = () => { setQuery(''); setShowResults(false); clearResults(); onResultsChanged?.([]); inputRef.current?.focus(); }; return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.searchContainer, children: [(0, jsx_runtime_1.jsx)(react_native_1.TextInput, { ref: inputRef, style: [styles.textInput, style], placeholder: placeholder, value: query, onChangeText: setQuery, onFocus: () => { if (lastResults.length > 0 && autoComplete) { setShowResults(true); } }, onBlur: () => { setTimeout(() => setShowResults(false), 150); }, returnKeyType: "search", onSubmitEditing: handleSearchPress }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.actionContainer, children: [isLoading && (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { style: styles.loader }), query.length > 0 && !isLoading && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.clearButton, onPress: handleClear, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.clearButtonText, children: "\u2715" }) })), !autoComplete && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.searchButton, onPress: handleSearchPress, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.searchButtonText, children: "\uD83D\uDD0D" }) }))] })] }), error && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.errorContainer, children: (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.errorText, children: ["\u26A0\uFE0F ", error] }) })), showResults && lastResults.length > 0 && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.resultsContainer, children: lastResults.slice(0, maxResults).map((result, index) => { // Split display name for styling const displayNameParts = result.displayName.split(','); const title = displayNameParts[0]; const subtitle = displayNameParts.slice(1).join(',').trim(); return ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPress: () => handleLocationSelect(result), style: ({ pressed }) => [ styles.resultItem, { backgroundColor: pressed ? '#F5F5F5' : '#FFFFFF' }, // Hover/press effect index === lastResults.slice(0, maxResults).length - 1 && styles.lastResultItem, ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.resultIcon, children: getCategoryIcon(result.category || '') }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.resultTextContainer, children: (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.resultText, numberOfLines: 1, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.resultTextBold, children: title }), subtitle ? (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.resultTextRegular, children: [" ", subtitle] }) : null] }) })] }, result.placeId || index)); }) }))] })); }; exports.SearchBox = SearchBox; /** * Get icon for category */ const getCategoryIcon = (category) => { const iconMap = { 'amenity': '🏪', 'shop': '🛍️', 'tourism': '🏛️', 'leisure': '🎯', 'natural': '🌲', 'place': '📍', 'highway': '🛣️', 'building': '🏢', 'landuse': '🏞️', 'waterway': '🌊', }; return iconMap[category] || '📍'; }; const styles = react_native_1.StyleSheet.create({ container: { position: 'relative', zIndex: 1000, }, searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'white', borderRadius: 12, borderWidth: 1, borderColor: '#E0E0E0', paddingHorizontal: 16, paddingVertical: react_native_1.Platform.OS === 'ios' ? 12 : 8, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, textInput: { flex: 1, fontSize: 16, color: '#000000', paddingVertical: 0, }, actionContainer: { flexDirection: 'row', alignItems: 'center', marginLeft: 8, }, loader: { marginRight: 8, }, clearButton: { padding: 4, marginRight: 4, }, clearButtonText: { fontSize: 16, color: '#999', fontWeight: 'bold', }, searchButton: { padding: 4, }, searchButtonText: { fontSize: 16, }, errorContainer: { backgroundColor: '#FFE6E6', borderRadius: 8, padding: 12, marginTop: 8, borderLeftWidth: 4, borderLeftColor: '#FF4444', }, errorText: { color: '#CC0000', fontSize: 14, fontWeight: '500', }, resultsContainer: { backgroundColor: 'white', borderRadius: 12, marginTop: 8, borderWidth: 1, borderColor: '#E0E0E0', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 5, maxHeight: 300, overflow: 'hidden', // NEW: Ensures border radius is respected by children }, // --- MODIFIED & NEW STYLES FOR RESULT ITEMS --- resultItem: { flexDirection: 'row', // MODIFIED: Align icon and text horizontally alignItems: 'center', // MODIFIED: Center items vertically paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 0.5, borderBottomColor: '#E8E8E8', // MODIFIED: Background color moved to Pressable style prop }, lastResultItem: { borderBottomWidth: 0, }, resultIcon: { fontSize: 20, marginRight: 16, color: '#555555', }, resultTextContainer: { flex: 1, }, resultText: { fontSize: 16, fontFamily: react_native_1.Platform.OS === 'ios' ? 'System' : 'Roboto', textAlign: 'left', }, resultTextBold: { fontWeight: '600', color: '#000000', }, resultTextRegular: { fontWeight: '400', color: '#555555', }, // --- REMOVED STYLES (Replaced by the above) --- // - resultContent // - resultTitle // - resultSubtitle // - resultCategory }); //# sourceMappingURL=SearchBox.js.map