@nika-js/onlymap 0.6.19 → 0.6.21

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/dist/{LercDecode.es-Butn4LVX.js → LercDecode.es-B_dD2_mY.js} +1 -1
  4. package/dist/actions.d.ts +94 -1
  5. package/dist/attribute-resolution.d.ts +12 -0
  6. package/dist/{basemap-qZH_aNYE.js → basemap-DK0AJ6ZH.js} +341 -313
  7. package/dist/basemap.d.ts +17 -1
  8. package/dist/declarative-filter.d.ts +2 -16
  9. package/dist/effect-track.d.ts +94 -0
  10. package/dist/effects.d.ts +2 -0
  11. package/dist/elements/om-map.d.ts +11 -8
  12. package/dist/elements/om-story.d.ts +18 -0
  13. package/dist/{geoparquet-DWQCUg6N.js → geoparquet-fc2daxMh.js} +1 -1
  14. package/dist/{index-CeeQ7CH4.js → index-9EEyGbU1.js} +1 -1
  15. package/dist/{index-4St6BumW.js → index-BNZFF535.js} +2 -2
  16. package/dist/{index-BTM6Mblt.js → index-BwoceNAo.js} +1 -1
  17. package/dist/{index-CagRqDDL.js → index-CuBGRyG-.js} +13970 -13685
  18. package/dist/{index-CNlULiHm.js → index-DOPjev5l.js} +1 -1
  19. package/dist/internal-ids.d.ts +2 -0
  20. package/dist/{lerc-cWjnr__g.js → lerc-CMJO8KZO.js} +2 -2
  21. package/dist/onlymap.standalone.js +24388 -24075
  22. package/dist/onlymapjs.js +1 -1
  23. package/dist/programmatic.d.ts +1 -2
  24. package/dist/{raster-BxxtJxxs.js → raster-GMsuQARC.js} +2 -2
  25. package/dist/{raster-pipeline-DlppdBYw.js → raster-pipeline-DNpKXgO3.js} +1 -1
  26. package/dist/runtime-core.d.ts +22 -0
  27. package/dist/snapshot.d.ts +15 -0
  28. package/dist/version.d.ts +1 -1
  29. package/dist/{zarr-BtxMPczZ.js → zarr-KiXluGoJ.js} +2 -2
  30. package/docs/stories.md +2 -0
  31. package/llms.txt +2 -2
  32. package/package.json +1 -1
  33. package/skills/onlymapjs/SKILL.md +1 -1
  34. package/skills/onlymapjs/references/syntax.md +3 -3
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-CagRqDDL.js";
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-CuBGRyG-.js";
2
2
  export {
3
3
  e as ALL_POSITION_VALUES,
4
4
  r as AUDIT_EXEMPTIONS,
@@ -171,7 +171,6 @@ export type PickListener = (selection: Selection | null) => void;
171
171
  * DOM-manifest concern. One controller per map container.
172
172
  */
173
173
  export declare class MapController {
174
- private firstSettleDrawDone;
175
174
  private core;
176
175
  private mount;
177
176
  private layerIRs;
@@ -322,7 +321,7 @@ export declare class MapController {
322
321
  resume(): void;
323
322
  /** Projects a lng/lat through the current viewport — undefined before the viewport resolves. */
324
323
  project(lngLat: [number, number]): [number, number] | undefined;
325
- /** Issue #37 — the controller-lane twin of om-map.whenSettled(): resolves once every live 3D tileset has refined for the current view plus one painted frame; {settled:false} + warning on timeout. */
324
+ /** Issue #37 — delegates to RuntimeCore.whenSettled, the single owner of the settle contract. */
326
325
  whenSettled(opts?: {
327
326
  timeout?: number;
328
327
  }): Promise<{
@@ -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-DlppdBYw.js";
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-DNpKXgO3.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-cWjnr__g.js").then((e) => e.l).then((e) => e.decode));
1072
+ A.set(m.Lerc, () => import("./lerc-CMJO8KZO.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-CagRqDDL.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-CuBGRyG-.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,
@@ -291,6 +291,7 @@ export declare class RuntimeCore {
291
291
  /** The lastIRs reference getLiveTilesets last pruned against (see its comment). */
292
292
  private liveTilesetsPrunedFor;
293
293
  private animatedFlushQueued;
294
+ private firstSettleDrawDone;
294
295
  private destroyed;
295
296
  private viewState;
296
297
  private callbacks;
@@ -522,6 +523,27 @@ export declare class RuntimeCore {
522
523
  basemapIdle(): boolean;
523
524
  /** deck's own per-layer readiness (async layer/pipeline init) — whenSettled gates on this too; a capture before it is stably missing content (issue #37's flaky first frame). Headless/basemap-pending count as ready. */
524
525
  layersReady(): boolean;
526
+ /**
527
+ * The map's "done drawing" promise (issue #37) — ONE definition for both
528
+ * front-ends (om-map and MapController delegate here; the settle contract
529
+ * previously existed as two editable copies with zero direct tests).
530
+ * Resolves once the basemap is idle (event latch: camera done, tiles
531
+ * loaded, fades complete), deck's own layers report loaded, every live 3D
532
+ * tileset has refined, and one further frame has been painted. The FIRST
533
+ * settle also pays a warm-up capture: on some GL stacks the first
534
+ * composite needs several draws to converge, and under the determinism
535
+ * switch captureStable's inner loop already guarantees byte-stability, so
536
+ * one stable capture is the entire warm-up (previously an outer 8-round
537
+ * loop re-derived what the inner loop guaranteed — up to 48 composites).
538
+ * Timeout resolves {settled:false} with a warning — a late frame is
539
+ * ordinary pop-in, not an error. v1 gates 3D tilesets + basemap; DOM
540
+ * overlays are outside the renderer and not awaited.
541
+ */
542
+ whenSettled(opts?: {
543
+ timeout?: number;
544
+ }): Promise<{
545
+ settled: boolean;
546
+ }>;
525
547
  /** Force one renderer draw without capturing (issue #37 — whenSettled's completed-render guarantee; a fresh page's first capture otherwise misses content that has never drawn). */
526
548
  forceDraw(): void;
527
549
  snapshot(): Promise<HTMLCanvasElement>;
@@ -12,3 +12,18 @@ export interface SnapshotOptions {
12
12
  as?: "dataURL" | "blob";
13
13
  }
14
14
  export declare function serializeSnapshot(canvas: HTMLCanvasElement, opts: SnapshotOptions): Promise<string | Blob>;
15
+ /**
16
+ * Deterministic byte-stable capture (issue #37): under the determinism
17
+ * switch a capture must be a pure function of story time, but on some GL
18
+ * stacks a capture can land while async renderer work is still converging —
19
+ * so capture until two consecutive captures are byte-identical and return
20
+ * that stable one (steady state: exactly two captures). ONE definition for
21
+ * both front-ends — byte-identity across lanes is the whole point, which
22
+ * two copies cannot guarantee.
23
+ */
24
+ export declare function captureStable(core: {
25
+ snapshot(): Promise<HTMLCanvasElement>;
26
+ }, opts?: SnapshotOptions, tuning?: {
27
+ maxRounds?: number;
28
+ requiredStableRuns?: number;
29
+ }): Promise<string | Blob>;
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.6.19";
8
+ export declare const LIBRARY_VERSION = "0.6.21";
@@ -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-DlppdBYw.js";
2
- import { ap as gr } from "./index-CagRqDDL.js";
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-DNpKXgO3.js";
2
+ import { ap as gr } from "./index-CuBGRyG-.js";
3
3
  import $t from "./index-CW1n5LdO.js";
4
4
  var Et;
5
5
  function h(e, t, n) {
package/docs/stories.md CHANGED
@@ -133,6 +133,8 @@ A flyby over a `Tile3DLayer` (photogrammetry, Google Photorealistic 3D Tiles) no
133
133
 
134
134
  Each paced frame emits `om-paced-tick` on the story (`detail = {t, waitedMs}`); `waitedMs > 0` means that frame paused for tiles — the hook for a "waiting for tiles" progress UI or a recorder. Two paced-run rules: only `fly-to` steps steer the camera (data-dependent camera steps like `zoom-to-feature` are skipped, with a console warning), and user gestures don't pause playback — pause via the player widget or the `story-pause` action.
135
135
 
136
+ Effect verbs animate on the **story clock** during a paced run: `fade`, `pulse`, `trace`, and `populate` are evaluated as functions of story time, so a 2 s trace spans exactly 2 s of output frames — half-drawn outlines and mid-fade opacities land in the frames exactly as authored, however long each frame takes to capture. (One exception: `trace follow` is skipped while the story drives the camera — the paced route owns it.)
137
+
136
138
  ### Recording a story to video
137
139
 
138
140
  ```bash
package/llms.txt CHANGED
@@ -44,10 +44,10 @@ Programmatic/native bridge rule: `MapController.setLayers()` accepts normal func
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`.
47
- - Pull-model frame rendering (external video frameworks such as Remotion): mark the map `data-om-recording` (the determinism switch — instant camera, effects snap to end state, no transitions/gesture interrupts, byte-stable `snapshot()`), then per frame `story.seek(t, {interpolateCamera: true})` + `await mapEl.whenSettled()` + capture; the same frame is byte-identical across processes and orderings. `onlymapjs record` remains the built-in push-model path.
47
+ - Pull-model frame rendering (external video frameworks such as Remotion): mark the map `data-om-recording` (the determinism switch — instant camera, no transitions/gesture interrupts, byte-stable `snapshot()`), then per frame `story.seek(t, {interpolateCamera: true})` + `await mapEl.whenSettled()` + capture; the same frame is byte-identical across processes and orderings. Story effect verbs (fade/pulse/trace/populate) evaluate as pure functions of story time under the switch — a seek landing mid-trace renders the half-drawn outline; only effects dispatched OUTSIDE a story (behaviors, ctx.emit) snap to their end state. `onlymapjs record` remains the built-in push-model path.
48
48
  - Travel-map animation: a `trace` story step takes `follow` (camera rides the drawing tip along the path) and `easing="linear|ease-in|ease-out|ease-in-out"`; combine with a TripsLayer route + pins toggled by later steps, and export with `npx onlymapjs record`.
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
- - 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.
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
52
  - 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.
53
53
  - 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nika-js/onlymap",
3
- "version": "0.6.19",
3
+ "version": "0.6.21",
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": {
@@ -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.19` (the bare package URL serves `dist/onlymap.standalone.js`) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.19/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.
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.21` (the bare package URL serves `dist/onlymap.standalone.js`) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.21/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.19/dist/onlymapjs.css">
20
- <script type="module" src="https://unpkg.com/@nika-js/onlymap@0.6.19"></script>
19
+ <link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.6.21/dist/onlymapjs.css">
20
+ <script type="module" src="https://unpkg.com/@nika-js/onlymap@0.6.21"></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).
@@ -674,7 +674,7 @@ Add `warm-tiles` on `<om-story>` to pre-load 3D tilesets along the story's fly-t
674
674
 
675
675
  Add `paced` on `<om-story>` for load-paced playback: the story 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 a frame 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 it for recorded takes or heavy tilesets like Google Photorealistic 3D Tiles, where no amount of pre-warming fits the flight in cache). Emits `om-paced-tick` (`detail = {t, waitedMs}`) on the story per frame; `waitedMs > 0` means that frame paused for tiles. Composes with `warm-tiles` (warm first → far shorter waits). During a paced run only `fly-to` steps steer the camera (data-dependent camera steps like `zoom-to-feature` are skipped with a warning) and user gestures don't pause playback — pause via the player widget or `story-pause`.
676
676
 
677
- To output an actual video: `npx onlymapjs record map.html --out flyby.mp4` (playwright dev-installed; ffmpeg assembles, else you get PNG frames + the command) plays the story paced in isolated headless Chromium and screenshots every frame — widgets, overlays, and attribution included, every frame fully refined. Options: `--story <id>`, `--fps <n>`, `--width/--height/--scale`, `--gpu` (hardware rendering; ~3× shorter tile holds on heavy 3D scenes — recommended), `--keep-frames`, `--timeout <s>` (frames survive a deadline hit in `<out>.frames/`), `--max-hold <s|none>` (per-frame tile-wait cap, default 10s — also authorable as `paced-max-hold` on `<om-story>`; frames that keep hitting the cap may stay slightly blurry — raise it, or `none` makes the gate absolute for guaranteed-sharp takes). It executes the page's scripts — trusted, browser-runnable manifests only. For custom recorders in-page, `storyEl.setPacedCapture(async (tick) => {...})` is awaited per frame before that frame's `om-paced-tick` (capture race-free; the ended-state tick = all frames captured).
677
+ To output an actual video: `npx onlymapjs record map.html --out flyby.mp4` (playwright dev-installed; ffmpeg assembles, else you get PNG frames + the command) plays the story paced in isolated headless Chromium and screenshots every frame — widgets, overlays, and attribution included, every frame fully refined. Options: `--story <id>`, `--fps <n>`, `--width/--height/--scale`, `--gpu` (hardware rendering; ~3× shorter tile holds on heavy 3D scenes — recommended), `--keep-frames`, `--timeout <s>` (frames survive a deadline hit in `<out>.frames/`), `--max-hold <s|none>` (per-frame tile-wait cap, default 10s — also authorable as `paced-max-hold` on `<om-story>`; frames that keep hitting the cap may stay slightly blurry — raise it, or `none` makes the gate absolute for guaranteed-sharp takes). It executes the page's scripts — trusted, browser-runnable manifests only. Effect verbs (fade/pulse/trace/populate) animate on the story clock during paced/recorded runs, so frames capture traces half-drawn and fades mid-flight exactly as authored (`trace follow` is skipped — the paced route owns the camera). For custom recorders in-page, `storyEl.setPacedCapture(async (tick) => {...})` is awaited per frame before that frame's `om-paced-tick` (capture race-free; the ended-state tick = all frames captured).
678
678
 
679
679
  Use `<om-story>` with `<om-step>` children. Stories are siblings of layers/overlays, not containers.
680
680