@nika-js/onlymap 0.2.0 → 0.2.1
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 +7 -5
- package/dist/{basemap-C0RKcLaL.js → basemap-COurZNDH.js} +2126 -2105
- package/dist/basemap-registry.d.ts +49 -0
- package/dist/basemap.d.ts +22 -2
- package/dist/html-data.d.ts +2 -2
- package/dist/{index-CZf9WlZe.js → index-COu-3-gN.js} +1 -1
- package/dist/{index-DeEur5Xk.js → index-CgyAD98B.js} +1 -1
- package/dist/{index-DnvxPnDF.js → index-CvfuISOc.js} +11661 -11466
- package/dist/{index-BDJ9hHXv.js → index-D-X8KPA1.js} +1 -1
- package/dist/{index-GTGi85ZO.js → index-DuvXK95V.js} +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/onlymapjs.js +20 -17
- package/dist/onlymapjs.umd.cjs +277 -271
- package/dist/programmatic.d.ts +11 -1
- package/dist/react/om-map.d.ts +5 -1
- package/dist/react.js +136 -128
- package/dist/runtime-core.d.ts +34 -1
- package/dist/widget-registry.d.ts +2 -0
- package/docs/basemaps.md +89 -0
- package/docs/react.md +1 -1
- package/llms.txt +3 -3
- package/onlymapjs.html-data.json +57 -1
- package/package.json +1 -1
- package/skills/onlymapjs/SKILL.md +14 -2
- package/skills/onlymapjs/references/react.md +68 -0
- package/skills/onlymapjs/references/syntax.md +5 -1
package/dist/programmatic.d.ts
CHANGED
|
@@ -75,8 +75,12 @@ export interface MapControllerOptions {
|
|
|
75
75
|
zoom?: number;
|
|
76
76
|
pitch?: number;
|
|
77
77
|
bearing?: number;
|
|
78
|
-
/** Same contract as the `basemap` attribute — "maplibre", a style URL, or omit for standalone deck.gl. */
|
|
78
|
+
/** Same contract as the `basemap` attribute — a preset name, "maplibre", a style URL, or omit for standalone deck.gl. */
|
|
79
79
|
basemap?: string;
|
|
80
|
+
/** Publishable provider key for keyed presets (`maptiler-*`) — the `basemap-key` attribute's programmatic twin (configureBasemap covers the global case). */
|
|
81
|
+
basemapKey?: string;
|
|
82
|
+
/** Set false to opt out of the compact attribution control (render your own credits). */
|
|
83
|
+
attribution?: boolean;
|
|
80
84
|
/** Headless mode (spec: "Consumer Testing Surface") — no renderer, real projection math; for jsdom/happy-dom tests. */
|
|
81
85
|
headless?: {
|
|
82
86
|
width?: number;
|
|
@@ -166,6 +170,12 @@ export declare class MapController {
|
|
|
166
170
|
private scheduleOverlayFlush;
|
|
167
171
|
/** Dispatches an action (spec: "Action payload contract") — camera moves, pulse/populate effects, custom registered actions. */
|
|
168
172
|
emit(event: string, payload?: Record<string, unknown>): void;
|
|
173
|
+
/**
|
|
174
|
+
* Live basemap change — a preset name, style URL, or "none". Style-only
|
|
175
|
+
* switches keep the renderer, camera, and layers; renderer-mode changes
|
|
176
|
+
* (none ↔ maplibre) remount seeded with the current camera.
|
|
177
|
+
*/
|
|
178
|
+
setBasemap(basemap: string | null): void;
|
|
169
179
|
flyTo(center: [number, number], zoom?: number, opts?: CameraOptions): void;
|
|
170
180
|
flyToBounds(bounds: [[number, number], [number, number]], padding?: number, opts?: CameraOptions): void;
|
|
171
181
|
setView(partial: Partial<CameraState>, opts?: CameraOptions): void;
|
package/dist/react/om-map.d.ts
CHANGED
|
@@ -7,8 +7,12 @@ export interface OmMapProps {
|
|
|
7
7
|
zoom?: number;
|
|
8
8
|
pitch?: number;
|
|
9
9
|
bearing?: number;
|
|
10
|
-
/** Same contract as the `basemap` attribute — "maplibre", a style URL, or omit for standalone deck.gl. Changing it remounts the renderer. */
|
|
10
|
+
/** Same contract as the `basemap` attribute — a preset name ("positron", "dark-matter", …), "maplibre", a style URL, or omit for standalone deck.gl. Changing it switches live (style-only changes keep camera + layers; none ↔ maplibre remounts the renderer inside the controller). */
|
|
11
11
|
basemap?: string;
|
|
12
|
+
/** Publishable provider key for keyed presets (maptiler-*) — or call configureBasemap once instead. */
|
|
13
|
+
basemapKey?: string;
|
|
14
|
+
/** Set false to opt out of the compact attribution control. */
|
|
15
|
+
attribution?: boolean;
|
|
12
16
|
/** Headless mode for tests (spec: "Consumer Testing Surface") — no renderer, real projection math under jsdom/happy-dom. */
|
|
13
17
|
headless?: boolean | {
|
|
14
18
|
width?: number;
|
package/dist/react.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { jsxs as
|
|
5
|
-
import { createContext as
|
|
6
|
-
import { MapController as
|
|
7
|
-
import { createPortal as
|
|
8
|
-
const
|
|
9
|
-
function
|
|
10
|
-
const t =
|
|
1
|
+
var J = Object.defineProperty;
|
|
2
|
+
var Q = (e, t, n) => t in e ? J(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
3
|
+
var A = (e, t, n) => Q(e, typeof t != "symbol" ? t + "" : t, n);
|
|
4
|
+
import { jsxs as U, jsx as x } from "react/jsx-runtime";
|
|
5
|
+
import { createContext as X, useContext as Y, forwardRef as Z, useRef as f, useState as k, useCallback as ee, useMemo as I, useLayoutEffect as te, useEffect as g, useImperativeHandle as re } from "react";
|
|
6
|
+
import { MapController as ne } from "@nika-js/onlymap";
|
|
7
|
+
import { createPortal as B } from "react-dom";
|
|
8
|
+
const H = X(null);
|
|
9
|
+
function K(e) {
|
|
10
|
+
const t = Y(H);
|
|
11
11
|
if (!t) throw new Error(`[onlymapjs] ${e} must be rendered inside <OmMap>.`);
|
|
12
12
|
return t;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function D(e, t) {
|
|
15
15
|
if (e === t) return !0;
|
|
16
16
|
const n = Object.keys(e ?? {}), r = Object.keys(t ?? {});
|
|
17
17
|
return n.length !== r.length ? !1 : n.every((l) => e[l] === t[l]);
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
const { props: n, updateTriggers: r, ...l } = e, { props: a, updateTriggers: u, ...
|
|
21
|
-
return
|
|
19
|
+
function oe(e, t) {
|
|
20
|
+
const { props: n, updateTriggers: r, ...l } = e, { props: a, updateTriggers: u, ...i } = t;
|
|
21
|
+
return D(l, i) && D(n, a) && D(r, u);
|
|
22
22
|
}
|
|
23
|
-
class
|
|
23
|
+
class se {
|
|
24
24
|
constructor(t) {
|
|
25
|
-
|
|
25
|
+
A(this, "descriptors", /* @__PURE__ */ new Map());
|
|
26
26
|
this.controller = t;
|
|
27
27
|
}
|
|
28
28
|
upsert(t) {
|
|
29
29
|
const n = this.descriptors.get(t.id);
|
|
30
|
-
n &&
|
|
30
|
+
n && oe(n, t) || (this.descriptors.set(t.id, t), this.controller.setLayers([...this.descriptors.values()]));
|
|
31
31
|
}
|
|
32
32
|
remove(t) {
|
|
33
33
|
this.descriptors.delete(t) && this.controller.setLayers([...this.descriptors.values()]);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
const
|
|
37
|
-
function
|
|
36
|
+
const q = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
37
|
+
function le(e) {
|
|
38
38
|
return {
|
|
39
39
|
position: "absolute",
|
|
40
40
|
[e.startsWith("top") ? "top" : "bottom"]: 10,
|
|
@@ -49,94 +49,102 @@ function re(e) {
|
|
|
49
49
|
pointerEvents: "none"
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function ie(e, t) {
|
|
53
53
|
return Object.entries(t).some(([n, r]) => r !== void 0 && e[n] !== r);
|
|
54
54
|
}
|
|
55
|
-
const
|
|
56
|
-
const { center: r, zoom: l, pitch: a, bearing: u, basemap: c, headless:
|
|
55
|
+
const he = Z(function(t, n) {
|
|
56
|
+
const { center: r, zoom: l, pitch: a, bearing: u, basemap: i, basemapKey: c, attribution: y, headless: m, className: O, style: z, children: R } = t, d = f(null), [o, T] = k(null), [C, M] = k(null), [j, L] = k({
|
|
57
57
|
"top-left": null,
|
|
58
58
|
"top-right": null,
|
|
59
59
|
"bottom-left": null,
|
|
60
60
|
"bottom-right": null
|
|
61
|
-
}),
|
|
62
|
-
|
|
63
|
-
}, []),
|
|
61
|
+
}), v = ee((s) => {
|
|
62
|
+
M((h) => h === s ? h : s);
|
|
63
|
+
}, []), w = I(
|
|
64
64
|
() => Object.fromEntries(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
(
|
|
68
|
-
|
|
65
|
+
q.map((s) => [
|
|
66
|
+
s,
|
|
67
|
+
(h) => {
|
|
68
|
+
L((p) => p[s] === h ? p : { ...p, [s]: h });
|
|
69
69
|
}
|
|
70
70
|
])
|
|
71
71
|
),
|
|
72
72
|
[]
|
|
73
|
-
),
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const N =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
73
|
+
), S = f({ center: r, zoom: l, pitch: a, bearing: u });
|
|
74
|
+
S.current = { center: r, zoom: l, pitch: a, bearing: u };
|
|
75
|
+
const b = f(t.onReady);
|
|
76
|
+
b.current = t.onReady;
|
|
77
|
+
const E = f(t.onViewStateChange);
|
|
78
|
+
E.current = t.onViewStateChange;
|
|
79
|
+
const P = f(t.onRuntimeError);
|
|
80
|
+
P.current = t.onRuntimeError;
|
|
81
|
+
const N = f(i);
|
|
82
|
+
N.current = i;
|
|
83
|
+
const V = f(void 0), G = m ? JSON.stringify(m) : "";
|
|
84
|
+
te(() => {
|
|
85
|
+
if (!d.current) return;
|
|
86
|
+
const s = S.current;
|
|
87
|
+
V.current = N.current;
|
|
88
|
+
const h = new ne(d.current, {
|
|
89
|
+
center: s.center,
|
|
90
|
+
zoom: s.zoom,
|
|
91
|
+
pitch: s.pitch,
|
|
92
|
+
bearing: s.bearing,
|
|
93
|
+
basemap: N.current,
|
|
94
|
+
basemapKey: c,
|
|
95
|
+
attribution: y,
|
|
96
|
+
headless: m ? m === !0 ? {} : m : void 0,
|
|
97
|
+
onRuntimeError: (F) => {
|
|
98
|
+
var $;
|
|
99
|
+
return ($ = P.current) == null ? void 0 : $.call(P, F);
|
|
94
100
|
}
|
|
95
101
|
});
|
|
96
|
-
|
|
97
|
-
let
|
|
98
|
-
return
|
|
99
|
-
var
|
|
100
|
-
|
|
102
|
+
T(h);
|
|
103
|
+
let p = !0;
|
|
104
|
+
return h.ready.then(() => {
|
|
105
|
+
var F;
|
|
106
|
+
p && ((F = b.current) == null || F.call(b));
|
|
101
107
|
}), () => {
|
|
102
|
-
|
|
108
|
+
p = !1, T(null), h.destroy();
|
|
103
109
|
};
|
|
104
|
-
}, [
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
}, [G]), g(() => {
|
|
111
|
+
!o || V.current === i || (V.current = i, o.setBasemap(i ?? null));
|
|
112
|
+
}, [o, i]), re(n, () => o, [o]), g(() => {
|
|
113
|
+
if (!o) return;
|
|
114
|
+
const s = { longitude: r == null ? void 0 : r[0], latitude: r == null ? void 0 : r[1], zoom: l, pitch: a, bearing: u };
|
|
115
|
+
ie(o.getViewState(), s) && o.setView(s);
|
|
116
|
+
}, [o, r == null ? void 0 : r[0], r == null ? void 0 : r[1], l, a, u]), g(() => {
|
|
117
|
+
if (o)
|
|
118
|
+
return o.watch(["viewport"], () => {
|
|
119
|
+
var s;
|
|
120
|
+
return (s = E.current) == null ? void 0 : s.call(E, o.getViewState());
|
|
113
121
|
});
|
|
114
|
-
}, [
|
|
115
|
-
const
|
|
116
|
-
() =>
|
|
117
|
-
[
|
|
122
|
+
}, [o]);
|
|
123
|
+
const W = I(() => o ? new se(o) : null, [o]), _ = I(
|
|
124
|
+
() => o && W && C ? { controller: o, layers: W, overlayPane: C, corners: j } : null,
|
|
125
|
+
[o, W, C, j]
|
|
118
126
|
);
|
|
119
|
-
return /* @__PURE__ */
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
/* @__PURE__ */
|
|
127
|
+
return /* @__PURE__ */ U("div", { className: O, style: { position: "relative", overflow: "hidden", ...z }, children: [
|
|
128
|
+
/* @__PURE__ */ x("div", { ref: d, style: { position: "absolute", inset: 0 } }),
|
|
129
|
+
/* @__PURE__ */ x(
|
|
122
130
|
"div",
|
|
123
131
|
{
|
|
124
|
-
ref:
|
|
132
|
+
ref: v,
|
|
125
133
|
style: { position: "absolute", top: 0, left: 0, zIndex: 10, pointerEvents: "none" }
|
|
126
134
|
}
|
|
127
135
|
),
|
|
128
|
-
|
|
136
|
+
q.map((s) => /* @__PURE__ */ x(
|
|
129
137
|
"div",
|
|
130
138
|
{
|
|
131
|
-
"data-om-widget-corner":
|
|
132
|
-
ref:
|
|
133
|
-
style:
|
|
139
|
+
"data-om-widget-corner": s,
|
|
140
|
+
ref: w[s],
|
|
141
|
+
style: le(s)
|
|
134
142
|
},
|
|
135
|
-
|
|
143
|
+
s
|
|
136
144
|
)),
|
|
137
|
-
|
|
145
|
+
_ && /* @__PURE__ */ x(H.Provider, { value: _, children: R })
|
|
138
146
|
] });
|
|
139
|
-
}),
|
|
147
|
+
}), ae = /* @__PURE__ */ new Set([
|
|
140
148
|
"id",
|
|
141
149
|
"type",
|
|
142
150
|
"data",
|
|
@@ -155,10 +163,10 @@ const fe = Q(function(t, n) {
|
|
|
155
163
|
"onClick",
|
|
156
164
|
"onHover"
|
|
157
165
|
]);
|
|
158
|
-
function
|
|
159
|
-
const { controller: t, layers: n } =
|
|
160
|
-
for (const [
|
|
161
|
-
!
|
|
166
|
+
function ge(e) {
|
|
167
|
+
const { controller: t, layers: n } = K("OmLayer"), { id: r } = e, l = {};
|
|
168
|
+
for (const [c, y] of Object.entries(e))
|
|
169
|
+
!ae.has(c) && y !== void 0 && (l[c] = y);
|
|
162
170
|
const a = {
|
|
163
171
|
id: r,
|
|
164
172
|
type: e.type,
|
|
@@ -177,32 +185,32 @@ function de(e) {
|
|
|
177
185
|
updateTriggers: e.updateTriggers,
|
|
178
186
|
props: l
|
|
179
187
|
};
|
|
180
|
-
|
|
188
|
+
g(() => {
|
|
181
189
|
n.upsert(a);
|
|
182
|
-
}),
|
|
183
|
-
const u =
|
|
190
|
+
}), g(() => () => n.remove(r), [n, r]);
|
|
191
|
+
const u = f(e.onClick);
|
|
184
192
|
u.current = e.onClick;
|
|
185
|
-
const
|
|
186
|
-
return
|
|
187
|
-
() => t.watchPicks((
|
|
188
|
-
var y,
|
|
189
|
-
if (
|
|
190
|
-
(y =
|
|
193
|
+
const i = f(e.onHover);
|
|
194
|
+
return i.current = e.onHover, g(
|
|
195
|
+
() => t.watchPicks((c) => {
|
|
196
|
+
var y, m, O;
|
|
197
|
+
if (c === null) {
|
|
198
|
+
(y = i.current) == null || y.call(i, null);
|
|
191
199
|
return;
|
|
192
200
|
}
|
|
193
|
-
|
|
201
|
+
c.layerId === r && (c.type === "click" ? (m = u.current) == null || m.call(u, c) : (O = i.current) == null || O.call(i, c));
|
|
194
202
|
}),
|
|
195
203
|
[t, r]
|
|
196
204
|
), null;
|
|
197
205
|
}
|
|
198
|
-
function
|
|
199
|
-
const { corners: l } =
|
|
200
|
-
return a ?
|
|
201
|
-
/* @__PURE__ */
|
|
206
|
+
function ve({ position: e = "top-left", className: t, style: n, children: r }) {
|
|
207
|
+
const { corners: l } = K("OmWidget"), a = l[e];
|
|
208
|
+
return a ? B(
|
|
209
|
+
/* @__PURE__ */ x("div", { className: t, style: { pointerEvents: "auto", ...n }, children: r }),
|
|
202
210
|
a
|
|
203
211
|
) : null;
|
|
204
212
|
}
|
|
205
|
-
const
|
|
213
|
+
const ce = {
|
|
206
214
|
"top-left": "translate(0, 0)",
|
|
207
215
|
"top-center": "translate(-50%, 0)",
|
|
208
216
|
"top-right": "translate(-100%, 0)",
|
|
@@ -213,32 +221,32 @@ const se = {
|
|
|
213
221
|
"bottom-center": "translate(-50%, -100%)",
|
|
214
222
|
"bottom-right": "translate(-100%, -100%)"
|
|
215
223
|
};
|
|
216
|
-
function
|
|
217
|
-
const { controller: t, overlayPane: n } =
|
|
218
|
-
|
|
219
|
-
() => t.registerOverlay((
|
|
220
|
-
const
|
|
221
|
-
if (!
|
|
222
|
-
const { anchor:
|
|
223
|
-
let
|
|
224
|
-
if (
|
|
225
|
-
|
|
224
|
+
function be(e) {
|
|
225
|
+
const { controller: t, overlayPane: n } = K("OmOverlay"), r = f(null), l = f(void 0), [a, u] = k(null), i = f(e);
|
|
226
|
+
i.current = e, g(
|
|
227
|
+
() => t.registerOverlay((R, d) => {
|
|
228
|
+
const o = r.current;
|
|
229
|
+
if (!o) return;
|
|
230
|
+
const { anchor: T, anchorFrom: C, layer: M, visible: j = !0, anchorOffset: L } = i.current;
|
|
231
|
+
let v;
|
|
232
|
+
if (C === "selection" ? ((!M || (d == null ? void 0 : d.layerId) === M) && (l.current = (d == null ? void 0 : d.coordinate) ?? void 0, u(d)), v = l.current) : v = T, !j || !v || !R) {
|
|
233
|
+
o.style.visibility = "hidden";
|
|
226
234
|
return;
|
|
227
235
|
}
|
|
228
|
-
const [
|
|
229
|
-
if (!(
|
|
230
|
-
|
|
236
|
+
const [w, S, b] = R.project([v[0], v[1], 0]);
|
|
237
|
+
if (!(w >= 0 && w <= R.width && S >= 0 && S <= R.height) || b !== void 0 && b < 0) {
|
|
238
|
+
o.style.visibility = "hidden";
|
|
231
239
|
return;
|
|
232
240
|
}
|
|
233
|
-
|
|
241
|
+
o.style.visibility = "visible", o.style.transform = `translate(${w}px, ${S}px) ${ce[L ?? "bottom-center"]}`;
|
|
234
242
|
}),
|
|
235
243
|
[t]
|
|
236
|
-
),
|
|
244
|
+
), g(() => {
|
|
237
245
|
t.refreshOverlays();
|
|
238
246
|
});
|
|
239
|
-
const { children:
|
|
240
|
-
return
|
|
241
|
-
/* @__PURE__ */
|
|
247
|
+
const { children: c, className: y, style: m, interactive: O = !0 } = e, z = typeof c == "function" ? c(a) : c;
|
|
248
|
+
return B(
|
|
249
|
+
/* @__PURE__ */ x(
|
|
242
250
|
"div",
|
|
243
251
|
{
|
|
244
252
|
ref: r,
|
|
@@ -250,23 +258,23 @@ function he(e) {
|
|
|
250
258
|
visibility: "hidden",
|
|
251
259
|
// hidden until the first flush positions it — never flashes at the origin
|
|
252
260
|
willChange: "transform",
|
|
253
|
-
pointerEvents:
|
|
254
|
-
...
|
|
261
|
+
pointerEvents: O ? "auto" : "none",
|
|
262
|
+
...m
|
|
255
263
|
},
|
|
256
|
-
children:
|
|
264
|
+
children: z
|
|
257
265
|
}
|
|
258
266
|
),
|
|
259
267
|
n
|
|
260
268
|
);
|
|
261
269
|
}
|
|
262
|
-
function
|
|
263
|
-
const { controller: t } =
|
|
264
|
-
return
|
|
270
|
+
function Oe(e = []) {
|
|
271
|
+
const { controller: t } = K("useOmMap"), n = e.join(" "), r = I(() => n ? n.split(" ") : [], [n]), [l, a] = k(() => t.buildCtx());
|
|
272
|
+
return g(() => t.watch(r, a), [t, r]), l;
|
|
265
273
|
}
|
|
266
274
|
export {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
ge as OmLayer,
|
|
276
|
+
he as OmMap,
|
|
277
|
+
be as OmOverlay,
|
|
278
|
+
ve as OmWidget,
|
|
279
|
+
Oe as useOmMap
|
|
272
280
|
};
|
package/dist/runtime-core.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface HeadlessOptions {
|
|
|
56
56
|
width: number;
|
|
57
57
|
height: number;
|
|
58
58
|
}
|
|
59
|
+
/** Basemap-preset options (spec: "Basemap presets & switching") — the publishable provider key and the attribution opt-out. */
|
|
60
|
+
export interface BasemapRuntimeOptions {
|
|
61
|
+
/** `basemap-key` — substituted into keyed presets' `{key}` placeholder (falls back to configureBasemap). */
|
|
62
|
+
key?: string;
|
|
63
|
+
/** `attribution="false"` opt-out — default true: the compact attribution control renders whenever a basemap is active. */
|
|
64
|
+
attribution?: boolean;
|
|
65
|
+
}
|
|
59
66
|
export declare class RuntimeCore {
|
|
60
67
|
private deck?;
|
|
61
68
|
private basemap?;
|
|
@@ -77,7 +84,33 @@ export declare class RuntimeCore {
|
|
|
77
84
|
private destroyed;
|
|
78
85
|
private viewState;
|
|
79
86
|
private callbacks;
|
|
80
|
-
|
|
87
|
+
private parent;
|
|
88
|
+
/** The live `basemap` attribute value — re-read by the lazy adapter chunk so a switch during its load lands the LATEST style. */
|
|
89
|
+
private basemapAttr?;
|
|
90
|
+
private basemapOptions?;
|
|
91
|
+
/** Bumped on every renderer (re)init — a superseded lazy chunk load must not install its adapter over a newer renderer. */
|
|
92
|
+
private rendererGeneration;
|
|
93
|
+
constructor(parent: HTMLElement, initialView: InitialView, callbacks?: RuntimeCoreCallbacks, basemapAttr?: string, headless?: HeadlessOptions, basemapOptions?: BasemapRuntimeOptions);
|
|
94
|
+
/** `basemap` attribute → concrete style, logging resolution problems (unknown preset / missing key) — the demo-style fallback still renders. */
|
|
95
|
+
private resolveBasemap;
|
|
96
|
+
/**
|
|
97
|
+
* Constructs the renderer for the requested mode — the constructor's
|
|
98
|
+
* body, extracted so setBasemap() can remount on a renderer-MODE change
|
|
99
|
+
* (none ↔ maplibre). Reads/seeds the camera from `this.viewState`.
|
|
100
|
+
*/
|
|
101
|
+
private initRenderer;
|
|
102
|
+
/**
|
|
103
|
+
* Live basemap change (spec: "Basemap presets & switching"), both paths:
|
|
104
|
+
* - maplibre → maplibre: `map.setStyle()` — deck layers survive (the
|
|
105
|
+
* overlay is a control, not style layers) and the camera is untouched.
|
|
106
|
+
* - renderer-MODE change (none ↔ maplibre, or a mapbox fallback): full
|
|
107
|
+
* remount seeded with the current camera; the retained IRs are re-handed
|
|
108
|
+
* to the new renderer, so the scene carries over.
|
|
109
|
+
* Headless ignores it (no renderer to switch); `om-map-ready` semantics
|
|
110
|
+
* are unaffected (readiness settles once, at initial load).
|
|
111
|
+
*/
|
|
112
|
+
setBasemap(attr: string | null): void;
|
|
113
|
+
private teardownRenderer;
|
|
81
114
|
/**
|
|
82
115
|
* Rebuilds every deck.gl Layer instance from the current IR and hands the
|
|
83
116
|
* full array to deck.gl (or the basemap's MapboxOverlay) on every
|
|
@@ -11,6 +11,8 @@ export interface WidgetHost {
|
|
|
11
11
|
$$(sel: string): NodeListOf<Element>;
|
|
12
12
|
getAttribute(name: string): string | null;
|
|
13
13
|
hasAttribute(name: string): boolean;
|
|
14
|
+
/** Element.closest — satisfied structurally by the widget element itself; widgets needing owning-map attributes use closest("om-map") (e.g. basemap-switcher's current selection). */
|
|
15
|
+
closest(selector: string): Element | null;
|
|
14
16
|
/** Per-instance mutable stash for state that must survive across renders (e.g. a Vega view to `.finalize()` before re-embedding). */
|
|
15
17
|
state: Record<string, unknown>;
|
|
16
18
|
}
|
package/docs/basemaps.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Basemaps
|
|
2
|
+
|
|
3
|
+
The `basemap` attribute on `<om-map>` selects what renders under your data layers. It accepts a **preset name**, a **MapLibre style URL**, `"maplibre"` (the demo style), or `"none"` (standalone deck.gl canvas). No preset needs a token except the MapTiler ones.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<om-map center="[-122.42, 37.77]" zoom="12" basemap="positron">
|
|
7
|
+
…layers, widgets…
|
|
8
|
+
<om-widget type="basemap-switcher" position="top-right"></om-widget>
|
|
9
|
+
</om-map>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Built-in presets
|
|
13
|
+
|
|
14
|
+
| Name | Provider | Key | Notes |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| `liberty` | [OpenFreeMap](https://openfreemap.org) | none | Full-detail OSM vector style. No registration, no limits — production use invited. |
|
|
17
|
+
| `bright` | OpenFreeMap | none | High-contrast OSM vector style. |
|
|
18
|
+
| `positron` | OpenFreeMap | none | Ultra-clean light style — the classic dataviz backdrop. |
|
|
19
|
+
| `dark-matter` | [CARTO](https://github.com/CartoDB/basemap-styles) | none | Dark dataviz style. Attribution required (rendered automatically). |
|
|
20
|
+
| `voyager` | CARTO | none | Balanced light style with labels. |
|
|
21
|
+
| `osm` | [openstreetmap.org](https://operations.osmfoundation.org/policies/tiles/) | none | The classic raster look. **Policy-constrained**: donation-funded, no uptime guarantee; commercial apps should prefer a vector preset or a paid provider. |
|
|
22
|
+
| `maptiler-streets` / `maptiler-dataviz` / `maptiler-satellite` | [MapTiler](https://docs.maptiler.com/cloud/api/maps/) | **required** | Free tier available; the satellite preset is the one imagery option here. |
|
|
23
|
+
| `maplibre` | MapLibre demo tiles | none | Country-level outlines only — fine for smoke tests, not production. |
|
|
24
|
+
|
|
25
|
+
## Switching at runtime
|
|
26
|
+
|
|
27
|
+
The `basemap` attribute is **live** — write it and the map switches in place, keeping the camera and every deck.gl layer:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
document.querySelector("om-map").setAttribute("basemap", "dark-matter");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Three equivalent surfaces, same as every action:
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<om-widget type="basemap-switcher" options="positron dark-matter liberty osm"></om-widget>
|
|
37
|
+
<button data-emit="set-basemap" data-basemap="dark-matter">Dark</button>
|
|
38
|
+
<om-behavior on="load" action="set-basemap" basemap="voyager"></om-behavior>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Omit `options` and the switcher lists every registered keyless preset. Switching between `none` and a basemap changes the renderer, so it remounts under the hood — still seamless (camera and layers carry over), just heavier than a style swap.
|
|
42
|
+
|
|
43
|
+
## MapTiler: keys and custom-edited styles
|
|
44
|
+
|
|
45
|
+
MapTiler keys are **publishable, origin-restricted client keys** — they belong in page source (restrict them to your domains in the MapTiler dashboard). Two ways to supply one:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<om-map basemap="maptiler-dataviz" basemap-key="YOUR_KEY">
|
|
49
|
+
```
|
|
50
|
+
```js
|
|
51
|
+
OmMap.configureBasemap({ maptilerKey: "YOUR_KEY" }); // once, covers every maptiler-* preset
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To **customize a basemap visually**, use MapTiler's Customize editor: pick a base style, edit colors/fonts/labels in the browser (it's editing MapLibre style JSON under the hood), save — you get a style URL for your edited style. Use it directly, or give it a name:
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<om-map basemap="https://api.maptiler.com/maps/YOUR_STYLE_ID/style.json?key=YOUR_KEY">
|
|
58
|
+
```
|
|
59
|
+
```js
|
|
60
|
+
OmMap.registerBasemap("brand", {
|
|
61
|
+
style: "https://api.maptiler.com/maps/YOUR_STYLE_ID/style.json?key={key}",
|
|
62
|
+
label: "Brand",
|
|
63
|
+
requiresKey: "maptiler",
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Register your own
|
|
68
|
+
|
|
69
|
+
`OmMap.registerBasemap(name, preset)` takes any MapLibre style URL **or inline style object** — self-hosted styles, other providers, or JSON-level edits of a preset:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
const style = await (await fetch("https://tiles.openfreemap.org/styles/positron")).json();
|
|
73
|
+
style.layers.find((l) => l.id === "background").paint["background-color"] = "#f4efe9";
|
|
74
|
+
OmMap.registerBasemap("warm-positron", { style, label: "Warm Positron" });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Registered names show up in the switcher widget, validation, and (after `npx @nika-js/onlymap init` regeneration in your own tooling) autocomplete.
|
|
78
|
+
|
|
79
|
+
## Attribution
|
|
80
|
+
|
|
81
|
+
Map data providers require attribution — OSM, OpenFreeMap, and CARTO all carry it in their style sources, and OnlyMapJS renders MapLibre's compact attribution control automatically whenever a basemap is active. Preset-level `attribution` text is appended. If you render your own credits, opt out with `attribution="false"` on `<om-map>` — but make sure your replacement satisfies the provider's terms.
|
|
82
|
+
|
|
83
|
+
## Validation
|
|
84
|
+
|
|
85
|
+
A typo'd preset name or a keyed preset without a key is a **validation error** (the live map falls back to the demo style so it still renders): `OmMap.validate(html)` lists the registered names or the two key routes in the `fix`.
|
|
86
|
+
|
|
87
|
+
## React
|
|
88
|
+
|
|
89
|
+
Same contract on the adapter: `<OmMap basemap="positron" basemapKey="…">` — changing the `basemap` prop switches live (style-only changes keep camera + layers). There's no switcher widget in React; it's a `useState` + the prop. `set-basemap` is not dispatched on the programmatic front-end — use the prop or `controller.setBasemap(name)`.
|
package/docs/react.md
CHANGED
|
@@ -54,7 +54,7 @@ Actions that mutate manifest attributes (`toggle-layer`, `show-overlay`, `fade`,
|
|
|
54
54
|
| Prop | Notes |
|
|
55
55
|
|---|---|
|
|
56
56
|
| `center` `zoom` `pitch` `bearing` | Initial camera; later prop changes move the camera (instant). While they're unchanged, user panning is never fought. |
|
|
57
|
-
| `basemap` | Same contract as the attribute — `"
|
|
57
|
+
| `basemap` | Same contract as the attribute — a preset name (`"positron"`, `"dark-matter"`, `"osm"`, …), a style URL, or omit for standalone deck.gl. Changing the prop switches live (camera + layers survive); `basemapKey` supplies the MapTiler key. See [docs/basemaps.md](basemaps.md). |
|
|
58
58
|
| `headless` | `true` or `{ width, height }` — no renderer, real projection math (tests under jsdom/happy-dom). |
|
|
59
59
|
| `onReady` | Renderer up + first commit + no data URL still loading. |
|
|
60
60
|
| `onViewStateChange` | Every camera change, with the current `CameraState`. |
|
package/llms.txt
CHANGED
|
@@ -16,11 +16,11 @@ OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the d
|
|
|
16
16
|
|
|
17
17
|
## Element vocabulary
|
|
18
18
|
|
|
19
|
-
- `<om-map center="[lng, lat]" zoom="11" pitch="55" bearing="20" basemap="
|
|
19
|
+
- `<om-map center="[lng, lat]" zoom="11" pitch="55" bearing="20" basemap="positron">` — the root. `basemap` accepts a free preset (`liberty`, `bright`, `positron`, `dark-matter`, `voyager`, `osm` — no keys; or `maptiler-streets`/`maptiler-dataviz`/`maptiler-satellite` with `basemap-key="…"` or `OmMap.configureBasemap({ maptilerKey })`), `maplibre` (bare demo style), a style URL (e.g. a MapTiler-customized style), or `none` (standalone canvas). The attribute is LIVE — writing it switches the basemap in place (camera + layers survive); the `set-basemap {basemap}` action and `<om-widget type="basemap-switcher" options="positron dark-matter osm">` do the same. Register more via `OmMap.registerBasemap(name, { style })`. Required provider attribution renders automatically (`attribution="false"` opts out). `pitch`/`bearing` tilt the initial camera (use for 3D content). `validate` attribute enables live validation + on-page error panel.
|
|
20
20
|
- `<om-layer id="..." type="ScatterplotLayer" data="./points.json">` — any deck.gl layer class by `type` (all 33 bundled), plus `PopupLayer` (WebGL badges/labels at scale: `layout="badge|pin-label|card"`, `min-zoom`/`max-zoom`). Data: `data` URL (JSON, GeoJSON, CSV/TSV `.csv` — parsed to typed columns — Shapefile `.shp` (+`.dbf` attributes) and KML `.kml` as GeoJSON features, or Arrow IPC `.arrow`/`.feather` — large point datasets stay columnar, GeoArrow line/polygon geometry becomes GeoJSON features, zstd-compressed IPC is handled; other formats plug in via `OmMap.registerFormat({match, parse})`), inline `<script type="application/json">` (row arrays or column-oriented `{"columns": {"lon": [...], "lat": [...]}}`), or `wss://` streaming (`key="mmsi"` upserts entities in place, `flush="250ms"` coalesces bursts, `source="name"` selects a `OmMap.registerSource` decoder plugin), or a polled REST snapshot (`refresh="5s"` re-fetches and replaces — for live endpoints that return the full current state). Authenticated endpoints: call `OmMap.configureData({ headers: {...} })` in a script — never put tokens in attributes. `$field` accessors work identically on all of them — never write column-index code yourself. One columnar restriction: the `js` full-JS opt-in is not allowed on Arrow/columnar layers (validation will tell you; use `$field` accessors instead). For 3D models use `type="ScenegraphLayer"` with `scenegraph="./model.glb"` (required) and `get-orientation="[0, $heading, 90]"` — the roll of 90 stands Y-up glTF models upright; see docs/3d-assets.md.
|
|
21
|
-
- `<om-widget type="legend|layer-switcher|zoom-controls|scale-bar|attribution|filter|vega-lite" position="bottom-right">` — static UI panels. No `type` + HTML + `<script type="om/widget">` = custom widget with `ctx` (`ctx.layers`, `ctx.data(id)`, `ctx.dataInViewport(id)`, `ctx.stats(id, field)`, `ctx.viewport`, `ctx.selection`, `ctx.emit(action, payload)`), `this.watch = ['data:<layerId>', 'viewport', 'selection', 'layers']`, `this.$(sel)`, `vegaEmbed`/`d3` as globals.
|
|
21
|
+
- `<om-widget type="legend|layer-switcher|basemap-switcher|zoom-controls|scale-bar|attribution|filter|vega-lite" position="bottom-right">` — static UI panels. No `type` + HTML + `<script type="om/widget">` = custom widget with `ctx` (`ctx.layers`, `ctx.data(id)`, `ctx.dataInViewport(id)`, `ctx.stats(id, field)`, `ctx.viewport`, `ctx.selection`, `ctx.emit(action, payload)`), `this.watch = ['data:<layerId>', 'viewport', 'selection', 'layers']`, `this.$(sel)`, `vegaEmbed`/`d3` as globals.
|
|
22
22
|
- `<om-overlay id="..." anchor-from="selection">` — rich geo-anchored HTML (≤ ~20 per map). Anchors: `anchor="[lng, lat]"` (static), `anchor-from="selection"` (follows picks), or `anchor-layer="regions" anchor-feature-id="mission"` (anchored to a feature's own geometry — bbox center — no coordinates in markup; `{{field}}` interpolates that feature's attributes). `{{field}}` interpolates the picked feature HTML-escaped; `{{{field}}}` is raw (avoid). For labels on many features use `PopupLayer`, not overlays.
|
|
23
|
-
- `<om-behavior on="click|hover|drag|load|data-loaded" layer="..." action="...">` — declarative interaction. Built-in actions: `show-overlay`, `hide-overlay`, `show-tooltip`, `hide-tooltip`, `toggle-layer`, `filter-layer`, `highlight-feature`, `zoom-to-feature`. One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
|
|
23
|
+
- `<om-behavior on="click|hover|drag|load|data-loaded" layer="..." action="...">` — declarative interaction. Built-in actions: `show-overlay`, `hide-overlay`, `show-tooltip`, `hide-tooltip`, `toggle-layer`, `filter-layer`, `highlight-feature`, `zoom-to-feature`, `set-basemap`. One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
|
|
24
24
|
- Animation: `transition="get-fill-color 800ms, get-radius 400ms"` on a layer GPU-animates prop changes (also smooths streaming updates via `get-position`). Camera: the `fly-to` action takes `center`/`zoom`/`pitch`/`bearing`/`duration` (e.g. `duration="2s"`) — use it in behaviors or `data-emit` buttons; `zoom-to-feature` also accepts `duration`.
|
|
25
25
|
- `<om-story id="tour" autoplay loop interrupt="pause|ignore">` — a storyboard of `<om-step>` children. Each step: `action="..."` + payload attributes (same kebab-case rule as behaviors) + `duration`/`delay`/`parallel` timing. Steps REFERENCE layers/overlays by id (`layer=`/`target=`) — a step must NEVER contain elements (validation error). Control: `<om-widget type="player" story="tour">`, the story-play/story-pause/story-seek actions, or `storyEl.play()/pause()/seek(ms)`. Seeking restores initial state then applies steps before T; use declarative payloads (e.g. `action="toggle-layer" visible="true"`, not bare toggles) so scrubbing is deterministic. Effect verbs as bare step attributes: `<om-step fade layer="regions" duration="1s">` (opacity reveal — start the layer at `opacity="0"`), `pulse` (attention flash), `trace` (progressive draw — whole-layer needs a TripsLayer; add `feature-id="..."` to make ONE polygon/line draw itself on inside any layer, or use it from a click behavior for click-to-trace), `populate` (rows drop in one by one — ordered by the authored filter-field, a payload `field`, or data order).
|
|
26
26
|
- Filtering: `filter-field="magnitude" filter-range="[4, 10]"` on a layer (GPU-side, live-updatable via the `filter-layer` action); pair with `<om-widget type="filter" layer="..." field="...">`.
|