@object-ui/plugin-map 17.5.0 → 17.7.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/CHANGELOG.md +2193 -0
- package/README.md +237 -241
- package/dist/ObjectMap.d.ts +33 -4
- package/dist/ObjectMap.d.ts.map +1 -1
- package/dist/camera.d.ts +102 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/index.d.ts +39 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +350 -259
- package/dist/index.umd.cjs +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
# @object-ui/plugin-map
|
|
2
2
|
|
|
3
|
-
Map
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
3
|
+
Map view plugin for Object UI.
|
|
4
|
+
|
|
5
|
+
Renders the records of an **ObjectQL query** as markers on a MapLibre map: every
|
|
6
|
+
marker comes from a record's own coordinate fields, and the first paint frames the
|
|
7
|
+
records that were fetched. It is a *view over data* — there is no authored marker
|
|
8
|
+
list, and no pin you place by hand.
|
|
9
|
+
|
|
10
|
+
Importing the package registers one component type on the `ComponentRegistry`:
|
|
11
|
+
`object-map`, the object-bound renderer. Inside an `ObjectView`, a stored `map`
|
|
12
|
+
view is compiled to an `object-map` node, so a saved map view ends at the same
|
|
13
|
+
component.
|
|
14
|
+
|
|
15
|
+
> **The bare `map` node type key is retired** (objectui#10393, following the
|
|
16
|
+
> objectui#8008 ruling that retired `gantt`). The registry used to accept a node
|
|
17
|
+
> authored `"type": "map"` (and its namespaced twin `view:map`) while the
|
|
18
|
+
> published declaration refused it — `ObjectMapSchema.type` is the literal
|
|
19
|
+
> `'object-map'` and no schema arm names `map` — so a validated document could not
|
|
20
|
+
> use the key the registry took. Write `"type": "object-map"`.
|
|
21
|
+
>
|
|
22
|
+
> ⚠️ The **stored view type** `"map"` — what a saved `listViews[].type` or
|
|
23
|
+
> `defaultViewType` holds — is a **different layer and is unchanged**. Do not
|
|
24
|
+
> rewrite it: `ObjectView` maps a stored `map` view onto the `object-map` node
|
|
25
|
+
> type, so no saved view moves.
|
|
13
26
|
|
|
14
27
|
## Installation
|
|
15
28
|
|
|
@@ -17,263 +30,246 @@ Map visualization plugin for Object UI - Display geographic data with interactiv
|
|
|
17
30
|
pnpm add @object-ui/plugin-map
|
|
18
31
|
```
|
|
19
32
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
### Automatic Registration (Side-Effect Import)
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// In your app entry point (e.g., App.tsx or main.tsx)
|
|
26
|
-
import '@object-ui/plugin-map';
|
|
27
|
-
|
|
28
|
-
// Now you can use map types in your schemas
|
|
29
|
-
const schema = {
|
|
30
|
-
type: 'map',
|
|
31
|
-
center: { lat: 37.7749, lng: -122.4194 },
|
|
32
|
-
zoom: 12,
|
|
33
|
-
markers: [
|
|
34
|
-
{ lat: 37.7749, lng: -122.4194, label: 'San Francisco' }
|
|
35
|
-
]
|
|
36
|
-
};
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Manual Registration
|
|
33
|
+
## Requires a bundler — plain Node cannot import this package
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
import
|
|
43
|
-
|
|
35
|
+
`ObjectMap` imports MapLibre's stylesheet at module scope
|
|
36
|
+
(`import 'maplibre-gl/dist/maplibre-gl.css'`), and Node has no loader for `.css` at all.
|
|
37
|
+
Importing the published entry from plain Node ESM — no bundler, no loader hooks — therefore
|
|
38
|
+
resolves and then fails during evaluation:
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
40
|
+
```text
|
|
41
|
+
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css"
|
|
42
|
+
for .../maplibre-gl/dist/maplibre-gl.css
|
|
49
43
|
```
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
**This is a supported-configuration statement, not a bug to report.** Unbundled Node
|
|
46
|
+
consumption is not supported for style-carrying plugin packages. It was ruled that way on
|
|
47
|
+
[objectui#5384](https://github.com/objectstack-ai/objectui/issues/5384) — deliberately, over
|
|
48
|
+
the alternative of moving the stylesheet out of module scope — because a MapLibre canvas
|
|
49
|
+
without its stylesheet is not a map, and no unbundled-Node consumer exists to serve.
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
Consume it through a host that handles CSS imports, which every supported host does: Vite,
|
|
52
|
+
webpack, or Next with the package listed in `transpilePackages`. If you have a real need to
|
|
53
|
+
import it under plain Node — SSR with no bundler, a Node-side script — please open an issue.
|
|
54
|
+
That reopens the question as a design decision rather than a defect, and the shape of your
|
|
55
|
+
consumer is the missing input.
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
## Usage
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
type: 'map',
|
|
60
|
-
center: { lat: number, lng: number },
|
|
61
|
-
zoom?: number, // Default: 10
|
|
62
|
-
markers?: MapMarker[],
|
|
63
|
-
layers?: MapLayer[],
|
|
64
|
-
height?: number | string,
|
|
65
|
-
onMarkerClick?: (marker) => void,
|
|
66
|
-
className?: string
|
|
67
|
-
}
|
|
68
|
-
```
|
|
59
|
+
Registration is a side effect of the import. There is no manual-registration
|
|
60
|
+
export to iterate over — the import *is* the registration.
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
label?: string;
|
|
77
|
-
icon?: string; // Icon URL or name
|
|
78
|
-
popup?: string | ReactNode;
|
|
79
|
-
color?: string; // Marker color
|
|
80
|
-
}
|
|
81
|
-
```
|
|
62
|
+
An authored `object-map` node takes its props in its `properties` bag, whose
|
|
63
|
+
members are `@objectstack/spec`'s `ComponentPropsMap['object-map']` row.
|
|
64
|
+
`objectui validate` refuses a prop written flat on the node by name, naming its
|
|
65
|
+
bag member, as the spec's own page component does (objectui#10859).
|
|
66
|
+
`SchemaRenderer` hoists the bag onto the node before `ObjectMap` runs, so
|
|
67
|
+
`ObjectMapSchema` (`@object-ui/types`) is the node as the component reads it.
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
const schema = {
|
|
89
|
-
type: 'map',
|
|
90
|
-
center: { lat: 40.7128, lng: -74.0060 },
|
|
91
|
-
zoom: 13,
|
|
92
|
-
height: 500,
|
|
93
|
-
markers: [
|
|
94
|
-
{
|
|
95
|
-
lat: 40.7128,
|
|
96
|
-
lng: -74.0060,
|
|
97
|
-
label: 'New York City',
|
|
98
|
-
popup: 'The Big Apple'
|
|
99
|
-
}
|
|
100
|
-
]
|
|
101
|
-
};
|
|
102
|
-
```
|
|
69
|
+
```ts
|
|
70
|
+
import '@object-ui/plugin-map';
|
|
71
|
+
import type { ObjectMapBlockNode } from '@object-ui/types';
|
|
103
72
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
lng: -122.4194,
|
|
115
|
-
label: 'San Francisco',
|
|
116
|
-
color: 'red',
|
|
117
|
-
popup: 'Golden Gate Bridge'
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
lat: 37.8044,
|
|
121
|
-
lng: -122.2712,
|
|
122
|
-
label: 'Oakland',
|
|
123
|
-
color: 'blue',
|
|
124
|
-
popup: 'Port of Oakland'
|
|
73
|
+
// Object-bound: the markers are the records the query returns.
|
|
74
|
+
const schema: ObjectMapBlockNode = {
|
|
75
|
+
type: 'object-map',
|
|
76
|
+
properties: {
|
|
77
|
+
objectName: 'stores',
|
|
78
|
+
map: {
|
|
79
|
+
latitudeField: 'lat',
|
|
80
|
+
longitudeField: 'lng',
|
|
81
|
+
titleField: 'name',
|
|
82
|
+
descriptionField: 'address',
|
|
125
83
|
},
|
|
126
|
-
{
|
|
127
|
-
lat: 37.3382,
|
|
128
|
-
lng: -121.8863,
|
|
129
|
-
label: 'San Jose',
|
|
130
|
-
color: 'green',
|
|
131
|
-
popup: 'Silicon Valley'
|
|
132
|
-
}
|
|
133
|
-
]
|
|
134
|
-
};
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Interactive Map
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
const schema = {
|
|
141
|
-
type: 'map',
|
|
142
|
-
center: { lat: 51.5074, lng: -0.1278 },
|
|
143
|
-
zoom: 12,
|
|
144
|
-
markers: [/* markers */],
|
|
145
|
-
onMarkerClick: (marker) => {
|
|
146
|
-
console.log('Marker clicked:', marker);
|
|
147
|
-
// Show marker details
|
|
148
84
|
},
|
|
149
|
-
onMapClick: (coordinates) => {
|
|
150
|
-
console.log('Map clicked at:', coordinates);
|
|
151
|
-
// Add new marker
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Map with Data Binding
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
const schema = {
|
|
160
|
-
type: 'map',
|
|
161
|
-
center: { lat: 37.7749, lng: -122.4194 },
|
|
162
|
-
zoom: 10,
|
|
163
|
-
markers: '${data.locations.map(loc => ({ lat: loc.latitude, lng: loc.longitude, label: loc.name }))}',
|
|
164
|
-
onMarkerClick: (marker) => {
|
|
165
|
-
// Handle marker click
|
|
166
|
-
}
|
|
167
85
|
};
|
|
168
86
|
```
|
|
169
87
|
|
|
170
|
-
|
|
88
|
+
A literal record array instead of a query, with the same `map` block:
|
|
171
89
|
|
|
172
|
-
|
|
90
|
+
```ts
|
|
91
|
+
import type { ObjectMapBlockNode } from '@object-ui/types';
|
|
173
92
|
|
|
174
|
-
|
|
175
|
-
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
176
|
-
|
|
177
|
-
const dataSource = createObjectStackAdapter({
|
|
178
|
-
baseUrl: 'https://api.example.com',
|
|
179
|
-
token: 'your-auth-token'
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const schema = {
|
|
93
|
+
const schema: ObjectMapBlockNode = {
|
|
183
94
|
type: 'object-map',
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
zoom: 10
|
|
192
|
-
};
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Map Features
|
|
196
|
-
|
|
197
|
-
### Custom Markers
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
const schema = {
|
|
201
|
-
type: 'map',
|
|
202
|
-
markers: [
|
|
203
|
-
{
|
|
204
|
-
lat: 40.7128,
|
|
205
|
-
lng: -74.0060,
|
|
206
|
-
icon: '/custom-marker.png',
|
|
207
|
-
popup: {
|
|
208
|
-
type: 'card',
|
|
209
|
-
title: 'Location Details',
|
|
210
|
-
body: 'Custom popup content'
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
]
|
|
214
|
-
};
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Geolocation
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
const schema = {
|
|
221
|
-
type: 'map',
|
|
222
|
-
useGeolocation: true, // Center map on user's location
|
|
223
|
-
zoom: 15,
|
|
224
|
-
markers: []
|
|
225
|
-
};
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Map Layers
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
const schema = {
|
|
232
|
-
type: 'map',
|
|
233
|
-
center: { lat: 37.7749, lng: -122.4194 },
|
|
234
|
-
layers: [
|
|
235
|
-
{ type: 'heatmap', data: [/* heatmap data */] },
|
|
236
|
-
{ type: 'polygon', coordinates: [/* polygon coordinates */], color: 'rgba(255, 0, 0, 0.3)' }
|
|
237
|
-
]
|
|
95
|
+
properties: {
|
|
96
|
+
staticData: [
|
|
97
|
+
{ id: 1, name: 'San Francisco HQ', lat: 37.7749, lng: -122.4194 },
|
|
98
|
+
{ id: 2, name: 'Oakland Office', lat: 37.8044, lng: -122.2711 },
|
|
99
|
+
],
|
|
100
|
+
map: { latitudeField: 'lat', longitudeField: 'lng', titleField: 'name' },
|
|
101
|
+
},
|
|
238
102
|
};
|
|
239
103
|
```
|
|
240
104
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
105
|
+
`filter` and `sort` are the **query's** filter and order — they reach the data
|
|
106
|
+
source as `$filter` / `$orderby`, and the spec's per-element `dataSource` binding
|
|
107
|
+
is honoured as well.
|
|
108
|
+
|
|
109
|
+
**The provider does not change which query keys apply** (objectui#9061, the port
|
|
110
|
+
of objectui#8769). An authored `filter` and `sort` narrow and order the rows on
|
|
111
|
+
**every** provider, inline ones included — `staticData` and
|
|
112
|
+
`data: { provider: 'value', items }` both reach the same in-memory adapter the
|
|
113
|
+
other providers go through, so `filter` is evaluated with the same matcher. A
|
|
114
|
+
bare array under `data` is not a record source on this map (objectui#8348): the
|
|
115
|
+
ladder falls through to `staticData`, then `objectName`, so inline rows belong
|
|
116
|
+
under `staticData`. Before objectui#9061 the inline provider skipped that query
|
|
117
|
+
and plotted every authored row with an authored `filter` silently dropped. The
|
|
118
|
+
platform row ceiling (2,000 drawn rows, with a footnote naming both numbers — objectui#7210,
|
|
119
|
+
ruling a′) applies to inline rows too, and it is applied to the **filtered** set,
|
|
120
|
+
never to the raw one: a large inline array that a `filter` cuts below the ceiling
|
|
121
|
+
plots every matching row and shows no footnote.
|
|
122
|
+
|
|
123
|
+
⚠️ Two consequences of routing inline rows through the adapter. They reach the
|
|
124
|
+
map as that adapter's own deep copy rather than as the authored array's object
|
|
125
|
+
identities, so code comparing a record handed to `onMarkerClick` against the
|
|
126
|
+
authored array with `===` needs `id` equality instead; and the copy is a JSON
|
|
127
|
+
round-trip, so inline rows must be JSON-serializable.
|
|
128
|
+
|
|
129
|
+
## The `map` block
|
|
130
|
+
|
|
131
|
+
The declared configuration input. Every key is optional, and the block is
|
|
132
|
+
**closed** (objectui#5157): a key outside this table — a typo such as
|
|
133
|
+
`latitudeFieId` — is refused by `objectui validate` with an `unrecognized_keys`
|
|
134
|
+
issue at `properties.map` that names it (on a root node; a nested node reports under
|
|
135
|
+
`invalid_union` at `children`, with the key in the arm detail). At runtime
|
|
136
|
+
`ObjectMap` does not throw: it renders from the declared keys and warns
|
|
137
|
+
`[ObjectMap] Invalid map configuration` in the console, naming the same key; the
|
|
138
|
+
undeclared key is never read. A typo that leaves the block with no coordinate
|
|
139
|
+
binding, as `latitudeFieId` does, therefore draws the "Map configuration
|
|
140
|
+
required" refusal below rather than a map.
|
|
141
|
+
|
|
142
|
+
| Key | Description |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `latitudeField` | Record field holding the latitude. Needs `longitudeField` alongside it; both values must be numbers. |
|
|
145
|
+
| `longitudeField` | Record field holding the longitude. |
|
|
146
|
+
| `locationField` | Single field holding both coordinates — see the formats below. Used when the lat/lng pair yields nothing. |
|
|
147
|
+
| `titleField` | Field shown as the marker title. Omitted, the title is resolved by the object's own record-title precedence (`@object-ui/core`'s `getRecordDisplayName`, ADR-0079): the declared `nameField`, its deprecated `displayNameField` alias, the legacy `titleFormat` template, a type-aware pick from the object's fields, then name-ish keys read straight off the record — the rung that answers when no object definition reached the view, as `staticData` and an inline `data: { provider: 'value', items }` configuration never fetch one. `Record #<id>` is the floor; `Marker` is reached only by a record carrying no id at all. |
|
|
148
|
+
| `descriptionField` | Field shown under the title in the marker popup. |
|
|
149
|
+
| `zoom` | Zoom level. Declaring it opts this view out of the auto-fit (see below). |
|
|
150
|
+
| `center` | `[latitude, longitude]` — a two-number **tuple**, latitude first. Declaring it opts this view out of the auto-fit. |
|
|
151
|
+
| `style` | MapLibre style URL/spec, replacing the default public demo style. The node-level `mapStyle` is read before it, so it applies when `mapStyle` is absent. |
|
|
152
|
+
|
|
153
|
+
**Nothing is guessed — an unbound map REFUSES.** A map with no coordinate binding
|
|
154
|
+
renders
|
|
155
|
+
|
|
156
|
+
> Map configuration required — declare `map.locationField` or `map.latitudeField` + `map.longitudeField`
|
|
157
|
+
|
|
158
|
+
in place of the map, rather than painting an empty one (objectui#8169, ruled
|
|
159
|
+
2026-09-07). *Unbound* is a property of the resolved config, not of the spelling
|
|
160
|
+
that produced it — an absent `map` block, a block naming only `titleField`, and a
|
|
161
|
+
`latitudeField` with no `longitudeField` beside it all refuse alike.
|
|
162
|
+
|
|
163
|
+
The four field-name defaults this component used to fall back to — `latitude` /
|
|
164
|
+
`longitude` / `location` / `description` — are gone, and so are the
|
|
165
|
+
`locationField: … || 'location'` floors that `plugin-list` and `plugin-view` used
|
|
166
|
+
to add on the way in. ⚠️ **Behaviour change for existing metadata**: a record set
|
|
167
|
+
that happens to carry `latitude` / `longitude` columns no longer plots on a view
|
|
168
|
+
that declared no binding — it refuses, and the fix is to declare the binding.
|
|
169
|
+
A title field is still never guessed (objectui#5953): an unconfigured marker
|
|
170
|
+
takes its title from the record-title precedence above.
|
|
171
|
+
|
|
172
|
+
## Node-level keys beside the block
|
|
173
|
+
|
|
174
|
+
| Key | Description |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `mapStyle` | MapLibre style URL or spec, replacing the demo tiles. Read before `map.style`, so it wins when both are written. Not the node's base `style`, which is an inline CSS record. |
|
|
177
|
+
| `enableClustering` | `true` groups nearby markers into numbered clusters. Absent, the map clusters only above 100 markers; `false` turns clustering off at any count. |
|
|
178
|
+
| `navigation` | What a marker click opens — the spec's `NavigationConfig`. `drawer`, `modal` and `popover` open the marker's record; `new_window` (or `openNewTab: true`, which outranks every mode except `none`) opens the record page in a new tab; `preventNavigation: true` opens nothing. An absent key, `none` and `split` open nothing (the map hands the split shell no main panel). `page`, and a block without `mode` (the spec's `page` default), open the record page of the map's `objectName` through the record navigator the host publishes (the console publishes one on its custom pages, record pages and list views); under a host that publishes none, or on a map that names no `objectName`, the click opens nothing. A parent view's click handler outranks the whole key. |
|
|
179
|
+
|
|
180
|
+
## Initial camera
|
|
181
|
+
|
|
182
|
+
There is no default zoom and no default centre. With records to show and no
|
|
183
|
+
camera declared, the map **fits the records**: their bounding box, measured along
|
|
184
|
+
the shortest arc that contains them (so a set straddling the antimeridian is
|
|
185
|
+
framed across the line, not around the far side of the planet), with 48px of
|
|
186
|
+
padding and a city-scale zoom ceiling of 12 — a single record does not become a
|
|
187
|
+
rooftop view.
|
|
188
|
+
|
|
189
|
+
Two cases sit outside the fit:
|
|
190
|
+
|
|
191
|
+
- **Nothing placeable** (empty result, or no record yielding coordinates): the
|
|
192
|
+
whole world, centred on `0, 0`.
|
|
193
|
+
- **A declared camera**: `zoom` or `center` in the `map` block wins and the fit is
|
|
194
|
+
skipped. Declaring one half keeps the other derived — `zoom` alone is applied at
|
|
195
|
+
the centre of the records, `center` alone at a continental zoom.
|
|
196
|
+
|
|
197
|
+
A `center` that is not a two-number tuple (the `{ lat, lng }` object form, say) is
|
|
198
|
+
rejected by the config schema, warned about in the console, and **not** adapted —
|
|
199
|
+
and it does not cost the view its fit.
|
|
200
|
+
|
|
201
|
+
## Coordinate formats
|
|
202
|
+
|
|
203
|
+
`locationField` names one record field, and the value in it is read at RUNTIME:
|
|
204
|
+
`extractCoordinates()` in `packages/plugin-map/src/ObjectMap.tsx` tests the value's
|
|
205
|
+
shape per record and takes the first of these three that answers. So this is a
|
|
206
|
+
reference read of what that parser accepts, not a value you author — which is why
|
|
207
|
+
it is a table rather than a snippet:
|
|
208
|
+
|
|
209
|
+
| Shape of `record[locationField]` | Example value | What the parser accepts |
|
|
210
|
+
| --- | --- | --- |
|
|
211
|
+
| Object | `{ lat: 37.7749, lng: -122.4194 }` | `lat` or `latitude` for the latitude; `lng`, `lon` or `longitude` for the longitude. Both must already be numbers — an object form is not string-parsed. |
|
|
212
|
+
| String | `'37.7749,-122.4194'` | `"lat,lng"`: split on the comma, each half trimmed and `parseFloat`-ed. |
|
|
213
|
+
| Array | `[37.7749, -122.4194]` | `[lat, lng]`, exactly two elements, each `parseFloat`-ed. |
|
|
214
|
+
|
|
215
|
+
The lat/lng PAIR (`latitudeField` + `longitudeField`) is tried first and is
|
|
216
|
+
stricter: both values must already be numbers, with no parsing step at all.
|
|
217
|
+
|
|
218
|
+
A record whose coordinates are missing, unparseable, or out of range (latitude
|
|
219
|
+
beyond ±90, longitude beyond ±180) is left off the map and counted in a notice
|
|
220
|
+
above it, rather than being silently dropped or rescued. The range test and the
|
|
221
|
+
notice live in that same file, alongside the parser.
|
|
222
|
+
|
|
223
|
+
## What this component does not read
|
|
224
|
+
|
|
225
|
+
Keys that look plausible on a map schema but have no read site here: `markers`
|
|
226
|
+
(markers are records), `layers`, `height` (the container is a fixed responsive
|
|
227
|
+
height, 300px through 600px), `useGeolocation` (the map carries a
|
|
228
|
+
user-initiated "show my location" button instead), and per-marker `icon` /
|
|
229
|
+
`color` / `popup` styling. A `map` configuration stashed under `filter.map` — a
|
|
230
|
+
shape predating the `map` input — is no longer read either, and says so in the
|
|
231
|
+
console.
|
|
232
|
+
|
|
233
|
+
## Using `ObjectMap` directly
|
|
234
|
+
|
|
235
|
+
`ObjectMap` (the component), `ObjectMapRenderer` (the registered wrapper, for a
|
|
236
|
+
host that registers types itself) and the `ObjectMapProps` type are the package's
|
|
237
|
+
exports. A component mounted directly is not behind `SchemaRenderer`, so nothing
|
|
238
|
+
hoists a `properties` bag: its `schema` prop takes the node as the component
|
|
239
|
+
reads it, with the keys flat:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { ObjectMap, type ObjectMapProps } from '@object-ui/plugin-map';
|
|
243
|
+
|
|
244
|
+
declare const dataSource: ObjectMapProps['dataSource'];
|
|
245
|
+
|
|
246
|
+
<ObjectMap
|
|
247
|
+
schema={{ type: 'object-map', objectName: 'stores', map: { latitudeField: 'lat', longitudeField: 'lng' } }}
|
|
248
|
+
dataSource={dataSource}
|
|
249
|
+
onMarkerClick={(record) => console.log(record)}
|
|
250
|
+
/>;
|
|
259
251
|
```
|
|
260
252
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
253
|
+
| Prop | Description |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `schema` | The map schema — the keys above. |
|
|
256
|
+
| `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data: { provider: 'value', items }` configuration. |
|
|
257
|
+
| `className` | Classes for the wrapper around the map. |
|
|
258
|
+
| `data` | Records to render directly, bypassing the component's own fetch — the shape `ListView` passes when it already holds the rows. Tracked live: passing a new array after mount (e.g. once a host's own in-flight query resolves) updates the map. |
|
|
259
|
+
| `onMarkerClick` | Called with the clicked record. |
|
|
260
|
+
| `onRowClick` | Record click handler; takes priority over the `navigation` overlay. |
|
|
261
|
+
| `onEdit` / `onDelete` | Passing either adds that button to the marker popup (and to the mobile record sheet). |
|
|
262
|
+
| `enableClustering` | Forces clustering on; without it, clustering starts above 100 visible markers. |
|
|
263
|
+
| `clusterRadius` | Clustering granularity (default `50`): the grid cell is `clusterRadius / 2 ** zoom`, so a larger value groups more aggressively. |
|
|
264
|
+
|
|
265
|
+
In a schema-driven page these handlers may equally be authored on the node
|
|
266
|
+
itself: `SchemaRenderer` spreads a node's non-metadata properties onto the
|
|
267
|
+
component.
|
|
273
268
|
|
|
274
269
|
## Links
|
|
275
270
|
|
|
276
|
-
- 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-map)
|
|
271
|
+
- 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-map) — the full
|
|
272
|
+
authoring reference for the schema and the `map` block
|
|
277
273
|
- 📦 [npm package](https://www.npmjs.com/package/@object-ui/plugin-map)
|
|
278
274
|
- 📝 [Changelog](./CHANGELOG.md)
|
|
279
275
|
- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
|
package/dist/ObjectMap.d.ts
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ObjectMapSchema, DataSource } from '@object-ui/types';
|
|
3
3
|
export interface ObjectMapProps {
|
|
4
|
-
schema:
|
|
4
|
+
schema: ObjectMapSchema;
|
|
5
5
|
dataSource?: DataSource;
|
|
6
6
|
className?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Records to render directly, bypassing this component's own fetch — the
|
|
9
|
+
* shape `ListView` passes when it already holds the rows. Declared as its
|
|
10
|
+
* own prop (not read off the `rest` spread) so the fetch effect can depend
|
|
11
|
+
* on this one value: naming the whole `rest` object in that effect's deps
|
|
12
|
+
* instead would refetch on every render, since `rest` is a fresh object
|
|
13
|
+
* each render (objectui#5003).
|
|
14
|
+
*/
|
|
15
|
+
data?: any[];
|
|
7
16
|
onMarkerClick?: (record: any) => void;
|
|
8
|
-
|
|
17
|
+
/**
|
|
18
|
+
* TWO parameters since objectui#9357, and the second is not decoration: this
|
|
19
|
+
* prop reaches `useNavigationOverlay` as its `onRowClick`, and `handleClick`
|
|
20
|
+
* invokes it as `onRowClick(record, event)` — the modifier payload a host
|
|
21
|
+
* needs to implement Cmd/Ctrl/middle-click for itself. Declaring one
|
|
22
|
+
* parameter hid the second on the ONE line a host reads. Spelled `any` and
|
|
23
|
+
* not `HandleClickModifiers` for the reason objectui#9341 measured on
|
|
24
|
+
* `ObjectKanbanSchema.onCardClick`: that interface lives in
|
|
25
|
+
* `@object-ui/react`, the published twins in `@object-ui/types` may not name
|
|
26
|
+
* it, and a host that discovered the payload from the implementation
|
|
27
|
+
* annotated it `React.MouseEvent` — which a narrower declaration refuses
|
|
28
|
+
* contravariantly. `BaseSchema`'s own `onClick` / `onChange` / `onSubmit`
|
|
29
|
+
* already use this spelling for exactly this situation.
|
|
30
|
+
*/
|
|
31
|
+
onRowClick?: (record: any, event?: any) => void;
|
|
9
32
|
onEdit?: (record: any) => void;
|
|
10
33
|
onDelete?: (record: any) => void;
|
|
11
34
|
/** Enable marker clustering for dense data */
|
|
12
35
|
enableClustering?: boolean;
|
|
13
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Clustering grid granularity (default: 50). NOT screen pixels — the grid
|
|
38
|
+
* cell edge is `clusterRadius / 2 ** zoom`, in coordinate DEGREES (the
|
|
39
|
+
* marker's `[lng, lat]`), so a larger value groups more aggressively and
|
|
40
|
+
* the effective granularity shrinks exponentially as zoom increases. See
|
|
41
|
+
* `clusterMarkers` below.
|
|
42
|
+
*/
|
|
14
43
|
clusterRadius?: number;
|
|
15
44
|
}
|
|
16
45
|
export declare const ObjectMap: React.FC<ObjectMapProps>;
|
package/dist/ObjectMap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAmB,UAAU,EAAY,MAAM,kBAAkB,CAAC;AA0B/F,OAAO,kCAAkC,CAAC;AAkB1C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACtC;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqfD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAq6B9C,CAAC;AAEF,eAAe,SAAS,CAAC"}
|