@geops/rvf-mobility-web-component 0.1.8 → 0.1.10

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/input.css CHANGED
@@ -32,3 +32,7 @@ html {
32
32
  @apply text-lg;
33
33
  }
34
34
  }
35
+
36
+ .button-map {
37
+ @apply bg-blue-500 text-white;
38
+ }
package/package.json CHANGED
@@ -2,19 +2,19 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.8",
5
+ "version": "0.1.10",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "maplibre-gl": "^4.7.1",
11
- "mobility-toolbox-js": "3.0.0-beta.38",
12
- "ol": "^10.3.0",
11
+ "mobility-toolbox-js": "3.0.0",
12
+ "ol": "^10.3.1",
13
13
  "preact": "^10.25.1",
14
14
  "preact-custom-element": "^4.3.0",
15
15
  "react": "npm:@preact/compat@^18.3.1",
16
16
  "react-dom": "npm:@preact/compat@^18.3.1",
17
- "react-icons": "^5.3.0",
17
+ "react-icons": "^5.4.0",
18
18
  "rosetta": "^1.1.0"
19
19
  },
20
20
  "devDependencies": {
@@ -32,7 +32,7 @@
32
32
  "eslint": "^9.16.0",
33
33
  "eslint-config-prettier": "9.1.0",
34
34
  "eslint-plugin-jsx-a11y": "^6.10.2",
35
- "eslint-plugin-perfectionist": "^4.1.2",
35
+ "eslint-plugin-perfectionist": "^4.2.0",
36
36
  "eslint-plugin-prettier": "^5.2.1",
37
37
  "eslint-plugin-react": "^7.37.2",
38
38
  "eslint-plugin-react-hooks": "^5.1.0-rc.0",
@@ -46,9 +46,9 @@
46
46
  "jest-preset-preact": "^4.1.1",
47
47
  "next": "15.0.3",
48
48
  "preact-render-to-string": "^6.5.11",
49
- "prettier": "^3.4.1",
49
+ "prettier": "^3.4.2",
50
50
  "standard-version": "^9.5.0",
51
- "tailwindcss": "^3.4.15",
51
+ "tailwindcss": "^3.4.16",
52
52
  "ts-jest": "^29.2.5",
53
53
  "typescript": "^5.7.2",
54
54
  "typescript-eslint": "^8.17.0"
@@ -5,6 +5,8 @@ import { unByKey } from "ol/Observable";
5
5
  import { fromLonLat } from "ol/proj";
6
6
  import { useEffect, useMemo } from "preact/hooks";
7
7
 
8
+ import GeoIcon from "../icons/Geolocation";
9
+ import RvfButton from "../RvfButton";
8
10
  import useMapContext from "../utils/hooks/useMapContext";
9
11
 
10
12
  export type GeolocationButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
@@ -53,28 +55,14 @@ function GeolocationButton({ ...props }: GeolocationButtonProps) {
53
55
  }, [geolocation, isTracking]);
54
56
 
55
57
  return (
56
- <button
57
- className="rounded-full bg-white p-1 shadow-lg"
58
+ <RvfButton
59
+ className={isTracking ? "animate-pulse" : ""}
60
+ Icon={GeoIcon}
58
61
  onClick={() => {
59
62
  setIsTracking(!isTracking);
60
63
  }}
61
- type="button"
62
64
  {...props}
63
- >
64
- <svg
65
- className={isTracking ? "animate-pulse" : ""}
66
- fill="currentColor"
67
- focusable="false"
68
- height="1.5em"
69
- stroke="currentColor"
70
- strokeWidth="0"
71
- viewBox="0 0 512 512"
72
- width="1.5em"
73
- xmlns="http://www.w3.org/2000/svg"
74
- >
75
- <path d="M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z" />
76
- </svg>
77
- </button>
65
+ />
78
66
  );
79
67
  }
80
68
 
package/src/Map/Map.tsx CHANGED
@@ -1,9 +1,10 @@
1
- import { Map as OlMap } from "ol";
1
+ import { Map as OlMap, View } from "ol";
2
+ import { unByKey } from "ol/Observable";
2
3
  // @ts-expect-error bad type definition
3
4
  import olStyle from "ol/ol.css";
4
5
  import { JSX, PreactDOMAttributes } from "preact";
5
6
  import { memo } from "preact/compat";
6
- import { useEffect, useRef } from "preact/hooks";
7
+ import { useEffect, useMemo, useRef } from "preact/hooks";
7
8
 
8
9
  import useMapContext from "../utils/hooks/useMapContext";
9
10
 
@@ -12,19 +13,50 @@ export type RealtimeMapProps = JSX.HTMLAttributes<HTMLDivElement> &
12
13
 
13
14
  function Map({ children, ...props }: RealtimeMapProps) {
14
15
  const mapRef = useRef();
15
- const {
16
- center = "831634,5933959",
17
- map,
18
- maxzoom,
19
- minzoom,
20
- setMap,
21
- zoom = "13",
22
- } = useMapContext();
16
+ const { center, extent, map, maxextent, maxzoom, minzoom, setMap, zoom } =
17
+ useMapContext();
18
+
19
+ const view = useMemo(() => {
20
+ if (!maxextent) {
21
+ return;
22
+ }
23
+ const bbox = maxextent.split(",").map((c) => {
24
+ return parseFloat(c);
25
+ });
26
+ return new View({
27
+ constrainOnlyCenter: false, // allow to have the same value as extent and max extent
28
+ extent: bbox,
29
+ showFullExtent: true,
30
+ });
31
+ }, [maxextent]);
32
+
33
+ useEffect(() => {
34
+ if (!map || !view) {
35
+ return;
36
+ }
37
+ const key = map.on("change:view", (evt) => {
38
+ const oldView = evt.oldValue;
39
+ if (oldView) {
40
+ view.setMinZoom(oldView.getMinZoom());
41
+ view.setMaxZoom(oldView.getMaxZoom());
42
+ view.setCenter(oldView.getCenter());
43
+ view.setZoom(oldView.getZoom());
44
+ }
45
+ });
46
+ map.setView(view);
47
+
48
+ return () => {
49
+ unByKey(key);
50
+ };
51
+ }, [map, view]);
23
52
 
24
53
  useEffect(() => {
25
54
  let newMap: OlMap;
26
55
  if (mapRef.current) {
27
- newMap = new OlMap({ controls: [], target: mapRef.current });
56
+ newMap = new OlMap({
57
+ controls: [],
58
+ target: mapRef.current,
59
+ });
28
60
  setMap(newMap);
29
61
  }
30
62
 
@@ -35,7 +67,19 @@ function Map({ children, ...props }: RealtimeMapProps) {
35
67
  }, [setMap]);
36
68
 
37
69
  useEffect(() => {
38
- if (!map) {
70
+ if (!map || !extent) {
71
+ return;
72
+ }
73
+ const bbox = extent.split(",").map((c) => {
74
+ return parseFloat(c);
75
+ });
76
+ if (bbox) {
77
+ map.getView().fit(bbox);
78
+ }
79
+ }, [map, extent]);
80
+
81
+ useEffect(() => {
82
+ if (!map || !center) {
39
83
  return;
40
84
  }
41
85
  const [x, y] = center.split(",").map((c) => {
@@ -41,8 +41,10 @@ export interface MobilityMapProps {
41
41
  apikey?: string;
42
42
  baselayer?: string;
43
43
  center?: string;
44
+ extent?: string;
44
45
  geolocation?: string;
45
46
  mapsurl?: string;
47
+ maxextent?: string;
46
48
  maxzoom?: string;
47
49
  minzoom?: string;
48
50
  mots?: string;
@@ -63,8 +65,10 @@ function MobilityMap({
63
65
  apikey = null,
64
66
  baselayer = "travic_v2",
65
67
  center = "831634,5933959",
68
+ extent = null,
66
69
  geolocation = "true",
67
70
  mapsurl = "https://maps.geops.io",
71
+ maxextent = null,
68
72
  maxzoom = null,
69
73
  minzoom = null,
70
74
  mots = null,
@@ -102,11 +106,13 @@ function MobilityMap({
102
106
  baselayer,
103
107
  baseLayer,
104
108
  center,
109
+ extent,
105
110
  geolocation,
106
111
  isFollowing,
107
112
  isTracking,
108
113
  map,
109
114
  mapsurl,
115
+ maxextent,
110
116
  maxzoom,
111
117
  minzoom,
112
118
  mots,
@@ -141,11 +147,13 @@ function MobilityMap({
141
147
  baselayer,
142
148
  baseLayer,
143
149
  center,
150
+ extent,
144
151
  geolocation,
145
152
  isFollowing,
146
153
  isTracking,
147
154
  map,
148
155
  mapsurl,
156
+ maxextent,
149
157
  maxzoom,
150
158
  minzoom,
151
159
  mots,
@@ -171,8 +179,10 @@ function MobilityMap({
171
179
  new MobilityEvent<MobilityMapProps>("mwc:attribute", {
172
180
  baselayer,
173
181
  center: x && y ? `${x},${y}` : center,
182
+ extent,
174
183
  geolocation,
175
184
  mapsurl,
185
+ maxextent,
176
186
  maxzoom,
177
187
  minzoom,
178
188
  mots,
@@ -190,8 +200,10 @@ function MobilityMap({
190
200
  }, [
191
201
  baselayer,
192
202
  center,
203
+ extent,
193
204
  geolocation,
194
205
  mapsurl,
206
+ maxextent,
195
207
  maxzoom,
196
208
  minzoom,
197
209
  mots,
@@ -0,0 +1,38 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo, SVGProps } from "preact/compat";
4
+
5
+ export type RvfButtonProps = ButtonProps &
6
+ JSX.ButtonHTMLAttributes<HTMLButtonElement> &
7
+ PreactDOMAttributes;
8
+
9
+ interface ButtonProps {
10
+ Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
11
+ theme?: "primary" | "secondary";
12
+ }
13
+
14
+ function RvfButton({
15
+ children,
16
+ className,
17
+ disabled,
18
+ Icon,
19
+ onClick,
20
+ theme,
21
+ }: RvfButtonProps) {
22
+ const baseClasses =
23
+ "flex h-8 w-8 md:h-9 md:w-9 lg:w-10 lg:h-10 p-1.75 max-w-button max-h-button items-center justify-center rounded-full border";
24
+ const themeClasses =
25
+ theme === "primary"
26
+ ? "border-red bg-red text-white"
27
+ : "border-grey bg-white text-grey";
28
+
29
+ const classes = `${baseClasses} ${themeClasses} ${className || ""}`;
30
+
31
+ return (
32
+ <button className={classes} disabled={disabled} onClick={onClick}>
33
+ {children || <Icon height={"100%"} width={"100%"} />}
34
+ </button>
35
+ );
36
+ }
37
+
38
+ export default memo(RvfButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfButton";
@@ -10,8 +10,7 @@ import {
10
10
  RealtimeTrainId,
11
11
  } from "mobility-toolbox-js/types";
12
12
  import { Map as OlMap } from "ol";
13
- import { getCenter } from "ol/extent";
14
- import { fromLonLat } from "ol/proj";
13
+ import { transformExtent } from "ol/proj";
15
14
  import { memo } from "preact/compat";
16
15
  import { useEffect, useMemo, useState } from "preact/hooks";
17
16
 
@@ -24,6 +23,8 @@ import NotificationLayer from "../NotificationLayer";
24
23
  import Overlay from "../Overlay";
25
24
  import RealtimeLayer from "../RealtimeLayer";
26
25
  import RouteSchedule from "../RouteSchedule";
26
+ import RvfSharedMobilityLayer from "../RvfSharedMobilityLayer";
27
+ import RvfZoomButtons from "../RvfZoomButtons";
27
28
  import ScaleLine from "../ScaleLine";
28
29
  import Search from "../Search";
29
30
  import SingleClickListener from "../SingleClickListener/SingleClickListener";
@@ -42,15 +43,22 @@ import style from "./index.css";
42
43
 
43
44
  export type RvfMobilityMapProps = {} & MobilityMapProps;
44
45
 
45
- const extent = [7.5, 47.7, 8.45, 48.4];
46
- const centerRvf = fromLonLat(getCenter(extent)).join(",");
46
+ const rvfExtent = [7.5, 47.7, 8.45, 48.4];
47
+ const rvfExtentTransformed = transformExtent(
48
+ rvfExtent,
49
+ "EPSG:4326",
50
+ "EPSG:3857",
51
+ );
52
+ const bbox = rvfExtentTransformed.join(",");
47
53
 
48
54
  function RvfMobilityMap({
49
55
  apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
50
56
  baselayer = "de.rvf",
51
- center = centerRvf,
57
+ center = null,
58
+ extent = bbox,
52
59
  geolocation = "true",
53
60
  mapsurl = "https://maps.geops.io",
61
+ maxextent = bbox,
54
62
  maxzoom = null,
55
63
  minzoom = null,
56
64
  mots = null,
@@ -64,7 +72,7 @@ function RvfMobilityMap({
64
72
  search = "false",
65
73
  stopsurl = "https://api.geops.io/stops/v1/",
66
74
  tenant = null,
67
- zoom = "9.8",
75
+ zoom = null,
68
76
  }: RvfMobilityMapProps) {
69
77
  const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
70
78
  const [isFollowing, setIsFollowing] = useState(false);
@@ -88,11 +96,13 @@ function RvfMobilityMap({
88
96
  baselayer,
89
97
  baseLayer,
90
98
  center,
99
+ extent,
91
100
  geolocation,
92
101
  isFollowing,
93
102
  isTracking,
94
103
  map,
95
104
  mapsurl,
105
+ maxextent,
96
106
  maxzoom,
97
107
  minzoom,
98
108
  mots,
@@ -127,11 +137,13 @@ function RvfMobilityMap({
127
137
  baselayer,
128
138
  baseLayer,
129
139
  center,
140
+ extent,
130
141
  geolocation,
131
142
  isFollowing,
132
143
  isTracking,
133
144
  map,
134
145
  mapsurl,
146
+ maxextent,
135
147
  maxzoom,
136
148
  minzoom,
137
149
  mots,
@@ -157,8 +169,10 @@ function RvfMobilityMap({
157
169
  new MobilityEvent<RvfMobilityMapProps>("mwc:attribute", {
158
170
  baselayer,
159
171
  center: x && y ? `${x},${y}` : center,
172
+ extent,
160
173
  geolocation,
161
174
  mapsurl,
175
+ maxextent,
162
176
  maxzoom,
163
177
  minzoom,
164
178
  mots,
@@ -193,6 +207,8 @@ function RvfMobilityMap({
193
207
  x,
194
208
  y,
195
209
  z,
210
+ extent,
211
+ maxextent,
196
212
  ]);
197
213
 
198
214
  return (
@@ -208,6 +224,33 @@ function RvfMobilityMap({
208
224
  {realtime === "true" && <RealtimeLayer />}
209
225
  {tenant && <StationsLayer />}
210
226
  {notification === "true" && <NotificationLayer />}
227
+ <RvfSharedMobilityLayer
228
+ color="red"
229
+ name="MobiData-BW:sharing_stations_bicycle"
230
+ url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_bicycle/ows"
231
+ />
232
+ <RvfSharedMobilityLayer
233
+ color="blue"
234
+ name="MobiData-BW:sharing_stations_cargo_bicycle"
235
+ url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_cargo_bicycle/ows"
236
+ />
237
+ <RvfSharedMobilityLayer
238
+ color="green"
239
+ name="MobiData-BW:sharing_stations_car"
240
+ url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_car/ows"
241
+ />
242
+ <RvfSharedMobilityLayer
243
+ color="orange"
244
+ name="MobiData-BW:sharing_stations_scooters_standing"
245
+ url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_scooters_standing/ows"
246
+ />
247
+ <RvfSharedMobilityLayer
248
+ color="orange"
249
+ minZoom={14.999}
250
+ name="MobiData-BW:sharing_vehicles"
251
+ url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_vehicles/ows"
252
+ />
253
+
211
254
  <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
212
255
  <ScaleLine className="bg-slate-50/70" />
213
256
  <Copyright className="bg-slate-50/70" />
@@ -220,6 +263,9 @@ function RvfMobilityMap({
220
263
  <Search />
221
264
  </div>
222
265
  )}
266
+ <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
267
+ <RvfZoomButtons />
268
+ </div>
223
269
  </Map>
224
270
 
225
271
  <Overlay
@@ -0,0 +1,147 @@
1
+ import { getUid } from "ol";
2
+ import { GeoJSON } from "ol/format";
3
+ import VectorLayer from "ol/layer/Vector";
4
+ import { bbox as bboxStrategy } from "ol/loadingstrategy.js";
5
+ import { Vector } from "ol/source";
6
+ import { Circle, Fill, Stroke, Style, Text } from "ol/style";
7
+ import { useEffect, useMemo } from "preact/hooks";
8
+
9
+ import useMapContext from "../utils/hooks/useMapContext";
10
+
11
+ export default function RvfSharedMobilityLayer({
12
+ color,
13
+ minZoom = 16.999,
14
+ name,
15
+ url,
16
+ }: {
17
+ color: string;
18
+ minZoom?: number;
19
+ name: string;
20
+ url: string;
21
+ }) {
22
+ const { map } = useMapContext();
23
+
24
+ const layer = useMemo(() => {
25
+ if (!name || !url) {
26
+ return null;
27
+ }
28
+ const source = new Vector({
29
+ format: new GeoJSON(),
30
+ strategy: bboxStrategy,
31
+ url: function (extent) {
32
+ return (
33
+ url +
34
+ "?service=WFS&" +
35
+ "version=1.1.0&request=GetFeature&typename=" +
36
+ name +
37
+ "&" +
38
+ "outputFormat=application/json&srsname=EPSG:3857&" +
39
+ "bbox=" +
40
+ extent.join(",") +
41
+ ",EPSG:3857"
42
+ );
43
+ },
44
+ });
45
+ const style = new Style({
46
+ image: new Circle({
47
+ declutterMode: "declutter",
48
+ fill: new Fill({
49
+ color: "white",
50
+ }),
51
+ radius: 10,
52
+ stroke: new Stroke({
53
+ color: color,
54
+ width: 2,
55
+ }),
56
+ }),
57
+ text: new Text({
58
+ declutterMode: "declutter",
59
+
60
+ fill: new Fill({
61
+ color: color,
62
+ }),
63
+ font: "bold 12px inherit",
64
+ // text: ["to-string", ["get", "num_vehicles_available"]],
65
+ }),
66
+ });
67
+
68
+ const styleFunction = (feature) => {
69
+ const clone = style.clone();
70
+ clone
71
+ .getText()
72
+ .setText(feature.get("num_vehicles_available")?.toString());
73
+ const isFreeFloat = !feature.get("num_vehicles_available")?.toString();
74
+ if (isFreeFloat) {
75
+ (clone.getImage() as Circle).setRadius(6);
76
+ }
77
+ clone.setZIndex(parseInt(getUid(feature), 10));
78
+ return clone;
79
+ };
80
+ const layer = new VectorLayer({
81
+ // declutter: true,
82
+ minZoom: minZoom,
83
+ source: source,
84
+ // style: (feature) => {
85
+ // console.log("feature", feature);
86
+ // style
87
+ // .getText()
88
+ // .setText(feature.get("num_vehicles_available").toString());
89
+ // return style;
90
+ // },
91
+
92
+ // style: {
93
+ // "circle-fill-color": "white",
94
+ // "circle-radius": 15,
95
+ // "circle-stroke-color": color,
96
+ // "circle-stroke-width": 2,
97
+ // // "fill-color": "rgba(100,100,100,0.25)",
98
+ // // "stroke-color": "white",
99
+ // // "stroke-width": 0.75,
100
+ // // "text-background-fill-color": "black",
101
+ // "text-fill-color": color,
102
+ // "text-font": "bold 12px sans-serif",
103
+ // "text-stroke-color": color,
104
+ // "text-value": ["to-string", ["get", "num_vehicles_available"]],
105
+ // },
106
+ });
107
+
108
+ source.on("addfeature", function (event) {
109
+ const feature = event.feature;
110
+ feature.setStyle(styleFunction);
111
+ });
112
+
113
+ return layer;
114
+ }, [color, minZoom, name, url]);
115
+
116
+ // Reload features every minutes
117
+ useEffect(() => {
118
+ const interval = window.setInterval(() => {
119
+ // @ts-expect-error - private property
120
+ layer.getSource().loadedExtentsRtree_.clear();
121
+ // vectorSource.clear(true);
122
+ layer.getSource().changed();
123
+ }, 60000);
124
+ return () => {
125
+ window.clearInterval(interval);
126
+ };
127
+ }, [layer]);
128
+
129
+ useEffect(() => {
130
+ if (!map || !layer) {
131
+ return;
132
+ }
133
+ map.on("moveend", () => {
134
+ console.log("ZOOM", map.getView().getZoom());
135
+ });
136
+
137
+ // map.addLayer(layer);
138
+ layer.setMap(map);
139
+
140
+ return () => {
141
+ // map.removeLayer(layer);
142
+ layer.setMap(null);
143
+ };
144
+ });
145
+
146
+ return null;
147
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./RvfSharedMobilityLayer";
@@ -0,0 +1,66 @@
1
+ import { memo } from "preact/compat";
2
+ import { useState } from "preact/hooks";
3
+
4
+ import Minus from "../icons/Minus";
5
+ import Plus from "../icons/Plus";
6
+ import RvfButton from "../RvfButton";
7
+ import useMapContext from "../utils/hooks/useMapContext";
8
+
9
+ function RvfZoomButtons() {
10
+ const { map } = useMapContext();
11
+ const [isZoomInDisabled, setIsZoomInDisabled] = useState(false);
12
+ const [isZoomOutDisabled, setIsZoomOutDisabled] = useState(false);
13
+
14
+ const handleZoomIn = () => {
15
+ const view = map.getView();
16
+ const zoom = view.getZoom();
17
+ const maxzoom = view.getMaxZoom();
18
+ const minzoom = view.getMinZoom();
19
+
20
+ if (maxzoom && zoom === Number(maxzoom)) {
21
+ setIsZoomInDisabled(true);
22
+ return;
23
+ }
24
+
25
+ view.setZoom(zoom + 1);
26
+
27
+ if (!minzoom || view.getZoom() > Number(minzoom)) {
28
+ setIsZoomOutDisabled(false);
29
+ }
30
+ };
31
+
32
+ const handleZoomOut = () => {
33
+ const view = map.getView();
34
+ const zoom = view.getZoom();
35
+ const maxzoom = view.getMaxZoom();
36
+ const minzoom = view.getMinZoom();
37
+
38
+ if (minzoom && zoom === Number(minzoom)) {
39
+ setIsZoomOutDisabled(true);
40
+ return;
41
+ }
42
+
43
+ view.setZoom(zoom - 1);
44
+
45
+ if (!maxzoom || view.getZoom() < Number(maxzoom)) {
46
+ setIsZoomInDisabled(false);
47
+ }
48
+ };
49
+
50
+ return (
51
+ <>
52
+ <RvfButton
53
+ disabled={isZoomInDisabled}
54
+ Icon={Plus}
55
+ onClick={handleZoomIn}
56
+ />
57
+ <RvfButton
58
+ disabled={isZoomOutDisabled}
59
+ Icon={Minus}
60
+ onClick={handleZoomOut}
61
+ />
62
+ </>
63
+ );
64
+ }
65
+
66
+ export default memo(RvfZoomButtons);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfZoomButtons";
@@ -0,0 +1,21 @@
1
+ import { SVGProps } from "preact/compat";
2
+
3
+ function Geolocation(props: SVGProps<SVGSVGElement>) {
4
+ return (
5
+ <svg
6
+ fill="currentColor"
7
+ focusable="false"
8
+ height="1.5em"
9
+ stroke="currentColor"
10
+ strokeWidth="0"
11
+ viewBox="0 0 512 512"
12
+ width="1.5em"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ {...props}
15
+ >
16
+ <path d="M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z" />
17
+ </svg>
18
+ );
19
+ }
20
+
21
+ export default Geolocation;
@@ -0,0 +1 @@
1
+ export { default } from "./Geolocation";