expo-osm-sdk
Version:
OpenStreetMap component for React Native with Expo
285 lines • 12 kB
JavaScript
"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