@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.
@@ -5,6 +5,7 @@ export interface ObjectMapProps {
5
5
  dataSource?: DataSource;
6
6
  className?: string;
7
7
  onMarkerClick?: (record: any) => void;
8
+ onRowClick?: (record: any) => void;
8
9
  onEdit?: (record: any) => void;
9
10
  onDelete?: (record: any) => void;
10
11
  }
@@ -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;AAI/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,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,CAyP9C,CAAC;AAEF,eAAe,SAAS,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.5.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.9.2",
27
+ "@objectstack/spec": "^3.0.2",
28
28
  "lucide-react": "^0.563.0",
29
- "maplibre-gl": "^5.17.0",
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.5.0",
33
- "@object-ui/core": "0.5.0",
34
- "@object-ui/react": "0.5.0",
35
- "@object-ui/types": "0.5.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": "^19.2.10",
43
- "@types/react-dom": "^19.2.3",
44
- "@vitejs/plugin-react": "^5.1.3",
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).value)) {
285
- items = (result as any).value;
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
- return data
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) return null;
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
- console.warn(`Invalid coordinates for marker ${index}: [${lat}, ${lng}]`);
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 (!markers.length) {
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 = markers.map(m => m.coordinates[0]);
372
- const lats = markers.map(m => m.coordinates[1]);
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
- }, [markers, mapConfig]);
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
- <div className="relative border rounded-lg overflow-hidden bg-muted" style={{ height: '600px', width: '100%' }}>
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
- {markers.map(marker => (
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: 'plugin',
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' },