156 lines
4.4 KiB
TypeScript
156 lines
4.4 KiB
TypeScript
import { IControl, Map } from "mapbox-gl";
|
|
import { ControlPosition } from "mapbox-gl";
|
|
import { cloneElement, memo, ReactElement, useEffect, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import { useControl } from "react-map-gl/mapbox";
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
// Updated props type to include addControl in children props
|
|
type OverlayProps = {
|
|
position: ControlPosition;
|
|
children: ReactElement<{
|
|
map?: Map;
|
|
addControl?: (control: IControl, position?: ControlPosition) => void;
|
|
}>;
|
|
id?: string;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
};
|
|
|
|
// Custom control for overlay
|
|
class OverlayControl implements IControl {
|
|
_map: Map | null = null;
|
|
_container: HTMLElement | null = null;
|
|
_position: ControlPosition;
|
|
_id: string;
|
|
_redraw?: () => void;
|
|
_className?: string;
|
|
_style?: React.CSSProperties;
|
|
|
|
constructor({
|
|
position,
|
|
id,
|
|
redraw,
|
|
className,
|
|
style,
|
|
}: {
|
|
position: ControlPosition;
|
|
id: string;
|
|
redraw?: () => void;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
}) {
|
|
this._position = position;
|
|
this._id = id;
|
|
this._redraw = redraw;
|
|
this._className = className;
|
|
this._style = style;
|
|
}
|
|
|
|
onAdd(map: Map) {
|
|
this._map = map;
|
|
this._container = document.createElement('div');
|
|
|
|
// Apply base classes but keep it minimal to avoid layout conflicts
|
|
this._container.className = `mapboxgl-ctrl ${this._className || ''}`;
|
|
this._container.id = this._id;
|
|
|
|
// Important: These styles make the overlay adapt to content
|
|
this._container.style.pointerEvents = 'auto';
|
|
this._container.style.display = 'inline-block'; // Allow container to size to content
|
|
this._container.style.maxWidth = 'none'; // Remove any max-width constraints
|
|
this._container.style.width = 'auto'; // Let width be determined by content
|
|
this._container.style.height = 'auto'; // Let height be determined by content
|
|
this._container.style.overflow = 'visible'; // Allow content to overflow if needed
|
|
|
|
// Apply any custom styles passed as props
|
|
if (this._style) {
|
|
Object.entries(this._style).forEach(([key, value]) => {
|
|
// @ts-ignore - dynamically setting style properties
|
|
this._container.style[key] = value;
|
|
});
|
|
}
|
|
|
|
if (this._redraw) {
|
|
map.on('move', this._redraw);
|
|
this._redraw();
|
|
}
|
|
|
|
return this._container;
|
|
}
|
|
|
|
onRemove() {
|
|
if (!this._map || !this._container) return;
|
|
|
|
if (this._redraw) {
|
|
this._map.off('move', this._redraw);
|
|
}
|
|
|
|
this._container.remove();
|
|
this._map = null;
|
|
}
|
|
|
|
getDefaultPosition() {
|
|
return this._position;
|
|
}
|
|
|
|
getMap() {
|
|
return this._map;
|
|
}
|
|
|
|
getElement() {
|
|
return this._container;
|
|
}
|
|
|
|
// Method to add other controls to the map
|
|
addControl(control: IControl, position?: ControlPosition) {
|
|
if (this._map) {
|
|
this._map.addControl(control, position);
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// Enhanced Overlay component
|
|
function _Overlay({ position, children, id = `overlay-${uuidv4()}`, className, style }: OverlayProps) {
|
|
const [container, setContainer] = useState<HTMLElement | null>(null);
|
|
const [map, setMap] = useState<Map | null>(null);
|
|
|
|
// Use useControl with unique ID to avoid conflicts
|
|
const ctrl = useControl<OverlayControl>(
|
|
() =>
|
|
new OverlayControl({
|
|
position,
|
|
id,
|
|
className,
|
|
style,
|
|
}),
|
|
{ position }
|
|
);
|
|
|
|
// Update container and map instance when control is ready
|
|
useEffect(() => {
|
|
if (ctrl) {
|
|
setContainer(ctrl.getElement());
|
|
setMap(ctrl.getMap());
|
|
}
|
|
}, [ctrl]);
|
|
|
|
// Only render if container is ready
|
|
if (!container || !map) return null;
|
|
|
|
// Use createPortal to render children to container and pass addControl method
|
|
// return createPortal(
|
|
// cloneElement(children, { map, addControl: ctrl.addControl.bind(ctrl) }),
|
|
// container
|
|
// );
|
|
|
|
return createPortal(
|
|
cloneElement(children, { map }),
|
|
container
|
|
)
|
|
}
|
|
|
|
// Export as memoized component
|
|
export const Overlay = memo(_Overlay);
|