@treasuryspatial/map-react 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.
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import type { Feature, Polygon } from 'geojson';
3
+ import * as THREE from 'three';
4
+ import { type FootprintStats, type MapFootprintSelection } from '@treasuryspatial/map-kit';
5
+ export type MapModeCandidate = {
6
+ selection: MapFootprintSelection;
7
+ feature: Feature<Polygon>;
8
+ pointsLocal: number[][];
9
+ stats: FootprintStats;
10
+ };
11
+ export type MapModeViewportHandle = {
12
+ setLightingPreset: (presetKey: string) => void;
13
+ animateToView: (_viewKey: string) => void;
14
+ capturePngDataUrl: () => string;
15
+ };
16
+ export type ApplyLightingPreset = (scene: THREE.Scene, presetKey: string) => void;
17
+ export type ResolveMeshGroup = (geometry3dm: string) => Promise<THREE.Group | null>;
18
+ export type MapModeViewportProps = {
19
+ active: boolean;
20
+ geometry3dm?: string;
21
+ meshGroup?: THREE.Group | null;
22
+ materialSettings?: {
23
+ color: string;
24
+ textureUrl?: string;
25
+ textureRepeat?: number;
26
+ };
27
+ lightingPreset?: string;
28
+ confirmedSelection?: MapFootprintSelection | null;
29
+ confirmedFeature?: Feature<Polygon> | null;
30
+ candidate?: MapModeCandidate | null;
31
+ mapVisibility: 'on' | 'dim' | 'off';
32
+ onCandidateSelect: (candidate: MapModeCandidate) => void;
33
+ onCandidateClear: () => void;
34
+ accessToken?: string;
35
+ mapStyle?: string;
36
+ applyLightingPreset?: ApplyLightingPreset;
37
+ resolveMeshGroup?: ResolveMeshGroup;
38
+ };
39
+ export declare const MapModeViewport: React.ForwardRefExoticComponent<MapModeViewportProps & React.RefAttributes<MapModeViewportHandle>>;
40
+ //# sourceMappingURL=MapModeViewport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapModeViewport.d.ts","sourceRoot":"","sources":["../src/MapModeViewport.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgF,MAAM,OAAO,CAAC;AAErG,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EASL,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,qBAAqB,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iBAAiB,EAAE,MAAM,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;AAClF,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;AAEpF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;IAC/B,gBAAgB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAClD,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3C,SAAS,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,aAAa,EAAE,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;IACpC,iBAAiB,EAAE,CAAC,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAMF,eAAO,MAAM,eAAe,oGAoU1B,CAAC"}
@@ -0,0 +1,385 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
4
+ import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
5
+ import * as THREE from 'three';
6
+ import { buildGeoJsonPolygon, computeCentroid, computeFootprintStats, createMapThreeLayer, ensureFootprintLayers, getOuterRing, toLocalMeters, toPolygonFeature, } from '@treasuryspatial/map-kit';
7
+ const DEFAULT_CENTER = [-87.6298, 41.8781];
8
+ const DEFAULT_ZOOM = 16.5;
9
+ const DEFAULT_PITCH = 55;
10
+ export const MapModeViewport = forwardRef(function MapModeViewport({ active, geometry3dm, meshGroup, materialSettings, lightingPreset = 'studio', confirmedSelection, confirmedFeature, candidate, mapVisibility, onCandidateSelect, onCandidateClear, accessToken: accessTokenProp, mapStyle: mapStyleProp, applyLightingPreset, resolveMeshGroup, }, ref) {
11
+ const containerRef = useRef(null);
12
+ const mapRef = useRef(null);
13
+ const mapboxRef = useRef(null);
14
+ const layerControllerRef = useRef(null);
15
+ const threeLayerRef = useRef(null);
16
+ const sceneRef = useRef(null);
17
+ const opacityCacheRef = useRef(new Map());
18
+ const lightingPresetRef = useRef(lightingPreset);
19
+ const handlersBoundRef = useRef(false);
20
+ const confirmedSelectionRef = useRef(confirmedSelection ?? null);
21
+ const onCandidateSelectRef = useRef(onCandidateSelect);
22
+ const onCandidateClearRef = useRef(onCandidateClear);
23
+ const [mapReady, setMapReady] = useState(false);
24
+ const [mapError, setMapError] = useState('');
25
+ useEffect(() => {
26
+ lightingPresetRef.current = lightingPreset;
27
+ }, [lightingPreset]);
28
+ useEffect(() => {
29
+ confirmedSelectionRef.current = confirmedSelection ?? null;
30
+ }, [confirmedSelection]);
31
+ useEffect(() => {
32
+ onCandidateSelectRef.current = onCandidateSelect;
33
+ }, [onCandidateSelect]);
34
+ useEffect(() => {
35
+ onCandidateClearRef.current = onCandidateClear;
36
+ }, [onCandidateClear]);
37
+ const accessToken = useMemo(() => accessTokenProp ?? process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN ?? '', [accessTokenProp]);
38
+ const mapStyle = useMemo(() => mapStyleProp ??
39
+ process.env.NEXT_PUBLIC_MAPBOX_FOOTPRINT_STYLE ??
40
+ 'mapbox://styles/treasuryadmin/cmc2gd70p00ui01spfnel4el2', [mapStyleProp]);
41
+ useImperativeHandle(ref, () => ({
42
+ setLightingPreset: (presetKey) => {
43
+ lightingPresetRef.current = presetKey;
44
+ if (sceneRef.current) {
45
+ (applyLightingPreset ?? defaultApplyLightingPreset)(sceneRef.current, presetKey);
46
+ }
47
+ },
48
+ animateToView: () => { },
49
+ capturePngDataUrl: () => {
50
+ const map = mapRef.current;
51
+ if (!map)
52
+ return '';
53
+ try {
54
+ return map.getCanvas().toDataURL('image/png');
55
+ }
56
+ catch {
57
+ return '';
58
+ }
59
+ },
60
+ }), [applyLightingPreset]);
61
+ useEffect(() => {
62
+ if (!active)
63
+ return;
64
+ if (!containerRef.current)
65
+ return;
66
+ if (!accessToken) {
67
+ setMapError('Mapbox access token missing.');
68
+ return;
69
+ }
70
+ let cancelled = false;
71
+ const boot = async () => {
72
+ try {
73
+ const module = await import('mapbox-gl');
74
+ if (cancelled)
75
+ return;
76
+ const mapboxgl = module.default;
77
+ mapboxgl.accessToken = accessToken;
78
+ mapboxRef.current = mapboxgl;
79
+ const startCenter = confirmedSelectionRef.current?.mapView?.center ?? { lng: DEFAULT_CENTER[0], lat: DEFAULT_CENTER[1] };
80
+ const startZoom = confirmedSelectionRef.current?.mapView?.zoom ?? DEFAULT_ZOOM;
81
+ const startBearing = confirmedSelectionRef.current?.mapView?.bearing ?? 0;
82
+ const startPitch = confirmedSelectionRef.current?.mapView?.pitch ?? DEFAULT_PITCH;
83
+ const map = new mapboxgl.Map({
84
+ container: containerRef.current,
85
+ style: mapStyle,
86
+ center: [startCenter.lng, startCenter.lat],
87
+ zoom: startZoom,
88
+ bearing: startBearing,
89
+ pitch: startPitch,
90
+ projection: 'globe',
91
+ antialias: true,
92
+ preserveDrawingBuffer: true,
93
+ attributionControl: false,
94
+ });
95
+ map.addControl(new mapboxgl.NavigationControl({ visualizePitch: true }), 'bottom-right');
96
+ const geocoder = new MapboxGeocoder({
97
+ accessToken,
98
+ mapboxgl: mapboxgl,
99
+ marker: false,
100
+ placeholder: 'search address',
101
+ proximity: { longitude: DEFAULT_CENTER[0], latitude: DEFAULT_CENTER[1] },
102
+ });
103
+ map.addControl(geocoder, 'top-left');
104
+ mapRef.current = map;
105
+ map.on('style.load', () => {
106
+ if (!map.isStyleLoaded())
107
+ return;
108
+ opacityCacheRef.current.clear();
109
+ layerControllerRef.current = ensureFootprintLayers(map);
110
+ ensureCustomLayer(map);
111
+ applyMapVisibility(map, mapVisibility, opacityCacheRef);
112
+ bindFootprintHandlers(map);
113
+ });
114
+ map.on('load', () => {
115
+ if (!map.isStyleLoaded())
116
+ return;
117
+ layerControllerRef.current = ensureFootprintLayers(map);
118
+ ensureCustomLayer(map);
119
+ applyMapVisibility(map, mapVisibility, opacityCacheRef);
120
+ setMapReady(true);
121
+ bindFootprintHandlers(map);
122
+ });
123
+ geocoder.on('result', (event) => {
124
+ const center = event?.result?.center;
125
+ if (!Array.isArray(center) || center.length < 2)
126
+ return;
127
+ map.easeTo({ center: [Number(center[0]), Number(center[1])], zoom: Math.max(map.getZoom(), 17) });
128
+ });
129
+ }
130
+ catch (err) {
131
+ const message = err instanceof Error ? err.message : 'Failed to initialize Mapbox';
132
+ setMapError(message);
133
+ }
134
+ };
135
+ void boot();
136
+ return () => {
137
+ cancelled = true;
138
+ const map = mapRef.current;
139
+ if (map) {
140
+ layerControllerRef.current?.destroy();
141
+ map.remove();
142
+ }
143
+ mapRef.current = null;
144
+ layerControllerRef.current = null;
145
+ threeLayerRef.current?.dispose();
146
+ threeLayerRef.current = null;
147
+ setMapReady(false);
148
+ handlersBoundRef.current = false;
149
+ };
150
+ }, [active, accessToken, mapStyle, mapVisibility]);
151
+ useEffect(() => {
152
+ const map = mapRef.current;
153
+ if (!map || !mapReady)
154
+ return;
155
+ applyMapVisibility(map, mapVisibility, opacityCacheRef);
156
+ }, [mapVisibility, mapReady]);
157
+ useEffect(() => {
158
+ const controller = layerControllerRef.current;
159
+ if (!controller)
160
+ return;
161
+ if (candidate?.feature) {
162
+ controller.setSelected(candidate.feature);
163
+ }
164
+ else if (confirmedFeature) {
165
+ controller.setSelected(confirmedFeature);
166
+ }
167
+ else {
168
+ controller.setSelected(null);
169
+ }
170
+ }, [candidate, confirmedFeature]);
171
+ useEffect(() => {
172
+ if (!mapReady)
173
+ return;
174
+ const map = mapRef.current;
175
+ if (!map || !threeLayerRef.current)
176
+ return;
177
+ if (!confirmedSelection) {
178
+ threeLayerRef.current.setOrigin(null);
179
+ return;
180
+ }
181
+ threeLayerRef.current.setOrigin(confirmedSelection.origin);
182
+ }, [mapReady, confirmedSelection]);
183
+ useEffect(() => {
184
+ if (!mapReady)
185
+ return;
186
+ const map = mapRef.current;
187
+ if (!map)
188
+ return;
189
+ if (!threeLayerRef.current)
190
+ return;
191
+ if (!geometry3dm && !meshGroup) {
192
+ threeLayerRef.current.setGroup(null);
193
+ return;
194
+ }
195
+ let cancelled = false;
196
+ const applyGroup = async () => {
197
+ let group = null;
198
+ if (meshGroup) {
199
+ group = meshGroup;
200
+ }
201
+ else if (geometry3dm && resolveMeshGroup) {
202
+ group = await resolveMeshGroup(geometry3dm);
203
+ }
204
+ if (!group || cancelled)
205
+ return;
206
+ applyMaterialOverrides(group, materialSettings);
207
+ threeLayerRef.current?.setGroup(group);
208
+ };
209
+ void applyGroup();
210
+ return () => {
211
+ cancelled = true;
212
+ };
213
+ }, [mapReady, geometry3dm, meshGroup, materialSettings, resolveMeshGroup]);
214
+ const ensureCustomLayer = (map) => {
215
+ if (!threeLayerRef.current) {
216
+ threeLayerRef.current = createMapThreeLayer({
217
+ id: 'courtyard-mesh',
218
+ zOffsetMeters: 0.05,
219
+ onInit: (scene) => {
220
+ sceneRef.current = scene;
221
+ (applyLightingPreset ?? defaultApplyLightingPreset)(scene, lightingPresetRef.current);
222
+ },
223
+ });
224
+ }
225
+ const layer = threeLayerRef.current.layer;
226
+ if (!map.getLayer(layer.id)) {
227
+ map.addLayer(layer);
228
+ }
229
+ };
230
+ const bindFootprintHandlers = (map) => {
231
+ if (handlersBoundRef.current)
232
+ return;
233
+ handlersBoundRef.current = true;
234
+ map.on('mousemove', 'building-footprints-fill', (e) => {
235
+ const feature = e.features?.[0];
236
+ const poly = feature ? toPolygonFeature(feature) : null;
237
+ layerControllerRef.current?.setHover(poly);
238
+ });
239
+ map.on('mouseleave', 'building-footprints-fill', () => {
240
+ layerControllerRef.current?.setHover(null);
241
+ });
242
+ map.on('click', 'building-footprints-fill', (e) => {
243
+ const feature = e.features?.[0];
244
+ const poly = feature ? toPolygonFeature(feature) : null;
245
+ if (!poly || !poly.geometry)
246
+ return;
247
+ const ring = getOuterRing(poly.geometry);
248
+ const centroid = computeCentroid(ring) ?? { lng: map.getCenter().lng, lat: map.getCenter().lat };
249
+ const mapboxgl = mapboxRef.current;
250
+ if (!mapboxgl)
251
+ return;
252
+ const pointsLocal = toLocalMeters(ring, centroid, mapboxgl);
253
+ if (pointsLocal.length < 3)
254
+ return;
255
+ const selection = {
256
+ geojson: buildGeoJsonPolygon(pointsLocal),
257
+ origin: centroid,
258
+ mapView: {
259
+ center: { lng: map.getCenter().lng, lat: map.getCenter().lat },
260
+ zoom: map.getZoom(),
261
+ bearing: map.getBearing(),
262
+ pitch: map.getPitch(),
263
+ },
264
+ };
265
+ const stats = computeFootprintStats(pointsLocal);
266
+ onCandidateSelectRef.current({ selection, feature: poly, pointsLocal, stats });
267
+ });
268
+ map.on('click', (e) => {
269
+ const features = map.queryRenderedFeatures(e.point, { layers: ['building-footprints-fill'] });
270
+ if (!features.length) {
271
+ onCandidateClearRef.current();
272
+ }
273
+ });
274
+ };
275
+ return (_jsxs("div", { className: "absolute inset-0", children: [_jsx("div", { ref: containerRef, className: "w-full h-full" }), !accessToken && (_jsx("div", { className: "absolute inset-0 z-[2] flex items-center justify-center bg-black/70 text-xs text-white font-nunito", children: "Mapbox access token missing." })), mapError && (_jsx("div", { className: "absolute inset-0 z-[2] flex items-center justify-center bg-black/70 text-xs text-white font-nunito", children: mapError })), !mapReady && !mapError && accessToken && (_jsx("div", { className: "absolute inset-0 z-[2] flex items-center justify-center bg-black/60 text-xs text-white font-nunito", children: "loading map\u2026" }))] }));
276
+ });
277
+ function applyMaterialOverrides(group, settings) {
278
+ const color = new THREE.Color(settings?.color ?? '#ffffff');
279
+ const textureUrl = settings?.textureUrl;
280
+ const repeat = settings?.textureRepeat ?? 2;
281
+ let texture = null;
282
+ if (textureUrl) {
283
+ const loader = new THREE.TextureLoader();
284
+ texture = loader.load(textureUrl);
285
+ texture.wrapS = THREE.RepeatWrapping;
286
+ texture.wrapT = THREE.RepeatWrapping;
287
+ texture.repeat.set(repeat, repeat);
288
+ texture.colorSpace = THREE.SRGBColorSpace;
289
+ }
290
+ group.traverse((child) => {
291
+ if (!(child instanceof THREE.Mesh))
292
+ return;
293
+ const mesh = child;
294
+ if (mesh.material) {
295
+ const material = mesh.material;
296
+ if (Array.isArray(material))
297
+ material.forEach((m) => m?.dispose?.());
298
+ else
299
+ material?.dispose?.();
300
+ }
301
+ mesh.material = new THREE.MeshStandardMaterial({
302
+ color,
303
+ roughness: 0.75,
304
+ metalness: 0,
305
+ side: THREE.DoubleSide,
306
+ map: texture ?? null,
307
+ });
308
+ });
309
+ }
310
+ function defaultApplyLightingPreset(scene, _presetKey) {
311
+ scene.background = null;
312
+ const lightsToRemove = scene.children.filter((child) => child instanceof THREE.Light);
313
+ lightsToRemove.forEach((light) => scene.remove(light));
314
+ scene.add(new THREE.AmbientLight('#ffffff', 0.6));
315
+ const key = new THREE.DirectionalLight('#ffffff', 0.8);
316
+ key.position.set(8, 12, 6);
317
+ key.castShadow = false;
318
+ scene.add(key);
319
+ const fill = new THREE.DirectionalLight('#ffffff', 0.4);
320
+ fill.position.set(-6, 8, -6);
321
+ scene.add(fill);
322
+ }
323
+ function applyMapVisibility(map, mode, cacheRef) {
324
+ if (!map?.getStyle?.())
325
+ return;
326
+ const style = map.getStyle();
327
+ if (!style?.layers)
328
+ return;
329
+ const factor = mode === 'on' ? 1 : mode === 'dim' ? 0.25 : 0;
330
+ const opacityProps = {
331
+ fill: ['fill-opacity'],
332
+ line: ['line-opacity'],
333
+ symbol: ['icon-opacity', 'text-opacity'],
334
+ circle: ['circle-opacity'],
335
+ raster: ['raster-opacity'],
336
+ background: ['background-opacity'],
337
+ 'fill-extrusion': ['fill-extrusion-opacity'],
338
+ heatmap: ['heatmap-opacity'],
339
+ };
340
+ style.layers.forEach((layer) => {
341
+ if (layer.id.startsWith('footprint-'))
342
+ return;
343
+ if (layer.id === 'courtyard-mesh')
344
+ return;
345
+ const props = opacityProps[layer.type] ?? [];
346
+ props.forEach((prop) => {
347
+ const cacheKey = `${layer.id}:${prop}`;
348
+ if (!cacheRef.current.has(cacheKey)) {
349
+ try {
350
+ cacheRef.current.set(cacheKey, map.getPaintProperty(layer.id, prop));
351
+ }
352
+ catch {
353
+ return;
354
+ }
355
+ }
356
+ if (mode === 'on') {
357
+ const original = cacheRef.current.get(cacheKey);
358
+ if (original !== undefined) {
359
+ try {
360
+ map.setPaintProperty(layer.id, prop, original);
361
+ }
362
+ catch {
363
+ // ignore
364
+ }
365
+ }
366
+ return;
367
+ }
368
+ try {
369
+ map.setPaintProperty(layer.id, prop, factor);
370
+ }
371
+ catch {
372
+ // ignore
373
+ }
374
+ });
375
+ });
376
+ if (mode === 'off') {
377
+ try {
378
+ map.setPaintProperty('background', 'background-color', '#1E1E1E');
379
+ map.setPaintProperty('background', 'background-opacity', 1);
380
+ }
381
+ catch {
382
+ // ignore
383
+ }
384
+ }
385
+ }
@@ -0,0 +1,2 @@
1
+ export * from './MapModeViewport';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './MapModeViewport';
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@treasuryspatial/map-react",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "UNLICENSED",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "dependencies": {
21
+ "@treasuryspatial/map-kit": "^0.1.1"
22
+ },
23
+ "peerDependencies": {
24
+ "react": ">=18",
25
+ "three": ">=0.178.0",
26
+ "mapbox-gl": "^3.13.0",
27
+ "@mapbox/mapbox-gl-geocoder": "^5.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "react": "19.1.0",
31
+ "three": "^0.180.0",
32
+ "mapbox-gl": "^3.13.0",
33
+ "@types/geojson": "^7946.0.16",
34
+ "@types/mapbox__mapbox-gl-geocoder": "^4.7.6",
35
+ "@types/react": "^19",
36
+ "@types/react-dom": "^19"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -b",
40
+ "typecheck": "tsc -b --pretty false --noEmit"
41
+ }
42
+ }