@dromney/mapthis 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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -0
  3. package/dist/ai/index.cjs +474 -0
  4. package/dist/ai/index.cjs.map +1 -0
  5. package/dist/ai/index.d.cts +117 -0
  6. package/dist/ai/index.d.ts +117 -0
  7. package/dist/ai/index.js +447 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/domain-CZ-L-ntu.d.ts +163 -0
  10. package/dist/domain-Dc1wSTkf.d.cts +163 -0
  11. package/dist/errors-Bw97z_4m.d.cts +12 -0
  12. package/dist/errors-Bw97z_4m.d.ts +12 -0
  13. package/dist/generate/index.cjs +222 -0
  14. package/dist/generate/index.cjs.map +1 -0
  15. package/dist/generate/index.d.cts +140 -0
  16. package/dist/generate/index.d.ts +140 -0
  17. package/dist/generate/index.js +220 -0
  18. package/dist/generate/index.js.map +1 -0
  19. package/dist/geocoding/index.cjs +90 -0
  20. package/dist/geocoding/index.cjs.map +1 -0
  21. package/dist/geocoding/index.d.cts +36 -0
  22. package/dist/geocoding/index.d.ts +36 -0
  23. package/dist/geocoding/index.js +86 -0
  24. package/dist/geocoding/index.js.map +1 -0
  25. package/dist/index.cjs +546 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +5 -0
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.js +469 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/parser-CzXzpmVv.d.cts +111 -0
  32. package/dist/parser-N7-fNxeu.d.ts +111 -0
  33. package/dist/react/index.cjs +394 -0
  34. package/dist/react/index.cjs.map +1 -0
  35. package/dist/react/index.js +383 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/schemas-Dy5coqXo.d.cts +484 -0
  38. package/dist/schemas-Dy5coqXo.d.ts +484 -0
  39. package/dist/scrape/index.cjs +133 -0
  40. package/dist/scrape/index.cjs.map +1 -0
  41. package/dist/scrape/index.d.cts +60 -0
  42. package/dist/scrape/index.d.ts +60 -0
  43. package/dist/scrape/index.js +125 -0
  44. package/dist/scrape/index.js.map +1 -0
  45. package/dist/search/index.cjs +76 -0
  46. package/dist/search/index.cjs.map +1 -0
  47. package/dist/search/index.d.cts +75 -0
  48. package/dist/search/index.d.ts +75 -0
  49. package/dist/search/index.js +71 -0
  50. package/dist/search/index.js.map +1 -0
  51. package/dist/types/index.cjs +215 -0
  52. package/dist/types/index.cjs.map +1 -0
  53. package/dist/types/index.d.cts +4 -0
  54. package/dist/types/index.d.ts +4 -0
  55. package/dist/types/index.js +171 -0
  56. package/dist/types/index.js.map +1 -0
  57. package/dist/types-BhqKlq0k.d.ts +31 -0
  58. package/dist/types-rFjK5YcJ.d.cts +31 -0
  59. package/dist/utils/index.cjs +335 -0
  60. package/dist/utils/index.cjs.map +1 -0
  61. package/dist/utils/index.d.cts +363 -0
  62. package/dist/utils/index.d.ts +363 -0
  63. package/dist/utils/index.js +301 -0
  64. package/dist/utils/index.js.map +1 -0
  65. package/package.json +150 -0
@@ -0,0 +1,383 @@
1
+ "use client";
2
+ import { createContext, useContext, useMemo, useState, useCallback, useRef, useLayoutEffect, useEffect } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { useAdvancedMarkerRef, AdvancedMarker, Pin, InfoWindow, APIProvider, Map, useMap as useMap$1 } from '@vis.gl/react-google-maps';
5
+
6
+ // src/utils/geo.ts
7
+ var cities = {
8
+ washingtonDC: { lat: 38.9072, lng: -77.0369 }};
9
+
10
+ // src/react/autofit.ts
11
+ var DEFAULT_CENTER_ZOOM = {
12
+ center: cities.washingtonDC,
13
+ zoom: 4
14
+ };
15
+ var WORLD_DIM = { height: 256, width: 256 };
16
+ var ZOOM_MAX = 21;
17
+ var PIN_SIZE = 60;
18
+ var EDGE_ADJUSTMENT = 5;
19
+ function getZoom(mapPx, worldPx, fraction) {
20
+ return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
21
+ }
22
+ function latRad(lat) {
23
+ const sin = Math.sin(lat * Math.PI / 180);
24
+ const radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
25
+ return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
26
+ }
27
+ function autofitCenterZoom(dims, places) {
28
+ if (places.length < 1) return DEFAULT_CENTER_ZOOM;
29
+ const coords = places.map((p) => ({ lat: p.lat, lng: p.lon }));
30
+ const firstCoord = coords[0];
31
+ if (coords.length === 1 && firstCoord) {
32
+ return { center: firstCoord, zoom: 10 };
33
+ }
34
+ const lats = coords.map((c) => c.lat);
35
+ const lngs = coords.map((c) => c.lng);
36
+ const latMax = Math.max(...lats);
37
+ const latMin = Math.min(...lats);
38
+ const lngMax = Math.max(...lngs);
39
+ const lngMin = Math.min(...lngs);
40
+ const latFraction = (latRad(latMax) - latRad(latMin)) / Math.PI;
41
+ const lngDiff = lngMax - lngMin;
42
+ const lngFraction = (lngDiff < 0 ? lngDiff + 360 : lngDiff) / 360;
43
+ const latZoom = getZoom(
44
+ dims.height - EDGE_ADJUSTMENT - PIN_SIZE,
45
+ WORLD_DIM.height,
46
+ latFraction || 1e-9
47
+ );
48
+ const lngZoom = getZoom(
49
+ dims.width - EDGE_ADJUSTMENT,
50
+ WORLD_DIM.width,
51
+ lngFraction || 1e-9
52
+ );
53
+ const zoom = Math.min(latZoom, lngZoom, ZOOM_MAX);
54
+ const scale = 1 << zoom;
55
+ const latitudeFractionPerPx = 180 / (scale * WORLD_DIM.height);
56
+ const latMaxAdjustment = PIN_SIZE * latitudeFractionPerPx;
57
+ return {
58
+ center: {
59
+ lat: (latMax + latMaxAdjustment + latMin) / 2,
60
+ lng: (lngMax + lngMin) / 2
61
+ },
62
+ zoom
63
+ };
64
+ }
65
+ var DEFAULT_CONTEXT = {
66
+ map: null,
67
+ places: [],
68
+ groups: [],
69
+ isLoading: false,
70
+ isError: false,
71
+ isEditable: false,
72
+ selectedPlaceId: null,
73
+ setSelectedPlaceId: () => {
74
+ },
75
+ selectedGroupId: null,
76
+ setSelectedGroupId: () => {
77
+ },
78
+ infoWindowPlaceId: null,
79
+ setInfoWindowPlaceId: () => {
80
+ },
81
+ hideAllInfoWindows: () => {
82
+ },
83
+ handleMarkerClick: () => {
84
+ },
85
+ handleListClick: () => {
86
+ }
87
+ };
88
+ var MapContext = createContext(DEFAULT_CONTEXT);
89
+ var useMap = () => useContext(MapContext);
90
+ function usePlace(id) {
91
+ const { places } = useMap();
92
+ return useMemo(() => {
93
+ if (!id) return null;
94
+ return places.find((p) => p.id === id) ?? null;
95
+ }, [id, places]);
96
+ }
97
+ function MapProvider({
98
+ map,
99
+ places,
100
+ groups,
101
+ isLoading = false,
102
+ isError = false,
103
+ isEditable = false,
104
+ children
105
+ }) {
106
+ const [selectedPlaceId, setSelectedPlaceId] = useState(null);
107
+ const [selectedGroupId, setSelectedGroupId] = useState(null);
108
+ const [infoWindowPlaceId, setInfoWindowPlaceId] = useState(null);
109
+ const hideAllInfoWindows = useCallback(() => {
110
+ setInfoWindowPlaceId(null);
111
+ }, []);
112
+ const handleMarkerClick = useCallback((placeId) => {
113
+ setSelectedPlaceId(placeId);
114
+ setInfoWindowPlaceId((current) => current === placeId ? null : placeId);
115
+ }, []);
116
+ const handleListClick = useCallback((placeId) => {
117
+ setSelectedPlaceId(placeId);
118
+ setInfoWindowPlaceId(placeId);
119
+ }, []);
120
+ const value = useMemo(
121
+ () => ({
122
+ map,
123
+ places,
124
+ groups,
125
+ isLoading,
126
+ isError,
127
+ isEditable,
128
+ selectedPlaceId,
129
+ setSelectedPlaceId,
130
+ selectedGroupId,
131
+ setSelectedGroupId,
132
+ infoWindowPlaceId,
133
+ setInfoWindowPlaceId,
134
+ hideAllInfoWindows,
135
+ handleMarkerClick,
136
+ handleListClick
137
+ }),
138
+ [
139
+ map,
140
+ places,
141
+ groups,
142
+ isLoading,
143
+ isError,
144
+ isEditable,
145
+ selectedPlaceId,
146
+ selectedGroupId,
147
+ infoWindowPlaceId,
148
+ hideAllInfoWindows,
149
+ handleMarkerClick,
150
+ handleListClick
151
+ ]
152
+ );
153
+ return /* @__PURE__ */ jsx(MapContext.Provider, { value, children });
154
+ }
155
+
156
+ // src/utils/color.ts
157
+ function rgbToHex(r, g, b) {
158
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
159
+ }
160
+ function hueToRGB(m, n, o) {
161
+ if (o < 0) o += 1;
162
+ if (o > 1) o -= 1;
163
+ if (o < 1 / 6) return m + (n - m) * 6 * o;
164
+ if (o < 1 / 2) return n;
165
+ if (o < 2 / 3) return m + (n - m) * (2 / 3 - o) * 6;
166
+ return m;
167
+ }
168
+ function hslToRgb(H, S, L) {
169
+ const h = H / 360;
170
+ const s = S / 100;
171
+ const l = L / 100;
172
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
173
+ const p = 2 * l - q;
174
+ const r = hueToRGB(p, q, h + 1 / 3);
175
+ const g = hueToRGB(p, q, h);
176
+ const b = hueToRGB(p, q, h - 1 / 3);
177
+ const rd = (a) => Math.floor(Math.max(Math.min(a * 256, 255), 0));
178
+ return [rd(r), rd(g), rd(b)];
179
+ }
180
+ var DARK_COLOR = "#000000";
181
+ var LIGHT_COLOR = "#f2f2f2";
182
+ function uuidPastelHsl(uuid) {
183
+ const val = BigInt("0x" + uuid.replace(/-/g, ""));
184
+ const h = val % BigInt(360);
185
+ const s = 100;
186
+ const l = 87;
187
+ return [Number(h), s, l];
188
+ }
189
+ function uuidPastelRgb(uuid) {
190
+ const [h, s, l] = uuidPastelHsl(uuid);
191
+ return hslToRgb(h, s, l);
192
+ }
193
+ function uuidPastelHex(uuid) {
194
+ const [r, g, b] = uuidPastelRgb(uuid);
195
+ return rgbToHex(r, g, b);
196
+ }
197
+ function getTextColor(bgColor) {
198
+ if (bgColor.length !== 7) return DARK_COLOR;
199
+ const color = bgColor.startsWith("#") ? bgColor.substring(1, 7) : bgColor;
200
+ const r = parseInt(color.substring(0, 2), 16);
201
+ const g = parseInt(color.substring(2, 4), 16);
202
+ const b = parseInt(color.substring(4, 6), 16);
203
+ return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? DARK_COLOR : LIGHT_COLOR;
204
+ }
205
+
206
+ // src/utils/text.ts
207
+ var capitalizeFirstLetter = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
208
+ function getPlaceDisplayName(place) {
209
+ if (!place) return "";
210
+ const raw = place.name ?? place.query ?? place.address?.split(",")[0] ?? "";
211
+ return capitalizeFirstLetter(raw) ?? "";
212
+ }
213
+ function PlaceMarker({ place, renderContent }) {
214
+ const [advMarkerRef, advMarker] = useAdvancedMarkerRef();
215
+ const { groups, selectedPlaceId, infoWindowPlaceId, setInfoWindowPlaceId, handleMarkerClick } = useMap();
216
+ const group = groups.find((g) => g.id === place.groupId) ?? null;
217
+ const isSelected = place.id === selectedPlaceId;
218
+ const isInfoWindowOpen = place.id === infoWindowPlaceId;
219
+ if (group?.hidden) return null;
220
+ if (place.hidden) return null;
221
+ if (place.lat == null || place.lon == null || place.error) return null;
222
+ const bgColor = group?.color ?? (group ? uuidPastelHex(group.id) : "red");
223
+ const foreColor = getTextColor(bgColor);
224
+ const displayName = getPlaceDisplayName(place);
225
+ return /* @__PURE__ */ jsxs(
226
+ AdvancedMarker,
227
+ {
228
+ position: { lat: place.lat, lng: place.lon },
229
+ ref: advMarkerRef,
230
+ onClick: () => handleMarkerClick(place.id),
231
+ zIndex: isSelected ? 20 : 10,
232
+ draggable: false,
233
+ children: [
234
+ /* @__PURE__ */ jsx(Pin, { background: bgColor, glyphColor: foreColor, borderColor: "#000" }),
235
+ isInfoWindowOpen && /* @__PURE__ */ jsx(
236
+ InfoWindow,
237
+ {
238
+ anchor: advMarker,
239
+ onCloseClick: () => setInfoWindowPlaceId(null),
240
+ zIndex: isSelected ? 20 : 10,
241
+ children: renderContent ? renderContent(place, group) : /* @__PURE__ */ jsxs("div", { style: { maxWidth: 240, display: "flex", flexDirection: "column", gap: 4 }, children: [
242
+ /* @__PURE__ */ jsx("strong", { children: displayName }),
243
+ place.description ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, margin: 0 }, children: capitalizeFirstLetter(place.description) }) : null
244
+ ] })
245
+ }
246
+ )
247
+ ]
248
+ }
249
+ );
250
+ }
251
+ function useContainerDims() {
252
+ const ref = useRef(null);
253
+ const [dims, setDims] = useState({ width: 0, height: 0 });
254
+ useLayoutEffect(() => {
255
+ const el = ref.current;
256
+ if (!el) return;
257
+ const update = () => {
258
+ const rect = el.getBoundingClientRect();
259
+ setDims({ width: rect.width, height: rect.height });
260
+ };
261
+ update();
262
+ const observer = new ResizeObserver(update);
263
+ observer.observe(el);
264
+ return () => observer.disconnect();
265
+ }, []);
266
+ return [ref, dims];
267
+ }
268
+ function Autofitter({
269
+ dims,
270
+ setCenterZoom
271
+ }) {
272
+ const { places } = useMap();
273
+ const googleMap = useMap$1();
274
+ const mappable = useMemo(() => {
275
+ return places.filter(
276
+ (p) => p.lat != null && p.lon != null && !p.error && !p.hidden
277
+ ).map((p) => ({ lat: p.lat, lon: p.lon }));
278
+ }, [places]);
279
+ useEffect(() => {
280
+ if (!googleMap || dims.width === 0 || dims.height === 0) return;
281
+ const cz = autofitCenterZoom(dims, mappable);
282
+ setCenterZoom(cz);
283
+ googleMap.setCenter(cz.center);
284
+ googleMap.setZoom(cz.zoom);
285
+ }, [googleMap, dims, mappable, setCenterZoom]);
286
+ return null;
287
+ }
288
+ function GoogleMapsViewer({
289
+ apiKey,
290
+ mapId,
291
+ defaultCenter,
292
+ defaultZoom,
293
+ className,
294
+ style,
295
+ loadingContent = null,
296
+ mapTypeId,
297
+ streetViewControl = false,
298
+ zoomControl = false,
299
+ mapTypeControl = false,
300
+ fullscreenControl = false,
301
+ renderMarkerContent
302
+ }) {
303
+ const [ready, setReady] = useState(false);
304
+ const [containerRef, dims] = useContainerDims();
305
+ const [centerZoom, setCenterZoom] = useState(() => ({
306
+ center: defaultCenter ?? DEFAULT_CENTER_ZOOM.center,
307
+ zoom: defaultZoom ?? DEFAULT_CENTER_ZOOM.zoom
308
+ }));
309
+ const { places } = useMap();
310
+ const containerStyle = {
311
+ position: "relative",
312
+ width: "100%",
313
+ height: "100%",
314
+ ...style
315
+ };
316
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style: containerStyle, children: /* @__PURE__ */ jsx(APIProvider, { apiKey, libraries: ["maps", "core", "marker"], onLoad: () => setReady(true), children: ready ? /* @__PURE__ */ jsxs(
317
+ Map,
318
+ {
319
+ center: centerZoom.center,
320
+ zoom: centerZoom.zoom,
321
+ streetViewControl,
322
+ fullscreenControl,
323
+ mapTypeControl,
324
+ zoomControl,
325
+ isFractionalZoomEnabled: true,
326
+ mapTypeId,
327
+ mapId,
328
+ style: { width: "100%", height: "100%" },
329
+ children: [
330
+ places.map((p) => /* @__PURE__ */ jsx(
331
+ PlaceMarker,
332
+ {
333
+ place: p,
334
+ renderContent: renderMarkerContent
335
+ },
336
+ p.id + "-marker"
337
+ )),
338
+ /* @__PURE__ */ jsx(Autofitter, { dims, setCenterZoom })
339
+ ]
340
+ }
341
+ ) : loadingContent }) });
342
+ }
343
+
344
+ // src/react/browser-autocomplete.ts
345
+ var GOOGLE_PLACE_RESULT_FIELDS = [
346
+ "name",
347
+ "geometry",
348
+ "formatted_address",
349
+ "place_id"
350
+ ];
351
+ async function getPlaceDetailsFromGoogleAutocompleteSuggestion(query, sug) {
352
+ const service = new google.maps.places.PlacesService(document.createElement("div"));
353
+ return new Promise((resolve, reject) => {
354
+ service.getDetails(
355
+ {
356
+ placeId: sug.place_id,
357
+ fields: GOOGLE_PLACE_RESULT_FIELDS
358
+ },
359
+ (place, status) => {
360
+ if (status !== google.maps.places.PlacesServiceStatus.OK) {
361
+ return reject(new Error(`Places service status: ${status}`));
362
+ }
363
+ if (!place?.geometry?.location || !place?.formatted_address) {
364
+ return reject(new Error("No place or invalid geometry/address"));
365
+ }
366
+ resolve({
367
+ query,
368
+ description: sug.description,
369
+ provider: "google",
370
+ providerId: sug.place_id,
371
+ name: place.name ?? sug.structured_formatting.main_text,
372
+ address: place.formatted_address,
373
+ lat: place.geometry.location.lat() ?? 0,
374
+ lng: place.geometry.location.lng() ?? 0
375
+ });
376
+ }
377
+ );
378
+ });
379
+ }
380
+
381
+ export { DEFAULT_CENTER_ZOOM, GoogleMapsViewer, MapContext, MapProvider, PlaceMarker, autofitCenterZoom, getPlaceDetailsFromGoogleAutocompleteSuggestion, getPlaceDisplayName, useMap, usePlace };
382
+ //# sourceMappingURL=index.js.map
383
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/geo.ts","../../src/react/autofit.ts","../../src/react/map-context.tsx","../../src/utils/color.ts","../../src/utils/text.ts","../../src/react/place-marker.tsx","../../src/react/google-maps-viewer.tsx","../../src/react/browser-autocomplete.ts"],"names":["jsx","useState","useGoogleMap","useMemo","jsxs","GoogleMap"],"mappings":"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","file":"index.js","sourcesContent":["import type { Coordinates } from \"../types/schemas\"\nimport { randomIntBetween } from \"./number\"\n\n/**\n * Known lat/lng for major cities. Useful as default map centers, test\n * fixtures, and fallback coordinates.\n */\nexport const cities = {\n london: { lat: 51.5074, lng: -0.1278 },\n washingtonDC: { lat: 38.9072, lng: -77.0369 },\n newYorkCity: { lat: 40.7128, lng: -74.006 },\n losAngeles: { lat: 34.0522, lng: -118.2437 },\n tokyo: { lat: 35.6895, lng: 139.6917 },\n paris: { lat: 48.8566, lng: 2.3522 },\n sydney: { lat: -33.8688, lng: 151.2093 },\n saoPaulo: { lat: -23.5505, lng: -46.6333 },\n moscow: { lat: 55.7558, lng: 37.6173 },\n beijing: { lat: 39.9042, lng: 116.4074 },\n buenosAires: { lat: -34.6037, lng: -58.3816 },\n rome: { lat: 41.9028, lng: 12.4964 },\n sanFrancisco: { lat: 37.7749, lng: -122.4194 },\n mexicoCity: { lat: 19.4326, lng: -99.1332 },\n newDelhi: { lat: 28.6139, lng: 77.209 },\n toronto: { lat: 43.6532, lng: -79.3832 },\n berlin: { lat: 52.52, lng: 13.405 },\n shanghai: { lat: 31.2304, lng: 121.4737 },\n bangkok: { lat: 13.7563, lng: 100.5018 },\n rioDeJaneiro: { lat: -22.9068, lng: -43.1729 },\n singapore: { lat: 1.3521, lng: 103.8198 },\n istanbul: { lat: 41.0082, lng: 28.9784 },\n melbourne: { lat: -37.8136, lng: 144.9631 },\n frankfurt: { lat: 50.1109, lng: 8.6821 },\n johannesburg: { lat: -26.2041, lng: 28.0473 },\n madrid: { lat: 40.4168, lng: -3.7038 },\n kualaLumpur: { lat: 3.139, lng: 101.6869 },\n helsinki: { lat: 60.1699, lng: 24.9384 },\n copenhagen: { lat: 55.6761, lng: 12.5683 },\n budapest: { lat: 47.4979, lng: 19.0402 },\n ankara: { lat: 39.9334, lng: 32.8597 },\n cairo: { lat: 30.0444, lng: 31.2357 },\n osaka: { lat: 34.6937, lng: 135.5023 },\n athens: { lat: 37.9838, lng: 23.7275 },\n seoul: { lat: 37.5665, lng: 126.978 },\n jakarta: { lat: -6.2088, lng: 106.8456 },\n dubai: { lat: 25.276987, lng: 55.296249 },\n telAviv: { lat: 32.0853, lng: 34.7818 },\n mumbai: { lat: 19.075983, lng: 72.877655 },\n nairobi: { lat: -1.2921, lng: 36.8219 },\n casablanca: { lat: 33.5731, lng: -7.5898 },\n calgary: { lat: 51.0447, lng: -114.0719 },\n montreal: { lat: 45.5017, lng: -73.5673 },\n zagreb: { lat: 45.815, lng: 15.9819 },\n stockholm: { lat: 59.3293, lng: 18.0686 },\n brasilia: { lat: -15.8267, lng: -47.9218 },\n canberra: { lat: -35.282, lng: 149.1286 },\n amman: { lat: 30.5852, lng: 36.2384 },\n dublin: { lat: 53.3498, lng: -6.2603 },\n phoenix: { lat: 33.4484, lng: -112.074 },\n portLouis: { lat: -20.1609, lng: 57.5012 },\n riyadh: { lat: 24.7136, lng: 46.6753 },\n bucharest: { lat: 44.4268, lng: 26.1025 },\n vancouver: { lat: 49.2827, lng: -123.1207 },\n baghdad: { lat: 33.3128, lng: 44.3615 },\n cancun: { lat: 21.1619, lng: -86.8515 },\n denpasar: { lat: -8.4095, lng: 115.1889 },\n vientiane: { lat: 17.9714, lng: 102.6141 },\n pune: { lat: 18.5204, lng: 73.8567 },\n karachi: { lat: 24.8607, lng: 67.0011 },\n denver: { lat: 39.7392, lng: -104.9903 },\n} as const satisfies Record<string, Coordinates>\n\nexport type KnownCity = keyof typeof cities\n\nexport const randomLat = (): number => randomIntBetween(-90, 90)\nexport const randomLng = (): number => randomIntBetween(-180, 180)\n\n/**\n * Development-friendly coordinates (centered over central Europe) so seeded\n * data never lands in the ocean during local testing.\n */\nexport const randomLatDev = (): number => randomIntBetween(40, 50)\nexport const randomLngDev = (): number => randomIntBetween(-5, 5)\n\nexport const randomDevCoordinates = (): Coordinates => ({\n lat: randomLatDev(),\n lng: randomLngDev(),\n})\n","import { cities } from \"../utils/geo\"\n\n/**\n * Pure autofit math. Given a viewport size and a list of mappable places,\n * compute a `{ center, zoom }` pair that frames all the places inside the\n * viewport with a small pin-height margin at the top.\n *\n * Adapted from the Stack Overflow solution for \"Google Maps v3: how to\n * calculate the zoom level for a given bounds\" — see\n * https://stackoverflow.com/questions/6048975 — because Google Maps'\n * `map.fitBounds` has never been consistently reliable for us.\n */\n\nexport interface CenterZoomAutofit {\n center: google.maps.LatLngLiteral\n zoom: number\n}\n\nexport interface ContainerDims {\n width: number\n height: number\n}\n\nexport interface MappablePlace {\n lat: number\n lon: number\n}\n\nexport const DEFAULT_CENTER_ZOOM: CenterZoomAutofit = {\n center: cities.washingtonDC,\n zoom: 4,\n}\n\nconst WORLD_DIM = { height: 256, width: 256 }\nconst ZOOM_MAX = 21\nconst PIN_SIZE = 60 // pixels\nconst EDGE_ADJUSTMENT = 5 // pixels\n\nfunction getZoom(mapPx: number, worldPx: number, fraction: number): number {\n return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2)\n}\n\nfunction latRad(lat: number): number {\n const sin = Math.sin((lat * Math.PI) / 180)\n const radX2 = Math.log((1 + sin) / (1 - sin)) / 2\n return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2\n}\n\n/**\n * Compute center and zoom for an autofit of the given places inside a\n * container of the given size.\n *\n * - Zero places → {@link DEFAULT_CENTER_ZOOM}.\n * - One place → centered on that place at zoom 10.\n * - Multiple places → tight bounding box with a pin-sized margin at top.\n */\nexport function autofitCenterZoom(\n dims: ContainerDims,\n places: MappablePlace[],\n): CenterZoomAutofit {\n if (places.length < 1) return DEFAULT_CENTER_ZOOM\n\n const coords = places.map((p) => ({ lat: p.lat, lng: p.lon }))\n\n const firstCoord = coords[0]\n if (coords.length === 1 && firstCoord) {\n return { center: firstCoord, zoom: 10 }\n }\n\n const lats = coords.map((c) => c.lat)\n const lngs = coords.map((c) => c.lng)\n const latMax = Math.max(...lats)\n const latMin = Math.min(...lats)\n const lngMax = Math.max(...lngs)\n const lngMin = Math.min(...lngs)\n\n const latFraction = (latRad(latMax) - latRad(latMin)) / Math.PI\n\n const lngDiff = lngMax - lngMin\n const lngFraction = (lngDiff < 0 ? lngDiff + 360 : lngDiff) / 360\n\n const latZoom = getZoom(\n dims.height - EDGE_ADJUSTMENT - PIN_SIZE,\n WORLD_DIM.height,\n latFraction || 1e-9,\n )\n const lngZoom = getZoom(\n dims.width - EDGE_ADJUSTMENT,\n WORLD_DIM.width,\n lngFraction || 1e-9,\n )\n\n const zoom = Math.min(latZoom, lngZoom, ZOOM_MAX)\n\n // Compensate for the pin height so the top of the viewport is not\n // cropped. The bigger the zoom, the smaller the compensation (in degrees\n // of latitude).\n const scale = 1 << zoom\n const latitudeFractionPerPx = 180 / (scale * WORLD_DIM.height)\n const latMaxAdjustment = PIN_SIZE * latitudeFractionPerPx\n\n return {\n center: {\n lat: (latMax + latMaxAdjustment + latMin) / 2.0,\n lng: (lngMax + lngMin) / 2.0,\n },\n zoom,\n }\n}\n","\"use client\"\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\"\nimport type { Place, PlaceGroup, PlaceMap } from \"../types/domain\"\n\n/**\n * The full context value exposed by {@link MapProvider}. Includes the three\n * data arrays, selection state, and a small set of click handlers derived\n * from them.\n *\n * This is deliberately data-only — no mutations, no network calls. The\n * consumer owns the data (typically via their own query layer) and passes it\n * to {@link MapProvider} as props. All mutation/persistence concerns stay in\n * the consumer's app.\n */\nexport interface MapContextValue {\n // --- Data (controlled by consumer via MapProvider props) -----------------\n map: PlaceMap | null\n places: Place[]\n groups: PlaceGroup[]\n isLoading: boolean\n isError: boolean\n isEditable: boolean\n\n // --- Selection state (internal) ------------------------------------------\n selectedPlaceId: string | null\n setSelectedPlaceId: (id: string | null) => void\n selectedGroupId: string | null\n setSelectedGroupId: (id: string | null) => void\n\n // --- Info window state ---------------------------------------------------\n /**\n * ID of the place whose info window is currently open, or null if none.\n * Only one info window is shown at a time.\n */\n infoWindowPlaceId: string | null\n setInfoWindowPlaceId: (id: string | null) => void\n hideAllInfoWindows: () => void\n\n // --- Derived click handlers ----------------------------------------------\n /**\n * Select a place and toggle its info window. Typically wired to marker\n * clicks.\n */\n handleMarkerClick: (placeId: string) => void\n /**\n * Select a place and force its info window open. Typically wired to list\n * item clicks.\n */\n handleListClick: (placeId: string) => void\n}\n\nconst DEFAULT_CONTEXT: MapContextValue = {\n map: null,\n places: [],\n groups: [],\n isLoading: false,\n isError: false,\n isEditable: false,\n\n selectedPlaceId: null,\n setSelectedPlaceId: () => {},\n selectedGroupId: null,\n setSelectedGroupId: () => {},\n\n infoWindowPlaceId: null,\n setInfoWindowPlaceId: () => {},\n hideAllInfoWindows: () => {},\n\n handleMarkerClick: () => {},\n handleListClick: () => {},\n}\n\nexport const MapContext = createContext<MapContextValue>(DEFAULT_CONTEXT)\n\nexport const useMap = (): MapContextValue => useContext(MapContext)\n\n/**\n * Look up a single place by id from the current {@link MapProvider} context.\n * Returns `null` if the id is not in the list.\n */\nexport function usePlace(id: string | null | undefined): Place | null {\n const { places } = useMap()\n return useMemo(() => {\n if (!id) return null\n return places.find((p) => p.id === id) ?? null\n }, [id, places])\n}\n\nexport interface MapProviderProps {\n /** The current map entity, or null if none is loaded. */\n map: PlaceMap | null\n /** Places belonging to this map. Order is preserved as render order. */\n places: Place[]\n /** Groups belonging to this map. */\n groups: PlaceGroup[]\n /** Whether any of the data is still loading. Defaults to false. */\n isLoading?: boolean\n /** Whether any of the data failed to load. Defaults to false. */\n isError?: boolean\n /**\n * Whether the current viewer should be allowed to edit the map. This\n * package does not perform any auth — the consumer decides.\n */\n isEditable?: boolean\n children: ReactNode\n}\n\n/**\n * Provide map data and selection state to descendants. Use in conjunction\n * with {@link GoogleMapsViewer} and {@link PlaceMarker}, or with your own\n * components that call {@link useMap} / {@link usePlace}.\n */\nexport function MapProvider({\n map,\n places,\n groups,\n isLoading = false,\n isError = false,\n isEditable = false,\n children,\n}: MapProviderProps) {\n const [selectedPlaceId, setSelectedPlaceId] = useState<string | null>(null)\n const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null)\n const [infoWindowPlaceId, setInfoWindowPlaceId] = useState<string | null>(null)\n\n const hideAllInfoWindows = useCallback(() => {\n setInfoWindowPlaceId(null)\n }, [])\n\n const handleMarkerClick = useCallback((placeId: string) => {\n setSelectedPlaceId(placeId)\n setInfoWindowPlaceId((current) => (current === placeId ? null : placeId))\n }, [])\n\n const handleListClick = useCallback((placeId: string) => {\n setSelectedPlaceId(placeId)\n setInfoWindowPlaceId(placeId)\n }, [])\n\n const value = useMemo<MapContextValue>(\n () => ({\n map,\n places,\n groups,\n isLoading,\n isError,\n isEditable,\n selectedPlaceId,\n setSelectedPlaceId,\n selectedGroupId,\n setSelectedGroupId,\n infoWindowPlaceId,\n setInfoWindowPlaceId,\n hideAllInfoWindows,\n handleMarkerClick,\n handleListClick,\n }),\n [\n map,\n places,\n groups,\n isLoading,\n isError,\n isEditable,\n selectedPlaceId,\n selectedGroupId,\n infoWindowPlaceId,\n hideAllInfoWindows,\n handleMarkerClick,\n handleListClick,\n ],\n )\n\n return <MapContext.Provider value={value}>{children}</MapContext.Provider>\n}\n","import { randomIntBetween } from \"./number\"\n\n/**\n * Pastel color utilities for coloring map markers, group pills, and seeds\n * derived from UUIDs.\n *\n * All functions are pure and deterministic when given a seed.\n */\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return `#${r.toString(16).padStart(2, \"0\")}${g.toString(16).padStart(2, \"0\")}${b.toString(16).padStart(2, \"0\")}`\n}\n\nfunction hueToRGB(m: number, n: number, o: number): number {\n if (o < 0) o += 1\n if (o > 1) o -= 1\n if (o < 1 / 6) return m + (n - m) * 6 * o\n if (o < 1 / 2) return n\n if (o < 2 / 3) return m + (n - m) * (2 / 3 - o) * 6\n return m\n}\n\n// HSL to RGB formula adapted from:\n// https://gist.github.com/mjackson/5311256\n// https://codepen.io/pliu/pen/BLEKwr\nfunction hslToRgb(H: number, S: number, L: number): [number, number, number] {\n const h = H / 360\n const s = S / 100\n const l = L / 100\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n const p = 2 * l - q\n\n const r = hueToRGB(p, q, h + 1 / 3)\n const g = hueToRGB(p, q, h)\n const b = hueToRGB(p, q, h - 1 / 3)\n\n const rd = (a: number) => Math.floor(Math.max(Math.min(a * 256, 255), 0))\n\n return [rd(r), rd(g), rd(b)]\n}\n\nexport const H_MIN = 50 // 0-360\nexport const H_MAX = 300 // 0-360\nexport const L_MIN = 90 // 75-95\nexport const L_MAX = 95 // 75-95\nexport const seedOffset = 50\n\nexport function randomPastelHsl(): [number, number, number] {\n const h = randomIntBetween(H_MIN, H_MAX)\n const s = 100\n const l = randomIntBetween(L_MIN, L_MAX)\n return [h, s, l]\n}\n\nexport function randomPastelHslString(): string {\n const [h, s, l] = randomPastelHsl()\n return `hsl(${h}, ${s}%, ${l}%)`\n}\n\nexport function randomPastelRgb(): [number, number, number] {\n const [h, s, l] = randomPastelHsl()\n return hslToRgb(h, s, l)\n}\n\nexport function randomPastelHex(): string {\n const [r, g, b] = randomPastelRgb()\n return rgbToHex(r, g, b)\n}\n\n/**\n * Deterministically map a non-negative integer seed to a hue in [min, max).\n *\n * The algorithm uses a base-2 level decomposition so that successive seeds\n * (0, 1, 2, 3, ...) stay maximally spaced within the hue range.\n */\nexport function seededHslH(seed: number, min: number = H_MIN, max: number = H_MAX): number {\n if (seed < 0) throw new Error(\"Seed must be a positive number\")\n\n if (seed === 0) return min + seedOffset\n\n const segmentsAtLevel = 2 ** Math.floor(Math.log2(seed))\n const indexWithinLevel = seed - segmentsAtLevel\n const range = max - min\n\n const hueFromMin = (indexWithinLevel + 0.5) * (range / segmentsAtLevel)\n return ((hueFromMin + seedOffset) % range) + min\n}\n\nexport function seededPastelHsl(seed: number): [number, number, number] {\n const h = seededHslH(seed)\n const s = 100\n const l = 87\n return [h, s, l]\n}\n\nexport function seededPastelRgb(seed: number): [number, number, number] {\n const [h, s, l] = seededPastelHsl(seed)\n return hslToRgb(h, s, l)\n}\n\nexport function seededPastelRgbA(seed: number, alpha: number): string {\n const [r, g, b] = seededPastelRgb(seed)\n return `rgba(${r}, ${g}, ${b}, ${alpha})`\n}\n\nexport function seededPastelHex(seed: number): string {\n const [r, g, b] = seededPastelRgb(seed)\n return rgbToHex(r, g, b)\n}\n\nexport interface PastelDocColors {\n bgColor: string\n txtColor: string\n primaryColor: string\n focusFocus: string\n}\n\nexport function randomPastelHslDocColors(): PastelDocColors {\n const [h, s, l] = randomPastelHsl()\n return {\n bgColor: `hsl(${h}, ${s}%, ${l}%)`,\n txtColor: `hsl(${h}, ${s}%, 5%)`,\n primaryColor: `hsl(${h}, ${s}%, 98%)`,\n focusFocus: `hsl(${h}, ${s}%, 95%)`,\n }\n}\n\nconst DARK_COLOR = \"#000000\"\nconst LIGHT_COLOR = \"#f2f2f2\"\n\n/**\n * Deterministic pastel hue derived from a UUID by interpreting its hex as a\n * big integer mod 360.\n */\nexport function uuidPastelHsl(uuid: string): [number, number, number] {\n const val = BigInt(\"0x\" + uuid.replace(/-/g, \"\"))\n const h = val % BigInt(360)\n const s = 100\n const l = 87\n return [Number(h), s, l]\n}\n\nexport function uuidPastelRgb(uuid: string): [number, number, number] {\n const [h, s, l] = uuidPastelHsl(uuid)\n return hslToRgb(h, s, l)\n}\n\nexport function uuidPastelRgbA(uuid: string, alpha: number): string {\n const [r, g, b] = uuidPastelRgb(uuid)\n return `rgba(${r}, ${g}, ${b}, ${alpha})`\n}\n\nexport function uuidPastelHex(uuid: string): string {\n const [r, g, b] = uuidPastelRgb(uuid)\n return rgbToHex(r, g, b)\n}\n\n/**\n * Choose a readable text color (dark or light) for a given hex background,\n * using the standard ITU-R BT.601 luminance coefficients.\n */\nexport function getTextColor(bgColor: string): string {\n if (bgColor.length !== 7) return DARK_COLOR\n const color = bgColor.startsWith(\"#\") ? bgColor.substring(1, 7) : bgColor\n const r = parseInt(color.substring(0, 2), 16)\n const g = parseInt(color.substring(2, 4), 16)\n const b = parseInt(color.substring(4, 6), 16)\n return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? DARK_COLOR : LIGHT_COLOR\n}\n","/**\n * Capitalize the first character of a string, passing through null/undefined.\n */\nexport const capitalizeFirstLetter = <T extends string | null | undefined>(s: T): T =>\n (s ? s.charAt(0).toUpperCase() + s.slice(1) : s) as T\n\n/**\n * Map the values of a plain object while preserving keys.\n */\nexport function objectMap<T>(\n obj: Record<string, T>,\n func: (key: string, val: T) => T,\n): Record<string, T> {\n return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, func(k, v)]))\n}\n\n/**\n * Normalize freeform list input for a textarea that expects one item per line.\n *\n * - Leading newline is swallowed so users cannot start a list with blank lines.\n * - Internal blank lines are collapsed.\n * - A trailing newline is preserved so the user can keep typing the next line.\n */\nexport const enforceListTextInput = (val: string): string => {\n if (val === \"\\n\") return \"\"\n const inner = val\n .trim()\n .split(\"\\n\\n\")\n .map((line) => line.trim())\n .filter((line) => line !== \"\")\n .join(\"\\n\\n\")\n return inner + (val.endsWith(\"\\n\") ? \"\\n\" : \"\")\n}\n\n/**\n * Alias of {@link enforceListTextInput} kept for semantic clarity in call sites.\n */\nexport const preventMultipleBlankLines = enforceListTextInput\n","\"use client\"\nimport {\n AdvancedMarker,\n InfoWindow,\n Pin,\n useAdvancedMarkerRef,\n} from \"@vis.gl/react-google-maps\"\nimport type { ReactNode } from \"react\"\nimport type { Place, PlaceGroup } from \"../types/domain\"\nimport { getTextColor, uuidPastelHex } from \"../utils/color\"\nimport { capitalizeFirstLetter } from \"../utils/text\"\nimport { useMap } from \"./map-context\"\n\n/**\n * Return the display name for a place, falling back through the common\n * sources: provider-resolved `name`, the raw `query` used at creation time,\n * the first segment of `address`, and finally an empty string.\n */\nexport function getPlaceDisplayName(place: Place | null | undefined): string {\n if (!place) return \"\"\n const raw = place.name ?? place.query ?? place.address?.split(\",\")[0] ?? \"\"\n return capitalizeFirstLetter(raw) ?? \"\"\n}\n\nexport interface PlaceMarkerProps {\n place: Place\n /**\n * Custom info window content. Receives the place and its group (if any).\n * When omitted, a basic \"name + description\" block is rendered.\n */\n renderContent?: (place: Place, group: PlaceGroup | null) => ReactNode\n}\n\n/**\n * A single marker + info window pair driven by a {@link Place}. Reads\n * selection / info window state from {@link useMap}.\n *\n * This component renders nothing if the place is missing coordinates, has a\n * geocoding error, or belongs to a hidden group — consumers don't need to\n * filter before passing it in.\n */\nexport function PlaceMarker({ place, renderContent }: PlaceMarkerProps) {\n const [advMarkerRef, advMarker] = useAdvancedMarkerRef()\n const { groups, selectedPlaceId, infoWindowPlaceId, setInfoWindowPlaceId, handleMarkerClick } =\n useMap()\n\n const group = groups.find((g) => g.id === place.groupId) ?? null\n const isSelected = place.id === selectedPlaceId\n const isInfoWindowOpen = place.id === infoWindowPlaceId\n\n if (group?.hidden) return null\n if (place.hidden) return null\n if (place.lat == null || place.lon == null || place.error) return null\n\n const bgColor = group?.color ?? (group ? uuidPastelHex(group.id) : \"red\")\n const foreColor = getTextColor(bgColor)\n\n const displayName = getPlaceDisplayName(place)\n\n return (\n <AdvancedMarker\n position={{ lat: place.lat, lng: place.lon }}\n ref={advMarkerRef}\n onClick={() => handleMarkerClick(place.id)}\n zIndex={isSelected ? 20 : 10}\n draggable={false}\n >\n <Pin background={bgColor} glyphColor={foreColor} borderColor=\"#000\" />\n {isInfoWindowOpen && (\n <InfoWindow\n anchor={advMarker}\n onCloseClick={() => setInfoWindowPlaceId(null)}\n zIndex={isSelected ? 20 : 10}\n >\n {renderContent ? (\n renderContent(place, group)\n ) : (\n <div style={{ maxWidth: 240, display: \"flex\", flexDirection: \"column\", gap: 4 }}>\n <strong>{displayName}</strong>\n {place.description ? (\n <p style={{ fontSize: 12, margin: 0 }}>\n {capitalizeFirstLetter(place.description)}\n </p>\n ) : null}\n </div>\n )}\n </InfoWindow>\n )}\n </AdvancedMarker>\n )\n}\n","\"use client\"\nimport { APIProvider, Map as GoogleMap, useMap as useGoogleMap } from \"@vis.gl/react-google-maps\"\nimport {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\"\nimport type { Place, PlaceGroup } from \"../types/domain\"\nimport {\n autofitCenterZoom,\n DEFAULT_CENTER_ZOOM,\n type CenterZoomAutofit,\n type ContainerDims,\n type MappablePlace,\n} from \"./autofit\"\nimport { useMap } from \"./map-context\"\nimport { PlaceMarker } from \"./place-marker\"\n\n/**\n * Track the size of a container element via ResizeObserver. Avoids the need\n * to hoist sizing state up to the consumer.\n */\nfunction useContainerDims(): [React.RefObject<HTMLDivElement>, ContainerDims] {\n const ref = useRef<HTMLDivElement>(null)\n const [dims, setDims] = useState<ContainerDims>({ width: 0, height: 0 })\n\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const update = () => {\n const rect = el.getBoundingClientRect()\n setDims({ width: rect.width, height: rect.height })\n }\n update()\n const observer = new ResizeObserver(update)\n observer.observe(el)\n return () => observer.disconnect()\n }, [])\n\n return [ref, dims]\n}\n\n/**\n * Internal: re-runs autofit whenever places or container dims change.\n */\nfunction Autofitter({\n dims,\n setCenterZoom,\n}: {\n dims: ContainerDims\n setCenterZoom: (c: CenterZoomAutofit) => void\n}) {\n const { places } = useMap()\n const googleMap = useGoogleMap()\n\n const mappable = useMemo<MappablePlace[]>(() => {\n return places\n .filter(\n (p): p is Place & { lat: number; lon: number } =>\n p.lat != null && p.lon != null && !p.error && !p.hidden,\n )\n .map((p) => ({ lat: p.lat, lon: p.lon }))\n }, [places])\n\n useEffect(() => {\n if (!googleMap || dims.width === 0 || dims.height === 0) return\n const cz = autofitCenterZoom(dims, mappable)\n setCenterZoom(cz)\n googleMap.setCenter(cz.center)\n googleMap.setZoom(cz.zoom)\n }, [googleMap, dims, mappable, setCenterZoom])\n\n return null\n}\n\nexport interface GoogleMapsViewerProps {\n /** Google Maps JS API key. */\n apiKey: string\n /**\n * Optional Google Map ID (from your Google Cloud console). Required if\n * you want custom styles or {@link AdvancedMarker} to work correctly.\n */\n mapId?: string\n /** Starting center if no places are loaded. Defaults to Washington, DC. */\n defaultCenter?: google.maps.LatLngLiteral\n /** Starting zoom if no places are loaded. Defaults to 4. */\n defaultZoom?: number\n /** Applied to the outer container `div`. */\n className?: string\n /** Applied to the outer container `div`. Defaults to full width/height. */\n style?: CSSProperties\n /** Content rendered while the Google Maps SDK is loading. */\n loadingContent?: ReactNode\n /**\n * Forwarded to `@vis.gl/react-google-maps`'s `<Map>`. Use this to enable\n * satellite/terrain views.\n */\n mapTypeId?: google.maps.MapTypeId\n /** Control visibility flags forwarded to `<Map>`. */\n streetViewControl?: boolean\n /** Control visibility flags forwarded to `<Map>`. */\n zoomControl?: boolean\n /** Control visibility flags forwarded to `<Map>`. */\n mapTypeControl?: boolean\n /** Control visibility flags forwarded to `<Map>`. */\n fullscreenControl?: boolean\n /**\n * Render prop for customizing the info window body for every marker.\n * Forwarded to {@link PlaceMarker}. When omitted, a basic default is used.\n */\n renderMarkerContent?: (place: Place, group: PlaceGroup | null) => ReactNode\n}\n\n/**\n * Data-driven Google Maps viewer. Must be rendered inside a\n * {@link MapProvider}.\n *\n * Pans and zooms automatically whenever the list of places in context\n * changes (via a ResizeObserver-measured container and the pure\n * {@link autofitCenterZoom} helper).\n */\nexport function GoogleMapsViewer({\n apiKey,\n mapId,\n defaultCenter,\n defaultZoom,\n className,\n style,\n loadingContent = null,\n mapTypeId,\n streetViewControl = false,\n zoomControl = false,\n mapTypeControl = false,\n fullscreenControl = false,\n renderMarkerContent,\n}: GoogleMapsViewerProps) {\n const [ready, setReady] = useState(false)\n const [containerRef, dims] = useContainerDims()\n const [centerZoom, setCenterZoom] = useState<CenterZoomAutofit>(() => ({\n center: defaultCenter ?? DEFAULT_CENTER_ZOOM.center,\n zoom: defaultZoom ?? DEFAULT_CENTER_ZOOM.zoom,\n }))\n\n const { places } = useMap()\n\n const containerStyle: CSSProperties = {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n ...style,\n }\n\n return (\n <div ref={containerRef} className={className} style={containerStyle}>\n <APIProvider apiKey={apiKey} libraries={[\"maps\", \"core\", \"marker\"]} onLoad={() => setReady(true)}>\n {ready ? (\n <GoogleMap\n center={centerZoom.center}\n zoom={centerZoom.zoom}\n streetViewControl={streetViewControl}\n fullscreenControl={fullscreenControl}\n mapTypeControl={mapTypeControl}\n zoomControl={zoomControl}\n isFractionalZoomEnabled={true}\n mapTypeId={mapTypeId}\n mapId={mapId}\n style={{ width: \"100%\", height: \"100%\" }}\n >\n {places.map((p) => (\n <PlaceMarker\n key={p.id + \"-marker\"}\n place={p}\n renderContent={renderMarkerContent}\n />\n ))}\n <Autofitter dims={dims} setCenterZoom={setCenterZoom} />\n </GoogleMap>\n ) : (\n loadingContent\n )}\n </APIProvider>\n </div>\n )\n}\n","import type { ProviderAutocompletePlace } from \"../types/schemas\"\n\n/**\n * Fields we request from the Google Places `getDetails` call. Kept to the\n * minimum needed to build a {@link ProviderAutocompletePlace}.\n */\nconst GOOGLE_PLACE_RESULT_FIELDS: Array<keyof google.maps.places.PlaceResult> = [\n \"name\",\n \"geometry\",\n \"formatted_address\",\n \"place_id\",\n]\n\n/**\n * Given a Google Autocomplete prediction (from an input-backed autocomplete\n * session), fetch the full place details from the browser-side Places API\n * and normalize into a {@link ProviderAutocompletePlace}.\n *\n * This function requires the Google Maps JS API with the `places` library to\n * be loaded in the browser (i.e. `google.maps.places.PlacesService` must\n * exist). Typical usage is in a React component that also renders\n * {@link GoogleMapsViewer}, which itself loads the API via `<APIProvider>`.\n *\n * Rejects if the Places call fails or the result lacks geometry/address.\n */\nexport async function getPlaceDetailsFromGoogleAutocompleteSuggestion(\n query: string,\n sug: google.maps.places.AutocompletePrediction,\n): Promise<ProviderAutocompletePlace> {\n const service = new google.maps.places.PlacesService(document.createElement(\"div\"))\n return new Promise((resolve, reject) => {\n service.getDetails(\n {\n placeId: sug.place_id,\n fields: GOOGLE_PLACE_RESULT_FIELDS,\n },\n (place, status) => {\n if (status !== google.maps.places.PlacesServiceStatus.OK) {\n return reject(new Error(`Places service status: ${status}`))\n }\n if (!place?.geometry?.location || !place?.formatted_address) {\n return reject(new Error(\"No place or invalid geometry/address\"))\n }\n resolve({\n query,\n description: sug.description,\n provider: \"google\",\n providerId: sug.place_id,\n name: place.name ?? sug.structured_formatting.main_text,\n address: place.formatted_address,\n lat: place.geometry.location.lat() ?? 0,\n lng: place.geometry.location.lng() ?? 0,\n })\n },\n )\n })\n}\n"]}