@nika-js/onlymap 0.6.25 → 0.6.26
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/CHANGELOG.md +9 -0
- package/README.md +3 -1
- package/dist/{LercDecode.es-DNwkPiJw.js → LercDecode.es-B4KknLMs.js} +1 -1
- package/dist/{basemap-Da9yotkI.js → basemap-C4bmWq6M.js} +9 -1
- package/dist/basemap.d.ts +4 -0
- package/dist/{geoparquet-D8-txekS.js → geoparquet-BPMda753.js} +1 -1
- package/dist/hover-pick.d.ts +53 -0
- package/dist/html-data.d.ts +1 -1
- package/dist/{index-Z6ZPKbXa.js → index-B5hsHnPl.js} +1 -1
- package/dist/{index-CPf0evzb.js → index-COuIQk2Z.js} +8489 -8387
- package/dist/{index-CUnb2y9k.js → index-DK5w4OxN.js} +1 -1
- package/dist/{index-D6SeYdqX.js → index-DMT_7aV3.js} +1 -1
- package/dist/{index-31e3kTgK.js → index-DtU_XhTX.js} +2 -2
- package/dist/{lerc-CkqppWZk.js → lerc-BNW5A6zN.js} +2 -2
- package/dist/onlymap.standalone.js +14411 -14301
- package/dist/onlymapjs.js +1 -1
- package/dist/programmatic.d.ts +5 -0
- package/dist/{raster-CXPV5d4b.js → raster-eMqzBMQf.js} +2 -2
- package/dist/{raster-pipeline-CKW1mTzH.js → raster-pipeline-Dg-GSDP3.js} +1 -1
- package/dist/runtime-core.d.ts +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/{zarr-9YGMeWT7.js → zarr-Dv7OiUcg.js} +2 -2
- package/llms.txt +2 -1
- package/onlymapjs.html-data.json +33 -0
- package/package.json +4 -2
- package/skills/onlymapjs/SKILL.md +1 -1
- package/skills/onlymapjs/references/syntax.md +7 -2
package/dist/onlymapjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { aA as e, aB as r, aC as t, aD as o, aE as b, aF as i, ap as l, aG as n, aH as S, aI as g, aJ as c, aK as E, aL as L, aM as T, aN as A, aO as d, aP as p, aQ as m, aR as _, aS as I, aT as u, aU as M, aV as y, aW as D, ao as O, aX as R, aw as f, aY as N, aZ as P, a_ as h, W as F, a$ as B, b0 as C, b1 as v, b2 as G, b3 as U, b4 as W, b5 as w, b6 as x, b7 as Y, b8 as H, b9 as X, ba as k, bb as J, bc as K, bd as V, be as z, bf as Q, bg as Z, bh as $, bi as j, bj as q, bk as aa, bl as sa, bm as ea, bn as ra, bo as ta, bp as oa, bq as ba, br as ia, bs as la, bt as na, bu as Sa, bv as ga, bw as ca, bx as Ea, by as La, bz as Ta, bA as Aa, bB as da, bC as pa, bD as ma, bE as _a, bF as Ia, bG as ua, bH as Ma, bI as ya, bJ as Da, bK as Oa, bL as Ra, bM as fa, bN as Na, bO as Pa, bP as ha, bQ as Fa, bR as Ba, bS as Ca, bT as va, bU as Ga, bV as Ua, bW as Wa, bX as wa, bY as xa, bZ as Ya, b_ as Ha, b$ as Xa, c0 as ka, c1 as Ja, c2 as Ka, c3 as Va, c4 as za, c5 as Qa, c6 as Za } from "./index-
|
|
1
|
+
import { aA as e, aB as r, aC as t, aD as o, aE as b, aF as i, ap as l, aG as n, aH as S, aI as g, aJ as c, aK as E, aL as L, aM as T, aN as A, aO as d, aP as p, aQ as m, aR as _, aS as I, aT as u, aU as M, aV as y, aW as D, ao as O, aX as R, aw as f, aY as N, aZ as P, a_ as h, W as F, a$ as B, b0 as C, b1 as v, b2 as G, b3 as U, b4 as W, b5 as w, b6 as x, b7 as Y, b8 as H, b9 as X, ba as k, bb as J, bc as K, bd as V, be as z, bf as Q, bg as Z, bh as $, bi as j, bj as q, bk as aa, bl as sa, bm as ea, bn as ra, bo as ta, bp as oa, bq as ba, br as ia, bs as la, bt as na, bu as Sa, bv as ga, bw as ca, bx as Ea, by as La, bz as Ta, bA as Aa, bB as da, bC as pa, bD as ma, bE as _a, bF as Ia, bG as ua, bH as Ma, bI as ya, bJ as Da, bK as Oa, bL as Ra, bM as fa, bN as Na, bO as Pa, bP as ha, bQ as Fa, bR as Ba, bS as Ca, bT as va, bU as Ga, bV as Ua, bW as Wa, bX as wa, bY as xa, bZ as Ya, b_ as Ha, b$ as Xa, c0 as ka, c1 as Ja, c2 as Ka, c3 as Va, c4 as za, c5 as Qa, c6 as Za } from "./index-COuIQk2Z.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ALL_POSITION_VALUES,
|
|
4
4
|
r as AUDIT_EXEMPTIONS,
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CameraOptions, type MandatedChromeHosts } from "./runtime-core";
|
|
2
|
+
import type { HoverPickMode } from "./hover-pick";
|
|
2
3
|
import { type LightingIR, type LightingOptions } from "./scene-lighting";
|
|
3
4
|
import type { TerrainIR } from "./terrain";
|
|
4
5
|
import { type SnapshotOptions } from "./snapshot";
|
|
@@ -331,5 +332,9 @@ export declare class MapController {
|
|
|
331
332
|
}>;
|
|
332
333
|
/** Issue #37 — the determinism switch: while on, camera moves land instantly, effect verbs snap to end state, and GPU transitions are disabled, so a captured frame is a pure function of story time. */
|
|
333
334
|
setDeterministic(on: boolean): void;
|
|
335
|
+
/** Hover-pick cadence (issue #31) — "rest" (default), "continuous", "off", or a throttle interval in ms. */
|
|
336
|
+
setHoverPicking(mode: HoverPickMode): void;
|
|
337
|
+
/** Pick-cursor affordance (issue #33) — a CSS cursor value shown over pickable features; "none"/"off" disables. */
|
|
338
|
+
setPickCursor(value: string | null): void;
|
|
334
339
|
destroy(): void;
|
|
335
340
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as re, t as Ae, i as nt, a as Re, s as rt, C as ot, b as it, F as at, A as st, d as lt, R as he, e as ct, p as ut, m as dt, f as ht, g as pt, h as gt } from "./raster-pipeline-
|
|
1
|
+
import { c as re, t as Ae, i as nt, a as Re, s as rt, C as ot, b as it, F as at, A as st, d as lt, R as he, e as ct, p as ut, m as dt, f as ht, g as pt, h as gt } from "./raster-pipeline-Dg-GSDP3.js";
|
|
2
2
|
import pe from "./index-CW1n5LdO.js";
|
|
3
3
|
function mt(e, t) {
|
|
4
4
|
const n = e.length / 3, r = new Uint8ClampedArray(n * 4), o = 0, i = n, a = n * 2;
|
|
@@ -1069,7 +1069,7 @@ A.set(m.Zstd, () => import("./zstd-jXobGRcq.js").then((e) => e.decode));
|
|
|
1069
1069
|
A.set(m.Jpeg, () => Promise.resolve(oe));
|
|
1070
1070
|
A.set(m.Jpeg6, () => Promise.resolve(oe));
|
|
1071
1071
|
A.set(m.Webp, () => Promise.resolve(oe));
|
|
1072
|
-
A.set(m.Lerc, () => import("./lerc-
|
|
1072
|
+
A.set(m.Lerc, () => import("./lerc-BNW5A6zN.js").then((e) => e.l).then((e) => e.decode));
|
|
1073
1073
|
async function ie(e, t, n) {
|
|
1074
1074
|
const r = A.get(t);
|
|
1075
1075
|
if (!r)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { w as ae } from "./mgrs-BY9bIvp4.js";
|
|
2
|
-
import { am as ce, an as le, ao as ee, ap as te, aq as ue, b as he, ar as de, l as Z, as as fe, d as pe, at as me, au as ge, av as ve, aw as ne } from "./index-
|
|
2
|
+
import { am as ce, an as le, ao as ee, ap as te, aq as ue, b as he, ar as de, l as Z, as as fe, d as pe, at as me, au as ge, av as ve, aw as ne } from "./index-COuIQk2Z.js";
|
|
3
3
|
function Pe(o, e, t) {
|
|
4
4
|
const { projectedCorners: n } = e, { topLeft: s, topRight: r, bottomRight: a, bottomLeft: i } = n, c = t(s[0], s[1]), u = t(r[0], r[1]), l = t(a[0], a[1]), d = t(i[0], i[1]), f = [
|
|
5
5
|
c,
|
package/dist/runtime-core.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HoverPickMode } from "./hover-pick";
|
|
1
2
|
import type { PickingInfo } from "@deck.gl/core";
|
|
2
3
|
import { type LightingIR } from "./scene-lighting";
|
|
3
4
|
import { type TerrainIR } from "./terrain";
|
|
@@ -347,6 +348,10 @@ export declare class RuntimeCore {
|
|
|
347
348
|
private renderedLayerIds;
|
|
348
349
|
/** Zoom-visibility gate fingerprint (see computeZoomGateKey) — camera moves rebuild layers only when this changes. */
|
|
349
350
|
private zoomGateKey;
|
|
351
|
+
/** Hover-pick cadence (issue #31) — a GETTER so an attribute edit applies to the very next pointer move. Default: rest-debounced. */
|
|
352
|
+
private hoverPickMode;
|
|
353
|
+
/** Pick-cursor value (issue #33) — a getter for the same live-edit reason; null/"none"/"off" disables the affordance. */
|
|
354
|
+
private pickCursorValue;
|
|
350
355
|
/** The decoded property table for a pick-features layer, or undefined before its first tile. */
|
|
351
356
|
getFeatureTable(layerId: string): Record<string, unknown>[] | undefined;
|
|
352
357
|
/**
|
|
@@ -421,6 +426,22 @@ export declare class RuntimeCore {
|
|
|
421
426
|
* `toSelection`'s `type` param on drag is a required tag, not meaningful —
|
|
422
427
|
* the behavior dispatch (om-map.ts) uses its own "drag" event name.
|
|
423
428
|
*/
|
|
429
|
+
/** Both front-ends install a live policy getter (the `hover-pick` attribute / MapController.setHoverPicking). */
|
|
430
|
+
setHoverPicking(mode: () => HoverPickMode): void;
|
|
431
|
+
/** Pick-cursor value getter (the `pick-cursor` attribute; default "pointer"). */
|
|
432
|
+
setPickCursor(value: () => string | null): void;
|
|
433
|
+
/**
|
|
434
|
+
* Hover-pick gate (issue #31): deck picks the scene — a render to the
|
|
435
|
+
* picking framebuffer plus a GPU ReadPixels stall — on every frame that
|
|
436
|
+
* saw a pointer move, with no cadence knob. `_onPointerMove` is an
|
|
437
|
+
* assignable instance property on the EXACT deck pin (the `_deck` /
|
|
438
|
+
* `_cacheBytes` seam precedent), so the gate wraps it with the
|
|
439
|
+
* rest-debounce policy. Applied to every deck this core constructs
|
|
440
|
+
* (standalone and the basemap overlay's, on every remount).
|
|
441
|
+
*/
|
|
442
|
+
private installHoverGate;
|
|
443
|
+
/** Writes the affordance cursor onto whichever canvas owns pointer events in the current mode; "" restores the renderer's own cursor management. */
|
|
444
|
+
private applyHoverCursor;
|
|
424
445
|
private pickCallbacks;
|
|
425
446
|
/** The current lighting as a deck `effects` array — [] restores deck's default lights. */
|
|
426
447
|
private buildEffects;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as ur, d as lr, R as zt, e as fr, m as dr, p as St, f as hr, g as pr, h as mr } from "./raster-pipeline-
|
|
2
|
-
import { ap as gr } from "./index-
|
|
1
|
+
import { A as ur, d as lr, R as zt, e as fr, m as dr, p as St, f as hr, g as pr, h as mr } from "./raster-pipeline-Dg-GSDP3.js";
|
|
2
|
+
import { ap as gr } from "./index-COuIQk2Z.js";
|
|
3
3
|
import $t from "./index-CW1n5LdO.js";
|
|
4
4
|
var Et;
|
|
5
5
|
function h(e, t, n) {
|
package/llms.txt
CHANGED
|
@@ -40,7 +40,7 @@ Programmatic/native bridge rule: `MapController.setLayers()` accepts normal func
|
|
|
40
40
|
- `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|clip-box|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite|dynamic-chart|measure" 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); provider attribution joins `bottom-end` and the license badge joins `bottom-start` as in-flow members, so required chrome never covers a widget; `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). At map widths ≤640px managed widgets auto-fold into one accessible drawer per map side; `fold="never"` exempts an essential widget, `widgets-fold="off"` opts the map out, `--om-widget-fold-breakpoint` changes the threshold. 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/-gap-y/-opacity/-radius/-fold-breakpoint` 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. `measure` is a geodesic ruler: `modes="distance area volume"` (default `distance area`), `units="metric|imperial|nautical"` — click the map to place points, live per-segment + total labels render on the map, and it dispatches an `om-measure` event; it reuses the draw capture stack (measure/draw mutually exclusive) and its geometry is ephemeral (never saved, not an undo step) — see the dedicated `measure`/`volume` bullet above for the full event shape and the `profile`/`density`/`swell`/`shrink`/`deadband` volume-mode attributes. `scale-bar` now takes the same `units`. `dynamic-chart` is the same Vega-Lite rendering as `vega-lite` but fed by a live DOM event instead of a layer: `on="<event-name>"` (required) + `series-field="<name>"` (default `series`) reads `event.detail[seriesField]` as `data.values` and re-embeds on every event where it's a present array, at a fixed `width`; omitting the field on a later event freezes the chart with no separate pause API — built for a feature (a drawn line's elevation profile) that computes its own series live and has no layer to bind to. 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.
|
|
41
41
|
- Custom-widget event emission (the #1 custom-widget bug — a widget that renders but does nothing): a widget DRIVES the map ONLY by EMITTING a registered action; it never mutates the map or dispatches its own `CustomEvent`. Two ways: (1) declarative `data-emit="<action>"` + `data-*` payload keys on an element (fires on click, or change for form controls whose `.value` is auto-added; `data-*` values are STRINGS — use `ctx.emit` for numeric/array payloads like a slider's range); (2) `ctx.emit(action, payload)` for typed payloads, wired INSIDE `render` (so `ctx` is in scope) by assigning `.oninput`/`.onclick` — e.g. a day slider: `this.render = (ctx) => { this.$("#day").oninput = e => ctx.emit("filter-layer", { layer: "quakes", field: "day", range: [+e.target.value, +e.target.value] }); }`. Actions + payloads: `filter-layer {layer, field?, range:[min,max]}`, `toggle-layer {layer, visible?}`, `fly-to {center:[lng,lat], zoom?, duration?}`, `zoom-to-feature {layer, featureId}`, `set-basemap {basemap}`, `highlight-feature {layer, featureId}`, `show-overlay`/`hide-overlay {target}`, `story-play`/`story-pause`/`story-seek {story, t?}`, `undo`/`redo`, `zoom-in`/`zoom-out`, `set-widgets-visible {visible}`; register more with `OmMap.registerAction(name, handler)`. NEVER inline `onclick=`/`oninput=` — `ctx` isn't a global and CSP blocks them, so it silently fires nothing (validation errors on it). For a plain value/time slider prefer the built-in `<om-widget type="filter" layer=… field=…>` — it wires `filter-layer` for you; hand-author only for bespoke UI.
|
|
42
42
|
- `<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). Selection-anchored overlays scope with `layer="…"` (one layer's picks only) and `selection-type="click"|"hover"` (one pick type only) — a click-opened popup should ALWAYS set `selection-type="click"`, else merely hovering any pickable feature drags it there and re-templates it against the hovered object; with it, hover is inert and a click on empty space still dismisses. `{{field}}` interpolates the picked feature HTML-escaped; `{{{field}}}` is raw (avoid); `{{z}}` is the pick's ELEVATION in meters, present only when a `pickable="3d"` layer ran deck's depth pass for that pick (absent — not `0` — otherwise, so "no elevation" is distinguishable from sea level). `clip-to-map` (opt-in) hides the overlay when its own BOX would spill past the map viewport rather than only when its anchor leaves — for small transient tips that track the cursor; an overhanging absolutely-positioned box inflates the page's scrollable overflow and the scrollbar -> map resize -> reprojection loop shows as view jitter. For labels on many features use `PopupLayer`, not overlays. STYLING: overlay content renders inside a shadow root (style isolation, like widgets) — page stylesheets/classes do NOT reach it; use inline `style="…"` on the content or a `<style>` element INSIDE the overlay (children move into the shadow root wholesale, so it applies there); inheritable props + CSS custom properties pierce.
|
|
43
|
-
- `<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`; scene/tool actions `set-lighting`, `set-terrain`, `set-clip-box` (`{min,max,invert?,highlight?}` / `{clear:true}`), `clip-box-edit` (`{editing}`), `export-region-3d` (`{target?, format?:"glb"|"b3dm"}` — what the draw widget's `export-3d` button emits), and the measure actions `measure-mode` (`{mode:"distance"|"area"|"volume"|null}`), `measure-units`, `measure-clear`, `measure-config` (`{profile?, baseSurface?, density?, swell?, shrink?, deadband?}`), `measure-flat-target-plane` (`{flat}`). One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
|
|
43
|
+
- `<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`, `set-pickable` (`{layer, pickable: true|false|"3d"}` — runtime picking/popup toggle, story-capturable; the per-layer popup on/off switch a viewer-facing export needs), `filter-layer`, `highlight-feature`, `zoom-to-feature`, `set-basemap`, `undo`, `redo`; scene/tool actions `set-lighting`, `set-terrain`, `set-clip-box` (`{min,max,invert?,highlight?}` / `{clear:true}`), `clip-box-edit` (`{editing}`), `export-region-3d` (`{target?, format?:"glb"|"b3dm"}` — what the draw widget's `export-3d` button emits), and the measure actions `measure-mode` (`{mode:"distance"|"area"|"volume"|null}`), `measure-units`, `measure-clear`, `measure-config` (`{profile?, baseSurface?, density?, swell?, shrink?, deadband?}`), `measure-flat-target-plane` (`{flat}`). One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
|
|
44
44
|
- 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")`.
|
|
45
45
|
- `<om-fallback>` — static no-JS fallback, direct child of `<om-map>` (one per map, no attributes, plain HTML content — links allowed). Shown ONLY where scripts never run (chat-app/email file previews — iOS QuickLook renders HTML attachments with JS off — file managers, sandboxed webviews); hidden automatically once the map boots. GOOD PRACTICE: include one on every complete page, especially pages that may be shared as a file ("This interactive map requires JavaScript — open this file in a web browser", plus a hosted-version link when one exists). Without one, the stylesheet shows a generic text-only banner. The gate is pure CSS (`om-map:not(:defined)` in onlymapjs.css), so the CSS must load without JS — a real `<link rel="stylesheet">` or inlined `<style>` on no-build pages; a bundler-emitted stylesheet is fine in npm projects.
|
|
46
46
|
- 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`.
|
|
@@ -49,6 +49,7 @@ Programmatic/native bridge rule: `MapController.setLayers()` accepts normal func
|
|
|
49
49
|
- Fixed-route 3D-tile pre-loading: `<om-story warm-tiles>` pre-fetches AND parses every 3D tileset's tiles along the story's fly-to route in the background at load (deck's own flight arc, sampled), so a flyby plays sharp instead of "blurry then clear" — or dispatch the `warm-tiles` action (`{story?, samples?, budget?}`; default budget raises each tileset cache to 256 MB, raise-only) manually before a take. Completion: `om-tiles-warmed` on `<om-map>`. Persistent per-layer knob: `load-options='{"tileset":{"maximumMemoryUsage":512}}'`.
|
|
50
50
|
- Load-paced ("clean") flyby: `<om-story paced>` steps its own clock frame by frame (optionally `paced="60"` story-fps, default 30), drives the camera itself along the fly-to route, and never advances while any 3D tileset is still refining — NO frame ever shows unrefined tiles, at the cost of wall-clock time (playback is not real-time; use for recorded takes or heavy tilesets — Google Photorealistic 3D Tiles — where no pre-warm fits the flight in cache). Per-frame `om-paced-tick` on the story (`detail = {t, waitedMs}`; `waitedMs > 0` = that frame paused for tiles). Composes with `warm-tiles` (warm first → shorter waits). Paced runs: only `fly-to` steps steer the camera (`zoom-to-feature` etc. are skipped with a warning), and user gestures do NOT pause playback — use the player widget or `story-pause`. VIDEO OUTPUT: `npx onlymapjs record map.html --out flyby.mp4` (needs dev-installed playwright; ffmpeg for assembly, else PNG frames + the command to run) plays the story paced in headless Chromium and writes a video where every frame is fully refined — widgets/overlays/attribution included; options `--story/--fps/--width/--height/--scale/--gpu/--keep-frames/--timeout/--max-hold` (`--gpu` = hardware rendering instead of headless software GL — ~3× shorter tile holds on heavy 3D scenes, recommended); frames survive a deadline hit in `<out>.frames/` for salvage. Per-frame tile waits are capped by `paced-max-hold` on `<om-story>` (duration grammar, default 10s; `--max-hold` sets it) — frames that keep hitting the cap may stay slightly blurry; raise it, or set `"none"` (`--max-hold none`) for an absolute gate: guaranteed-sharp takes, only the overall timeout bounds the run. Custom recorders: `storyEl.setPacedCapture(async (tick) => {...})` — awaited per frame BEFORE that frame's om-paced-tick, so capture is race-free and the ended-state tick means all frames captured. Effect verbs (fade/pulse/trace/populate) animate on the STORY clock during paced runs — recorded frames capture traces half-drawn and fades mid-flight exactly as authored (`trace follow` is skipped; the paced route owns the camera).
|
|
51
51
|
- `<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. Scene actions are story-steppable AND scrub-capturable: `set-basemap`, `set-lighting` (a sunset story: steps walking sun-elevation down; a bare preset step is a clean reset), and `set-terrain` all rewind on seek — the story captures the map's scene attributes before first play. 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).
|
|
52
|
+
- Hover picking & the pointer cursor: hovering a pickable feature shows `cursor: pointer` automatically (the is-this-interactive affordance; `pick-cursor` on `<om-map>` overrides the cursor value, `pick-cursor="none"` disables). Hover GPU picks are REST-DEBOUNCED by default — one pick when the pointer stops moving, none while it sweeps — which eliminates the per-mousemove GPU ReadPixels stall that made hover popups freeze integrated-graphics machines (measured ~1.1s of main-thread stall per move on real layers). `hover-pick` on `<om-map>` tunes it: `"rest"` (default), `"continuous"` (deck-native per-frame — for hover-follow UIs on capable hardware), `"off"` (click picking only), or a throttle interval in ms (leading + trailing). Click picking is never gated.
|
|
52
53
|
- Zoom visibility: `visible-zoom-range="[8, 14]"` on ANY layer type — scale-dependent visibility (hidden outside min ≤ zoom < max, the minzoom/maxzoom convention; the tile-only visible-min-zoom/visible-max-zoom deck props remain tile-layer-specific). Composes with `visible`; a zoom-hidden layer stays in the legend/switcher/ctx. For CI that emits om-layer markup, validate attributes per layer type against the shipped `onlymapjs.attributes.json` (generated from the same registry the runtime checks — `universal` + `layers[<type>]`), not the flat per-tag union in the IntelliSense html-data file. Also: a layer whose REQUIRED position accessor resolves undefined for every row (e.g. a TextLayer fed GeoJSON with no get-position) now warns at runtime with the exact fix (`get-position="$geometry.coordinates"`) instead of silently drawing nothing.
|
|
53
54
|
- 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="...">`. For an epoch-millisecond field, make the slider labels readable with `<om-widget type="filter" layer="quakes" field="time" format="date" date-style="datetime" time-zone="UTC"></om-widget>`. Up to 4 numeric dimensions at once via `filter-fields='[{"field":"magnitude","range":[4,10]},{"field":"time","range":[…]}]'` (JSON array, additive to filter-field/filter-range — wins if both are authored) — one `<om-widget type="filter">` per field, each moves its own dimension independently (filter-layer merges the range onto the matching field rather than replacing the whole filter); a row must pass every active dimension (AND). A dimension can't be added live — the full set is declared up front in filter-fields. Categorical filtering is a SEPARATE mechanism (deck.gl's own discrete keep-list test, not a range) with its own attributes: `filter-category="fuel" filter-categories='["Coal","Gas"]'` (single) or `filter-category-fields='[{"field":"fuel","categories":[...]},...]'` (up to 4); the SAME `<om-widget type="filter">` auto-renders checkboxes instead of a slider when its `field` is declared categorically (mode is inferred from the layer's own filter, never a separate widget attribute) — one checkbox per distinct value present in the data, with its row count. A category dimension with no keep-list is dropped from the active filter (there is no "matches everything" category the way a numeric range has [-Infinity, Infinity]). Numeric and categorical filters on the same layer combine — a row must pass both. `ctx.stats`/`ctx.dataInViewport` respect whichever kind(s) are active by default (`{filtered:false}` opts out). CLASSIFIED SYMBOLOGY: `classify-by="<numeric field>"` (+ `classify-scale="quantile|equal-interval|jenks"`, `classify-classes="2-12"` default 5, `classify-ramp="viridis|plasma|inferno|magma|cividis|turbo|blues|greens|oranges|purples|reds|ylorrd|rdbu|spectral"`) computes class breaks FROM THE DATA at reconcile time, installs the fill-color accessor and the auto classes legend — use it when the user asks for graduated/choropleth styling WITHOUT hand-authoring domains; an authored get-fill-color/color always wins (validation warns on the conflict); URL-backed layers classify when their data arrives. TEMPORAL PLAYBACK: `<om-widget type="time-slider" layer="…" field="<numeric/epoch-ms field>" duration="20s" window="<span in field units>" loop format="date" date-style time-zone>` — play/pause/scrub emitting the ordinary filter-layer action (cumulative from the domain start, or a sliding window with `window`); manifest stays the source of truth so undo/story/external filter edits re-sync the thumb — prefer it over hand-rolling a playback loop.
|
|
54
55
|
- Routing & tracking are two library-owned layer types (not `PathLayer`/`IconLayer` hand-wired) that expand into ordinary `PathLayer`/`IconLayer` instances internally, same pattern as `BIMLayer`→`Tile3DLayer`. `<om-layer type="Route" geometry='{"type":"LineString","coordinates":[[lng,lat],...]}'>` draws a styled route (casing + line + origin/destination pins) from geometry you already have — resolves SYNCHRONOUSLY, no network. `<om-layer type="Route" origin="[lng,lat]" destination="[lng,lat]" provider="nika" profile="driving">` (+ optional `waypoints`) resolves one ASYNCHRONOUSLY via a `RoutingProvider` named by `provider` — `"nika"` is registered by default but its endpoint is an UNVERIFIED PLACEHOLDER until NIKA's real routing service ships (register a working one with `OmMap.registerRoutingProvider(name, provider)` — a ~15-line adapter over OSRM's keyless public demo server (`router.project-osrm.org/route/v1/{profile}/{lng},{lat};{lng},{lat}?geometries=geojson&overview=full`, map `distance`/`duration`/`legs` onto `distanceMeters`/`durationSec`/`legs[]`) is the verified keyless real-data recipe; the skill's syntax.md carries it in full). `geometry` wins outright if both are authored (validation warns). `color`/`casing-color` style the line; `follow="fit-route"` auto-fits the camera once resolved. `<om-layer type="Tracking" get-position="[$lng,$lat]">` renders ONE moving entity (v1 — a fleet is one `Tracking` layer per vehicle) with bearing-derived icon rotation; position data arrives through the ORDINARY `data`/`source` mechanism, no separate tracking-subscription API. `bearing-field` (default `"bearing"`) names the plain field to rotate by (checks `properties.<field>` on GeoJSON rows, `<field>` directly on flat rows); `interpolate-ms` (default `1000`) glides the marker between two fixes via the per-frame channel instead of jumping; `follow="follow"` eases the camera along with it, same timing. `color`/`size` style the marker; `icon="arrow|car|motorcycle"` picks the shape (default arrow — all nose-up, baked in `color`, unknown names fall back with a validation warning). Tail modes: on the ROUTE layer, `progress-from="<tracking-layer-id>"` + `tail="none"` (client view — only current position → destination renders, origin pin dropped) or `tail="dim"` (operator view — traveled portion darkened; `tail-color` overrides) split the route at the marker's interpolated position per frame; default full ignores the split; validation warns on partial wiring.
|
package/onlymapjs.html-data.json
CHANGED
|
@@ -64,6 +64,33 @@
|
|
|
64
64
|
"name": "basemap-key",
|
|
65
65
|
"description": "Provider API key for keyed basemap presets (maptiler-*) — publishable origin-restricted key; or call OmMap.configureBasemap once."
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
"name": "hover-pick",
|
|
69
|
+
"description": "Hover-pick cadence: \"rest\" (default — one GPU pick when the pointer stops, none while it sweeps; kills the per-mousemove ReadPixels stall on integrated graphics), \"continuous\" (deck-native, per frame), \"off\" (click picking only), or a throttle interval in ms (leading + trailing).",
|
|
70
|
+
"values": [
|
|
71
|
+
{
|
|
72
|
+
"name": "rest"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "continuous"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "off"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "pick-cursor",
|
|
84
|
+
"description": "CSS cursor shown while the pointer is over a pickable feature (default \"pointer\" — the is-this-interactive affordance). \"none\" disables.",
|
|
85
|
+
"values": [
|
|
86
|
+
{
|
|
87
|
+
"name": "pointer"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "none"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
67
94
|
{
|
|
68
95
|
"name": "attribution",
|
|
69
96
|
"description": "Set \"false\" to opt out of the compact attribution control shown with basemaps.",
|
|
@@ -1822,6 +1849,9 @@
|
|
|
1822
1849
|
{
|
|
1823
1850
|
"name": "toggle-layer"
|
|
1824
1851
|
},
|
|
1852
|
+
{
|
|
1853
|
+
"name": "set-pickable"
|
|
1854
|
+
},
|
|
1825
1855
|
{
|
|
1826
1856
|
"name": "zoom-in"
|
|
1827
1857
|
},
|
|
@@ -2424,6 +2454,9 @@
|
|
|
2424
2454
|
{
|
|
2425
2455
|
"name": "toggle-layer"
|
|
2426
2456
|
},
|
|
2457
|
+
{
|
|
2458
|
+
"name": "set-pickable"
|
|
2459
|
+
},
|
|
2427
2460
|
{
|
|
2428
2461
|
"name": "zoom-in"
|
|
2429
2462
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nika-js/onlymap",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
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": {
|
|
@@ -112,7 +112,9 @@
|
|
|
112
112
|
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
113
113
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.e2e.json --noEmit && tsc -p cloud/workers/telemetry/tsconfig.json && tsc -p cloud/workers/examples/tsconfig.json",
|
|
114
114
|
"test": "vitest run",
|
|
115
|
-
"
|
|
115
|
+
"comment:e2e-smoke": "Release gate for rendered-pixel guarantees the unit suite can't see: the license-condition badge chrome (a red badge e2e shipped across 0.6.24 AND 0.6.25 because only vitest gated publish) and frame determinism. Deliberately tiny — the full e2e suite stays a manual/CI concern.",
|
|
116
|
+
"test:e2e:smoke": "playwright test e2e/deterministic-frames.spec.ts && playwright test e2e/examples.spec.ts -g \"attribution badge\"",
|
|
117
|
+
"prepublishOnly": "node dev/tools/announce-release.mjs --check && npm run build && npm run test:e2e:smoke",
|
|
116
118
|
"postpublish": "node dev/tools/announce-release.mjs",
|
|
117
119
|
"try": "vite-node dev/expr-repl.mjs",
|
|
118
120
|
"test:e2e": "playwright test",
|
|
@@ -23,7 +23,7 @@ Use OnlyMapJS as a declarative HTML map library. Write custom elements such as `
|
|
|
23
23
|
</script>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
For no-build CDN pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.6.
|
|
26
|
+
For no-build CDN pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.6.26` (the bare package URL serves `dist/onlymap.standalone.js`) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.26/dist/onlymapjs.css">`. Never a rebundling CDN (esm.sh, skypack): re-bundling duplicates the deck.gl/luma.gl runtime and every layer fails shader compilation.
|
|
27
27
|
|
|
28
28
|
## React Projects
|
|
29
29
|
|
|
@@ -16,8 +16,8 @@ Vite/npm project:
|
|
|
16
16
|
Static CDN page (raw-file CDNs only — unpkg/jsDelivr; never esm.sh or another rebundling CDN, which duplicates the WebGL runtime and breaks layer shaders):
|
|
17
17
|
|
|
18
18
|
```html
|
|
19
|
-
<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.
|
|
20
|
-
<script type="module" src="https://unpkg.com/@nika-js/onlymap@0.6.
|
|
19
|
+
<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.26/dist/onlymapjs.css">
|
|
20
|
+
<script type="module" src="https://unpkg.com/@nika-js/onlymap@0.6.26"></script>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Always include `onlymapjs.css` — it carries the MapLibre basemap styles and the no-JS fallback rules (`<om-fallback>` / default banner). For the fallback to work in script-disabled previews it must load without JavaScript: a real `<link rel="stylesheet">` or inlined `<style>` on no-build pages (a bundler-emitted stylesheet is fine in npm projects).
|
|
@@ -648,6 +648,7 @@ Common built-in actions:
|
|
|
648
648
|
- `show-overlay`, `hide-overlay`
|
|
649
649
|
- `show-tooltip`, `hide-tooltip`
|
|
650
650
|
- `toggle-layer`
|
|
651
|
+
- `set-pickable` — payload `{ layer, pickable: true|false|"3d" }`; writes/removes the layer's `pickable` attribute at runtime (turns popups/tooltips/hover behaviors on or off per layer — the viewer-facing popup toggle), story-capturable and undoable
|
|
651
652
|
- `highlight-feature` — sets the layer's `highlighted-id`; style the selection with `highlight-color="[220, 38, 38, 255]"` (or hex) on the `<om-layer>` — a constant color, not a `get-*` accessor
|
|
652
653
|
- `zoom-to-feature`
|
|
653
654
|
- `filter-layer`
|
|
@@ -664,6 +665,10 @@ Common built-in actions:
|
|
|
664
665
|
|
|
665
666
|
Payload attributes are kebab-case and become camelCase payload keys.
|
|
666
667
|
|
|
668
|
+
## Hover picking & the pointer cursor
|
|
669
|
+
|
|
670
|
+
Hovering a pickable feature shows `cursor: pointer` automatically — the "is this map interactive?" affordance; `pick-cursor` on `<om-map>` overrides the cursor value and `pick-cursor="none"` disables it. Hover GPU picks are REST-DEBOUNCED by default: one pick when the pointer stops, none while it sweeps — this eliminates the per-mousemove GPU ReadPixels stall that froze hover-popup maps on integrated graphics. `hover-pick` on `<om-map>` tunes the cadence: `"rest"` (default), `"continuous"` (deck-native per-frame, for hover-follow UIs on capable hardware), `"off"` (click picking only), or a throttle interval in ms (leading + trailing picks). Click picking is never gated. `MapController` twins: `setHoverPicking(mode)` / `setPickCursor(value)`.
|
|
671
|
+
|
|
667
672
|
Example:
|
|
668
673
|
|
|
669
674
|
```html
|