@nika-js/onlymap 0.3.4 → 0.4.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/.vscode/onlymap.code-snippets +2 -0
- package/README.md +4 -3
- package/dist/{LercDecode.es-D6eXA5jP.js → LercDecode.es-CWLcQT4F.js} +1 -1
- package/dist/{basemap-BRh_zvzt.js → basemap-CZupn0P2.js} +8 -2
- package/dist/basemap.d.ts +8 -1
- package/dist/ctx.d.ts +4 -0
- package/dist/elements/om-map.d.ts +3 -3
- package/dist/external-store.d.ts +95 -0
- package/dist/{index-BR1HjJoE.js → index-BoxZ3Mec.js} +6009 -5892
- package/dist/{index-9_RmnPoW.js → index-C8uDiJN4.js} +1 -1
- package/dist/{index-xaNTkA9k.js → index-CC4aVelb.js} +1 -1
- package/dist/{index-B3NQy6CS.js → index-SWWESFOk.js} +2 -2
- package/dist/{index-Y_CL37KN.js → index-Vw-Top85.js} +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/{lerc-vBCRLD5s.js → lerc-CSc9BX_F.js} +2 -2
- package/dist/onlymap.standalone.js +6762 -6639
- package/dist/onlymapjs.js +20 -19
- package/dist/programmatic.d.ts +42 -5
- package/dist/{raster-BwULn87I.js → raster-DDi9gXqf.js} +2 -2
- package/dist/react.js +178 -143
- package/dist/runtime-core.d.ts +9 -2
- package/dist/version.d.ts +1 -1
- package/docs/external-stores.md +135 -0
- package/docs/react.md +3 -1
- package/llms.txt +3 -2
- package/package.json +10 -6
- package/skills/onlymapjs/SKILL.md +3 -2
- package/skills/onlymapjs/references/react.md +2 -1
- package/skills/onlymapjs/references/syntax.md +2 -2
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
" data=\"${4:../data/bikes.json}\"",
|
|
29
29
|
" label=\"${5:Points}\" color=\"${6:#e63946}\"",
|
|
30
30
|
" get-position=\"[\\$lon, \\$lat]\"",
|
|
31
|
+
" radius=\"${7:6}\" radius-units=\"pixels\"",
|
|
31
32
|
" pickable></om-layer>",
|
|
32
33
|
"",
|
|
33
34
|
" <!-- Rich HTML popup — follows the last click via anchor-from=\"selection\" -->",
|
|
@@ -110,6 +111,7 @@
|
|
|
110
111
|
" data=\"${2:../data/bikes.json}\"",
|
|
111
112
|
" label=\"${3:Points}\" color=\"${4:#e63946}\"",
|
|
112
113
|
" get-position=\"[\\$lon, \\$lat]\"",
|
|
114
|
+
" radius=\"${5:6}\" radius-units=\"pixels\"",
|
|
113
115
|
" pickable></om-layer>$0"
|
|
114
116
|
]
|
|
115
117
|
},
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Or with no build step at all, straight from a CDN:
|
|
|
69
69
|
|
|
70
70
|
The bare package URL serves `dist/onlymap.standalone.js`, a single-file bundle built for exactly this (jsDelivr too). Use a CDN that serves the package's raw files — **not** a rebundling CDN like esm.sh, which re-splits the bundle into duplicate copies of the deck.gl/luma.gl runtime and breaks every layer's shader compilation.
|
|
71
71
|
|
|
72
|
-
Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with
|
|
72
|
+
Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 503 unit/behavioral tests and 30 Playwright GPU tests.
|
|
73
73
|
|
|
74
74
|
The [examples](https://github.com/NikaGeospatial/onlymapjs/tree/main/examples) are the best tour: widgets, behaviors & overlays, basemaps, columnar/Arrow data, manual drawing, 3D models, scene lighting (with the native lighting widget), DEM terrain, a live WebSocket ship feed, and a polled driver fleet.
|
|
75
75
|
|
|
@@ -223,8 +223,9 @@ Plus `OmMap.snapshotIR(html)` to lock down what a manifest *means* in a snapshot
|
|
|
223
223
|
|
|
224
224
|
- **`OmMap.*`** — `validate`, `snapshotIR`, `registerLayer`, `registerWidget`, `registerAction`, `registerSource`, `registerFormat`, `registerBasemap`, `configureBasemap`, `configureData`, `configureTelemetry`, `configureLicense`, `getLayerSchema`
|
|
225
225
|
- **`@nika-js/onlymap/deck`** — the bundled deck.gl classes (`CompositeLayer`, `TileLayer`, …) for building custom layer types: shims must extend the same class hierarchy the core renders with, not a second installed deck.gl copy. Recipe: [docs/custom-layers.md](docs/custom-layers.md)
|
|
226
|
-
- **On a `<om-map>` element** — `ready` (promise), `flyTo(coords, zoom?)`, `setLayerVisible(id, bool)`, `getLayers()`, `emit(action, payload)`, `snapshot(opts?)` (canvas-only PNG of basemap + layers at device pixels — DOM widgets/overlays and provider attribution are NOT captured, so exports must render credits themselves; `{as: "blob"}` for files, default dataURL); the `om-view-changed` event fires once the camera settles (debounced; `detail` = `{longitude, latitude, zoom, pitch, bearing}`) — the camera-persistence hook; `document.querySelector("om-map")` is fully typed
|
|
227
|
-
- **`MapController`** — the framework-grade programmatic front-end (typed `LayerDescriptor`s → the same reconcile core, no DOM manifest): `setLayers`, `watch`, `emit`, camera methods, `injectPick`, `ready`, `snapshot`, an `onViewChange` option (the `om-view-changed` twin). The React adapter rides it; usable directly from vanilla TS or other frameworks
|
|
226
|
+
- **On a `<om-map>` element** — `ready` (promise), `flyTo(coords, zoom?)`, `setLayerVisible(id, bool)`, `getLayers()`, `emit(action, payload)`, `snapshot(opts?)` (canvas-only PNG of basemap + layers at device pixels — DOM widgets/overlays and provider attribution are NOT captured, so exports must render credits themselves; `{as: "blob"}` for files, default dataURL); the `om-view-changed` event fires once the camera settles (debounced; `detail` = `{longitude, latitude, zoom, pitch, bearing, origin}`, where `origin` is `"user"` for gesture-driven bursts vs `"programmatic"` for API/story moves — the echo-suppression signal for state sync) — the camera-persistence hook; `document.querySelector("om-map")` is fully typed
|
|
227
|
+
- **`MapController`** — the framework-grade programmatic front-end (typed `LayerDescriptor`s → the same reconcile core, no DOM manifest): `setLayers`, `watch`, `emit`, camera methods, `injectPick`, `ready`, `snapshot`, an `onViewChange(view, origin)` option (the `om-view-changed` twin). The React adapter rides it; usable directly from vanilla TS or other frameworks
|
|
228
|
+
- **`getStore(token)`** — the external-store contract: per-token `{subscribe, getSnapshot}` stores (`viewport`/`selection`/`layers`/`data:<id>`) with cached immutable plain-data snapshots and `origin` tagging — directly consumable by `useSyncExternalStore` (the React adapter's own hooks ride it), MobX autoruns, Redux listeners, Zustand mirrors. ~20-line integration-tested recipes for Redux Toolkit, MobX/mobx-keystone, Zustand, and Jotai: [docs/external-stores.md](docs/external-stores.md)
|
|
228
229
|
- **Testing** — `mountForTest`, and imports are SSR-safe (importing in Node/jsdom never touches browser globals)
|
|
229
230
|
|
|
230
231
|
## Free tier & licensing
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-
|
|
1
|
+
import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-BoxZ3Mec.js";
|
|
2
2
|
const Cd = Math.PI / 180, gy = 180 / Math.PI;
|
|
3
3
|
function em(Pe, Q = 0) {
|
|
4
4
|
const ge = Math.min(180, Pe) * Cd;
|
|
@@ -25177,8 +25177,14 @@ class Ry {
|
|
|
25177
25177
|
this.initialized ? Q() : this.loadCallbacks.push(Q);
|
|
25178
25178
|
}
|
|
25179
25179
|
/** Fires continuously during pan/zoom/rotate — MapLibre's own camera-change event, standing in for deck's onViewStateChange (which doesn't apply — deck isn't driving the camera). */
|
|
25180
|
+
/**
|
|
25181
|
+
* Viewport-change stream with origin tagging (spec: "External-Store
|
|
25182
|
+
* Contract") — MapLibre attaches `originalEvent` (the DOM pointer/wheel
|
|
25183
|
+
* event) to gesture-driven move events; our own easeTo/jumpTo/fitBounds
|
|
25184
|
+
* fire without one. The ecosystem's standard user-vs-programmatic signal.
|
|
25185
|
+
*/
|
|
25180
25186
|
onMove(Q) {
|
|
25181
|
-
this.map.on("move", Q);
|
|
25187
|
+
this.map.on("move", (ge) => Q(ge.originalEvent ? "user" : "programmatic"));
|
|
25182
25188
|
}
|
|
25183
25189
|
setLayers(Q) {
|
|
25184
25190
|
this.overlay.setProps({ layers: Q });
|
package/dist/basemap.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "maplibre-gl/dist/maplibre-gl.css";
|
|
2
2
|
import type { DeckProps, Viewport } from "@deck.gl/core";
|
|
3
3
|
import type { ResolvedBasemap } from "./basemap-registry";
|
|
4
|
+
import type { ViewOrigin } from "./external-store";
|
|
4
5
|
export interface ViewportLike {
|
|
5
6
|
project(xyz: number[]): number[];
|
|
6
7
|
getBounds(): [number, number, number, number];
|
|
@@ -65,7 +66,13 @@ export declare class MapLibreBasemapAdapter {
|
|
|
65
66
|
private markInitialized;
|
|
66
67
|
onLoad(cb: () => void): void;
|
|
67
68
|
/** Fires continuously during pan/zoom/rotate — MapLibre's own camera-change event, standing in for deck's onViewStateChange (which doesn't apply — deck isn't driving the camera). */
|
|
68
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Viewport-change stream with origin tagging (spec: "External-Store
|
|
71
|
+
* Contract") — MapLibre attaches `originalEvent` (the DOM pointer/wheel
|
|
72
|
+
* event) to gesture-driven move events; our own easeTo/jumpTo/fitBounds
|
|
73
|
+
* fire without one. The ecosystem's standard user-vs-programmatic signal.
|
|
74
|
+
*/
|
|
75
|
+
onMove(cb: (origin: ViewOrigin) => void): void;
|
|
69
76
|
setLayers(layers: DeckProps["layers"]): void;
|
|
70
77
|
/**
|
|
71
78
|
* Scene lighting (spec: "Scene Lighting") — MapboxOverlay accepts full
|
package/dist/ctx.d.ts
CHANGED
|
@@ -57,5 +57,9 @@ export interface BuildCtxDeps {
|
|
|
57
57
|
canRedo: boolean;
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
/** `ctx.viewport.bounds` / the `viewport` external-store snapshot — one bounds derivation (incl. the world-extent fallback) for both (spec: "External-Store Contract"). */
|
|
61
|
+
export declare function viewportBounds(viewport: ReturnType<RuntimeCore["getViewport"]>): [[number, number], [number, number]];
|
|
62
|
+
/** `ctx.layers` / the `layers` external store — one builder for both (spec: "External-Store Contract"). */
|
|
63
|
+
export declare function buildLayerMetas(layerIRs: ReadonlyMap<string, LayerIR>): LayerMetaSnapshot[];
|
|
60
64
|
/** Built fresh on every widget-render invocation — never mutated, never reused. */
|
|
61
65
|
export declare function buildCtx({ layerIRs, core, mapEl, selection, history }: BuildCtxDeps): RuntimeContext;
|
|
@@ -47,14 +47,12 @@ export declare class OmMapElement extends HTMLElementBase {
|
|
|
47
47
|
private readyFired;
|
|
48
48
|
private resolveReady;
|
|
49
49
|
readonly ready: Promise<void>;
|
|
50
|
-
private
|
|
50
|
+
private readonly viewSettle;
|
|
51
51
|
private firstReconcileDone;
|
|
52
52
|
private rendererLoadPending;
|
|
53
53
|
private loadDispatched;
|
|
54
54
|
connectedCallback(): void;
|
|
55
55
|
disconnectedCallback(): void;
|
|
56
|
-
/** Trailing-debounced `om-view-changed` dispatch — see the field's doc comment. */
|
|
57
|
-
private scheduleViewChangedEvent;
|
|
58
56
|
registerWidgetInternal(el: Element, watch: string[], notify: (ctx: RuntimeContext) => void): void;
|
|
59
57
|
unregisterWidgetInternal(el: Element): void;
|
|
60
58
|
registerOverlayInternal(overlay: OverlayHost): void;
|
|
@@ -192,6 +190,8 @@ declare global {
|
|
|
192
190
|
zoom: number;
|
|
193
191
|
pitch: number;
|
|
194
192
|
bearing: number;
|
|
193
|
+
/** "user" if any change in the settled burst came from a canvas gesture; "programmatic" for pure API/action/story moves. */
|
|
194
|
+
origin: "user" | "programmatic";
|
|
195
195
|
}>;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External-store contract (spec: "External-Store Contract — `getStore` &
|
|
3
|
+
* `useSyncExternalStore`") — the framework-free `{subscribe, getSnapshot}`
|
|
4
|
+
* surface `MapController.getStore(token)` hands to React's
|
|
5
|
+
* `useSyncExternalStore`, MobX autoruns, Redux listeners, Zustand mirrors,
|
|
6
|
+
* and any future framework adapter. One contract, N consumers. Also home to
|
|
7
|
+
* the shared settle/burst-origin tracker both front-ends' "camera settled"
|
|
8
|
+
* signals ride — one settle contract, two front-ends, one implementation.
|
|
9
|
+
*
|
|
10
|
+
* The load-bearing rule is CACHED IDENTITY: `getSnapshot()` returns the
|
|
11
|
+
* SAME reference until that token's event fires (a fresh object per call
|
|
12
|
+
* makes `useSyncExternalStore` loop forever — zustand v5's headline
|
|
13
|
+
* breaking change was exactly this fix). Invalidation only marks dirty;
|
|
14
|
+
* the next read rebuilds — a 60fps camera stream with no subscribers
|
|
15
|
+
* builds nothing.
|
|
16
|
+
*
|
|
17
|
+
* Snapshots are PLAIN SERIALIZABLE DATA — no closures — so Redux devtools
|
|
18
|
+
* and persistence middleware never choke on them. Rich reads (`project`,
|
|
19
|
+
* `data()`, `stats()`) stay on `ctx`; `data:<id>` exposes a VERSION STAMP,
|
|
20
|
+
* not the rows (mirroring a 100k-row array into a store per tick is the
|
|
21
|
+
* anti-pattern the Redux performance guidance warns about).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Who moved the camera: a pointer/wheel gesture on the canvas ("user") or
|
|
25
|
+
* a camera API / action / transition frame ("programmatic"). The echo-loop
|
|
26
|
+
* half of two-way store binding — a bridge writes back only user-originated
|
|
27
|
+
* changes; the map's idempotent setters are the other half.
|
|
28
|
+
*/
|
|
29
|
+
export type ViewOrigin = "user" | "programmatic";
|
|
30
|
+
/** The per-token store — directly consumable by `useSyncExternalStore(store.subscribe, store.getSnapshot)`. */
|
|
31
|
+
export interface TokenStore<T = unknown> {
|
|
32
|
+
/** `cb` fires after this token's state changed (read the new snapshot inside it). Returns the unsubscribe function. */
|
|
33
|
+
subscribe(cb: () => void): () => void;
|
|
34
|
+
/** Cached immutable snapshot — the SAME reference until the next change event for this token. */
|
|
35
|
+
getSnapshot(): T;
|
|
36
|
+
}
|
|
37
|
+
/** `viewport` store snapshot — CameraState plus bounds and the origin of the current settle burst. */
|
|
38
|
+
export interface ViewportSnapshot {
|
|
39
|
+
longitude: number;
|
|
40
|
+
latitude: number;
|
|
41
|
+
zoom: number;
|
|
42
|
+
pitch: number;
|
|
43
|
+
bearing: number;
|
|
44
|
+
/** [[west, south], [east, north]] — same shape as `ctx.viewport.bounds`. */
|
|
45
|
+
bounds: [[number, number], [number, number]];
|
|
46
|
+
/**
|
|
47
|
+
* Burst-latched origin: "user" if any change since the last settle was a
|
|
48
|
+
* gesture — so a drag's inertia tail (MapLibre inertial moves carry no
|
|
49
|
+
* `originalEvent`) can't relabel the gesture mid-burst. Matches what the
|
|
50
|
+
* settled signals (`om-view-changed`, `onViewChange`) report.
|
|
51
|
+
*/
|
|
52
|
+
origin: ViewOrigin;
|
|
53
|
+
}
|
|
54
|
+
/** `data:<layerId>` store snapshot — a version stamp, not the rows (fetch rows via `ctx.data()`). */
|
|
55
|
+
export interface DataVersionSnapshot {
|
|
56
|
+
/** Bumps on every data change for the layer (load, stream tick, poll refresh). 0 = never loaded / unknown layer. */
|
|
57
|
+
version: number;
|
|
58
|
+
/** Current row count (0 for unknown layers). */
|
|
59
|
+
rows: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The store-token roster — ONE source of truth shared by
|
|
63
|
+
* `MapController.getStore` (throws otherwise) and the React adapter's
|
|
64
|
+
* warn-skip, so the two can never disagree about which tokens are stores.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isStoreToken(token: string): boolean;
|
|
67
|
+
/** The per-token store table — memoized store objects (calling `get` twice for one token returns the identical store). */
|
|
68
|
+
export declare class TokenStoreRegistry {
|
|
69
|
+
private readonly entries;
|
|
70
|
+
/** Memoized per token — `build` is captured on first call and reused. */
|
|
71
|
+
get<T>(token: string, build: () => T): TokenStore<T>;
|
|
72
|
+
/** Marks the token dirty and notifies its subscribers. Unknown/never-requested tokens no-op (nothing to invalidate). */
|
|
73
|
+
invalidate(token: string): void;
|
|
74
|
+
/** Drop every entry and subscriber — controller teardown: a consumer that never unsubscribed must not pin the controller in memory. */
|
|
75
|
+
clear(): void;
|
|
76
|
+
}
|
|
77
|
+
/** Trailing debounce for the settled-camera signals — long enough to coalesce a pan/flight's per-frame churn, short enough that camera persistence feels immediate. */
|
|
78
|
+
export declare const VIEW_SETTLE_DEBOUNCE_MS = 150;
|
|
79
|
+
/**
|
|
80
|
+
* The settle/burst tracker behind `om-view-changed` and `onViewChange` —
|
|
81
|
+
* shared by both front-ends so the debounce window and the burst-origin
|
|
82
|
+
* rule ("user" if ANY change since the last settle was a gesture; an
|
|
83
|
+
* inertia tail can't relabel the gesture) can never drift apart.
|
|
84
|
+
*/
|
|
85
|
+
export declare class ViewSettleTracker {
|
|
86
|
+
private readonly onSettle;
|
|
87
|
+
private timer;
|
|
88
|
+
private burstHadUser;
|
|
89
|
+
constructor(onSettle: (origin: ViewOrigin) => void);
|
|
90
|
+
/** Record one viewport change and (re)arm the trailing settle timer. */
|
|
91
|
+
bump(origin: ViewOrigin): void;
|
|
92
|
+
/** Burst-latched origin for snapshots taken mid-burst: the gesture wins over a trailing programmatic frame. */
|
|
93
|
+
latchedOrigin(lastFrameOrigin: ViewOrigin): ViewOrigin;
|
|
94
|
+
cancel(): void;
|
|
95
|
+
}
|