@goongmaps/goong-map-react
Version:
A fork of react-map-gl. React components for Goong JS
376 lines • 11.4 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useContext, useRef, useMemo, useEffect, useImperativeHandle, forwardRef } from 'react';
import * as PropTypes from 'prop-types';
import StaticMap, { getViewport } from './static-map';
import { MAPBOX_LIMITS } from '../utils/map-state';
import TransitionManager from '../utils/transition-manager';
import MapContext, { MapContextProvider } from './map-context';
import { EventManager } from 'mjolnir.js';
import MapController from '../utils/map-controller';
import useIsomorphicLayoutEffect from '../utils/use-isomorphic-layout-effect';
import { getTerrainElevation } from '../utils/terrain';
const propTypes = Object.assign({}, StaticMap.propTypes, {
maxZoom: PropTypes.number,
minZoom: PropTypes.number,
maxPitch: PropTypes.number,
minPitch: PropTypes.number,
onViewStateChange: PropTypes.func,
onViewportChange: PropTypes.func,
onInteractionStateChange: PropTypes.func,
transitionDuration: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
transitionInterpolator: PropTypes.object,
transitionInterruption: PropTypes.number,
transitionEasing: PropTypes.func,
onTransitionStart: PropTypes.func,
onTransitionInterrupt: PropTypes.func,
onTransitionEnd: PropTypes.func,
scrollZoom: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
dragPan: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
dragRotate: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
doubleClickZoom: PropTypes.bool,
touchZoom: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
touchRotate: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
keyboard: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
onHover: PropTypes.func,
onClick: PropTypes.func,
onDblClick: PropTypes.func,
onContextMenu: PropTypes.func,
onMouseDown: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseUp: PropTypes.func,
onTouchStart: PropTypes.func,
onTouchMove: PropTypes.func,
onTouchEnd: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
onMouseOut: PropTypes.func,
onWheel: PropTypes.func,
touchAction: PropTypes.string,
eventRecognizerOptions: PropTypes.object,
clickRadius: PropTypes.number,
interactiveLayerIds: PropTypes.array,
getCursor: PropTypes.func,
controller: PropTypes.instanceOf(MapController)
});
const getDefaultCursor = _ref => {
let {
isDragging,
isHovering
} = _ref;
return isDragging ? 'grabbing' : isHovering ? 'pointer' : 'grab';
};
const defaultProps = Object.assign({}, StaticMap.defaultProps, MAPBOX_LIMITS, TransitionManager.defaultProps, {
onViewStateChange: null,
onViewportChange: null,
onClick: null,
onNativeClick: null,
onHover: null,
onContextMenu: event => event.preventDefault(),
scrollZoom: true,
dragPan: true,
dragRotate: true,
doubleClickZoom: true,
touchZoom: true,
touchRotate: false,
keyboard: true,
touchAction: 'none',
eventRecognizerOptions: {},
clickRadius: 0,
getCursor: getDefaultCursor
});
function normalizeEvent(event) {
if (event.lngLat || !event.offsetCenter) {
return event;
}
const {
offsetCenter: {
x,
y
}
} = event;
if (!Number.isFinite(x) || !Number.isFinite(y)) {
return event;
}
const pos = [x, y];
event.point = pos;
const {
viewport
} = this;
const location = viewport.unproject(pos, {
targetZ: viewport.meterOffset[2]
});
event.lngLat = [location[0], location[1]];
return event;
}
function getFeatures(pos) {
const {
map
} = this;
if (!map || !pos) {
return null;
}
const queryParams = {};
const size = this.props.clickRadius;
if (this.props.interactiveLayerIds) {
queryParams.layers = this.props.interactiveLayerIds;
}
try {
return map.queryRenderedFeatures(size ? [[pos[0] - size, pos[1] + size], [pos[0] + size, pos[1] - size]] : pos, queryParams);
} catch {
return null;
}
}
function onEvent(callbackName, event) {
const func = this.props[callbackName];
if (func) {
func(normalizeEvent.call(this, event));
}
}
function onPointerDown(event) {
onEvent.call(this, event.pointerType === 'touch' ? 'onTouchStart' : 'onMouseDown', event);
}
function onPointerUp(event) {
onEvent.call(this, event.pointerType === 'touch' ? 'onTouchEnd' : 'onMouseUp', event);
}
function onPointerMove(event) {
onEvent.call(this, event.pointerType === 'touch' ? 'onTouchMove' : 'onMouseMove', event);
if (!this.state.isDragging) {
const {
onHover,
interactiveLayerIds
} = this.props;
let features;
event = normalizeEvent.call(this, event);
if (interactiveLayerIds || onHover) {
features = getFeatures.call(this, event.point);
}
const isHovering = Boolean(interactiveLayerIds && features && features.length > 0);
const isEntering = isHovering && !this.state.isHovering;
const isExiting = !isHovering && this.state.isHovering;
if (onHover || isEntering) {
event.features = features;
if (onHover) {
onHover(event);
}
}
if (isEntering) {
onEvent.call(this, 'onMouseEnter', event);
}
if (isExiting) {
onEvent.call(this, 'onMouseLeave', event);
}
if (isEntering || isExiting) {
this.setState({
isHovering
});
}
}
}
function onPointerClick(event) {
const {
onClick,
onNativeClick,
onDblClick,
doubleClickZoom
} = this.props;
let callbacks = [];
const isDoubleClickEnabled = onDblClick || doubleClickZoom;
switch (event.type) {
case 'anyclick':
callbacks.push(onNativeClick);
if (!isDoubleClickEnabled) {
callbacks.push(onClick);
}
break;
case 'click':
if (isDoubleClickEnabled) {
callbacks.push(onClick);
}
break;
default:
}
callbacks = callbacks.filter(Boolean);
if (callbacks.length) {
event = normalizeEvent.call(this, event);
event.features = getFeatures.call(this, event.point);
callbacks.forEach(cb => cb(event));
}
}
function getRefHandles(staticMapRef) {
return {
getMap: staticMapRef.current && staticMapRef.current.getMap,
queryRenderedFeatures: staticMapRef.current && staticMapRef.current.queryRenderedFeatures
};
}
const InteractiveMap = forwardRef((props, ref) => {
const parentContext = useContext(MapContext);
const controller = useMemo(() => props.controller || new MapController(), []);
const eventManager = useMemo(() => new EventManager(null, {
touchAction: props.touchAction,
recognizerOptions: props.eventRecognizerOptions
}), []);
const eventCanvasRef = useRef(null);
const staticMapRef = useRef(null);
const _thisRef = useRef({
width: 0,
height: 0,
state: {
isHovering: false,
isDragging: false
}
});
const thisRef = _thisRef.current;
thisRef.props = props;
thisRef.map = staticMapRef.current && staticMapRef.current.getMap();
thisRef.setState = newState => {
thisRef.state = {
...thisRef.state,
...newState
};
eventCanvasRef.current.style.cursor = props.getCursor(thisRef.state);
};
let inRender = true;
let viewportUpdateRequested;
let stateUpdateRequested;
const handleViewportChange = (viewState, interactionState, oldViewState) => {
if (inRender) {
viewportUpdateRequested = [viewState, interactionState, oldViewState];
return;
}
const {
onViewStateChange,
onViewportChange
} = thisRef.props;
Object.defineProperty(viewState, 'position', {
get: () => [0, 0, getTerrainElevation(thisRef.map, viewState)]
});
if (onViewStateChange) {
onViewStateChange({
viewState,
interactionState,
oldViewState
});
}
if (onViewportChange) {
onViewportChange(viewState, interactionState, oldViewState);
}
};
useImperativeHandle(ref, () => getRefHandles(staticMapRef), []);
const context = useMemo(() => ({
...parentContext,
eventManager,
container: parentContext.container || eventCanvasRef.current
}), [parentContext, eventCanvasRef.current]);
context.onViewportChange = handleViewportChange;
context.viewport = parentContext.viewport || getViewport(thisRef);
thisRef.viewport = context.viewport;
const handleInteractionStateChange = interactionState => {
const {
isDragging = false
} = interactionState;
if (isDragging !== thisRef.state.isDragging) {
thisRef.setState({
isDragging
});
}
if (inRender) {
stateUpdateRequested = interactionState;
return;
}
const {
onInteractionStateChange
} = thisRef.props;
if (onInteractionStateChange) {
onInteractionStateChange(interactionState);
}
};
const updateControllerOpts = () => {
if (thisRef.width && thisRef.height) {
controller.setOptions({
...thisRef.props,
...thisRef.props.viewState,
isInteractive: Boolean(thisRef.props.onViewStateChange || thisRef.props.onViewportChange),
onViewportChange: handleViewportChange,
onStateChange: handleInteractionStateChange,
eventManager,
width: thisRef.width,
height: thisRef.height
});
}
};
const onResize = _ref2 => {
let {
width,
height
} = _ref2;
thisRef.width = width;
thisRef.height = height;
updateControllerOpts();
thisRef.props.onResize({
width,
height
});
};
useEffect(() => {
eventManager.setElement(eventCanvasRef.current);
eventManager.on({
pointerdown: onPointerDown.bind(thisRef),
pointermove: onPointerMove.bind(thisRef),
pointerup: onPointerUp.bind(thisRef),
pointerleave: onEvent.bind(thisRef, 'onMouseOut'),
click: onPointerClick.bind(thisRef),
anyclick: onPointerClick.bind(thisRef),
dblclick: onEvent.bind(thisRef, 'onDblClick'),
wheel: onEvent.bind(thisRef, 'onWheel'),
contextmenu: onEvent.bind(thisRef, 'onContextMenu')
});
return () => {
eventManager.destroy();
};
}, []);
useIsomorphicLayoutEffect(() => {
if (viewportUpdateRequested) {
handleViewportChange(...viewportUpdateRequested);
}
if (stateUpdateRequested) {
handleInteractionStateChange(stateUpdateRequested);
}
});
updateControllerOpts();
const {
width,
height,
style,
getCursor
} = props;
const eventCanvasStyle = useMemo(() => ({
position: 'relative',
...style,
width,
height,
cursor: getCursor(thisRef.state)
}), [style, width, height, getCursor, thisRef.state]);
if (!viewportUpdateRequested || !thisRef._child) {
thisRef._child = React.createElement(MapContextProvider, {
value: context
}, React.createElement("div", {
key: "event-canvas",
ref: eventCanvasRef,
style: eventCanvasStyle
}, React.createElement(StaticMap, _extends({}, props, {
width: "100%",
height: "100%",
style: null,
onResize: onResize,
ref: staticMapRef
}))));
}
inRender = false;
return thisRef._child;
});
InteractiveMap.supported = StaticMap.supported;
InteractiveMap.propTypes = propTypes;
InteractiveMap.defaultProps = defaultProps;
export default InteractiveMap;
//# sourceMappingURL=interactive-map.js.map