react-bkoi-gl
Version:
React components for Barikoi GL JS
114 lines (100 loc) • 3.38 kB
text/typescript
/* eslint-disable @typescript-eslint/no-floating-promises */
/* 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, PopupOptions } from "../types/lib";
import type { PopupEvent } from "../types/events";
import { MapContext } from "./map";
import { deepEqual } from "../utils/deep-equal";
import { compareClassNames } from "../utils/compare-class-names";
export type PopupProps = PopupOptions & {
/** Longitude of the anchor location */
longitude: number;
/** Latitude of the anchor location */
latitude: number;
/** CSS style override, applied to the control's container */
style?: React.CSSProperties;
onOpen?: (e: PopupEvent) => void;
onClose?: (e: PopupEvent) => void;
children?: React.ReactNode;
};
/* eslint-disable complexity,max-statements */
export const Popup: React.FC<PopupProps> = memo(
forwardRef((props: PopupProps, ref: React.Ref<PopupInstance>) => {
const { map, mapLib } = useContext(MapContext);
const container = useMemo(() => {
return document.createElement("div");
}, []);
const thisRef = useRef({ props });
const popup: PopupInstance = useMemo(() => {
const options = { ...props };
const pp = new mapLib.Popup(options);
pp.setLngLat([props.longitude, props.latitude]);
pp.once("open", (e) => {
thisRef.current.props.onOpen?.(e as PopupEvent);
});
return pp;
}, []);
useEffect(() => {
const onClose = (e) => {
thisRef.current.props.onClose?.(e as PopupEvent);
};
popup.on("close", onClose);
popup.setDOMContent(container).addTo(map.getMap());
return () => {
// https://github.com/visgl/react-map-gl/issues/1825
// onClose should not be fired if the popup is removed by unmounting
// When using React strict mode, the component is mounted twice.
// Firing the onClose callback here would be a false signal to remove the component.
popup.off("close", onClose);
if (popup.isOpen()) {
popup.remove();
}
};
}, []);
useEffect(() => {
applyReactStyle(popup.getElement(), props.style);
}, [props.style]);
useImperativeHandle(ref, () => popup, []);
if (popup.isOpen()) {
const oldProps = thisRef.current.props;
if (
popup.getLngLat().lng !== props.longitude ||
popup.getLngLat().lat !== props.latitude
) {
popup.setLngLat([props.longitude, props.latitude]);
}
if (props.offset && !deepEqual(oldProps.offset, props.offset)) {
popup.setOffset(props.offset);
}
if (
oldProps.anchor !== props.anchor ||
oldProps.maxWidth !== props.maxWidth
) {
popup.options.anchor = props.anchor;
popup.setMaxWidth(props.maxWidth);
}
const classNameDiff = compareClassNames(
oldProps.className,
props.className,
);
if (classNameDiff) {
for (const c of classNameDiff) {
popup.toggleClassName(c);
}
}
thisRef.current.props = props;
}
return createPortal(props.children, container);
}),
);