@nika-js/onlymap 0.4.1 → 0.4.3

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/dist/onlymapjs.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ay as e, az as r, aA as o, aB as t, aC as i, aq as n, aD as b, aE as l, aF as S, aG as L, aH as p, aI as c, aJ as g, aK as m, aL as T, aM as y, aN as E, ap as A, aO as I, ax as u, aP as d, W as M, aQ as _, aR as C, aS as D, aT as O, aU as f, aV as x, aW as N, aX as h, aY as B, aZ as G, a_ as R, a$ as W, b0 as v, b1 as w, b2 as Y, b3 as F, b4 as P, b5 as k, b6 as z, b7 as H, b8 as J, b9 as V, ba as q, bb as K, bc as U, bd as j, be as Q, bf as X, bg as Z, bh as $, bi as aa, bj as sa, bk as ea, bl as ra, bm as oa, bn as ta, bo as ia, bp as na, bq as ba, br as la, bs as Sa, bt as La } from "./index-BKiyX-jQ.js";
1
+ import { ay as e, az as r, aA as o, aB as t, aC as i, aq as n, aD as b, aE as l, aF as S, aG as L, aH as p, aI as c, aJ as g, aK as m, aL as T, aM as y, aN as E, ap as A, aO as I, ax as u, aP as d, W as M, aQ as _, aR as C, aS as D, aT as O, aU as f, aV as x, aW as N, aX as h, aY as B, aZ as G, a_ as R, a$ as W, b0 as v, b1 as w, b2 as Y, b3 as F, b4 as P, b5 as k, b6 as z, b7 as H, b8 as J, b9 as V, ba as q, bb as K, bc as U, bd as j, be as Q, bf as X, bg as Z, bh as $, bi as aa, bj as sa, bk as ea, bl as ra, bm as oa, bn as ta, bo as ia, bp as na, bq as ba, br as la, bs as Sa, bt as La } from "./index-DzyVbu_T.js";
2
2
  export {
3
3
  e as ALL_POSITION_VALUES,
4
4
  r as AccessorCache,
@@ -1,5 +1,5 @@
1
1
  import { w as kt, t as Ni, f as Oi, m as Ri } from "./mgrs-BY9bIvp4.js";
2
- import { an as Fi, ao as zi, ap as Ks, aq as Ws, ar as Di, b as Bi, as as Ui, l as es, at as ki, d as qi, au as Ki, av as Wi, aw as Xi, ax as Xs } from "./index-BKiyX-jQ.js";
2
+ import { an as Fi, ao as zi, ap as Ks, aq as Ws, ar as Di, b as Bi, as as Ui, l as es, at as ki, d as qi, au as Ki, av as Wi, aw as Xi, ax as Xs } from "./index-DzyVbu_T.js";
3
3
  function Hi(t, e, s) {
4
4
  const { projectedCorners: i } = e, { topLeft: a, topRight: r, bottomRight: o, bottomLeft: n } = i, h = s(a[0], a[1]), l = s(r[0], r[1]), c = s(o[0], o[1]), u = s(n[0], n[1]), d = [
5
5
  h,
@@ -2377,7 +2377,7 @@ it.set(G.Zstd, () => import("./zstd-jXobGRcq.js").then((t) => t.decode));
2377
2377
  it.set(G.Jpeg, () => Promise.resolve(ke));
2378
2378
  it.set(G.Jpeg6, () => Promise.resolve(ke));
2379
2379
  it.set(G.Webp, () => Promise.resolve(ke));
2380
- it.set(G.Lerc, () => import("./lerc-filCwJiM.js").then((t) => t.l).then((t) => t.decode));
2380
+ it.set(G.Lerc, () => import("./lerc-DEpGyngt.js").then((t) => t.l).then((t) => t.decode));
2381
2381
  async function qe(t, e, s) {
2382
2382
  const i = it.get(e);
2383
2383
  if (!i)
@@ -20,6 +20,8 @@ export interface OmMapProps {
20
20
  };
21
21
  /** Layout-token sugar, the `widget-style` attribute's twin: `"gap:10 opacity:0.9"` → `--om-widget-*` custom properties on the widget layer. */
22
22
  widgetStyle?: string;
23
+ /** Hide-all, the `widgets-hidden` attribute's twin: hides every slot container (visibility, not removal — widget state survives). MapLibre's attribution control is outside the layer and never hides. */
24
+ widgetsHidden?: boolean;
23
25
  className?: string;
24
26
  style?: CSSProperties;
25
27
  children?: ReactNode;
package/dist/react.js CHANGED
@@ -1,143 +1,144 @@
1
- var X = Object.defineProperty;
2
- var Z = (t, e, n) => e in t ? X(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
3
- var B = (t, e, n) => Z(t, typeof e != "symbol" ? e + "" : e, n);
4
- import { jsxs as ee, jsx as O } from "react/jsx-runtime";
5
- import { createContext as te, useContext as re, forwardRef as ne, useRef as g, useState as N, useCallback as se, useMemo as x, useLayoutEffect as oe, useEffect as w, useImperativeHandle as ie, useSyncExternalStore as H } from "react";
6
- import { MANAGED_SLOTS as ae, slotContainerStyle as ce, MapController as le, parseWidgetStyle as ue, resolveSlot as fe, isStoreToken as de } from "@nika-js/onlymap";
7
- import { createPortal as J } from "react-dom";
8
- const Y = te(null);
9
- function $(t) {
10
- const e = re(Y);
1
+ var Z = Object.defineProperty;
2
+ var ee = (t, e, n) => e in t ? Z(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
3
+ var B = (t, e, n) => ee(t, typeof e != "symbol" ? e + "" : e, n);
4
+ import { jsxs as te, jsx as w } from "react/jsx-runtime";
5
+ import { createContext as re, useContext as ne, forwardRef as se, useRef as m, useState as V, useCallback as oe, useMemo as x, useLayoutEffect as ie, useEffect as E, useImperativeHandle as ae, useSyncExternalStore as J } from "react";
6
+ import { MANAGED_SLOTS as ce, slotContainerStyle as le, MapController as ue, parseWidgetStyle as de, resolveSlot as fe, isStoreToken as ye } from "@nika-js/onlymap";
7
+ import { createPortal as Y } from "react-dom";
8
+ const Q = re(null);
9
+ function z(t) {
10
+ const e = ne(Q);
11
11
  if (!e) throw new Error(`[onlymapjs] ${t} must be rendered inside <OmMap>.`);
12
12
  return e;
13
13
  }
14
- function V(t, e) {
14
+ function W(t, e) {
15
15
  if (t === e) return !0;
16
16
  const n = Object.keys(t ?? {}), r = Object.keys(e ?? {});
17
17
  return n.length !== r.length ? !1 : n.every((l) => t[l] === e[l]);
18
18
  }
19
- function ye(t, e) {
20
- const { props: n, updateTriggers: r, ...l } = t, { props: f, updateTriggers: c, ...s } = e;
21
- return V(l, s) && V(n, f) && V(r, c);
19
+ function me(t, e) {
20
+ const { props: n, updateTriggers: r, ...l } = t, { props: d, updateTriggers: c, ...o } = e;
21
+ return W(l, o) && W(n, d) && W(r, c);
22
22
  }
23
- class me {
23
+ class ge {
24
24
  constructor(e) {
25
25
  B(this, "descriptors", /* @__PURE__ */ new Map());
26
26
  this.controller = e;
27
27
  }
28
28
  upsert(e) {
29
29
  const n = this.descriptors.get(e.id);
30
- n && ye(n, e) || (this.descriptors.set(e.id, e), this.controller.setLayers([...this.descriptors.values()]));
30
+ n && me(n, e) || (this.descriptors.set(e.id, e), this.controller.setLayers([...this.descriptors.values()]));
31
31
  }
32
32
  remove(e) {
33
33
  this.descriptors.delete(e) && this.controller.setLayers([...this.descriptors.values()]);
34
34
  }
35
35
  }
36
- const I = ae, ge = Object.fromEntries(I.map((t) => [t, ce(t)]));
37
- function he(t, e) {
36
+ const $ = ce, he = Object.fromEntries($.map((t) => [t, le(t)]));
37
+ function be(t, e) {
38
38
  return Object.entries(e).some(([n, r]) => r !== void 0 && t[n] !== r);
39
39
  }
40
- const Re = ne(function(e, n) {
41
- const { center: r, zoom: l, pitch: f, bearing: c, basemap: s, basemapKey: a, attribution: d, headless: u, widgetStyle: p, className: z, style: h, children: b } = e, y = g(null), [o, v] = N(null), [S, T] = N(null), [E, R] = N(
42
- () => Object.fromEntries(I.map((i) => [i, null]))
43
- ), k = se((i) => {
44
- T((m) => m === i ? m : i);
45
- }, []), j = x(
40
+ const Ce = se(function(e, n) {
41
+ const { center: r, zoom: l, pitch: d, bearing: c, basemap: o, basemapKey: a, attribution: f, headless: u, widgetStyle: v, widgetsHidden: L, className: h, style: b, children: g } = e, p = m(null), [s, R] = V(null), [S, T] = V(null), [O, k] = V(
42
+ () => Object.fromEntries($.map((i) => [i, null]))
43
+ ), j = oe((i) => {
44
+ T((y) => y === i ? y : i);
45
+ }, []), P = x(
46
46
  () => Object.fromEntries(
47
- I.map((i) => [
47
+ $.map((i) => [
48
48
  i,
49
- (m) => {
50
- R((M) => M[i] === m ? M : { ...M, [i]: m });
49
+ (y) => {
50
+ k((M) => M[i] === y ? M : { ...M, [i]: y });
51
51
  }
52
52
  ])
53
53
  ),
54
54
  []
55
- ), L = g({ center: r, zoom: l, pitch: f, bearing: c });
56
- L.current = { center: r, zoom: l, pitch: f, bearing: c };
57
- const P = g(e.onReady);
58
- P.current = e.onReady;
59
- const C = g(e.onViewStateChange);
55
+ ), _ = m({ center: r, zoom: l, pitch: d, bearing: c });
56
+ _.current = { center: r, zoom: l, pitch: d, bearing: c };
57
+ const F = m(e.onReady);
58
+ F.current = e.onReady;
59
+ const C = m(e.onViewStateChange);
60
60
  C.current = e.onViewStateChange;
61
- const F = g(e.onRuntimeError);
62
- F.current = e.onRuntimeError;
63
- const _ = g(s);
64
- _.current = s;
65
- const A = g(void 0), Q = u ? JSON.stringify(u) : "";
66
- oe(() => {
67
- if (!y.current) return;
68
- const i = L.current;
69
- A.current = _.current;
70
- const m = new le(y.current, {
61
+ const K = m(e.onRuntimeError);
62
+ K.current = e.onRuntimeError;
63
+ const A = m(o);
64
+ A.current = o;
65
+ const D = m(void 0), U = u ? JSON.stringify(u) : "";
66
+ ie(() => {
67
+ if (!p.current) return;
68
+ const i = _.current;
69
+ D.current = A.current;
70
+ const y = new ue(p.current, {
71
71
  center: i.center,
72
72
  zoom: i.zoom,
73
73
  pitch: i.pitch,
74
74
  bearing: i.bearing,
75
- basemap: _.current,
75
+ basemap: A.current,
76
76
  basemapKey: a,
77
- attribution: d,
77
+ attribution: f,
78
78
  headless: u ? u === !0 ? {} : u : void 0,
79
- onRuntimeError: (K) => {
79
+ onRuntimeError: (I) => {
80
80
  var q;
81
- return (q = F.current) == null ? void 0 : q.call(F, K);
81
+ return (q = K.current) == null ? void 0 : q.call(K, I);
82
82
  }
83
83
  });
84
- v(m);
84
+ R(y);
85
85
  let M = !0;
86
- return m.ready.then(() => {
87
- var K;
88
- M && ((K = P.current) == null || K.call(P));
86
+ return y.ready.then(() => {
87
+ var I;
88
+ M && ((I = F.current) == null || I.call(F));
89
89
  }), () => {
90
- M = !1, v(null), m.destroy();
90
+ M = !1, R(null), y.destroy();
91
91
  };
92
- }, [Q]), w(() => {
93
- !o || A.current === s || (A.current = s, o.setBasemap(s ?? null));
94
- }, [o, s]), ie(n, () => o, [o]), w(() => {
95
- if (!o) return;
96
- const i = { longitude: r == null ? void 0 : r[0], latitude: r == null ? void 0 : r[1], zoom: l, pitch: f, bearing: c };
97
- he(o.getViewState(), i) && o.setView(i);
98
- }, [o, r == null ? void 0 : r[0], r == null ? void 0 : r[1], l, f, c]), w(() => {
92
+ }, [U]), E(() => {
93
+ !s || D.current === o || (D.current = o, s.setBasemap(o ?? null));
94
+ }, [s, o]), ae(n, () => s, [s]), E(() => {
95
+ if (!s) return;
96
+ const i = { longitude: r == null ? void 0 : r[0], latitude: r == null ? void 0 : r[1], zoom: l, pitch: d, bearing: c };
97
+ be(s.getViewState(), i) && s.setView(i);
98
+ }, [s, r == null ? void 0 : r[0], r == null ? void 0 : r[1], l, d, c]), E(() => {
99
99
  var i;
100
- if (o)
101
- return (i = C.current) == null || i.call(C, o.getViewState()), o.getStore("viewport").subscribe(() => {
102
- var m;
103
- return (m = C.current) == null ? void 0 : m.call(C, o.getViewState());
100
+ if (s)
101
+ return (i = C.current) == null || i.call(C, s.getViewState()), s.getStore("viewport").subscribe(() => {
102
+ var y;
103
+ return (y = C.current) == null ? void 0 : y.call(C, s.getViewState());
104
104
  });
105
- }, [o]);
106
- const U = x(
105
+ }, [s]);
106
+ const X = x(
107
107
  () => ({
108
108
  position: "absolute",
109
109
  inset: 0,
110
110
  zIndex: 20,
111
111
  pointerEvents: "none",
112
112
  opacity: "var(--om-widget-opacity, 1)",
113
- ...p ? ue(p).properties : void 0
113
+ ...L ? { visibility: "hidden" } : void 0,
114
+ ...v ? de(v).properties : void 0
114
115
  }),
115
- [p]
116
- ), D = x(() => o ? new me(o) : null, [o]), W = x(
117
- () => o && D && S ? { controller: o, layers: D, overlayPane: S, corners: E } : null,
118
- [o, D, S, E]
116
+ [v, L]
117
+ ), N = x(() => s ? new ge(s) : null, [s]), H = x(
118
+ () => s && N && S ? { controller: s, layers: N, overlayPane: S, corners: O } : null,
119
+ [s, N, S, O]
119
120
  );
120
- return /* @__PURE__ */ ee("div", { className: z, style: { position: "relative", overflow: "hidden", ...h }, children: [
121
- /* @__PURE__ */ O("div", { ref: y, style: { position: "absolute", inset: 0 } }),
122
- /* @__PURE__ */ O(
121
+ return /* @__PURE__ */ te("div", { className: h, style: { position: "relative", overflow: "hidden", ...b }, children: [
122
+ /* @__PURE__ */ w("div", { ref: p, style: { position: "absolute", inset: 0 } }),
123
+ /* @__PURE__ */ w(
123
124
  "div",
124
125
  {
125
- ref: k,
126
+ ref: j,
126
127
  style: { position: "absolute", top: 0, left: 0, zIndex: 10, pointerEvents: "none" }
127
128
  }
128
129
  ),
129
- /* @__PURE__ */ O("div", { "data-om-widget-layer": "", style: U, children: I.map((i) => /* @__PURE__ */ O(
130
+ /* @__PURE__ */ w("div", { "data-om-widget-layer": "", style: X, children: $.map((i) => /* @__PURE__ */ w(
130
131
  "div",
131
132
  {
132
133
  "data-om-widget-slot": i,
133
- ref: j[i],
134
- style: ge[i]
134
+ ref: P[i],
135
+ style: he[i]
135
136
  },
136
137
  i
137
138
  )) }),
138
- W && /* @__PURE__ */ O(Y.Provider, { value: W, children: b })
139
+ H && /* @__PURE__ */ w(Q.Provider, { value: H, children: g })
139
140
  ] });
140
- }), be = /* @__PURE__ */ new Set([
141
+ }), ve = /* @__PURE__ */ new Set([
141
142
  "id",
142
143
  "type",
143
144
  "data",
@@ -157,11 +158,11 @@ const Re = ne(function(e, n) {
157
158
  "onClick",
158
159
  "onHover"
159
160
  ]);
160
- function Ce(t) {
161
- const { controller: e, layers: n } = $("OmLayer"), { id: r } = t, l = {};
162
- for (const [a, d] of Object.entries(t))
163
- !be.has(a) && d !== void 0 && (l[a] = d);
164
- const f = {
161
+ function xe(t) {
162
+ const { controller: e, layers: n } = z("OmLayer"), { id: r } = t, l = {};
163
+ for (const [a, f] of Object.entries(t))
164
+ !ve.has(a) && f !== void 0 && (l[a] = f);
165
+ const d = {
165
166
  id: r,
166
167
  type: t.type,
167
168
  data: t.data,
@@ -180,38 +181,38 @@ function Ce(t) {
180
181
  updateTriggers: t.updateTriggers,
181
182
  props: l
182
183
  };
183
- w(() => {
184
- n.upsert(f);
185
- }), w(() => () => n.remove(r), [n, r]);
186
- const c = g(t.onClick);
184
+ E(() => {
185
+ n.upsert(d);
186
+ }), E(() => () => n.remove(r), [n, r]);
187
+ const c = m(t.onClick);
187
188
  c.current = t.onClick;
188
- const s = g(t.onHover);
189
- return s.current = t.onHover, w(
189
+ const o = m(t.onHover);
190
+ return o.current = t.onHover, E(
190
191
  () => e.watchPicks((a) => {
191
- var d, u, p;
192
+ var f, u, v;
192
193
  if (a === null) {
193
- (d = s.current) == null || d.call(s, null);
194
+ (f = o.current) == null || f.call(o, null);
194
195
  return;
195
196
  }
196
- a.layerId === r && (a.type === "click" ? (u = c.current) == null || u.call(c, a) : (p = s.current) == null || p.call(s, a));
197
+ a.layerId === r && (a.type === "click" ? (u = c.current) == null || u.call(c, a) : (v = o.current) == null || v.call(o, a));
197
198
  }),
198
199
  [e, r]
199
200
  ), null;
200
201
  }
201
- function xe({ position: t = "top-start", order: e, className: n, style: r, children: l }) {
202
- const { corners: f } = $("OmWidget"), c = fe(t) ?? "top-start";
202
+ function Te({ position: t = "top-start", order: e, className: n, style: r, children: l }) {
203
+ const { corners: d } = z("OmWidget"), c = fe(t) ?? "top-start";
203
204
  if (c === "manual")
204
- return /* @__PURE__ */ O("div", { className: n, style: { pointerEvents: "auto", ...r }, children: l });
205
- const s = f[c];
206
- return s ? J(
207
- /* @__PURE__ */ O("div", { className: n, style: { pointerEvents: "auto", ...e !== void 0 ? { order: e } : void 0, ...r }, children: l }),
208
- s
205
+ return /* @__PURE__ */ w("div", { className: n, style: { pointerEvents: "auto", ...r }, children: l });
206
+ const o = d[c];
207
+ return o ? Y(
208
+ /* @__PURE__ */ w("div", { className: n, style: { pointerEvents: "auto", ...e !== void 0 ? { order: e } : void 0, ...r }, children: l }),
209
+ o
209
210
  ) : null;
210
211
  }
211
212
  function G(t, e) {
212
213
  return !e || (t == null ? void 0 : t.layerId) === e;
213
214
  }
214
- const ve = {
215
+ const pe = {
215
216
  "top-left": "translate(0, 0)",
216
217
  "top-center": "translate(-50%, 0)",
217
218
  "top-right": "translate(-100%, 0)",
@@ -222,52 +223,52 @@ const ve = {
222
223
  "bottom-center": "translate(-50%, -100%)",
223
224
  "bottom-right": "translate(-100%, -100%)"
224
225
  };
225
- function Te(t) {
226
- const { controller: e, overlayPane: n } = $("OmOverlay"), r = g(null), l = g(void 0), f = g(t);
227
- f.current = t;
226
+ function Me(t) {
227
+ const { controller: e, overlayPane: n } = z("OmOverlay"), r = m(null), l = m(void 0), d = m(t);
228
+ d.current = t;
228
229
  const c = x(() => {
229
230
  const h = e.getStore("selection");
230
- let b, y, o = null;
231
+ let b, g, p = null;
231
232
  return {
232
233
  subscribe: h.subscribe,
233
234
  getSnapshot: () => {
234
- const v = h.getSnapshot(), { anchorFrom: S, layer: T } = f.current, E = `${S ?? ""}|${T ?? ""}`;
235
- return (v !== b || E !== y) && (b = v, y = E, S === "selection" && G(v, T) && (o = v)), o;
235
+ const s = h.getSnapshot(), { anchorFrom: R, layer: S } = d.current, T = `${R ?? ""}|${S ?? ""}`;
236
+ return (s !== b || T !== g) && (b = s, g = T, R === "selection" && G(s, S) && (p = s)), p;
236
237
  }
237
238
  };
238
- }, [e]), s = H(
239
+ }, [e]), o = J(
239
240
  c.subscribe,
240
241
  c.getSnapshot,
241
242
  c.getSnapshot
242
243
  );
243
- w(
244
+ E(
244
245
  () => e.registerOverlay((h, b) => {
245
- const y = r.current;
246
- if (!y) return;
247
- const { anchor: o, anchorFrom: v, layer: S, visible: T = !0, anchorOffset: E } = f.current;
248
- let R;
249
- if (v === "selection" ? (G(b, S) && (l.current = (b == null ? void 0 : b.coordinate) ?? void 0), R = l.current) : R = o, !T || !R || !h) {
250
- y.style.visibility = "hidden";
246
+ const g = r.current;
247
+ if (!g) return;
248
+ const { anchor: p, anchorFrom: s, layer: R, visible: S = !0, anchorOffset: T } = d.current;
249
+ let O;
250
+ if (s === "selection" ? (G(b, R) && (l.current = (b == null ? void 0 : b.coordinate) ?? void 0), O = l.current) : O = p, !S || !O || !h) {
251
+ g.style.visibility = "hidden";
251
252
  return;
252
253
  }
253
- const [k, j, L] = h.project([R[0], R[1], 0]);
254
- if (!(k >= 0 && k <= h.width && j >= 0 && j <= h.height) || L !== void 0 && L < 0) {
255
- y.style.visibility = "hidden";
254
+ const [k, j, P] = h.project([O[0], O[1], 0]);
255
+ if (!(k >= 0 && k <= h.width && j >= 0 && j <= h.height) || P !== void 0 && P < 0) {
256
+ g.style.visibility = "hidden";
256
257
  return;
257
258
  }
258
- y.style.visibility = "visible", y.style.transform = `translate(${k}px, ${j}px) ${ve[E ?? "bottom-center"]}`;
259
+ g.style.visibility = "visible", g.style.transform = `translate(${k}px, ${j}px) ${pe[T ?? "bottom-center"]}`;
259
260
  }),
260
261
  [e]
261
- ), w(() => {
262
+ ), E(() => {
262
263
  e.refreshOverlays();
263
264
  });
264
- const { children: a, className: d, style: u, interactive: p = !0 } = t, z = typeof a == "function" ? a(s) : a;
265
- return J(
266
- /* @__PURE__ */ O(
265
+ const { children: a, className: f, style: u, interactive: v = !0 } = t, L = typeof a == "function" ? a(o) : a;
266
+ return Y(
267
+ /* @__PURE__ */ w(
267
268
  "div",
268
269
  {
269
270
  ref: r,
270
- className: d,
271
+ className: f,
271
272
  style: {
272
273
  position: "absolute",
273
274
  top: 0,
@@ -275,38 +276,38 @@ function Te(t) {
275
276
  visibility: "hidden",
276
277
  // hidden until the first flush positions it — never flashes at the origin
277
278
  willChange: "transform",
278
- pointerEvents: p ? "auto" : "none",
279
+ pointerEvents: v ? "auto" : "none",
279
280
  ...u
280
281
  },
281
- children: z
282
+ children: L
282
283
  }
283
284
  ),
284
285
  n
285
286
  );
286
287
  }
287
- function Me(t = []) {
288
- const { controller: e } = $("useOmMap"), n = t.join(" "), r = x(() => {
289
- const c = (n ? n.split(" ") : []).flatMap((a) => de(a) ? [e.getStore(a)] : (console.warn(`[onlymapjs] useOmMap: "${a}" is not a watch token on the React front-end — expected viewport, selection, layers, or data:<layerId>.`), []));
290
- let s;
288
+ function ke(t = []) {
289
+ const { controller: e } = z("useOmMap"), n = t.join(" "), r = x(() => {
290
+ const c = (n ? n.split(" ") : []).flatMap((a) => ye(a) ? [e.getStore(a)] : (console.warn(`[onlymapjs] useOmMap: "${a}" is not a watch token on the React front-end — expected viewport, selection, layers, or data:<layerId>.`), []));
291
+ let o;
291
292
  return {
292
293
  subscribe: (a) => {
293
- const d = c.map((u) => u.subscribe(a));
294
+ const f = c.map((u) => u.subscribe(a));
294
295
  return () => {
295
- for (const u of d) u();
296
+ for (const u of f) u();
296
297
  };
297
298
  },
298
299
  getSnapshot: () => {
299
- const a = c.map((d) => d.getSnapshot());
300
- return (!s || a.some((d, u) => d !== s[u])) && (s = a), s;
300
+ const a = c.map((f) => f.getSnapshot());
301
+ return (!o || a.some((f, u) => f !== o[u])) && (o = a), o;
301
302
  }
302
303
  };
303
- }, [e, n]), l = H(r.subscribe, r.getSnapshot, r.getSnapshot);
304
+ }, [e, n]), l = J(r.subscribe, r.getSnapshot, r.getSnapshot);
304
305
  return x(() => e.buildCtx(), [e, l]);
305
306
  }
306
307
  export {
307
- Ce as OmLayer,
308
- Re as OmMap,
309
- Te as OmOverlay,
310
- xe as OmWidget,
311
- Me as useOmMap
308
+ xe as OmLayer,
309
+ Ce as OmMap,
310
+ Me as OmOverlay,
311
+ Te as OmWidget,
312
+ ke as useOmMap
312
313
  };
package/dist/version.d.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  * the build rootDir, and a `define` would need repeating across vite/vitest/
6
6
  * vite-node configs.
7
7
  */
8
- export declare const LIBRARY_VERSION = "0.4.1";
8
+ export declare const LIBRARY_VERSION = "0.4.3";
@@ -19,7 +19,27 @@ export interface WidgetHost {
19
19
  export interface WidgetImpl {
20
20
  /** Default watch tokens when the element has no `watch` attribute. */
21
21
  watch?: string[];
22
+ /**
23
+ * Compact button widget (spec: "Widget Layout Manager / Button
24
+ * clustering") — adjacent compact widgets in one slot auto-merge into a
25
+ * single visual control group (shared rounded container, dividers, one
26
+ * shadow). Per-widget opt-out: `cluster="false"`. Non-compact widgets
27
+ * (panels: legend, filter, search) never cluster.
28
+ */
29
+ compact?: boolean;
30
+ /**
31
+ * Never affected by hide-all (spec: "Widget Layout Manager / Hide-all") —
32
+ * `widgets-hidden` leaves this widget visible. Attribution declares it
33
+ * (license compliance) and so does the widgets-toggle button (it must
34
+ * stay clickable to un-hide). Third-party always-visible chrome sets it
35
+ * here rather than the core hardcoding a type list.
36
+ */
37
+ neverHides?: boolean;
22
38
  render(ctx: RuntimeContext, host: WidgetHost): void | Promise<void>;
23
39
  }
40
+ /** Clusterable (spec: "Widget Layout Manager") — the type is compact AND the author didn't opt out. One predicate for the cluster pass AND the validation lint. */
41
+ export declare function isClusterableWidget(el: Element): boolean;
42
+ /** Exempt from hide-all — the type declares `neverHides` (attribution, widgets-toggle). */
43
+ export declare function widgetNeverHides(type: string | null): boolean;
24
44
  export declare function registerWidget(name: string, impl: WidgetImpl): void;
25
45
  export declare function getWidget(name: string): WidgetImpl | undefined;
package/llms.txt CHANGED
@@ -24,9 +24,9 @@ OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the d
24
24
 
25
25
  ## Element vocabulary
26
26
 
27
- - `<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; any scheme fetch supports, including desktop asset protocols like Tauri's, query strings fine), 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). Terrain: `terrain="terrarium"` (keyless AWS DEM; also `maptiler-terrain` with a key, a raw `{z}/{x}/{y}` DEM URL + `terrain-decoder="terrarium|mapbox-rgb|<JSON>"`, or `off`) raises a 3D surface — geographic layers DRAPE onto it automatically (per-layer `terrain="drape|offset|off"` overrides; 3D-model layers sit ON it), `terrain-exaggeration` scales relief, `terrain-texture` drapes imagery; terrain REPLACES an active basemap while on (restored when off); `set-terrain` action, `terrain` watch token, `OmMap.registerTerrain(name, {...})` for more sources. Scene lighting for 3D content: `lighting="daylight|studio|flat|custom"` (absent = deck defaults) with `lighting-ambient`/`lighting-sun`/`lighting-sun-azimuth`/`lighting-sun-elevation`/`lighting-camera` overrides and `lighting-sun-date` (ISO/epoch — solar-position sun computed at the map center, wins over azimuth/elevation); attribute-backed (undoable, live-editable), story-steppable via the `set-lighting {lighting, sunAzimuth, ...}` action (`lighting="default"` removes the attribute set; a bare preset is a clean reset — stale overrides clear), native UI via `<om-widget type="lighting">` (preset radios + tuning sliders), widget watch token `lighting`. GeoJsonLayer extrudes polygons declaratively: `extruded get-elevation="$height"` (+ `wireframe`). `validate` attribute enables live validation + on-page error panel. Include a `map-id="<random UUID>"` on complete pages (identifies the map artifact for usage telemetry — not a visitor id; the page author deletes it to opt out); `telemetry="off"` disables usage telemetry + library-error reporting for the map (global: `OmMap.configureTelemetry({ disabled: true })`; schema: docs/telemetry.md). Free plan limits: 5 layers, 25k rows per layer — layers past a limit don't render and validation says why; a `license-key="om_live_…"` attribute (publishable, origin-restricted) or `OmMap.configureLicense(key)` lifts limits and removes the corner badge. When a user's map exceeds free limits, say so and point at the validation message rather than silently restructuring their data. Events on the element: `om-map-ready` (boot complete; `await mapEl.ready` is the promise twin), `om-validation-error`, `om-view-changed` (camera settled after a move, debounced; `detail = {longitude, latitude, zoom, pitch, bearing, origin}` where `origin` is `"user"` for gesture bursts vs `"programmatic"` for API/story moves — the camera-persistence hook, and the echo-suppression signal when syncing camera state to an app store). `await mapEl.snapshot()` returns a canvas-only PNG dataURL of the scene (basemap + layers at device pixels; `{as:"blob"}` for files) — DOM widgets/overlays/attribution are NOT captured, so exports must render provider credits themselves.
27
+ - `<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; any scheme fetch supports, including desktop asset protocols like Tauri's, query strings fine), 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). Terrain: `terrain="terrarium"` (keyless AWS DEM; also `maptiler-terrain` with a key, a raw `{z}/{x}/{y}` DEM URL + `terrain-decoder="terrarium|mapbox-rgb|<JSON>"`, or `off`) raises a 3D surface — geographic layers DRAPE onto it automatically (per-layer `terrain="drape|offset|off"` overrides; 3D-model layers sit ON it), `terrain-exaggeration` scales relief, `terrain-texture` drapes imagery; terrain REPLACES an active basemap while on (restored when off); `set-terrain` action, `terrain` watch token, `OmMap.registerTerrain(name, {...})` for more sources. Scene lighting for 3D content: `lighting="daylight|studio|flat|custom"` (absent = deck defaults) with `lighting-ambient`/`lighting-sun`/`lighting-sun-azimuth`/`lighting-sun-elevation`/`lighting-camera` overrides and `lighting-sun-date` (ISO/epoch — solar-position sun computed at the map center, wins over azimuth/elevation); attribute-backed (undoable, live-editable), story-steppable via the `set-lighting {lighting, sunAzimuth, ...}` action (`lighting="default"` removes the attribute set; a bare preset is a clean reset — stale overrides clear), native UI via `<om-widget type="lighting">` (preset radios + tuning sliders), widget watch token `lighting`. GeoJsonLayer extrudes polygons declaratively: `extruded get-elevation="$height"` (+ `wireframe`). `widgets-hidden` attribute (or the `set-widgets-visible {visible}` action / `<om-widget type="widgets-toggle">` button) hides every widget WITHOUT destroying state — attribution never hides (license); transient (not an undo step) but story-steppable, so a step can clear chrome for a cinematic take. Slots auto-dim while an open `<om-overlay>` popup covers them (position stability over the popup dodging; `widgets-dim="off"` disables). `validate` attribute enables live validation + on-page error panel. Include a `map-id="<random UUID>"` on complete pages (identifies the map artifact for usage telemetry — not a visitor id; the page author deletes it to opt out); `telemetry="off"` disables usage telemetry + library-error reporting for the map (global: `OmMap.configureTelemetry({ disabled: true })`; schema: docs/telemetry.md). Free plan limits: 5 layers, 25k rows per layer — layers past a limit don't render and validation says why; a `license-key="om_live_…"` attribute (publishable, origin-restricted) or `OmMap.configureLicense(key)` lifts limits and removes the corner badge. When a user's map exceeds free limits, say so and point at the validation message rather than silently restructuring their data. Events on the element: `om-map-ready` (boot complete; `await mapEl.ready` is the promise twin), `om-validation-error`, `om-view-changed` (camera settled after a move, debounced; `detail = {longitude, latitude, zoom, pitch, bearing, origin}` where `origin` is `"user"` for gesture bursts vs `"programmatic"` for API/story moves — the camera-persistence hook, and the echo-suppression signal when syncing camera state to an app store). `await mapEl.snapshot()` returns a canvas-only PNG dataURL of the scene (basemap + layers at device pixels; `{as:"blob"}` for files) — DOM widgets/overlays/attribution are NOT captured, so exports must render provider credits themselves.
28
28
  - `<om-layer id="..." type="ScatterplotLayer" data="./points.json">` — any deck.gl layer class by `type` (all 33 bundled, plus the native `COGLayer` raster type), plus `PopupLayer` (WebGL badges/labels at scale: `layout="badge|pin-label|card"`, `min-zoom`/`max-zoom`). Every type's full attribute list ships in the package's `onlymapjs.html-data.json` — consult it instead of guessing attribute names or reading the minified dist. TextLayer's default font atlas covers ASCII only: set `character-set` when label text carries other glyphs (`—`, `·`, accents) or deck warns and renders them blank. External layer classes register via `OmMap.registerLayer({type, deckClass, props})` — build them on `@nika-js/onlymap/deck` (the bundled `CompositeLayer`/`TileLayer`/… re-exports), never a separately-installed deck.gl (different class hierarchy, breaks in the renderer); function-valued props ride the subclass's `static defaultProps`; register at module top level BEFORE the manifest mounts (see docs/custom-layers.md). 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})`; data URLs accept any scheme the runtime's fetch supports — desktop webviews (Tauri, Electron) pass asset-protocol URLs straight in), inline `<script type="application/json">` (row arrays or column-oriented `{"columns": {"lon": [...], "lat": [...]}}`; must be a DIRECT child of the `<om-layer>`, and when present it wins — omit the `data` attribute), 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. GeoTIFF/COG rasters use `type="COGLayer"` with `src="./dem.tif"` (NOT `data` — rasters stream tiles by Range request, they are not parsed rows): `min`/`max` set the rescale window (default 0–255; ALWAYS set them for float/16-bit data like DEMs), `colormap` picks a bundled ramp for single-band sources (gray, viridis, plasma, inferno, magma, cividis, rdylgn, rdbu, spectral, terrain, jet, turbo), `nodata` overrides the source sentinel (renders transparent); plain 8-bit RGB COGs need no styling attributes; restretch/recolor are GPU uniforms (no refetch) and the legend ramp derives from colormap+min/max automatically. Sources must be Cloud-Optimized (`gdal_translate -of COG` otherwise).
29
- - `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite" position="bottom-end">` — static UI panels. `position` takes one of 8 managed slots (logical, RTL-aware: `top-start|top-center|top-end|center-start|center-end|bottom-start|bottom-center|bottom-end`; legacy corners `top-left` etc. alias) — same-slot widgets stack with flush edges and a shared gap (never overlap); `order="1"` orders within a slot; `position="manual"` renders a plain block you place with your own CSS (even outside the map). Layout tokens on `<om-map>`: `widget-style="gap:10 opacity:0.9 inset:16"` (keys inset/gap/inset-x/-y/gap-x/-y/opacity/radius/size, px except opacity) or the `--om-widget-inset-x/-y/-gap-x/-y/-opacity/-radius` custom properties. Built-ins are themeable from page CSS via custom properties (they inherit through the shadow root): `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` — full set: `--om-widget-bg/-fg/-muted/-border/-hover-bg/-accent`; scope to a single widget with an `om-widget[type=legend]` selector instead. 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', 'history', 'basemap']` (`layers` also fires on visibility/filter changes; `basemap` on basemap switches; `history` on undo/redo availability), `this.$(sel)`, `vegaEmbed`/`d3` as globals.
29
+ - `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite" position="bottom-end">` — static UI panels. `position` takes one of 8 managed slots (logical, RTL-aware: `top-start|top-center|top-end|center-start|center-end|bottom-start|bottom-center|bottom-end`; legacy corners `top-left` etc. alias) — same-slot widgets stack with flush edges and a shared gap (never overlap); `order="1"` orders within a slot; adjacent compact button widgets (zoom-controls, undo-redo, widgets-toggle) auto-merge into ONE control group with dividers (`cluster="false"` opts a widget out); `position="manual"` renders a plain block you place with your own CSS (even outside the map). Layout tokens on `<om-map>`: `widget-style="gap:10 opacity:0.9 inset:16"` (keys inset/gap/inset-x/-y/gap-x/-y/opacity/radius/size, px except opacity) or the `--om-widget-inset-x/-y/-gap-x/-y/-opacity/-radius` custom properties. Built-ins are themeable from page CSS via custom properties (they inherit through the shadow root): `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` — full set: `--om-widget-bg/-fg/-muted/-border/-hover-bg/-accent`; scope to a single widget with an `om-widget[type=legend]` selector instead. 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', 'history', 'basemap']` (`layers` also fires on visibility/filter changes; `basemap` on basemap switches; `history` on undo/redo availability), `this.$(sel)`, `vegaEmbed`/`d3` as globals.
30
30
  - `<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.
31
31
  - `<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`, `undo`, `redo`. One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
32
32
  - Undo/redo is built in: user-facing manifest changes (layer toggles, filter changes, basemap switches, element add/remove, drawn sketches) are recorded automatically — the manifest is the state. `<om-widget type="undo-redo">` renders the buttons; Cmd/Ctrl-Z, Shift-Cmd/Ctrl-Z, and Ctrl-Y work on any map (text inputs keep their native undo). Camera moves, hover effects, and story playback are deliberately NOT undo steps. Widget scripts: `ctx.history.canUndo/canRedo` with watch token `history`; `ctx.emit("undo")`/`ctx.emit("redo")`.
@@ -158,6 +158,19 @@
158
158
  "name": "widget-style",
159
159
  "description": "Layout-token sugar: space-separated key:number pairs → --om-widget-* custom properties (e.g. \"gap:10 opacity:0.9\"). Keys: inset, gap (shorthands), inset-x/-y, gap-x/-y, opacity, radius, size. Numbers are px except opacity."
160
160
  },
161
+ {
162
+ "name": "widgets-hidden",
163
+ "description": "Hide-all: every widget goes visibility:hidden (state survives — no removal) except attribution and widgets-toggle, which never hide. Toggle via the set-widgets-visible action or <om-widget type=\"widgets-toggle\">; transient UI, not an undo step; story-scrub-capturable."
164
+ },
165
+ {
166
+ "name": "widgets-dim",
167
+ "description": "Set \"off\" to disable collision-dim: by default a slot dims (--om-widget-opacity-dimmed, 0.35) while an open <om-overlay> popup covers it, rather than the popup dodging.",
168
+ "values": [
169
+ {
170
+ "name": "off"
171
+ }
172
+ ]
173
+ },
161
174
  {
162
175
  "name": "validate",
163
176
  "description": "Run manifest validation and show the on-page error panel."
@@ -1582,6 +1595,9 @@
1582
1595
  {
1583
1596
  "name": "set-terrain"
1584
1597
  },
1598
+ {
1599
+ "name": "set-widgets-visible"
1600
+ },
1585
1601
  {
1586
1602
  "name": "undo"
1587
1603
  },
@@ -1643,6 +1659,9 @@
1643
1659
  },
1644
1660
  {
1645
1661
  "name": "undo-redo"
1662
+ },
1663
+ {
1664
+ "name": "widgets-toggle"
1646
1665
  }
1647
1666
  ]
1648
1667
  },
@@ -1695,9 +1714,18 @@
1695
1714
  "name": "order",
1696
1715
  "description": "Deterministic in-slot ordering (flex order; lower renders first). Default: DOM order."
1697
1716
  },
1717
+ {
1718
+ "name": "cluster",
1719
+ "description": "Set \"false\" to keep a compact button widget (zoom-controls, undo-redo, widgets-toggle) out of the automatic control-group merge with adjacent compact widgets.",
1720
+ "values": [
1721
+ {
1722
+ "name": "false"
1723
+ }
1724
+ ]
1725
+ },
1698
1726
  {
1699
1727
  "name": "watch",
1700
- "description": "Space-separated watch tokens: viewport, selection, layers, history, basemap, lighting, terrain, data:<layerId>."
1728
+ "description": "Space-separated watch tokens: viewport, selection, layers, history, basemap, lighting, terrain, widgets, data:<layerId>."
1701
1729
  },
1702
1730
  {
1703
1731
  "name": "layer",
@@ -1896,6 +1924,9 @@
1896
1924
  {
1897
1925
  "name": "set-terrain"
1898
1926
  },
1927
+ {
1928
+ "name": "set-widgets-visible"
1929
+ },
1899
1930
  {
1900
1931
  "name": "undo"
1901
1932
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nika-js/onlymap",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Declarative deck.gl maps for HTML and React — interactive WebGL mapping with GeoJSON/CSV/Arrow data, MapLibre basemaps, widgets, popups, and live streams from a custom-element manifest or typed React components. TypeScript, no build step.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "publishConfig": {