@vinctus/react-maplibre 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ ISC License (ISC)
2
+
3
+ Copyright 2022 Vinctus Technologies Inc.
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ ## react-mapbox
2
+
3
+ **react-mapbox** is a very minimalist React.js wrapper library for the
4
+ [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/guides/)
5
+ Typescript library.
6
+
7
+ Documentation can be found [here](https://vinctustech.github.io/react-mapbox/).
8
+
9
+ Testing
10
+ -------
11
+ There is a test program in the `test` directory. It gets the Mapbox access token from environment variable
12
+ `REACT_APP_MAPBOX_API_KEY` in `.env.development`, which can be decrypted using the usual encryption key.
13
+
14
+ To start the test program type:
15
+ 1. ```bash
16
+ npm i
17
+ ```
18
+ 2. ```bash
19
+ tsc
20
+ ```
21
+ 3. ```bash
22
+ cd test
23
+ ```
24
+ 4. ```bash
25
+ npm i
26
+ ```
27
+ 5. ```bash
28
+ npm start
29
+ ```
30
+
@@ -0,0 +1,15 @@
1
+ import React, { Component, ErrorInfo, ReactNode } from 'react';
2
+ interface Props {
3
+ children?: ReactNode;
4
+ fallback?: ReactNode;
5
+ }
6
+ interface State {
7
+ hasError: boolean;
8
+ }
9
+ export declare class ErrorBoundary extends Component<Props, State> {
10
+ constructor(props: any);
11
+ static getDerivedStateFromError(_: Error): State;
12
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
13
+ render(): string | number | boolean | Iterable<React.ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
14
+ }
15
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Component } from 'react';
3
+ export class ErrorBoundary extends Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = { hasError: false };
7
+ }
8
+ static getDerivedStateFromError(_) {
9
+ // Update state so the next render will show the fallback UI.
10
+ return { hasError: true };
11
+ }
12
+ componentDidCatch(error, errorInfo) {
13
+ console.error('Uncaught map error:', error, errorInfo);
14
+ }
15
+ render() {
16
+ if (this.state.hasError)
17
+ return this.props.fallback || _jsx("h1", { children: "Sorry.. there was a map error" });
18
+ return this.props.children;
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ import { FC } from 'react';
2
+ import { ControlPosition } from './types';
3
+ export type GeolocateControlProps = {
4
+ position?: ControlPosition;
5
+ showUserLocation?: boolean;
6
+ };
7
+ export declare const GeolocateControl: FC<GeolocateControlProps>;
@@ -0,0 +1,29 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { useEffect } from 'react';
13
+ import maplibre from 'maplibre-gl';
14
+ import { useMap } from './Map';
15
+ export const GeolocateControl = (_a) => {
16
+ var { position = 'top-right' } = _a, options = __rest(_a, ["position"]);
17
+ const { map } = useMap();
18
+ const optionsString = JSON.stringify(options);
19
+ useEffect(() => {
20
+ const control = new maplibre.GeolocateControl(Object.assign({}, JSON.parse(optionsString)));
21
+ if (map) {
22
+ map.addControl(control, position);
23
+ }
24
+ return () => {
25
+ map === null || map === void 0 ? void 0 : map.removeControl(control);
26
+ };
27
+ }, [position, optionsString, map]);
28
+ return null;
29
+ };
@@ -0,0 +1,7 @@
1
+ import { FC } from 'react';
2
+ import { LayerSpecification, GeoJSONSourceSpecification } from 'maplibre-gl';
3
+ export type LayerProps = {
4
+ layer: Omit<LayerSpecification, 'source'>;
5
+ source: GeoJSONSourceSpecification;
6
+ };
7
+ export declare const Layer: FC<LayerProps>;
package/dist/Layer.js ADDED
@@ -0,0 +1,24 @@
1
+ import { useEffect } from 'react';
2
+ import { useMap } from './Map';
3
+ export const Layer = ({ layer, source }) => {
4
+ const { map, mapLoaded } = useMap();
5
+ const layerString = JSON.stringify(Object.assign({ source: layer.id }, layer));
6
+ const sourceString = JSON.stringify(source);
7
+ useEffect(() => {
8
+ let added = false;
9
+ const parsedSource = JSON.parse(sourceString);
10
+ const parsedLayer = JSON.parse(layerString);
11
+ if (map && mapLoaded) {
12
+ added = true;
13
+ map.addSource(parsedLayer.id, parsedSource); // when adding, the source must be added first, and then the layer
14
+ map.addLayer(parsedLayer);
15
+ }
16
+ return () => {
17
+ if (added) {
18
+ map === null || map === void 0 ? void 0 : map.removeLayer(parsedLayer.id); // when removing, the layer must be removed first, and then the source
19
+ map === null || map === void 0 ? void 0 : map.removeSource(parsedLayer.id);
20
+ }
21
+ };
22
+ }, [layerString, sourceString, map, mapLoaded]);
23
+ return null;
24
+ };
package/dist/Map.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import React, { FC, RefAttributes, ReactNode, CSSProperties } from 'react';
2
+ import maplibre, { MapLibreEvent } from 'maplibre-gl';
3
+ export type ViewState = {
4
+ longitude: number;
5
+ latitude: number;
6
+ zoom: number;
7
+ };
8
+ export type ViewStateChangeEvent = (MapLibreEvent<MouseEvent | TouchEvent | WheelEvent | undefined> & {
9
+ type: 'movestart' | 'move' | 'moveend' | 'zoomstart' | 'zoom' | 'zoomend';
10
+ viewState: ViewState;
11
+ }) | (MapLibreEvent<MouseEvent | TouchEvent | undefined> & {
12
+ type: 'rotatestart' | 'rotate' | 'rotateend' | 'dragstart' | 'drag' | 'dragend' | 'pitchstart' | 'pitch' | 'pitchend';
13
+ viewState: ViewState;
14
+ });
15
+ export declare const viewStateFromMap: (map: maplibre.Map) => {
16
+ longitude: number;
17
+ latitude: number;
18
+ zoom: number;
19
+ };
20
+ export declare const useMap: () => {
21
+ map: maplibre.Map | null;
22
+ setMap: React.Dispatch<React.SetStateAction<maplibre.Map | null>>;
23
+ mapLoaded: boolean;
24
+ setMapLoaded: React.Dispatch<React.SetStateAction<boolean>>;
25
+ };
26
+ export declare const MapProvider: FC<{
27
+ children?: ReactNode;
28
+ }>;
29
+ export type MapProps = {
30
+ accessToken?: string;
31
+ antialias?: boolean;
32
+ children?: ReactNode | ReactNode[];
33
+ fallback?: ReactNode;
34
+ id?: string;
35
+ longitude: number;
36
+ latitude: number;
37
+ logoPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
38
+ maxPitch?: number;
39
+ mapStyle?: string;
40
+ onLoad?: (e: import('maplibre-gl').MapLibreEvent) => void;
41
+ onDragEnd?: (e: ViewStateChangeEvent) => void;
42
+ onMoveEnd?: (e: ViewStateChangeEvent) => void;
43
+ onZoomEnd?: (e: ViewStateChangeEvent) => void;
44
+ ref?: RefAttributes<maplibre.Map | null>;
45
+ style?: CSSProperties;
46
+ zoom: number;
47
+ };
48
+ export declare const Map: React.ForwardRefExoticComponent<Omit<MapProps, "ref"> & React.RefAttributes<maplibre.Map | null>>;
package/dist/Map.js ADDED
@@ -0,0 +1,81 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import React, { useRef, useEffect, useState, useContext, useImperativeHandle, useMemo, } from 'react';
14
+ import maplibre from 'maplibre-gl';
15
+ import { ErrorBoundary } from './ErrorBoundary'; // eslint-disable-line import/no-webpack-loader-syntax
16
+ const DEFAULT_STYLE = 'https://demotiles.maplibre.org/style.json';
17
+ const DISABLE_PITCH = 0;
18
+ const MapContext = React.createContext({
19
+ map: null,
20
+ setMap: () => {
21
+ throw new Error('Use <MapProvider>');
22
+ },
23
+ mapLoaded: false,
24
+ setMapLoaded: () => {
25
+ throw new Error('Use <MapProvider>');
26
+ },
27
+ });
28
+ export const viewStateFromMap = (map) => ({
29
+ longitude: map.getCenter().lng,
30
+ latitude: map.getCenter().lat,
31
+ zoom: map.getZoom(),
32
+ });
33
+ export const useMap = () => useContext(MapContext);
34
+ export const MapProvider = ({ children }) => {
35
+ const [map, setMap] = useState(null);
36
+ const [mapLoaded, setMapLoaded] = useState(false);
37
+ return (_jsx(MapContext.Provider, Object.assign({ value: { map, setMap, mapLoaded, setMapLoaded } }, { children: children })));
38
+ };
39
+ function addEventHandler(map, type, handler) {
40
+ map.on(type, (ev) => handler(Object.assign({ viewState: viewStateFromMap(map) }, ev)));
41
+ }
42
+ export const Map = React.forwardRef((_a, ref) => {
43
+ var { children, style, id, longitude, latitude, maxPitch = DISABLE_PITCH, mapStyle = DEFAULT_STYLE, zoom, onDragEnd, onLoad, onMoveEnd, onZoomEnd, fallback } = _a, options = __rest(_a, ["children", "style", "id", "longitude", "latitude", "maxPitch", "mapStyle", "zoom", "onDragEnd", "onLoad", "onMoveEnd", "onZoomEnd", "fallback"]);
44
+ const mapContainer = useRef(null);
45
+ const { map, setMap, setMapLoaded } = useMap();
46
+ useImperativeHandle(ref, () => map, [map]);
47
+ useEffect(() => {
48
+ if (map)
49
+ return; // initialize map only once
50
+ if (mapContainer.current) {
51
+ const m = new maplibre.Map(Object.assign({ container: mapContainer.current, style: mapStyle, center: [longitude, latitude], zoom }, options));
52
+ if (onDragEnd)
53
+ addEventHandler(m, 'dragend', onDragEnd);
54
+ if (onLoad)
55
+ addEventHandler(m, 'load', (ev) => {
56
+ onLoad(ev);
57
+ setMapLoaded(true);
58
+ });
59
+ if (onMoveEnd)
60
+ addEventHandler(m, 'moveend', onMoveEnd);
61
+ if (onZoomEnd)
62
+ addEventHandler(m, 'zoomend', onZoomEnd);
63
+ setMap(m);
64
+ }
65
+ }, [
66
+ map,
67
+ onDragEnd,
68
+ onLoad,
69
+ onMoveEnd,
70
+ onZoomEnd,
71
+ setMap,
72
+ setMapLoaded,
73
+ mapStyle,
74
+ longitude,
75
+ latitude,
76
+ zoom,
77
+ options,
78
+ ]);
79
+ const memoizedStyle = useMemo(() => (Object.assign({ position: 'relative', width: '100%', height: '100%' }, style)), [style]);
80
+ return (_jsx(ErrorBoundary, Object.assign({ fallback: fallback }, { children: _jsx("div", Object.assign({ id: id, ref: mapContainer, style: memoizedStyle }, { children: children })) })));
81
+ });
@@ -0,0 +1,12 @@
1
+ import { FC, ReactElement, ReactNode } from 'react';
2
+ import { PositionAnchor } from 'maplibre-gl';
3
+ export type MarkerProps = {
4
+ longitude: number;
5
+ latitude: number;
6
+ popup?: ReactElement;
7
+ closeOnClick?: boolean;
8
+ anchor?: PositionAnchor;
9
+ color?: string;
10
+ children?: ReactNode;
11
+ };
12
+ export declare const Marker: FC<MarkerProps>;
package/dist/Marker.js ADDED
@@ -0,0 +1,43 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { useEffect, useState, Children } from 'react';
13
+ import maplibre from 'maplibre-gl';
14
+ import { useMap } from './Map';
15
+ import ReactDOM from 'react-dom';
16
+ import ReactDOMServer from 'react-dom/server'; // eslint-disable-line import/no-webpack-loader-syntax
17
+ export const Marker = (_a) => {
18
+ var { children, longitude, latitude, popup, closeOnClick } = _a, options = __rest(_a, ["children", "longitude", "latitude", "popup", "closeOnClick"]);
19
+ const { map } = useMap();
20
+ const [result, setResult] = useState(null);
21
+ const optionsString = JSON.stringify(options);
22
+ useEffect(() => {
23
+ let marker = null;
24
+ const isEmpty = Children.count(children) === 0;
25
+ if (map) {
26
+ const element = document.createElement('div');
27
+ const parsedOptions = JSON.parse(optionsString);
28
+ if (!isEmpty)
29
+ setResult(ReactDOM.createPortal(children, element));
30
+ marker = isEmpty
31
+ ? new maplibre.Marker(Object.assign({}, parsedOptions))
32
+ : new maplibre.Marker(Object.assign(Object.assign({}, parsedOptions), { element }));
33
+ if (popup)
34
+ marker.setPopup(new maplibre.Popup({ offset: [0, -15], closeOnClick }).setHTML(ReactDOMServer.renderToStaticMarkup(popup)));
35
+ marker = marker.setLngLat([longitude, latitude]).addTo(map);
36
+ }
37
+ return () => {
38
+ if (marker)
39
+ marker.remove();
40
+ };
41
+ }, [closeOnClick, optionsString, setResult, children, latitude, longitude, map, popup]);
42
+ return result;
43
+ };
@@ -0,0 +1,9 @@
1
+ import { FC, ReactElement } from 'react';
2
+ export type PopupProps = {
3
+ children: ReactElement;
4
+ longitude: number;
5
+ latitude: number;
6
+ className?: string;
7
+ closeOnClick?: boolean;
8
+ };
9
+ export declare const Popup: FC<PopupProps>;
package/dist/Popup.js ADDED
@@ -0,0 +1,34 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { useEffect } from 'react';
13
+ import maplibre from 'maplibre-gl';
14
+ import { useMap } from './Map';
15
+ import ReactDOMServer from 'react-dom/server'; // eslint-disable-line import/no-webpack-loader-syntax
16
+ export const Popup = (_a) => {
17
+ var { children, longitude, latitude } = _a, options = __rest(_a, ["children", "longitude", "latitude"]);
18
+ const { map } = useMap();
19
+ const optionsString = JSON.stringify(options);
20
+ useEffect(() => {
21
+ let popup = null;
22
+ if (map) {
23
+ popup = new maplibre.Popup(Object.assign({ offset: [0, -15] }, JSON.parse(optionsString)))
24
+ .setLngLat([longitude, latitude])
25
+ .setHTML(ReactDOMServer.renderToStaticMarkup(children))
26
+ .addTo(map);
27
+ }
28
+ return () => {
29
+ if (popup)
30
+ popup.remove();
31
+ };
32
+ }, [children, latitude, longitude, map, optionsString]);
33
+ return null;
34
+ };
@@ -0,0 +1,6 @@
1
+ import { FC } from 'react';
2
+ import { ControlPosition } from './types';
3
+ export type ScaleControlProps = {
4
+ position?: ControlPosition;
5
+ };
6
+ export declare const ScaleControl: FC<ScaleControlProps>;
@@ -0,0 +1,29 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { useEffect } from 'react';
13
+ import maplibre from 'maplibre-gl';
14
+ import { useMap } from './Map';
15
+ export const ScaleControl = (_a) => {
16
+ var { position = 'top-right' } = _a, options = __rest(_a, ["position"]);
17
+ const { map } = useMap();
18
+ const optionsString = JSON.stringify(options);
19
+ useEffect(() => {
20
+ const control = new maplibre.ScaleControl();
21
+ if (map) {
22
+ map.addControl(control, position);
23
+ }
24
+ return () => {
25
+ map === null || map === void 0 ? void 0 : map.removeControl(control);
26
+ };
27
+ }, [position, optionsString, map]);
28
+ return null;
29
+ };
@@ -0,0 +1,7 @@
1
+ export { Map, useMap, MapProvider, viewStateFromMap } from './Map';
2
+ export type { ViewState } from './Map';
3
+ export { Marker } from './Marker';
4
+ export { Popup } from './Popup';
5
+ export { Layer } from './Layer';
6
+ export { GeolocateControl } from './GeolocateControl';
7
+ export { ScaleControl } from './ScaleControl';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { Map, useMap, MapProvider, viewStateFromMap } from './Map';
2
+ export { Marker } from './Marker';
3
+ export { Popup } from './Popup';
4
+ export { Layer } from './Layer';
5
+ export { GeolocateControl } from './GeolocateControl';
6
+ export { ScaleControl } from './ScaleControl';
@@ -0,0 +1 @@
1
+ export type ControlPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@vinctus/react-maplibre",
3
+ "version": "0.1.0",
4
+ "description": "Minimalist MapLibre GL JS React wrapper library",
5
+ "types": "dist/index.d.ts",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "/dist"
11
+ ],
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./dist/*": "./dist/*.js"
15
+ },
16
+ "dependencies": {
17
+ "react-scripts": "5.0.1"
18
+ },
19
+ "peerDependencies": {
20
+ "maplibre-gl": "^4.5.2",
21
+ "react": "^18.3.1",
22
+ "react-dom": "^18.3.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^18.14.6",
26
+ "@types/react": "^18.3.3",
27
+ "@types/react-dom": "^18.3.0",
28
+ "typescript": "^4.9.5"
29
+ },
30
+ "scripts": {
31
+ "start": "react-scripts start",
32
+ "build": "react-scripts build",
33
+ "test": "react-scripts test",
34
+ "eject": "react-scripts eject",
35
+ "prepublishOnly": "tsc"
36
+ },
37
+ "eslintConfig": {
38
+ "extends": [
39
+ "react-app",
40
+ "react-app/jest"
41
+ ]
42
+ },
43
+ "browserslist": {
44
+ "production": [
45
+ ">0.2%",
46
+ "not dead",
47
+ "not op_mini all"
48
+ ],
49
+ "development": [
50
+ "last 1 chrome version",
51
+ "last 1 firefox version",
52
+ "last 1 safari version"
53
+ ]
54
+ }
55
+ }