@object-ui/plugin-map 0.5.0 → 3.0.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/.turbo/turbo-build.log +8 -25
- package/CHANGELOG.md +34 -0
- package/dist/index.css +1 -1
- package/dist/index.js +1068 -1021
- package/dist/index.umd.cjs +83 -83
- package/dist/{maplibre-gl-CNsW26De.js → maplibre-gl-DSpYxujd.js} +7994 -7981
- package/dist/src/ObjectMap.d.ts +1 -0
- package/dist/src/ObjectMap.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/ObjectMap.tsx +79 -15
- package/src/index.tsx +11 -1
package/dist/src/ObjectMap.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAY,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAM/E,OAAO,kCAAkC,CAAC;AAY1C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAsKD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAsT9C,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-map",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Map visualization plugin for Object UI",
|
|
@@ -24,24 +24,24 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@objectstack/spec": "^0.
|
|
27
|
+
"@objectstack/spec": "^3.0.2",
|
|
28
28
|
"lucide-react": "^0.563.0",
|
|
29
|
-
"maplibre-gl": "^5.
|
|
29
|
+
"maplibre-gl": "^5.18.0",
|
|
30
30
|
"react-map-gl": "^8.1.0",
|
|
31
31
|
"zod": "^4.3.6",
|
|
32
|
-
"@object-ui/components": "0.
|
|
33
|
-
"@object-ui/
|
|
34
|
-
"@object-ui/
|
|
35
|
-
"@object-ui/types": "0.
|
|
32
|
+
"@object-ui/components": "3.0.0",
|
|
33
|
+
"@object-ui/react": "3.0.0",
|
|
34
|
+
"@object-ui/core": "3.0.0",
|
|
35
|
+
"@object-ui/types": "3.0.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": "^18.0.0 || ^19.0.0",
|
|
39
39
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/react": "
|
|
43
|
-
"@types/react-dom": "
|
|
44
|
-
"@vitejs/plugin-react": "^5.1.
|
|
42
|
+
"@types/react": "19.2.13",
|
|
43
|
+
"@types/react-dom": "19.2.3",
|
|
44
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"vite": "^7.3.1",
|
|
47
47
|
"vite-plugin-dts": "^4.5.4"
|
package/src/ObjectMap.tsx
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
import React, { useEffect, useState, useMemo } from 'react';
|
|
24
24
|
import type { ObjectGridSchema, DataSource, ViewData } from '@object-ui/types';
|
|
25
|
+
import { useNavigationOverlay } from '@object-ui/react';
|
|
26
|
+
import { NavigationOverlay } from '@object-ui/components';
|
|
25
27
|
import { z } from 'zod';
|
|
26
28
|
import Map, { NavigationControl, Marker, Popup } from 'react-map-gl/maplibre';
|
|
27
29
|
import maplibregl from 'maplibre-gl';
|
|
@@ -42,6 +44,7 @@ export interface ObjectMapProps {
|
|
|
42
44
|
dataSource?: DataSource;
|
|
43
45
|
className?: string;
|
|
44
46
|
onMarkerClick?: (record: any) => void;
|
|
47
|
+
onRowClick?: (record: any) => void;
|
|
45
48
|
onEdit?: (record: any) => void;
|
|
46
49
|
onDelete?: (record: any) => void;
|
|
47
50
|
}
|
|
@@ -215,6 +218,9 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
215
218
|
dataSource,
|
|
216
219
|
className,
|
|
217
220
|
onMarkerClick,
|
|
221
|
+
onRowClick,
|
|
222
|
+
onEdit,
|
|
223
|
+
onDelete,
|
|
218
224
|
...rest
|
|
219
225
|
}) => {
|
|
220
226
|
const [data, setData] = useState<any[]>([]);
|
|
@@ -222,6 +228,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
222
228
|
const [error, setError] = useState<Error | null>(null);
|
|
223
229
|
const [objectSchema, setObjectSchema] = useState<any>(null);
|
|
224
230
|
const [selectedMarkerId, setSelectedMarkerId] = useState<string | null>(null);
|
|
231
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
225
232
|
|
|
226
233
|
const rawDataConfig = getDataConfig(schema);
|
|
227
234
|
// Memoize dataConfig using deep comparison to prevent infinite loops
|
|
@@ -281,8 +288,8 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
281
288
|
} else if (result && typeof result === 'object') {
|
|
282
289
|
if (Array.isArray((result as any).data)) {
|
|
283
290
|
items = (result as any).data;
|
|
284
|
-
} else if (Array.isArray((result as any).
|
|
285
|
-
items = (result as any).
|
|
291
|
+
} else if (Array.isArray((result as any).records)) {
|
|
292
|
+
items = (result as any).records;
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
setData(items);
|
|
@@ -326,19 +333,23 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
326
333
|
}, [schema.objectName, dataSource, hasInlineData, dataConfig]);
|
|
327
334
|
|
|
328
335
|
// Transform data to map markers
|
|
329
|
-
const markers = useMemo(() => {
|
|
330
|
-
|
|
336
|
+
const { markers, invalidCount } = useMemo(() => {
|
|
337
|
+
let invalid = 0;
|
|
338
|
+
const validMarkers = data
|
|
331
339
|
.map((record, index) => {
|
|
332
340
|
const coordinates = extractCoordinates(record, mapConfig);
|
|
333
|
-
if (!coordinates)
|
|
341
|
+
if (!coordinates) {
|
|
342
|
+
invalid++;
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
334
345
|
|
|
335
346
|
const title = mapConfig.titleField ? record[mapConfig.titleField] : 'Marker';
|
|
336
347
|
const description = mapConfig.descriptionField ? record[mapConfig.descriptionField] : undefined;
|
|
337
348
|
|
|
338
349
|
// Ensure lat/lng are within valid ranges
|
|
339
350
|
const [lat, lng] = coordinates;
|
|
340
|
-
if (lat < -90 || lat > 90 || lng < -180 || lng > 180) {
|
|
341
|
-
|
|
351
|
+
if (!isFinite(lat) || !isFinite(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) {
|
|
352
|
+
invalid++;
|
|
342
353
|
return null;
|
|
343
354
|
}
|
|
344
355
|
|
|
@@ -351,15 +362,32 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
351
362
|
};
|
|
352
363
|
})
|
|
353
364
|
.filter((marker): marker is NonNullable<typeof marker> => marker !== null);
|
|
365
|
+
|
|
366
|
+
return { markers: validMarkers, invalidCount: invalid };
|
|
354
367
|
}, [data, mapConfig]);
|
|
355
368
|
|
|
356
369
|
const selectedMarker = useMemo(() =>
|
|
357
370
|
markers.find(m => m.id === selectedMarkerId),
|
|
358
371
|
[markers, selectedMarkerId]);
|
|
359
372
|
|
|
373
|
+
const navigation = useNavigationOverlay({
|
|
374
|
+
navigation: (schema as any).navigation,
|
|
375
|
+
objectName: schema.objectName,
|
|
376
|
+
onRowClick,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const filteredMarkers = useMemo(() => {
|
|
380
|
+
if (!searchQuery.trim()) return markers;
|
|
381
|
+
const q = searchQuery.toLowerCase();
|
|
382
|
+
return markers.filter(m =>
|
|
383
|
+
m.title?.toLowerCase().includes(q) ||
|
|
384
|
+
m.description?.toLowerCase().includes(q)
|
|
385
|
+
);
|
|
386
|
+
}, [markers, searchQuery]);
|
|
387
|
+
|
|
360
388
|
// Calculate map bounds
|
|
361
389
|
const initialViewState = useMemo(() => {
|
|
362
|
-
if (!
|
|
390
|
+
if (!filteredMarkers.length) {
|
|
363
391
|
return {
|
|
364
392
|
longitude: mapConfig.center?.[1] || 0,
|
|
365
393
|
latitude: mapConfig.center?.[0] || 0,
|
|
@@ -368,8 +396,8 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
368
396
|
}
|
|
369
397
|
|
|
370
398
|
// Simple bounds calculation
|
|
371
|
-
const lngs =
|
|
372
|
-
const lats =
|
|
399
|
+
const lngs = filteredMarkers.map(m => m.coordinates[0]);
|
|
400
|
+
const lats = filteredMarkers.map(m => m.coordinates[1]);
|
|
373
401
|
|
|
374
402
|
const minLng = Math.min(...lngs);
|
|
375
403
|
const maxLng = Math.max(...lngs);
|
|
@@ -381,7 +409,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
381
409
|
latitude: (minLat + maxLat) / 2,
|
|
382
410
|
zoom: mapConfig.zoom || 3, // Auto-zoom logic could be improved here
|
|
383
411
|
};
|
|
384
|
-
}, [
|
|
412
|
+
}, [filteredMarkers, mapConfig]);
|
|
385
413
|
|
|
386
414
|
if (loading) {
|
|
387
415
|
return (
|
|
@@ -405,15 +433,34 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
405
433
|
|
|
406
434
|
return (
|
|
407
435
|
<div className={className}>
|
|
408
|
-
|
|
436
|
+
{invalidCount > 0 && (
|
|
437
|
+
<div className="mb-2 p-2 text-sm text-yellow-800 bg-yellow-50 border border-yellow-200 rounded">
|
|
438
|
+
{`${invalidCount} record${invalidCount !== 1 ? 's' : ''} with missing or invalid coordinates excluded from the map.`}
|
|
439
|
+
</div>
|
|
440
|
+
)}
|
|
441
|
+
{markers.length > 0 && (
|
|
442
|
+
<div className="mb-2">
|
|
443
|
+
<input
|
|
444
|
+
type="text"
|
|
445
|
+
value={searchQuery}
|
|
446
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
447
|
+
placeholder="Search locations…"
|
|
448
|
+
className="w-full px-3 py-2 text-sm border rounded-md bg-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
449
|
+
/>
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
452
|
+
<div className="relative border rounded-lg overflow-hidden bg-muted h-[300px] sm:h-[400px] md:h-[500px] lg:h-[600px] w-full">
|
|
409
453
|
<Map
|
|
410
454
|
initialViewState={initialViewState}
|
|
411
455
|
style={{ width: '100%', height: '100%' }}
|
|
412
456
|
mapStyle="https://demotiles.maplibre.org/style.json"
|
|
457
|
+
touchZoomRotate={true}
|
|
458
|
+
dragRotate={true}
|
|
459
|
+
touchPitch={true}
|
|
413
460
|
>
|
|
414
|
-
<NavigationControl position="top-right" />
|
|
461
|
+
<NavigationControl position="top-right" showCompass={true} showZoom={true} />
|
|
415
462
|
|
|
416
|
-
{
|
|
463
|
+
{filteredMarkers.map(marker => (
|
|
417
464
|
<Marker
|
|
418
465
|
key={marker.id}
|
|
419
466
|
longitude={marker.coordinates[0]}
|
|
@@ -422,6 +469,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
422
469
|
onClick={(e) => {
|
|
423
470
|
e.originalEvent.stopPropagation();
|
|
424
471
|
setSelectedMarkerId(marker.id);
|
|
472
|
+
navigation.handleClick(marker.data);
|
|
425
473
|
onMarkerClick?.(marker.data);
|
|
426
474
|
}}
|
|
427
475
|
>
|
|
@@ -439,7 +487,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
439
487
|
onClose={() => setSelectedMarkerId(null)}
|
|
440
488
|
closeOnClick={false}
|
|
441
489
|
>
|
|
442
|
-
<div className="p-2 min-w-[200px]">
|
|
490
|
+
<div className="p-2 min-w-[150px] sm:min-w-[200px]">
|
|
443
491
|
<h3 className="font-bold text-sm mb-1">{selectedMarker.title}</h3>
|
|
444
492
|
{selectedMarker.description && (
|
|
445
493
|
<p className="text-xs text-muted-foreground">{selectedMarker.description}</p>
|
|
@@ -457,6 +505,22 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
|
|
|
457
505
|
)}
|
|
458
506
|
</Map>
|
|
459
507
|
</div>
|
|
508
|
+
{navigation.isOverlay && (
|
|
509
|
+
<NavigationOverlay {...navigation} title="Location Details">
|
|
510
|
+
{(record) => (
|
|
511
|
+
<div className="space-y-3">
|
|
512
|
+
{Object.entries(record).map(([key, value]) => (
|
|
513
|
+
<div key={key} className="flex flex-col">
|
|
514
|
+
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
515
|
+
{key.replace(/_/g, ' ')}
|
|
516
|
+
</span>
|
|
517
|
+
<span className="text-sm">{String(value ?? '—')}</span>
|
|
518
|
+
</div>
|
|
519
|
+
))}
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
</NavigationOverlay>
|
|
523
|
+
)}
|
|
460
524
|
</div>
|
|
461
525
|
);
|
|
462
526
|
};
|
package/src/index.tsx
CHANGED
|
@@ -25,7 +25,17 @@ console.log('Registering object-map...');
|
|
|
25
25
|
ComponentRegistry.register('object-map', ObjectMapRenderer, {
|
|
26
26
|
namespace: 'plugin-map',
|
|
27
27
|
label: 'Object Map',
|
|
28
|
-
category: '
|
|
28
|
+
category: 'view',
|
|
29
|
+
inputs: [
|
|
30
|
+
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
31
|
+
{ name: 'map', type: 'object', label: 'Map Config', description: 'latitudeField, longitudeField, titleField' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
ComponentRegistry.register('map', ObjectMapRenderer, {
|
|
36
|
+
namespace: 'view',
|
|
37
|
+
label: 'Map View',
|
|
38
|
+
category: 'view',
|
|
29
39
|
inputs: [
|
|
30
40
|
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
31
41
|
{ name: 'map', type: 'object', label: 'Map Config', description: 'latitudeField, longitudeField, titleField' },
|