import { memo, useState, useEffect, useContext, PureComponent, type ContextType, } from 'react' import { unregisterEvents, applyUpdatersToPropsAndRegisterEvents, } from '../../utils/helper.js' import MapContext from '../../map-context.js' const eventMap = { onCenterChanged: 'center_changed', onRadiusChanged: 'radius_changed', onClick: 'click', onDblClick: 'dblclick', onDrag: 'drag', onDragEnd: 'dragend', onDragStart: 'dragstart', onMouseDown: 'mousedown', onMouseMove: 'mousemove', onMouseOut: 'mouseout', onMouseOver: 'mouseover', onMouseUp: 'mouseup', onRightClick: 'rightclick', } const updaterMap = { center(instance: google.maps.Circle, center: google.maps.LatLng): void { instance.setCenter(center) }, draggable(instance: google.maps.Circle, draggable: boolean): void { instance.setDraggable(draggable) }, editable(instance: google.maps.Circle, editable: boolean): void { instance.setEditable(editable) }, map(instance: google.maps.Circle, map: google.maps.Map): void { instance.setMap(map) }, options( instance: google.maps.Circle, options: google.maps.CircleOptions ): void { instance.setOptions(options) }, radius(instance: google.maps.Circle, radius: number): void { instance.setRadius(radius) }, visible(instance: google.maps.Circle, visible: boolean): void { instance.setVisible(visible) }, } type CircleState = { circle: google.maps.Circle | null } export type CircleProps = { options?: google.maps.CircleOptions | undefined /** sets the center of the circle */ center?: google.maps.LatLng | google.maps.LatLngLiteral | undefined // required /** Sets the radius of this circle (in meters) */ radius?: number | undefined /** If set to true, the user can drag this circle over the map */ draggable?: boolean | undefined /** If set to true, the user can edit this circle by dragging the control points shown at the center and around the circumference of the circle. */ editable?: boolean | undefined /** Hides this circle if set to false. */ visible?: boolean | undefined /** This event is fired when the DOM dblclick event is fired on the circle. */ onDblClick?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the user stops dragging the circle. */ onDragEnd?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the user starts dragging the circle. */ onDragStart?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the DOM mousedown event is fired on the circle. */ onMouseDown?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the DOM mousemove event is fired on the circle. */ onMouseMove?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired on circle mouseout. */ onMouseOut?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired on circle mouseover. */ onMouseOver?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the DOM mouseup event is fired on the circle. */ onMouseUp?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the circle is right-clicked on. */ onRightClick?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the DOM click event is fired on the circle. */ onClick?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is repeatedly fired while the user drags the circle. */ onDrag?: ((e: google.maps.MapMouseEvent) => void) | undefined /** This event is fired when the circle's center is changed. */ onCenterChanged?: (() => void) | undefined /** This event is fired when the circle's radius is changed. */ onRadiusChanged?: (() => void) | undefined /** This callback is called when the circle instance has loaded. It is called with the circle instance. */ onLoad?: ((circle: google.maps.Circle) => void) | undefined /** This callback is called when the component unmounts. It is called with the circle instance. */ onUnmount?: ((circle: google.maps.Circle) => void) | undefined } const defaultOptions = {} function CircleFunctional({ options, center, radius, draggable, editable, visible, onDblClick, onDragEnd, onDragStart, onMouseDown, onMouseMove, onMouseOut, onMouseOver, onMouseUp, onRightClick, onClick, onDrag, onCenterChanged, onRadiusChanged, onLoad, onUnmount, }: CircleProps): null { const map = useContext(MapContext) const [instance, setInstance] = useState(null) const [dblclickListener, setDblclickListener] = useState(null) const [dragendListener, setDragendListener] = useState(null) const [dragstartListener, setDragstartListener] = useState(null) const [mousedownListener, setMousedownListener] = useState(null) const [mousemoveListener, setMousemoveListener] = useState(null) const [mouseoutListener, setMouseoutListener] = useState(null) const [mouseoverListener, setMouseoverListener] = useState(null) const [mouseupListener, setMouseupListener] = useState(null) const [rightclickListener, setRightclickListener] = useState(null) const [clickListener, setClickListener] = useState(null) const [dragListener, setDragListener] = useState(null) const [centerChangedListener, setCenterChangedListener] = useState(null) const [radiusChangedListener, setRadiusChangedListener] = useState(null) // Order does matter useEffect(() => { if (instance !== null) { instance.setMap(map) } }, [map]) useEffect(() => { if (typeof options !== 'undefined' && instance !== null) { instance.setOptions(options) } }, [instance, options]) useEffect(() => { if (typeof draggable !== 'undefined' && instance !== null) { instance.setDraggable(draggable) } }, [instance, draggable]) useEffect(() => { if (typeof editable !== 'undefined' && instance !== null) { instance.setEditable(editable) } }, [instance, editable]) useEffect(() => { if (typeof visible !== 'undefined' && instance !== null) { instance.setVisible(visible) } }, [instance, visible]) useEffect(() => { if (typeof radius === 'number' && instance !== null) { instance.setRadius(radius) } }, [instance, radius]) useEffect(() => { if (typeof center !== 'undefined' && instance !== null) { instance.setCenter(center) } }, [instance, center]) useEffect(() => { if (instance && onDblClick) { if (dblclickListener !== null) { google.maps.event.removeListener(dblclickListener) } setDblclickListener( google.maps.event.addListener(instance, 'dblclick', onDblClick) ) } }, [onDblClick]) useEffect(() => { if (instance && onDragEnd) { if (dragendListener !== null) { google.maps.event.removeListener(dragendListener) } setDragendListener( google.maps.event.addListener(instance, 'dragend', onDragEnd) ) } }, [onDragEnd]) useEffect(() => { if (instance && onDragStart) { if (dragstartListener !== null) { google.maps.event.removeListener(dragstartListener) } setDragstartListener( google.maps.event.addListener(instance, 'dragstart', onDragStart) ) } }, [onDragStart]) useEffect(() => { if (instance && onMouseDown) { if (mousedownListener !== null) { google.maps.event.removeListener(mousedownListener) } setMousedownListener( google.maps.event.addListener(instance, 'mousedown', onMouseDown) ) } }, [onMouseDown]) useEffect(() => { if (instance && onMouseMove) { if (mousemoveListener !== null) { google.maps.event.removeListener(mousemoveListener) } setMousemoveListener( google.maps.event.addListener(instance, 'mousemove', onMouseMove) ) } }, [onMouseMove]) useEffect(() => { if (instance && onMouseOut) { if (mouseoutListener !== null) { google.maps.event.removeListener(mouseoutListener) } setMouseoutListener( google.maps.event.addListener(instance, 'mouseout', onMouseOut) ) } }, [onMouseOut]) useEffect(() => { if (instance && onMouseOver) { if (mouseoverListener !== null) { google.maps.event.removeListener(mouseoverListener) } setMouseoverListener( google.maps.event.addListener(instance, 'mouseover', onMouseOver) ) } }, [onMouseOver]) useEffect(() => { if (instance && onMouseUp) { if (mouseupListener !== null) { google.maps.event.removeListener(mouseupListener) } setMouseupListener( google.maps.event.addListener(instance, 'mouseup', onMouseUp) ) } }, [onMouseUp]) useEffect(() => { if (instance && onRightClick) { if (rightclickListener !== null) { google.maps.event.removeListener(rightclickListener) } setRightclickListener( google.maps.event.addListener(instance, 'rightclick', onRightClick) ) } }, [onRightClick]) useEffect(() => { if (instance && onClick) { if (clickListener !== null) { google.maps.event.removeListener(clickListener) } setClickListener( google.maps.event.addListener(instance, 'click', onClick) ) } }, [onClick]) useEffect(() => { if (instance && onDrag) { if (dragListener !== null) { google.maps.event.removeListener(dragListener) } setDragListener(google.maps.event.addListener(instance, 'drag', onDrag)) } }, [onDrag]) useEffect(() => { if (instance && onCenterChanged) { if (centerChangedListener !== null) { google.maps.event.removeListener(centerChangedListener) } setCenterChangedListener( google.maps.event.addListener( instance, 'center_changed', onCenterChanged ) ) } }, [onClick]) useEffect(() => { if (instance && onRadiusChanged) { if (radiusChangedListener !== null) { google.maps.event.removeListener(radiusChangedListener) } setRadiusChangedListener( google.maps.event.addListener( instance, 'radius_changed', onRadiusChanged ) ) } }, [onRadiusChanged]) useEffect(() => { const circle = new google.maps.Circle({ ...(options || defaultOptions), map, }) if (typeof radius === 'number') { circle.setRadius(radius) } if (typeof center !== 'undefined') { circle.setCenter(center) } if (typeof radius === 'number') { circle.setRadius(radius) } if (typeof visible !== 'undefined') { circle.setVisible(visible) } if (typeof editable !== 'undefined') { circle.setEditable(editable) } if (typeof draggable !== 'undefined') { circle.setDraggable(draggable) } if (onDblClick) { setDblclickListener( google.maps.event.addListener(circle, 'dblclick', onDblClick) ) } if (onDragEnd) { setDragendListener( google.maps.event.addListener(circle, 'dragend', onDragEnd) ) } if (onDragStart) { setDragstartListener( google.maps.event.addListener(circle, 'dragstart', onDragStart) ) } if (onMouseDown) { setMousedownListener( google.maps.event.addListener(circle, 'mousedown', onMouseDown) ) } if (onMouseMove) { setMousemoveListener( google.maps.event.addListener(circle, 'mousemove', onMouseMove) ) } if (onMouseOut) { setMouseoutListener( google.maps.event.addListener(circle, 'mouseout', onMouseOut) ) } if (onMouseOver) { setMouseoverListener( google.maps.event.addListener(circle, 'mouseover', onMouseOver) ) } if (onMouseUp) { setMouseupListener( google.maps.event.addListener(circle, 'mouseup', onMouseUp) ) } if (onRightClick) { setRightclickListener( google.maps.event.addListener(circle, 'rightclick', onRightClick) ) } if (onClick) { setClickListener(google.maps.event.addListener(circle, 'click', onClick)) } if (onDrag) { setDragListener(google.maps.event.addListener(circle, 'drag', onDrag)) } if (onCenterChanged) { setCenterChangedListener( google.maps.event.addListener(circle, 'center_changed', onCenterChanged) ) } if (onRadiusChanged) { setRadiusChangedListener( google.maps.event.addListener(circle, 'radius_changed', onRadiusChanged) ) } setInstance(circle) if (onLoad) { onLoad(circle) } return () => { if (dblclickListener !== null) { google.maps.event.removeListener(dblclickListener) } if (dragendListener !== null) { google.maps.event.removeListener(dragendListener) } if (dragstartListener !== null) { google.maps.event.removeListener(dragstartListener) } if (mousedownListener !== null) { google.maps.event.removeListener(mousedownListener) } if (mousemoveListener !== null) { google.maps.event.removeListener(mousemoveListener) } if (mouseoutListener !== null) { google.maps.event.removeListener(mouseoutListener) } if (mouseoverListener !== null) { google.maps.event.removeListener(mouseoverListener) } if (mouseupListener !== null) { google.maps.event.removeListener(mouseupListener) } if (rightclickListener !== null) { google.maps.event.removeListener(rightclickListener) } if (clickListener !== null) { google.maps.event.removeListener(clickListener) } if (centerChangedListener !== null) { google.maps.event.removeListener(centerChangedListener) } if (radiusChangedListener !== null) { google.maps.event.removeListener(radiusChangedListener) } if (onUnmount) { onUnmount(circle) } circle.setMap(null) } }, []) return null } export const CircleF = memo(CircleFunctional) export class Circle extends PureComponent { static override contextType = MapContext declare context: ContextType registeredEvents: google.maps.MapsEventListener[] = [] override state: CircleState = { circle: null, } setCircleCallback = (): void => { if (this.state.circle !== null && this.props.onLoad) { this.props.onLoad(this.state.circle) } } override componentDidMount(): void { const circle = new google.maps.Circle({ ...this.props.options, map: this.context, }) this.registeredEvents = applyUpdatersToPropsAndRegisterEvents({ updaterMap, eventMap, prevProps: {}, nextProps: this.props, instance: circle, }) this.setState(function setCircle() { return { circle, } }, this.setCircleCallback) } override componentDidUpdate(prevProps: CircleProps): void { if (this.state.circle !== null) { unregisterEvents(this.registeredEvents) this.registeredEvents = applyUpdatersToPropsAndRegisterEvents({ updaterMap, eventMap, prevProps, nextProps: this.props, instance: this.state.circle, }) } } override componentWillUnmount(): void { if (this.state.circle !== null) { if (this.props.onUnmount) { this.props.onUnmount(this.state.circle) } unregisterEvents(this.registeredEvents) this.state.circle?.setMap(null) } } override render(): null { return null } } export default Circle