react-bkoi-gl
Version:
React components for Barikoi GL JS
156 lines (137 loc) • 4.16 kB
text/typescript
/* global document */
import * as React from "react";
import { createPortal } from "react-dom";
import {
useImperativeHandle,
useEffect,
useMemo,
useRef,
useContext,
forwardRef,
memo,
} from "react";
import { applyReactStyle } from "../utils/apply-react-style";
import type {
Popup as PopupInstance,
Marker as MarkerInstance,
MarkerOptions,
} from "../types/lib";
import type { MarkerEvent, MarkerDragEvent } from "../types/events";
import { MapContext } from "./map";
import { arePointsEqual } from "../utils/deep-equal";
import { compareClassNames } from "../utils/compare-class-names";
export type MarkerProps = MarkerOptions & {
/** Longitude of the anchor location */
longitude: number;
/** Latitude of the anchor location */
latitude: number;
popup?: PopupInstance;
/** CSS style override, applied to the control's container */
style?: React.CSSProperties;
onClick?: (e: MarkerEvent<MouseEvent>) => void;
onDragStart?: (e: MarkerDragEvent) => void;
onDrag?: (e: MarkerDragEvent) => void;
onDragEnd?: (e: MarkerDragEvent) => void;
children?: React.ReactNode;
};
/* eslint-disable complexity,max-statements */
export const Marker: React.FC<MarkerProps> = memo(
forwardRef((props: MarkerProps, ref: React.Ref<MarkerInstance>) => {
const { map, mapLib } = useContext(MapContext);
const thisRef = useRef({ props });
const marker: MarkerInstance = useMemo(() => {
let hasChildren = false;
React.Children.forEach(props.children, (el) => {
if (el) {
hasChildren = true;
}
});
const options = {
...props,
element: hasChildren ? document.createElement("div") : undefined,
};
const mk = new mapLib.Marker(options);
mk.setLngLat([props.longitude, props.latitude]);
mk.getElement().addEventListener("click", (e: MouseEvent) => {
thisRef.current.props.onClick?.({
type: "click",
target: mk,
originalEvent: e,
});
});
mk.on("dragstart", (e) => {
const evt = e as MarkerDragEvent;
evt.lngLat = marker.getLngLat();
thisRef.current.props.onDragStart?.(evt);
});
mk.on("drag", (e) => {
const evt = e as MarkerDragEvent;
evt.lngLat = marker.getLngLat();
thisRef.current.props.onDrag?.(evt);
});
mk.on("dragend", (e) => {
const evt = e as MarkerDragEvent;
evt.lngLat = marker.getLngLat();
thisRef.current.props.onDragEnd?.(evt);
});
return mk;
}, []);
useEffect(() => {
marker.addTo(map.getMap());
return () => {
marker.remove();
};
}, []);
const {
longitude,
latitude,
offset,
style,
draggable = false,
popup = null,
rotation = 0,
rotationAlignment = "auto",
pitchAlignment = "auto",
} = props;
useEffect(() => {
applyReactStyle(marker.getElement(), style);
}, [style]);
useImperativeHandle(ref, () => marker, []);
const oldProps = thisRef.current.props;
if (
marker.getLngLat().lng !== longitude ||
marker.getLngLat().lat !== latitude
) {
marker.setLngLat([longitude, latitude]);
}
if (offset && !arePointsEqual(marker.getOffset(), offset)) {
marker.setOffset(offset);
}
if (marker.isDraggable() !== draggable) {
marker.setDraggable(draggable);
}
if (marker.getRotation() !== rotation) {
marker.setRotation(rotation);
}
if (marker.getRotationAlignment() !== rotationAlignment) {
marker.setRotationAlignment(rotationAlignment);
}
if (marker.getPitchAlignment() !== pitchAlignment) {
marker.setPitchAlignment(pitchAlignment);
}
if (marker.getPopup() !== popup) {
marker.setPopup(popup);
}
const classNameDiff = compareClassNames(
oldProps.className,
props.className,
);
if (classNameDiff) {
for (const c of classNameDiff) {
marker.toggleClassName(c);
}
}
thisRef.current.props = props;
return createPortal(props.children, marker.getElement());
}),
);