import { Paper, IconButton } from '@mui/material' import { MyLocationOutlined, LocationDisabledOutlined, } from '@mui/icons-material' import type { GeolocationControlProps } from './types' import { useCallback, useEffect, useEffectEvent, useReducer, useRef, type JSX, } from 'react' import { ariaLabel, tooltipLabelsDefault } from './const' import { Tooltip } from '../tooltip/tooltip' /** * Provides a seamless interface for accessing device location through the browser's Geolocation API, with permission handling, continuous tracking, and error management. * * @example * ```tsx * console.log(coords.latitude, coords.longitude)} * onError={(error) => console.error(error.message)} * /> * ``` */ export function GeolocationControls({ disabled, labels, PaperProps, TooltipProps, watch, onChange, onError, }: GeolocationControlProps): JSX.Element { const watchRef = useRef(null) const hasGeolocation = 'geolocation' in navigator const [allowed, dispatch] = useReducer( (_: boolean, action: 'granted' | 'denied') => action === 'granted', hasGeolocation, ) // Read the latest `onError` without making the permission-query effect // re-run (and re-register the `result.onchange` permission listener) // whenever the consumer passes a fresh callback identity. const notifyError = useEffectEvent( (error: Parameters[0]): void => { onError?.(error) }, ) useEffect(() => { if (hasGeolocation) { navigator.permissions .query({ name: 'geolocation' }) .then((result) => { dispatch(result.state === 'granted' ? 'granted' : 'denied') result.onchange = () => { dispatch(result.state === 'granted' ? 'granted' : 'denied') } }) .catch((error: Parameters[0]) => { dispatch('denied') notifyError(error) }) } return () => { if (watchRef.current) { navigator.geolocation.clearWatch(watchRef.current) } } }, [hasGeolocation]) const success = useCallback( (position: Parameters[0]) => { onChange(position.coords) }, [onChange], ) const error = useCallback( (error: Parameters[0]) => { dispatch('denied') onError?.(error) }, [onError], ) const handleClick = useCallback(() => { if (watchRef.current) { navigator.geolocation.clearWatch(watchRef.current) } if (watch) { watchRef.current = navigator.geolocation.watchPosition(success, error) return } navigator.geolocation.getCurrentPosition(success, error) }, [watch, success, error]) const tooltipLabels = labels?.tooltip ?? tooltipLabelsDefault return ( {allowed ? : } ) }