@object-ui/plugin-map 17.6.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/README.md CHANGED
@@ -7,13 +7,22 @@ marker comes from a record's own coordinate fields, and the first paint frames t
7
7
  records that were fetched. It is a *view over data* — there is no authored marker
8
8
  list, and no pin you place by hand.
9
9
 
10
- Importing the package registers two component types on the `ComponentRegistry`,
11
- both resolving to the same renderer:
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.
12
14
 
13
- - `object-map` — the object-bound renderer
14
- - `map` — the bare spec view-type name (`ViewTypeSchema`'s `'map'`), for a node
15
- authored with it directly. Inside an `ObjectView`, a `map` view is compiled to
16
- an `object-map` node, so both spellings end at the same component.
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.
17
26
 
18
27
  ## Installation
19
28
 
@@ -50,18 +59,28 @@ consumer is the missing input.
50
59
  Registration is a side effect of the import. There is no manual-registration
51
60
  export to iterate over — the import *is* the registration.
52
61
 
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.
68
+
53
69
  ```ts
54
70
  import '@object-ui/plugin-map';
71
+ import type { ObjectMapBlockNode } from '@object-ui/types';
55
72
 
56
73
  // Object-bound: the markers are the records the query returns.
57
- const schema = {
74
+ const schema: ObjectMapBlockNode = {
58
75
  type: 'object-map',
59
- objectName: 'stores',
60
- map: {
61
- latitudeField: 'lat',
62
- longitudeField: 'lng',
63
- titleField: 'name',
64
- descriptionField: 'address',
76
+ properties: {
77
+ objectName: 'stores',
78
+ map: {
79
+ latitudeField: 'lat',
80
+ longitudeField: 'lng',
81
+ titleField: 'name',
82
+ descriptionField: 'address',
83
+ },
65
84
  },
66
85
  };
67
86
  ```
@@ -69,41 +88,94 @@ const schema = {
69
88
  A literal record array instead of a query, with the same `map` block:
70
89
 
71
90
  ```ts
72
- const schema = {
91
+ import type { ObjectMapBlockNode } from '@object-ui/types';
92
+
93
+ const schema: ObjectMapBlockNode = {
73
94
  type: 'object-map',
74
- staticData: [
75
- { id: 1, name: 'San Francisco HQ', lat: 37.7749, lng: -122.4194 },
76
- { id: 2, name: 'Oakland Office', lat: 37.8044, lng: -122.2711 },
77
- ],
78
- map: { latitudeField: 'lat', longitudeField: 'lng', titleField: 'name' },
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
+ },
79
102
  };
80
103
  ```
81
104
 
82
105
  `filter` and `sort` are the **query's** filter and order — they reach the data
83
106
  source as `$filter` / `$orderby`, and the spec's per-element `dataSource` binding
84
- is honoured as well. The map issues no row cap of its own.
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.
85
128
 
86
129
  ## The `map` block
87
130
 
88
- The declared configuration input. Every key is optional:
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.
89
141
 
90
142
  | Key | Description |
91
143
  | --- | --- |
92
144
  | `latitudeField` | Record field holding the latitude. Needs `longitudeField` alongside it; both values must be numbers. |
93
145
  | `longitudeField` | Record field holding the longitude. |
94
146
  | `locationField` | Single field holding both coordinates — see the formats below. Used when the lat/lng pair yields nothing. |
95
- | `titleField` | Field shown as the marker title. Omitted, markers are titled `Marker`. |
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. |
96
148
  | `descriptionField` | Field shown under the title in the marker popup. |
97
149
  | `zoom` | Zoom level. Declaring it opts this view out of the auto-fit (see below). |
98
150
  | `center` | `[latitude, longitude]` — a two-number **tuple**, latitude first. Declaring it opts this view out of the auto-fit. |
99
- | `style` | MapLibre style URL/spec, replacing the default public demo style. |
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.
100
171
 
101
- **The block replaces the field-name defaults, it is not merged with them.** With
102
- no map configuration at all the component falls back to the field names
103
- `latitude` / `longitude` / `location` / `name` / `description`; the moment a `map`
104
- block is present, only what it declares is read. So `map: { titleField: 'name' }`
105
- on its own names no coordinate field, places nothing, and renders an empty map
106
- under the excluded-records notice — the defaults do not fill the gap.
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. |
107
179
 
108
180
  ## Initial camera
109
181
 
@@ -128,17 +200,25 @@ and it does not cost the view its fit.
128
200
 
129
201
  ## Coordinate formats
130
202
 
131
- `locationField` reads any of:
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:
132
208
 
133
- ```ts
134
- { location: { lat: 37.7749, lng: -122.4194 } } // also latitude/longitude, lon
135
- { location: '37.7749,-122.4194' } // "lat,lng"
136
- { location: [37.7749, -122.4194] } // [lat, lng]
137
- ```
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.
138
217
 
139
218
  A record whose coordinates are missing, unparseable, or out of range (latitude
140
219
  beyond ±90, longitude beyond ±180) is left off the map and counted in a notice
141
- above it, rather than being silently dropped or rescued.
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.
142
222
 
143
223
  ## What this component does not read
144
224
 
@@ -154,10 +234,14 @@ console.
154
234
 
155
235
  `ObjectMap` (the component), `ObjectMapRenderer` (the registered wrapper, for a
156
236
  host that registers types itself) and the `ObjectMapProps` type are the package's
157
- exports:
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:
158
240
 
159
241
  ```tsx
160
- import { ObjectMap } from '@object-ui/plugin-map';
242
+ import { ObjectMap, type ObjectMapProps } from '@object-ui/plugin-map';
243
+
244
+ declare const dataSource: ObjectMapProps['dataSource'];
161
245
 
162
246
  <ObjectMap
163
247
  schema={{ type: 'object-map', objectName: 'stores', map: { latitudeField: 'lat', longitudeField: 'lng' } }}
@@ -169,7 +253,7 @@ import { ObjectMap } from '@object-ui/plugin-map';
169
253
  | Prop | Description |
170
254
  | --- | --- |
171
255
  | `schema` | The map schema — the keys above. |
172
- | `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data` array. |
256
+ | `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data: { provider: 'value', items }` configuration. |
173
257
  | `className` | Classes for the wrapper around the map. |
174
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. |
175
259
  | `onMarkerClick` | Called with the clicked record. |
@@ -14,7 +14,21 @@ export interface ObjectMapProps {
14
14
  */
15
15
  data?: any[];
16
16
  onMarkerClick?: (record: any) => void;
17
- onRowClick?: (record: any) => void;
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;
18
32
  onEdit?: (record: any) => void;
19
33
  onDelete?: (record: any) => void;
20
34
  /** Enable marker clustering for dense data */
@@ -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,eAAe,EAAmB,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAY/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,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;IACjC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkcD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA2jB9C,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,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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,44 @@
1
1
  import { default as React } from 'react';
2
- import { ObjectMap, ObjectMapProps } from './ObjectMap';
2
+ import { ObjectMap, ObjectMapProps } from './ObjectMap.js';
3
3
  export { ObjectMap };
4
4
  export type { ObjectMapProps };
5
5
  export declare const ObjectMapRenderer: React.FC<any>;
6
+ /**
7
+ * ⛔ The bare `map` node type key is RETIRED (objectui#10393, executing the
8
+ * objectui#8008 family ruling of 2026-09-09, route 3). `object-map` is the one
9
+ * spelling this plugin serves.
10
+ *
11
+ * ## What was here, and why it went
12
+ *
13
+ * `ComponentRegistry.register('map', ObjectMapRenderer, { namespace: 'view',
14
+ * ... })` — a second key on the SAME renderer, which stored both `view:map`
15
+ * and the bare `map` fallback. The declared face admitted only one of the two:
16
+ * `ObjectMapSchema.type` is the literal `'object-map'` and `AnyComponentSchema`
17
+ * has no `map` arm, so a node authored `type: 'map'` failed validation
18
+ * (`invalid_union`) while the registry mounted it. Two published faces,
19
+ * opposite verdicts — the shape objectui#8008 retired for `gantt`.
20
+ *
21
+ * ## Why unregistering is the whole retirement here — measured, not assumed
22
+ *
23
+ * ⚠️ `BaseSchema` closes with `[key: string]: any` and `BaseSchemaCore` ends
24
+ * `.passthrough()`, so a dropped MEMBER KEY is KEPT, not refused (the
25
+ * objectui#7664 failure). That hazard needs a schema face to arise on, and this
26
+ * TYPE NAME never had one: in `@object-ui/types` the literal `'map'` appears
27
+ * only in the stored view-type unions (`NamedListView.type`,
28
+ * `defaultViewType`), never as a component node type, against a firing control
29
+ * of two for `object-map` (`objectql.ts` + its Zod mirror). There is no arm to
30
+ * convert into a named refusal, so unregistering IS the retirement.
31
+ *
32
+ * ## ⛔ Two layers, and only one of them moved
33
+ *
34
+ * The string `map` also names a STORED `NamedListView.type` — the value
35
+ * `CreateViewDialog` writes and every tenant's database holds. That layer is
36
+ * untouched: `plugin-view`'s `ObjectView` and `plugin-list`'s `ListView` each
37
+ * map a stored `map` view onto the node type they emit (their `case 'map'`
38
+ * branches), and both already emit `object-map`. ⇒ Every map view any user
39
+ * ever created through the console already renders through the surviving
40
+ * spelling; this retirement moves zero stored documents.
41
+ *
42
+ * Pinned in `src/index.bareMapKeyRetired-10393.test.tsx`.
43
+ */
6
44
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,CAAC;AAwB/B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAgB3C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,CAAC;AAwB/B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAgB1C,CAAC;AAmEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG"}