@sjvair/map-sdk 0.1.0-beta.1 → 0.1.0-beta.10
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 +243 -17
- package/dist/core/create-map.svelte.d.ts +72 -5
- package/dist/core/create-map.svelte.js +229 -14
- package/dist/core/gl.d.ts +5 -0
- package/dist/core/interactions.svelte.d.ts +11 -3
- package/dist/core/interactions.svelte.js +128 -11
- package/dist/core/plugin-host.svelte.d.ts +30 -5
- package/dist/core/plugin-host.svelte.js +93 -14
- package/dist/core/plugin.d.ts +44 -2
- package/dist/core/shape-view.d.ts +27 -0
- package/dist/core/shape-view.js +323 -0
- package/dist/data/format.d.ts +1 -1
- package/dist/data/format.js +16 -3
- package/dist/data/messages/en.json +12 -2
- package/dist/data/selection.d.ts +6 -0
- package/dist/data/selection.js +21 -0
- package/dist/data/types.d.ts +5 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/plugins/points.svelte.d.ts +14 -2
- package/dist/plugins/points.svelte.js +34 -5
- package/dist/plugins/shapes.svelte.d.ts +5 -0
- package/dist/plugins/shapes.svelte.js +20 -2
- package/dist/presets/FireTooltip.svelte +70 -0
- package/dist/presets/FireTooltip.svelte.d.ts +13 -0
- package/dist/presets/MonitorClusterTooltip.svelte +21 -2
- package/dist/presets/cluster-area.d.ts +13 -0
- package/dist/presets/cluster-area.js +34 -0
- package/dist/presets/hms-fire.d.ts +11 -7
- package/dist/presets/hms-fire.js +16 -17
- package/dist/presets/hms-smoke.d.ts +2 -1
- package/dist/presets/hms-smoke.js +4 -2
- package/dist/stores/hms-stores.svelte.d.ts +6 -3
- package/dist/stores/hms-stores.svelte.js +4 -4
- package/dist/ui/FeatureList.svelte +17 -17
- package/dist/ui/Legend.svelte +58 -16
- package/dist/ui/Legend.svelte.d.ts +9 -0
- package/dist/ui/MapTooltip.svelte +253 -14
- package/dist/ui/MapTooltip.svelte.d.ts +9 -0
- package/dist/ui/PickNotice.svelte +56 -0
- package/dist/ui/PickNotice.svelte.d.ts +7 -0
- package/dist/ui/TooltipItem.svelte +12 -12
- package/dist/ui/card.d.ts +29 -0
- package/dist/ui/card.js +1 -0
- package/dist/ui/feature-list.d.ts +17 -2
- package/dist/ui/feature-list.js +45 -10
- package/dist/ui/value-format.d.ts +3 -2
- package/dist/ui/value-format.js +9 -2
- package/dist/view/MapView.svelte +72 -2
- package/dist/view/MapView.svelte.d.ts +8 -0
- package/dist/view/create-gl-map.js +4 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ Supported browsers: Chrome/Edge 111+, Safari 16.4+, Firefox 128+. MIT licensed.
|
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
map-sdk is in beta. Betas are published under the `next` tag:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @sjvair/map-sdk
|
|
15
|
+
npm install @sjvair/map-sdk@next
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Peer dependencies: `svelte` (^5.57) and `@sjvair/sdk`, which is published to
|
|
@@ -129,13 +129,30 @@ buttons, `"engine"` the MapTiler SDK's default zoom/compass and locate-me contro
|
|
|
129
129
|
monitor-map uses today), and `"none"` none; the credits show in every case. Choose `controls` up
|
|
130
130
|
front: changing it after mount recreates the map. `tooltip` is `"pinned"` (default: anchored to the
|
|
131
131
|
feature) or `"pointer"` (shown at the pointer, as sjvair.com's map does, and hidden on zoom until
|
|
132
|
-
the pointer moves again); a feature focused from the list is always pinned.
|
|
132
|
+
the pointer moves again); a feature focused from the list is always pinned. `card` (default
|
|
133
|
+
`false`) pins the reading card on a click or tap: the card takes the pointer, has a Close button,
|
|
134
|
+
and closes with Esc, Close, or a click elsewhere on the map; focusing a feature-list row shows the
|
|
135
|
+
same card, and Tab from the row reaches it. Esc closes the card of the map that has focus; with
|
|
136
|
+
focus nowhere, it closes the page's most recently pinned card. One card shows at a time, and no hover card shows while
|
|
137
|
+
one is pinned. Selection and `featureclick` are unchanged: a click still selects as before and
|
|
138
|
+
also pins. `map.pinned` is the pinned feature (reactive), and `map.unpin()` closes it.
|
|
139
|
+
`card={{ actions }}` also puts your buttons in a row at the card's foot: `actions` is a snippet
|
|
140
|
+
given a `CardContext` (`pluginId`, `ref`, `refKey`, the listed `item` or `null`, reactive
|
|
141
|
+
`selectable` and `selected`, and `toggle()`, which is `map.togglePick` for that feature). Show
|
|
142
|
+
Select/Remove only when `selectable` is true: a fire hotspot, a smoke plume, or a layer in mode
|
|
143
|
+
`"none"` can't be selected, and `toggle()` then does nothing. Anything else goes in
|
|
133
144
|
the `overlays` snippet, where `getMapContext()` returns the map. `config.basemaps` replaces the
|
|
134
145
|
default MapTiler Streets v4 variants (build on `defaultBasemaps()`, which `createMap` uses: today's
|
|
135
146
|
sjvair.com setup is `{ ...defaultBasemaps(), excludedLayers: [] }`); `config.initialView` and
|
|
136
147
|
`config.cooperativeGestures` set the camera and gesture handling. `config.projection` (`"mercator"`
|
|
137
148
|
by default, or `"globe"`) and `config.space` (`false` by default, or `{ preset: "milkyway" }` for a
|
|
138
|
-
space background behind the globe) are passed to MapTiler only when set.
|
|
149
|
+
space background behind the globe) are passed to MapTiler only when set. `config.telemetry: false`
|
|
150
|
+
turns off MapTiler's telemetry (a usage report of the SDK version, API key, and session id, sent
|
|
151
|
+
about 2 s after each map is created) before the map is created. It is a page-wide setting in
|
|
152
|
+
MapTiler's SDK: once one map turns it off, it stays off for every map on the page. MapTiler's
|
|
153
|
+
session id (`mtsid`) stays on either way, since MapTiler bills SDK maps by session (without one,
|
|
154
|
+
every tile request counts on its own) and its tile cache needs it. By default (`true`) MapTiler's
|
|
155
|
+
own default applies.
|
|
139
156
|
|
|
140
157
|
Without `createMonitorsStore`, build a `DataSource` yourself (below).
|
|
141
158
|
|
|
@@ -156,22 +173,48 @@ create one per map (adding an object that is set up on another map throws). The
|
|
|
156
173
|
ones they are built on. Presets don't fetch: the host supplies a `DataSource`. `monitorsPlugin` is
|
|
157
174
|
selectable by default, like the shape presets.
|
|
158
175
|
|
|
176
|
+
An added plugin is in `map.plugins` (and the legend and selection state) as soon as `addPlugin`
|
|
177
|
+
returns. On a map that is already showing, its `setup` runs just after the current code finishes,
|
|
178
|
+
so its layers aren't there yet on the next line. Code that must see them uses `await tick()`;
|
|
179
|
+
`flushSync()` does not run setup.
|
|
180
|
+
|
|
159
181
|
Selected points show a highlight ring and check mark by default. `selectedStyle: "enlarge"` on
|
|
160
182
|
`pointsPlugin` or `monitorsPlugin` instead draws the selected icon at 1.3x with no ring (icon
|
|
161
183
|
points only), as sjvair.com's map does today.
|
|
162
184
|
|
|
163
185
|
`outline` on `shapesPlugin` is `"theme"` (default, a 1px `--foreground` edge), `"level"` (the edge
|
|
164
186
|
in each level's color at the fill's opacity; choropleth only), or `"none"` (no outline layer).
|
|
165
|
-
`hmsSmokePlugin` uses `"level"`.
|
|
187
|
+
`hmsSmokePlugin` uses `"level"`. `hoverable: false` on `shapesPlugin` sets the plugin's
|
|
188
|
+
`hoverable` (see [Writing a plugin](#writing-a-plugin)): hovering a shape shows no tooltip and
|
|
189
|
+
doesn't hide what is beneath it, while focusing it from the feature list still pins its tooltip.
|
|
190
|
+
`hmsSmokePlugin` uses it, so a plume, which can cover much of the map, never pops up a tooltip.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
map.addPlugin(
|
|
194
|
+
shapesPlugin({
|
|
195
|
+
id: "plumes",
|
|
196
|
+
source: staticSource(plumes, { dataset: "hms-smoke", entryType: "smoke-density" }),
|
|
197
|
+
layerLabel: "Plumes",
|
|
198
|
+
mode: "choropleth",
|
|
199
|
+
hoverable: false
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
```
|
|
166
203
|
|
|
167
204
|
`zoomRange: { min?, max? }` on `pointsPlugin` limits every layer it draws (points, clusters,
|
|
168
|
-
selection) to zooms from `min` (inclusive) up to `max` (exclusive), switching live while
|
|
205
|
+
selection) to camera zooms from `min` (inclusive) up to `max` (exclusive), switching live while
|
|
206
|
+
zooming. Two plugins whose ranges meet hand over with no gap, on the globe too.
|
|
169
207
|
|
|
170
208
|
`hmsFirePlugin({ id?, source, hotspots?, layerLabel })` draws fires as flame icons sized and
|
|
171
209
|
colored by FRP. With `hotspots`, it switches by itself while zooming, as sjvair.com's map does:
|
|
172
210
|
`source` (the fire store's `groups`) below zoom 10 and `hotspots` from zoom 10. It stays one
|
|
173
211
|
layer: one legend section, one feature list (the groups, so keyboard users reach every fire;
|
|
174
|
-
hotspots aren't listed), and tooltips for both.
|
|
212
|
+
hotspots aren't listed), and tooltips for both. The tooltip is the monitors' reading box: "Fire
|
|
213
|
+
(MW)", the power in its level's color, beside the name ("3 fire detections", "1 fire detection"
|
|
214
|
+
for a group, "Fire detection" for one detection) and the level ("50–149 MW"); a group of two or
|
|
215
|
+
more says "Average". A feature with a `count` (`FireFeatureProperties`, which the fire store's
|
|
216
|
+
groups carry) is a group. Fire values are cut off at the display precision, not rounded, in the
|
|
217
|
+
tooltip, the list and its accessible names, so 9.96 MW reads "9.9" beside "<10".
|
|
175
218
|
|
|
176
219
|
`legendDisplay: "gradient" | "blocks"` on `pointsPlugin` sets how the legend draws a range scale
|
|
177
220
|
(default a gradient; `hmsFirePlugin` uses `"blocks"`, labeled with the levels' own labels). A blocks
|
|
@@ -180,7 +223,29 @@ each level's label under its segment; long labels wrap. `legendTitle: "layer" |
|
|
|
180
223
|
the legend section's title: `layerLabel` (default) or the entry type's label from the metadata
|
|
181
224
|
provider (`monitorsPlugin` uses the latter, so its section reads "PM2.5"; `layerLabel` still names
|
|
182
225
|
the layer in the list, selection bar and summary). `Legend` takes `order` (`"bottom-first"`,
|
|
183
|
-
default, or `"top-first"`)
|
|
226
|
+
default, or `"top-first"`) and `framed` (`true`, default, draws its own card; `false` draws no
|
|
227
|
+
border, background, padding or fixed width, so a host's own card frames it). The framed legend
|
|
228
|
+
and the feature list panel are 21rem wide at most, and never wider than the map less 2rem (the
|
|
229
|
+
window less 2rem outside a `MapView`), so they fit a small map on a wide screen. Without a frame,
|
|
230
|
+
translucent swatches blend over the nearest ancestor with an opaque background (a see-through
|
|
231
|
+
card is skipped), else `--background`; that color is read on mount, when `framed` changes, and on
|
|
232
|
+
a theme change, but not when the card changes color through its own class or style.
|
|
233
|
+
|
|
234
|
+
```svelte
|
|
235
|
+
<div class="w-84 rounded-md bg-white shadow">
|
|
236
|
+
<h3>Legend</h3>
|
|
237
|
+
<Legend {map} framed={false} />
|
|
238
|
+
</div>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`detail?(props)` on `pointsPlugin` gives a feature with no level a short text (such as "Level 2
|
|
242
|
+
charger") shown where "No level" would be: in the feature list (plain text, no swatch), the row's
|
|
243
|
+
accessible name ("Fresno Station, Level 2 charger"), and the tooltip. A feature with a level
|
|
244
|
+
always shows its level. A plugin of your own sets the same `detail` on its `FeatureListItem`s.
|
|
245
|
+
|
|
246
|
+
`truncateValues: true` on `pointsPlugin` cuts values off at the display precision instead of
|
|
247
|
+
rounding them (`truncate` on each `FeatureListItem`), in the list, its accessible names, and the
|
|
248
|
+
generic tooltip, so a value never reads as the next level up. `hmsFirePlugin` uses it.
|
|
184
249
|
|
|
185
250
|
`collocationSitesPlugin({ id?, source, layerLabel })` draws collocation sites as sjvair.com does:
|
|
186
251
|
each at its reference monitor, in that monitor's shape and level color (inactive gray, inside
|
|
@@ -207,7 +272,14 @@ and reads "No level"; a level with an empty color draws neutral with its own lab
|
|
|
207
272
|
source loads, fails, or is unavailable, the legend says so (a failure offers "Try again", which
|
|
208
273
|
calls `status.retry()`). Values are shown with the metadata's display precision, except in the
|
|
209
274
|
monitor, cluster, and collocation tooltips, which match monitor-map: the reading and a cluster's
|
|
210
|
-
average are whole numbers, and a cluster's max and min are as reported.
|
|
275
|
+
average are whole numbers, and a cluster's max and min are as reported. Fire values are cut off
|
|
276
|
+
rather than rounded (see `truncateValues`). On a category scale, a value that is its level's code
|
|
277
|
+
(smoke's `"light"`, the heat score's `2`) isn't shown: the list row, the tooltip, and the row's
|
|
278
|
+
accessible name give only the level label. Times in the tooltips read like sjvair.com's map, in
|
|
279
|
+
Pacific time: "4:00 pm Friday Nov 06, 2026", with " PT" after them on a device in another zone.
|
|
280
|
+
The monitor cluster tooltip's count line gives the area the monitors are within, "3 monitors
|
|
281
|
+
within a 22.619 mi² area": the area of their convex hull, left out when it would read 0.000 (two
|
|
282
|
+
monitors, or all on one line or one spot).
|
|
211
283
|
|
|
212
284
|
`createMonitorsStore()` (from `@sjvair/map-sdk/stores`) loads the monitors list and latest readings
|
|
213
285
|
for an entry type, polls every two minutes, and exposes `dataSource`. `init()`, `update()`,
|
|
@@ -280,8 +352,10 @@ not selectable). `encodeRef`/`decodeRef` convert between refs and keys.
|
|
|
280
352
|
|
|
281
353
|
A plugin is an object with an `id` and a `setup(ctx)` that adds sources, layers, and images
|
|
282
354
|
through its `PluginContext` and returns a teardown. The host runs `setup` once the map's style is
|
|
283
|
-
ready and again after a basemap or theme change
|
|
284
|
-
|
|
355
|
+
ready and again after a basemap or theme change. For a plugin added to a ready map, `setup` runs
|
|
356
|
+
just after the current code finishes (a microtask), never inside the caller's code, so adding and
|
|
357
|
+
removing plugins from an `$effect` is safe. Everything added through `ctx` is removed on teardown,
|
|
358
|
+
and ids are namespaced per map. `features()` feeds the feature list, tooltip, and
|
|
285
359
|
summary; `legend()` the legend. A plugin's `tooltip` content sits under the feature's name; set
|
|
286
360
|
`tooltipShowsName` when the content shows the name itself (the monitor and collocation tooltips
|
|
287
361
|
do), and the name header is left out.
|
|
@@ -321,6 +395,19 @@ function sitesPlugin(sites: Sites): MapPlugin {
|
|
|
321
395
|
}
|
|
322
396
|
```
|
|
323
397
|
|
|
398
|
+
An item for a shape (a polygon or line) can carry its `geometry` and, optionally, its `bbox`
|
|
399
|
+
(`[west, south, east, north]`, from `geometryBbox(geometry)`). The list and the summary then count
|
|
400
|
+
it as in view when the shape overlaps the view, not only when its `lngLat` is inside it: a smoke
|
|
401
|
+
plume that fills the screen is listed even though its center is off-screen. Measure `bbox` once per
|
|
402
|
+
data load (both are tested on every move); without it the box is computed each time. Focusing such
|
|
403
|
+
a shape from the list while its `lngLat` is off-screen leaves the camera where it is, and its
|
|
404
|
+
tooltip pins at a point of the shape in view: the view's center (beside the list panel) when the
|
|
405
|
+
shape covers it, else a point inside its visible part. `map.focusedAnchor` is where the focused
|
|
406
|
+
tooltip pins, and `map.anchorFor(item)` gives that point for any item without moving the camera
|
|
407
|
+
(the hover tooltip uses it). `intersectsBounds(item, bounds)` is the in-view test. `shapesPlugin`
|
|
408
|
+
and the presets built on it set `geometry` and `bbox` already. Bounds across the antimeridian are
|
|
409
|
+
not handled.
|
|
410
|
+
|
|
324
411
|
A plugin can also add a MapLibre custom layer, such as a wind layer from a library you add
|
|
325
412
|
yourself, with `ctx.addCustomLayer(local, create)`. The host calls `create` with the namespaced id
|
|
326
413
|
(the layer must use it) and adds the object it returns as is, in the plugin's draw-order slot. It is
|
|
@@ -329,8 +416,13 @@ time), and `movePlugin` moves it with the plugin's other layers.
|
|
|
329
416
|
|
|
330
417
|
The cursor is a pointer over any interactive feature or cluster bubble. Where several plugins'
|
|
331
418
|
features are under the pointer, the hover tooltip shows the one whose plugin has the highest
|
|
332
|
-
`hoverPriority` (default 0), the topmost on a tie
|
|
333
|
-
|
|
419
|
+
`hoverPriority` (default 0), the topmost on a tie. Over a cluster bubble, the bubble's tooltip
|
|
420
|
+
shows unless a feature beneath belongs to a plugin with a higher `hoverPriority` than the
|
|
421
|
+
bubble's (collocation sites under monitor clusters). Clicks always go to the topmost, so a click
|
|
422
|
+
on a bubble still zooms into it. A plugin with `hoverable: false` never takes the hover: its
|
|
423
|
+
features show no hover tooltip or pointer cursor and are passed over as if they weren't drawn, so
|
|
424
|
+
whatever is beneath gets the hover. They are still clicked, returned by `featuresAt`, and pin
|
|
425
|
+
their tooltip when focused from the feature list.
|
|
334
426
|
|
|
335
427
|
```ts
|
|
336
428
|
import type { CustomLayerInterface, MapPlugin, PluginContext } from "@sjvair/map-sdk";
|
|
@@ -380,9 +472,35 @@ map.addPlugin(plugin, {
|
|
|
380
472
|
in the selection bar, and a feature that appears later shows as selected. `map.getSelection(id)`
|
|
381
473
|
and `getSelectionMode(id)` are reactive.
|
|
382
474
|
|
|
475
|
+
`pickWith: "modifier"` (an `addPlugin` option; default `"click"`) is for maps where a click should
|
|
476
|
+
only show the reading: a plain click or tap pins the card (use it with `MapView`'s `card`), and
|
|
477
|
+
Shift/Cmd/Ctrl-click, or the list's Space or Shift+Enter, toggles the feature in the selection (in
|
|
478
|
+
`single` mode it selects, or clears it when it's already selected). A tap never selects; the card's
|
|
479
|
+
own buttons do (below). Shift-click goes through MapLibre's box zoom, which takes every Shift press:
|
|
480
|
+
map-sdk turns a Shift release in place into the pick for `"modifier"` plugins only, and Shift-drag
|
|
481
|
+
still zooms to a box. With `pickWith: "click"`, a Shift-click still does nothing on the map.
|
|
482
|
+
|
|
483
|
+
`canSelect(next, ref)` (also an `addPlugin` option) refuses a user's pick before it shows: it gets
|
|
484
|
+
the selection the pick would produce and returns `true` to let it through or a message to refuse
|
|
485
|
+
it. A refused pick changes nothing and calls no `onselectionchange`; the map shows the message for
|
|
486
|
+
4 s and announces it to screen readers (`map.notice` holds it). It isn't asked for `setSelection`
|
|
487
|
+
or the selection bar's Clear.
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
map.addPlugin(plugin, {
|
|
491
|
+
selectionMode: "multiple",
|
|
492
|
+
pickWith: "modifier",
|
|
493
|
+
canSelect: (next) => (next?.ids.length ?? 0) <= 3 || "Up to 3 monitors"
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`map.togglePick(pluginId, ref)` is a user's toggle for your own controls (the card's Select or
|
|
498
|
+
Remove): the same rule, `canSelect`, and `onselectionchange` as a modifier-click.
|
|
499
|
+
|
|
383
500
|
`map.on("featureclick", ({ pluginId, ref, lngLat }) => …)` fires for every click on a feature,
|
|
384
501
|
whatever the mode, and also for a pick in the feature list, so keyboard users get the same event;
|
|
385
|
-
`lngLat` is the click point
|
|
502
|
+
`lngLat` is the click point. For a list pick it's where the item's tooltip pins: the item's
|
|
503
|
+
position, or for a shape whose label point is off-screen, a point of the shape in view. It returns an unsubscribe
|
|
386
504
|
function. For your own context menu, `map.featuresAt({ x, y })` returns the hits under
|
|
387
505
|
a canvas point, topmost first, and `map.featureForElement(element)` returns the hit for a row of
|
|
388
506
|
the feature list (an element with `data-mapsdk-ref`). Both return nothing while the map is
|
|
@@ -402,9 +520,10 @@ map.easeTo({ center: monitor.lngLat, minZoom: 12 });
|
|
|
402
520
|
|
|
403
521
|
## Ready, and reaching the GL map
|
|
404
522
|
|
|
405
|
-
`map.ready` is reactive: true once the attached map's style has loaded
|
|
406
|
-
for the first time,
|
|
407
|
-
|
|
523
|
+
`map.ready` is reactive: true once the attached map's style has loaded, the map has gone idle
|
|
524
|
+
for the first time, and any queued plugin setups have run; false again on detach and while a
|
|
525
|
+
basemap switch reloads the style. Adding a plugin to a ready map leaves it true. Show a load screen
|
|
526
|
+
until it is true. `map.gl` is the attached GL map (or `null`), reactive.
|
|
408
527
|
|
|
409
528
|
`maptilerMap(map)` returns it as a MapTiler `Map` (or `null` while detached). It is an escape hatch
|
|
410
529
|
for markers and popups only: never add sources or layers through it, because plugins own those.
|
|
@@ -473,6 +592,113 @@ local sjvair.com stack at `http://localhost:8000`.
|
|
|
473
592
|
podman exec sjvaircom_web_1 python manage.py loaddata default-calibrations.yaml
|
|
474
593
|
```
|
|
475
594
|
|
|
595
|
+
## Changes
|
|
596
|
+
|
|
597
|
+
### 0.1.0-beta.10
|
|
598
|
+
|
|
599
|
+
- **Buttons on the pinned card.** `card={{ actions }}`: a snippet of the host's own buttons, given
|
|
600
|
+
the card's feature, whether it's selected, and `toggle()` (the dashboard's Select and Remove).
|
|
601
|
+
- **Pick with a modifier.** `pickWith: "modifier"`: a plain click or tap only pins the card, and
|
|
602
|
+
Shift/Cmd/Ctrl-click (or the list's Space or Shift+Enter) toggles the selection. Shift-click now
|
|
603
|
+
reaches the map through MapLibre's box zoom for these plugins; Shift-drag still zooms to a box.
|
|
604
|
+
- **Refuse a pick before it shows.** `canSelect(next, ref)` returns `true` or a message: a refused
|
|
605
|
+
pick changes nothing and calls no `onselectionchange`, and the map shows and announces the
|
|
606
|
+
message for 4 s (`map.notice`). An empty message refuses quietly.
|
|
607
|
+
- **The feature list's Shift+Enter now picks additively**, as Shift-click on a row does (Enter
|
|
608
|
+
alone replaces, Space adds or removes, as before). This applies with any `pickWith`.
|
|
609
|
+
- **New API:** `pickWith`, `canSelect`, `MapContext.togglePick`, `getPickWith`, `notice`,
|
|
610
|
+
`CardOptions`, `CardContext`, and `GlMapLike.unproject` (for hosts that wrap the GL map).
|
|
611
|
+
- **Every `MapView` now has a `role="status"` element** (empty unless a pick is refused), so a host
|
|
612
|
+
test that finds "the" status by role inside a map may need to narrow it.
|
|
613
|
+
|
|
614
|
+
### 0.1.0-beta.9
|
|
615
|
+
|
|
616
|
+
- **A map with `tooltip="pointer"` comes back after releasing WebGL.** Setting `MapView`'s
|
|
617
|
+
`active` to `false` threw "Cannot read properties of null (reading 'off')" from the tooltip's
|
|
618
|
+
zoom listener, and the map never drew again until a reload. It now releases and rebuilds cleanly.
|
|
619
|
+
In 0.1.0-beta.7 and 0.1.0-beta.8; maps with the default `tooltip="pinned"` weren't affected.
|
|
620
|
+
|
|
621
|
+
### 0.1.0-beta.8
|
|
622
|
+
|
|
623
|
+
- **The pinned reading card.** `MapView`'s new `card` prop: a click or tap on a feature pins its
|
|
624
|
+
card, which takes the pointer, has a Close button, and closes with Esc, Close, or a click
|
|
625
|
+
elsewhere on the map. Focusing a feature-list row shows the same card, and Tab reaches it; closing
|
|
626
|
+
it returns focus where it came from. With `card`, a tap always shows the reading: without
|
|
627
|
+
it, a tap's emulated `mouseout` (Chromium, WebKit) still hides the hover card right after the
|
|
628
|
+
tap, as before. Nothing changes without `card`, and with it, selection and `featureclick` behave
|
|
629
|
+
as before.
|
|
630
|
+
- **New API:** `MapView`'s `card`, `MapContext.pinned` and `unpin()`. New message key for hosts
|
|
631
|
+
with their own catalog: `mapsdk_card_close` ("Close").
|
|
632
|
+
|
|
633
|
+
### 0.1.0-beta.7
|
|
634
|
+
|
|
635
|
+
- **Plugins come back at once after a basemap switch.** After picking another basemap, monitors,
|
|
636
|
+
fire, smoke and the other layers were missing until every tile of the new basemap had loaded
|
|
637
|
+
and drawn (4–10 s on a slow connection). They are now added as soon as the new style loads, as
|
|
638
|
+
sjvair.com's map does. A theme change while tiles are loading no longer waits either.
|
|
639
|
+
- **`config.telemetry: false`** turns off MapTiler's telemetry (its usage report) for the page,
|
|
640
|
+
before the map is created. Sessions stay on, so billing by session and the tile cache are
|
|
641
|
+
unchanged. By default nothing changes.
|
|
642
|
+
- **The feature list panel and the framed legend fit the map.** They were capped at the window's
|
|
643
|
+
width less 2rem; inside a `MapView` they are now capped at the map's width less 2rem, so a small
|
|
644
|
+
map on a wide screen doesn't overflow. MapView sets `--mapsdk-map-width` for this.
|
|
645
|
+
|
|
646
|
+
### 0.1.0-beta.6
|
|
647
|
+
|
|
648
|
+
- **A category reading is shown once.** When a feature's value is its category level's code, the
|
|
649
|
+
list row, the tooltip, and the row's accessible name show only the level label. Smoke rows read
|
|
650
|
+
"Smoke, Light" (was "light Light"), burn status "Burning Discouraged", and heat score
|
|
651
|
+
"Moderate" (the 0–4 number is no longer shown). Number scales such as PM2.5 and fire power are
|
|
652
|
+
unchanged, and a category value that matches no level is still shown.
|
|
653
|
+
- **New message key for hosts with their own catalog:** `mapsdk_list_row_level`
|
|
654
|
+
(`"{name}, {level}"`).
|
|
655
|
+
|
|
656
|
+
### 0.1.0-beta.5
|
|
657
|
+
|
|
658
|
+
- **Shapes that cover the view are in view.** The feature list and the view summary count a shape
|
|
659
|
+
when it overlaps the view, not only when its center (label point) is inside it. A smoke plume or
|
|
660
|
+
a big region that fills the screen, with its center off-screen, now has a list row and is
|
|
661
|
+
counted. Points are unchanged.
|
|
662
|
+
- **Focusing such a shape doesn't move the map.** Before, focusing a row panned to center the
|
|
663
|
+
shape's label point (a plume's could be off Baja). Now the camera stays, and the tooltip pins at
|
|
664
|
+
a point of the shape in view, with no move event. A shape whose label point is on screen still
|
|
665
|
+
pans to it.
|
|
666
|
+
- **Hover tooltips** in `"pinned"` mode follow the same rule: a hovered shape whose label point is
|
|
667
|
+
off-screen pins its tooltip inside the view instead of at the map's edge. If the map moves while
|
|
668
|
+
the pointer rests on the shape, the pin is found again when the map settles.
|
|
669
|
+
- **`featureclick` from the list** (Enter on a row) reports that same in-view point for such a
|
|
670
|
+
shape, instead of its off-screen label point. A handler that pans to `lngLat` no longer sends
|
|
671
|
+
the map off to the shape's far-away center. Points and on-screen label points report their
|
|
672
|
+
position, as before.
|
|
673
|
+
- **New API (all optional):** `FeatureListItem.geometry` and `.bbox`, `MapContext.focusedAnchor`
|
|
674
|
+
and `.anchorFor(item)`, `geometryBbox`, `intersectsBounds`, and the `Bbox` type.
|
|
675
|
+
|
|
676
|
+
### 0.1.0-beta.4
|
|
677
|
+
|
|
678
|
+
Defaults from monitor-map 5.0's parity review against sjvair.com's map (4.3.0). Every map gets
|
|
679
|
+
them; none is an option.
|
|
680
|
+
|
|
681
|
+
- **Tooltip times** read like sjvair.com's map, in Pacific time: "4:00 pm Friday Nov 06, 2026"
|
|
682
|
+
(was "Nov 6, 2026, 4:00 PM"). A device in another zone still gets " PT" after the time.
|
|
683
|
+
- **The cluster area line is back.** The monitor cluster tooltip's count line reads "3 monitors
|
|
684
|
+
within a 22.619 mi² area". This reverses the 2026-11-04 parity decision of "no area line". The
|
|
685
|
+
wording is new (sjvair.com said "covering"), and the area is the convex hull of the cluster's
|
|
686
|
+
monitors, so clusters of four or more can differ from sjvair.com's number, which followed the
|
|
687
|
+
monitors' order and could cross itself. No line when the area would read 0.000.
|
|
688
|
+
- **Fire tooltips are the reading box**: "Fire (MW)", the power in its level's color, beside the
|
|
689
|
+
name and level; "Average" for a group of two or more. Fire values are cut off, not rounded, in
|
|
690
|
+
the tooltip, the list and its accessible names. Every group names its count, a group of 1
|
|
691
|
+
included ("1 fire detection").
|
|
692
|
+
- **Smoke plumes show no hover tooltip** and never take the hover from what is beneath them;
|
|
693
|
+
focusing a plume from the feature list still pins its tooltip. This reverses part of milestone
|
|
694
|
+
2's accessibility additions ("tooltips on smoke and fire"): fire keeps a tooltip.
|
|
695
|
+
- **Breaking for hosts with their own message catalog:** `mapsdk_hms_fire_detections` is now
|
|
696
|
+
`mapsdk_hms_fire_detections_one` / `_other`. New keys: `mapsdk_hms_fire_box_header`,
|
|
697
|
+
`mapsdk_hms_fire_box_header_no_units`, `mapsdk_hms_fire_average`, `mapsdk_level_with_units`,
|
|
698
|
+
and `mapsdk_monitor_cluster_count_area_one` / `_other`.
|
|
699
|
+
- **New API:** `MapPlugin.hoverable`, `shapesPlugin`'s `hoverable`, `pointsPlugin`'s
|
|
700
|
+
`truncateValues`, `FeatureListItem.truncate`, and `FireFeatureProperties`.
|
|
701
|
+
|
|
476
702
|
## Development
|
|
477
703
|
|
|
478
704
|
```bash
|
|
@@ -5,7 +5,7 @@ import type { FeatureRef, SelectionMode, SpatialSelection } from "../data/types.
|
|
|
5
5
|
import { BasemapState, type BasemapConfig } from "./basemap.svelte.js";
|
|
6
6
|
import type { GlMapLike } from "./gl.js";
|
|
7
7
|
import { type Hovered } from "./interactions.svelte.js";
|
|
8
|
-
import type { FeatureHit, MapPlugin } from "./plugin.js";
|
|
8
|
+
import type { FeatureHit, FeatureListItem, MapPlugin } from "./plugin.js";
|
|
9
9
|
export interface CameraState {
|
|
10
10
|
center: [number, number];
|
|
11
11
|
zoom: number;
|
|
@@ -32,6 +32,14 @@ export interface MapConfig {
|
|
|
32
32
|
space?: false | {
|
|
33
33
|
preset: string;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Default `true`: MapTiler's own default. `false` turns off MapTiler's telemetry (a usage
|
|
37
|
+
* report of the SDK version, the API key, and the session id, sent about 2 s after each map is
|
|
38
|
+
* created) before the GL map is created. It is a page-wide setting in MapTiler's SDK, so once
|
|
39
|
+
* one map turns it off it stays off for every map on the page. MapTiler's session id (`mtsid`)
|
|
40
|
+
* stays on: MapTiler bills SDK maps by session, and its tile cache needs sessions.
|
|
41
|
+
*/
|
|
42
|
+
telemetry?: boolean;
|
|
35
43
|
}
|
|
36
44
|
export interface CreateMapOptions {
|
|
37
45
|
config: MapConfig;
|
|
@@ -48,11 +56,28 @@ export interface PluginOptions {
|
|
|
48
56
|
selectionMode?: SelectionMode;
|
|
49
57
|
selection?: SpatialSelection | null;
|
|
50
58
|
onselectionchange?(sel: SpatialSelection | null): void;
|
|
59
|
+
/**
|
|
60
|
+
* `"click"` (default): today's rules (a click selects; Shift/⌘/Ctrl or a tap adds in
|
|
61
|
+
* `"multiple"` mode). `"modifier"`: a plain click or tap only pins the card (use it with
|
|
62
|
+
* `MapView`'s `card`), and Shift/⌘/Ctrl-click, or the list's Space or Shift+Enter, toggles the
|
|
63
|
+
* feature in the selection.
|
|
64
|
+
*/
|
|
65
|
+
pickWith?: "click" | "modifier";
|
|
66
|
+
/**
|
|
67
|
+
* Called before a user's pick (a click, a list pick, or the card's `toggle`) changes the
|
|
68
|
+
* selection, with the selection it would produce. `true` lets it through; a string refuses it
|
|
69
|
+
* and is the message the map shows (`MapContext.notice`). Not called for `setSelection` or the
|
|
70
|
+
* selection bar's Clear.
|
|
71
|
+
*/
|
|
72
|
+
canSelect?(next: SpatialSelection | null, ref: FeatureRef): true | string;
|
|
51
73
|
}
|
|
52
74
|
export type FeatureClickHandler = (e: {
|
|
53
75
|
pluginId: string;
|
|
54
76
|
ref: FeatureRef;
|
|
55
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* The click point. For a pick in the feature list, where the item's tooltip pins: its position,
|
|
79
|
+
* or for a shape whose label point is off-screen, a point of the shape in view.
|
|
80
|
+
*/
|
|
56
81
|
lngLat: [number, number];
|
|
57
82
|
}) => void;
|
|
58
83
|
/** Where a camera move ends up. `zoom` wins over `minZoom`. */
|
|
@@ -82,8 +107,9 @@ export interface MapContext {
|
|
|
82
107
|
/** Reactive. */
|
|
83
108
|
readonly attached: boolean;
|
|
84
109
|
/**
|
|
85
|
-
* Reactive. True once the attached map's style has loaded
|
|
86
|
-
* first time; false again on `detach()` and while a
|
|
110
|
+
* Reactive. True once the attached map's style has loaded, the map has gone idle for the
|
|
111
|
+
* first time, and any queued plugin setups have run; false again on `detach()` and while a
|
|
112
|
+
* basemap switch reloads the style. Adding a plugin to a ready map leaves it true. A load
|
|
87
113
|
* screen can wait for it.
|
|
88
114
|
*/
|
|
89
115
|
readonly ready: boolean;
|
|
@@ -158,6 +184,34 @@ export interface MapContext {
|
|
|
158
184
|
* lists, with the bubble's `properties`, never a feature beneath it.
|
|
159
185
|
*/
|
|
160
186
|
readonly hovered: Hovered | null;
|
|
187
|
+
/**
|
|
188
|
+
* Reactive. The feature whose reading card is pinned, and where, while `MapView`'s `card` is
|
|
189
|
+
* on; `null` otherwise. A click on a feature pins it (selection and `featureclick` are
|
|
190
|
+
* unchanged), focusing a feature-list row pins that row's feature, and a click on nothing (or
|
|
191
|
+
* on a cluster bubble or a feature without a hover card) unpins.
|
|
192
|
+
*/
|
|
193
|
+
readonly pinned: Hovered | null;
|
|
194
|
+
/** Reactive. The plugin's `pickWith`: `"click"` for an unknown plugin. */
|
|
195
|
+
getPickWith(pluginId: string): "click" | "modifier";
|
|
196
|
+
/**
|
|
197
|
+
* A user's toggle of `ref` (the card's Select/Remove): the modifier-click rule, with the
|
|
198
|
+
* plugin's `canSelect` and `onselectionchange`, in either `pickWith` mode.
|
|
199
|
+
*/
|
|
200
|
+
togglePick(pluginId: string, ref: FeatureRef): void;
|
|
201
|
+
/**
|
|
202
|
+
* Reactive. The latest refused pick's message; `id` grows with each refusal, so the same
|
|
203
|
+
* message twice is announced again. Cleared by the next pick, `clearNotice()`, and detach.
|
|
204
|
+
*/
|
|
205
|
+
readonly notice: {
|
|
206
|
+
message: string;
|
|
207
|
+
id: number;
|
|
208
|
+
} | null;
|
|
209
|
+
/** @internal For `PickNotice`: clears the notice. */
|
|
210
|
+
clearNotice(): void;
|
|
211
|
+
/** Closes the pinned card. Does nothing when nothing is pinned. */
|
|
212
|
+
unpin(): void;
|
|
213
|
+
/** @internal For `MapView` only: turns pinning on or off. Off also unpins. */
|
|
214
|
+
setCardEnabled(on: boolean): void;
|
|
161
215
|
/**
|
|
162
216
|
* Marks the feature focused and pans to it (no animation under reduced motion): the item in
|
|
163
217
|
* `pluginId`'s features when given, else the first plugin listing `refKey`. `null` clears the
|
|
@@ -184,6 +238,18 @@ export interface MapContext {
|
|
|
184
238
|
readonly focusedKey: string | null;
|
|
185
239
|
/** Reactive. The plugin `focusFeature` was given with `focusedKey`, or `null`. */
|
|
186
240
|
readonly focusedPluginId: string | null;
|
|
241
|
+
/**
|
|
242
|
+
* Reactive. Where the focused feature's tooltip pins, set by `focusFeature` (`null` without
|
|
243
|
+
* focus): its `lngLat`, or for a shape that overlaps the view with its `lngLat` off-screen, a
|
|
244
|
+
* point of the shape in the part of the view the list panel leaves clear. Fixed at focus
|
|
245
|
+
* time, so it stays on the feature if the map then moves.
|
|
246
|
+
*/
|
|
247
|
+
readonly focusedAnchor: [number, number] | null;
|
|
248
|
+
/**
|
|
249
|
+
* Where `item`'s tooltip pins in the current view, without moving the camera: its `lngLat`,
|
|
250
|
+
* or for a shape (`geometry`) with its `lngLat` off-screen, a point of the shape in view.
|
|
251
|
+
*/
|
|
252
|
+
anchorFor(item: FeatureListItem): [number, number];
|
|
187
253
|
/** For MapView only: uses `gl` from now on. */
|
|
188
254
|
attach(gl: GlMapLike): void;
|
|
189
255
|
/** For MapView only: stops using the current GL map. */
|
|
@@ -197,7 +263,8 @@ export interface MapContext {
|
|
|
197
263
|
updateView(camera: CameraState, bounds: Bounds): void;
|
|
198
264
|
/**
|
|
199
265
|
* For MapView only: the host's theme changed, so plugins set up again and re-read the theme
|
|
200
|
-
* colors they bake into paint and images. Does nothing while detached.
|
|
266
|
+
* colors they bake into paint and images. Does nothing while detached. Sets plugins up
|
|
267
|
+
* synchronously, so never call it from inside an effect (see `PluginHost`).
|
|
201
268
|
*/
|
|
202
269
|
themeChanged(): void;
|
|
203
270
|
/** Detaches, removes every plugin, and drops handlers. The context can't be used after. */
|