@nika-js/onlymap 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +5 -4
  2. package/dist/{LercDecode.es-C1xVetIa.js → LercDecode.es-B35eeLUh.js} +1 -1
  3. package/dist/{basemap-BkoYlZXu.js → basemap--znNIrJq.js} +8 -2
  4. package/dist/basemap.d.ts +8 -1
  5. package/dist/ctx.d.ts +4 -0
  6. package/dist/elements/om-map.d.ts +33 -3
  7. package/dist/elements/om-widget.d.ts +15 -0
  8. package/dist/external-store.d.ts +95 -0
  9. package/dist/{index-i_KAvtQZ.js → index-BKiyX-jQ.js} +10349 -10051
  10. package/dist/{index-CY7o8j7i.js → index-C8twrrDP.js} +2 -2
  11. package/dist/{index-Ck0N90Ri.js → index-CHmCCmPV.js} +1 -1
  12. package/dist/{index-BJvRiLIi.js → index-ClqgDar6.js} +1 -1
  13. package/dist/{index-DAx8ax6u.js → index-_K3U-Ww1.js} +1 -1
  14. package/dist/index.d.ts +5 -0
  15. package/dist/{lerc-DWGhMWvo.js → lerc-filCwJiM.js} +2 -2
  16. package/dist/onlymap.standalone.js +14343 -14039
  17. package/dist/onlymapjs.js +57 -48
  18. package/dist/programmatic.d.ts +42 -5
  19. package/dist/{raster-Da1auFQH.js → raster-xUPg8Z-C.js} +2 -2
  20. package/dist/react/context.d.ts +5 -3
  21. package/dist/react/om-map.d.ts +2 -0
  22. package/dist/react/om-widget.d.ts +4 -1
  23. package/dist/react.js +216 -186
  24. package/dist/runtime-core.d.ts +9 -2
  25. package/dist/version.d.ts +1 -1
  26. package/dist/widget-layout.d.ts +62 -0
  27. package/docs/external-stores.md +135 -0
  28. package/docs/react.md +3 -1
  29. package/llms.txt +3 -3
  30. package/onlymapjs.html-data.json +36 -1
  31. package/package.json +11 -6
  32. package/skills/onlymapjs/SKILL.md +2 -2
  33. package/skills/onlymapjs/references/react.md +4 -3
  34. package/skills/onlymapjs/references/syntax.md +1 -1
package/README.md CHANGED
@@ -69,7 +69,7 @@ Or with no build step at all, straight from a CDN:
69
69
 
70
70
  The bare package URL serves `dist/onlymap.standalone.js`, a single-file bundle built for exactly this (jsDelivr too). Use a CDN that serves the package's raw files — **not** a rebundling CDN like esm.sh, which re-splits the bundle into duplicate copies of the deck.gl/luma.gl runtime and breaks every layer's shader compilation.
71
71
 
72
- Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 451 unit/behavioral tests and 27 Playwright GPU tests.
72
+ Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 518 unit/behavioral tests and 41 Playwright GPU tests.
73
73
 
74
74
  The [examples](https://github.com/NikaGeospatial/onlymapjs/tree/main/examples) are the best tour: widgets, behaviors & overlays, basemaps, columnar/Arrow data, manual drawing, 3D models, scene lighting (with the native lighting widget), DEM terrain, a live WebSocket ship feed, and a polled driver fleet.
75
75
 
@@ -81,7 +81,7 @@ A handful of elements, one rule: **attributes are kebab-case versions of deck.gl
81
81
  |---|---|
82
82
  | `<om-map>` | The map. `center`, `zoom`, `pitch`, `bearing`; `basemap` takes a free preset (`positron`, `liberty`, `dark-matter`, `osm`, …), a style URL, or `"none"` (standalone canvas) — and switches **live**; `validate` for a live on-page error panel. |
83
83
  | `<om-layer>` | Any of **34 layer types** by name — all of deck.gl's core, geo, aggregation, and mesh layers (Scatterplot, GeoJson, Arc, Path, Heatmap, Hexagon, Trips, Tile, Tile3D, Scenegraph, …) plus the built-in `PopupLayer` for WebGL badges/labels at scale and the native `COGLayer` for GeoTIFF rasters. `id` required; `label`/`color` feed the legend. |
84
- | `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar`, `attribution`, `filter`, `draw`, `vega-lite` (live charts). Or write your own inline with HTML + a `<script type="om/widget">`. Themeable from plain page CSS via custom properties: `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` (also `-muted`, `-border`, `-hover-bg`, `-accent`). |
84
+ | `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar`, `attribution`, `filter`, `draw`, `vega-lite` (live charts). Or write your own inline with HTML + a `<script type="om/widget">`. **Placement is managed**: `position` takes one of 8 logical, RTL-aware slots (`top-start`, `top-center`, `top-end`, `center-start`, `center-end`, `bottom-start`, `bottom-center`, `bottom-end`; legacy corner names alias) — same-slot widgets stack with flush edges and a shared gap, `order` sets in-slot ordering, and `position="manual"` opts out entirely (a plain block you style yourself, even outside the map). Themeable from plain page CSS via custom properties: `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` (also `-muted`, `-border`, `-hover-bg`, `-accent`), plus layout tokens (`--om-widget-inset-x/-y`, `--om-widget-gap-x/-y`, `--om-widget-opacity`, `--om-widget-radius`) or the no-CSS sugar `<om-map widget-style="gap:10 opacity:0.9">`. |
85
85
  | `<om-overlay>` | Rich HTML anchored to a map location — a static `anchor="[lng, lat]"`, the current selection, or a feature's own geometry via `anchor-layer`/`anchor-feature-id`. `{{field}}` interpolates the picked feature, HTML-escaped by default. |
86
86
  | `<om-behavior>` | Declarative interactions: `on="click|hover|drag|load|data-loaded"` → a named action. |
87
87
  | `<om-story>` | A storyboard: `<om-step>` children fire actions on a timeline. Controlled by the `player` widget, behaviors, or `storyEl.play()/pause()/seek()`. |
@@ -223,8 +223,9 @@ Plus `OmMap.snapshotIR(html)` to lock down what a manifest *means* in a snapshot
223
223
 
224
224
  - **`OmMap.*`** — `validate`, `snapshotIR`, `registerLayer`, `registerWidget`, `registerAction`, `registerSource`, `registerFormat`, `registerBasemap`, `configureBasemap`, `configureData`, `configureTelemetry`, `configureLicense`, `getLayerSchema`
225
225
  - **`@nika-js/onlymap/deck`** — the bundled deck.gl classes (`CompositeLayer`, `TileLayer`, …) for building custom layer types: shims must extend the same class hierarchy the core renders with, not a second installed deck.gl copy. Recipe: [docs/custom-layers.md](docs/custom-layers.md)
226
- - **On a `<om-map>` element** — `ready` (promise), `flyTo(coords, zoom?)`, `setLayerVisible(id, bool)`, `getLayers()`, `emit(action, payload)`, `snapshot(opts?)` (canvas-only PNG of basemap + layers at device pixels — DOM widgets/overlays and provider attribution are NOT captured, so exports must render credits themselves; `{as: "blob"}` for files, default dataURL); the `om-view-changed` event fires once the camera settles (debounced; `detail` = `{longitude, latitude, zoom, pitch, bearing}`) — the camera-persistence hook; `document.querySelector("om-map")` is fully typed
227
- - **`MapController`** — the framework-grade programmatic front-end (typed `LayerDescriptor`s → the same reconcile core, no DOM manifest): `setLayers`, `watch`, `emit`, camera methods, `injectPick`, `ready`, `snapshot`, an `onViewChange` option (the `om-view-changed` twin). The React adapter rides it; usable directly from vanilla TS or other frameworks
226
+ - **On a `<om-map>` element** — `ready` (promise), `flyTo(coords, zoom?)`, `setLayerVisible(id, bool)`, `getLayers()`, `emit(action, payload)`, `snapshot(opts?)` (canvas-only PNG of basemap + layers at device pixels — DOM widgets/overlays and provider attribution are NOT captured, so exports must render credits themselves; `{as: "blob"}` for files, default dataURL); the `om-view-changed` event fires once the camera settles (debounced; `detail` = `{longitude, latitude, zoom, pitch, bearing, origin}`, where `origin` is `"user"` for gesture-driven bursts vs `"programmatic"` for API/story moves — the echo-suppression signal for state sync) — the camera-persistence hook; `document.querySelector("om-map")` is fully typed
227
+ - **`MapController`** — the framework-grade programmatic front-end (typed `LayerDescriptor`s → the same reconcile core, no DOM manifest): `setLayers`, `watch`, `emit`, camera methods, `injectPick`, `ready`, `snapshot`, an `onViewChange(view, origin)` option (the `om-view-changed` twin). The React adapter rides it; usable directly from vanilla TS or other frameworks
228
+ - **`getStore(token)`** — the external-store contract: per-token `{subscribe, getSnapshot}` stores (`viewport`/`selection`/`layers`/`data:<id>`) with cached immutable plain-data snapshots and `origin` tagging — directly consumable by `useSyncExternalStore` (the React adapter's own hooks ride it), MobX autoruns, Redux listeners, Zustand mirrors. ~20-line integration-tested recipes for Redux Toolkit, MobX/mobx-keystone, Zustand, and Jotai: [docs/external-stores.md](docs/external-stores.md)
228
229
  - **Testing** — `mountForTest`, and imports are SSR-safe (importing in Node/jsdom never touches browser globals)
229
230
 
230
231
  ## Free tier & licensing
@@ -19,7 +19,7 @@ async function v(N = {}) {
19
19
  if (n) {
20
20
  const { createRequire: I } = await import(
21
21
  /*webpackIgnore:true*/
22
- "./lerc-DWGhMWvo.js"
22
+ "./lerc-filCwJiM.js"
23
23
  ).then((g) => g._);
24
24
  var O = I(import.meta.url);
25
25
  }
@@ -1,4 +1,4 @@
1
- import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-i_KAvtQZ.js";
1
+ import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-BKiyX-jQ.js";
2
2
  const Cd = Math.PI / 180, gy = 180 / Math.PI;
3
3
  function em(Pe, Q = 0) {
4
4
  const ge = Math.min(180, Pe) * Cd;
@@ -25177,8 +25177,14 @@ class Ry {
25177
25177
  this.initialized ? Q() : this.loadCallbacks.push(Q);
25178
25178
  }
25179
25179
  /** Fires continuously during pan/zoom/rotate — MapLibre's own camera-change event, standing in for deck's onViewStateChange (which doesn't apply — deck isn't driving the camera). */
25180
+ /**
25181
+ * Viewport-change stream with origin tagging (spec: "External-Store
25182
+ * Contract") — MapLibre attaches `originalEvent` (the DOM pointer/wheel
25183
+ * event) to gesture-driven move events; our own easeTo/jumpTo/fitBounds
25184
+ * fire without one. The ecosystem's standard user-vs-programmatic signal.
25185
+ */
25180
25186
  onMove(Q) {
25181
- this.map.on("move", Q);
25187
+ this.map.on("move", (ge) => Q(ge.originalEvent ? "user" : "programmatic"));
25182
25188
  }
25183
25189
  setLayers(Q) {
25184
25190
  this.overlay.setProps({ layers: Q });
package/dist/basemap.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import "maplibre-gl/dist/maplibre-gl.css";
2
2
  import type { DeckProps, Viewport } from "@deck.gl/core";
3
3
  import type { ResolvedBasemap } from "./basemap-registry";
4
+ import type { ViewOrigin } from "./external-store";
4
5
  export interface ViewportLike {
5
6
  project(xyz: number[]): number[];
6
7
  getBounds(): [number, number, number, number];
@@ -65,7 +66,13 @@ export declare class MapLibreBasemapAdapter {
65
66
  private markInitialized;
66
67
  onLoad(cb: () => void): void;
67
68
  /** Fires continuously during pan/zoom/rotate — MapLibre's own camera-change event, standing in for deck's onViewStateChange (which doesn't apply — deck isn't driving the camera). */
68
- onMove(cb: () => void): void;
69
+ /**
70
+ * Viewport-change stream with origin tagging (spec: "External-Store
71
+ * Contract") — MapLibre attaches `originalEvent` (the DOM pointer/wheel
72
+ * event) to gesture-driven move events; our own easeTo/jumpTo/fitBounds
73
+ * fire without one. The ecosystem's standard user-vs-programmatic signal.
74
+ */
75
+ onMove(cb: (origin: ViewOrigin) => void): void;
69
76
  setLayers(layers: DeckProps["layers"]): void;
70
77
  /**
71
78
  * Scene lighting (spec: "Scene Lighting") — MapboxOverlay accepts full
package/dist/ctx.d.ts CHANGED
@@ -57,5 +57,9 @@ export interface BuildCtxDeps {
57
57
  canRedo: boolean;
58
58
  };
59
59
  }
60
+ /** `ctx.viewport.bounds` / the `viewport` external-store snapshot — one bounds derivation (incl. the world-extent fallback) for both (spec: "External-Store Contract"). */
61
+ export declare function viewportBounds(viewport: ReturnType<RuntimeCore["getViewport"]>): [[number, number], [number, number]];
62
+ /** `ctx.layers` / the `layers` external store — one builder for both (spec: "External-Store Contract"). */
63
+ export declare function buildLayerMetas(layerIRs: ReadonlyMap<string, LayerIR>): LayerMetaSnapshot[];
60
64
  /** Built fresh on every widget-render invocation — never mutated, never reused. */
61
65
  export declare function buildCtx({ layerIRs, core, mapEl, selection, history }: BuildCtxDeps): RuntimeContext;
@@ -21,6 +21,7 @@ import { HTMLElementBase } from "../env";
21
21
  import { type RuntimeContext } from "../ctx";
22
22
  import { type LightingIR } from "../scene-lighting";
23
23
  import { type TerrainIR } from "../terrain";
24
+ import { type WidgetSlot } from "../widget-layout";
24
25
  import { type SnapshotOptions } from "../snapshot";
25
26
  import type { LayerIR } from "../ir";
26
27
  import type { Selection } from "../selection";
@@ -47,14 +48,41 @@ export declare class OmMapElement extends HTMLElementBase {
47
48
  private readyFired;
48
49
  private resolveReady;
49
50
  readonly ready: Promise<void>;
50
- private viewChangedTimer;
51
+ private readonly viewSettle;
51
52
  private firstReconcileDone;
52
53
  private rendererLoadPending;
53
54
  private loadDispatched;
54
55
  connectedCallback(): void;
55
56
  disconnectedCallback(): void;
56
- /** Trailing-debounced `om-view-changed` dispatch see the field's doc comment. */
57
- private scheduleViewChangedEvent;
57
+ /** Library-owned stacking layer: one absolutely-positioned flex container per managed slot, created lazily. Rendering plumbing, never authored state. */
58
+ private widgetLayer;
59
+ private readonly slotContainers;
60
+ /** Is this node the widget layer or one of its slot containers? (Their childList churn is plumbing, not manifest edits.) */
61
+ private isWidgetLayoutPlumbing;
62
+ /**
63
+ * Places an <om-widget> into its managed slot container (creating layer/
64
+ * container on demand), or back into the light DOM for the manual tier.
65
+ * The reparent is RENDERING PLUMBING: history-suppressed (undo must never
66
+ * replay it) and filtered out of manifest-change detection. The widget's
67
+ * authored state — its `position` attribute — never changes here.
68
+ */
69
+ /** Per-widget comment anchors marking the AUTHORED light-DOM position — a managed→manual flip restores the widget where the author wrote it, not at the end. */
70
+ private readonly slotAnchors;
71
+ /** The widget layer div — lazy, shared by slot containers AND the layout tokens (both lanes put tokens on the layer, never on an authored element). */
72
+ private ensureWidgetLayer;
73
+ slotWidgetInternal(el: HTMLElement, slot: WidgetSlot): void;
74
+ /** Custom properties applied from widget-style — tracked so an edit reverts removed pairs to their defaults (CSSStyleDeclaration iteration of custom props is not portable). */
75
+ private appliedWidgetStyleProps;
76
+ /**
77
+ * `widget-style` sugar → custom properties on the WIDGET LAYER, never the
78
+ * authored <om-map> element (matching the React lane): inline style on an
79
+ * authored element would collide with author styles and leak into any
80
+ * outerHTML serialization. Unparseable/out-of-range pairs are skipped
81
+ * live; validation carries the loud version. Author page-CSS tokens on
82
+ * `om-map { --om-widget-*: … }` still cascade INTO the layer; the sugar
83
+ * (being inline on the layer) wins over page CSS, matching attr > CSS.
84
+ */
85
+ private applyWidgetStyle;
58
86
  registerWidgetInternal(el: Element, watch: string[], notify: (ctx: RuntimeContext) => void): void;
59
87
  unregisterWidgetInternal(el: Element): void;
60
88
  registerOverlayInternal(overlay: OverlayHost): void;
@@ -192,6 +220,8 @@ declare global {
192
220
  zoom: number;
193
221
  pitch: number;
194
222
  bearing: number;
223
+ /** "user" if any change in the settled burst came from a canvas gesture; "programmatic" for pure API/action/story moves. */
224
+ origin: "user" | "programmatic";
195
225
  }>;
196
226
  }
197
227
  }
@@ -21,8 +21,23 @@ export declare class OmWidgetElement extends HTMLElementBase implements WidgetHo
21
21
  private mapEl;
22
22
  private renderFn?;
23
23
  private watchTokens;
24
+ private hostStyle;
25
+ /** Live placement attributes (spec: "Widget Layout Manager") — position/order edits (incl. undo replays) re-slot without a remount. */
26
+ static readonly observedAttributes: string[];
24
27
  connectedCallback(): void;
25
28
  disconnectedCallback(): void;
29
+ attributeChangedCallback(name: string): void;
30
+ /**
31
+ * Slot placement (spec: "Widget Layout Manager"). Managed slots: the host
32
+ * is a plain flex item inside the map-owned slot container (flush edges,
33
+ * gap, stacking come from the container — never from the widget). Manual:
34
+ * a plain block the author styles and places, including outside the map.
35
+ * `order` (managed tier) is deterministic in-slot ordering via flex
36
+ * `order`; default is DOM order.
37
+ */
38
+ /** Last-applied tier — hostStyle only rewrites (CSSOM reparse) on an actual manual↔managed flip, not on every replayed identical write. */
39
+ private appliedTier;
40
+ private applyPlacement;
26
41
  private setUpRenderer;
27
42
  $(selector: string): Element | null;
28
43
  $$(selector: string): NodeListOf<Element>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * External-store contract (spec: "External-Store Contract — `getStore` &
3
+ * `useSyncExternalStore`") — the framework-free `{subscribe, getSnapshot}`
4
+ * surface `MapController.getStore(token)` hands to React's
5
+ * `useSyncExternalStore`, MobX autoruns, Redux listeners, Zustand mirrors,
6
+ * and any future framework adapter. One contract, N consumers. Also home to
7
+ * the shared settle/burst-origin tracker both front-ends' "camera settled"
8
+ * signals ride — one settle contract, two front-ends, one implementation.
9
+ *
10
+ * The load-bearing rule is CACHED IDENTITY: `getSnapshot()` returns the
11
+ * SAME reference until that token's event fires (a fresh object per call
12
+ * makes `useSyncExternalStore` loop forever — zustand v5's headline
13
+ * breaking change was exactly this fix). Invalidation only marks dirty;
14
+ * the next read rebuilds — a 60fps camera stream with no subscribers
15
+ * builds nothing.
16
+ *
17
+ * Snapshots are PLAIN SERIALIZABLE DATA — no closures — so Redux devtools
18
+ * and persistence middleware never choke on them. Rich reads (`project`,
19
+ * `data()`, `stats()`) stay on `ctx`; `data:<id>` exposes a VERSION STAMP,
20
+ * not the rows (mirroring a 100k-row array into a store per tick is the
21
+ * anti-pattern the Redux performance guidance warns about).
22
+ */
23
+ /**
24
+ * Who moved the camera: a pointer/wheel gesture on the canvas ("user") or
25
+ * a camera API / action / transition frame ("programmatic"). The echo-loop
26
+ * half of two-way store binding — a bridge writes back only user-originated
27
+ * changes; the map's idempotent setters are the other half.
28
+ */
29
+ export type ViewOrigin = "user" | "programmatic";
30
+ /** The per-token store — directly consumable by `useSyncExternalStore(store.subscribe, store.getSnapshot)`. */
31
+ export interface TokenStore<T = unknown> {
32
+ /** `cb` fires after this token's state changed (read the new snapshot inside it). Returns the unsubscribe function. */
33
+ subscribe(cb: () => void): () => void;
34
+ /** Cached immutable snapshot — the SAME reference until the next change event for this token. */
35
+ getSnapshot(): T;
36
+ }
37
+ /** `viewport` store snapshot — CameraState plus bounds and the origin of the current settle burst. */
38
+ export interface ViewportSnapshot {
39
+ longitude: number;
40
+ latitude: number;
41
+ zoom: number;
42
+ pitch: number;
43
+ bearing: number;
44
+ /** [[west, south], [east, north]] — same shape as `ctx.viewport.bounds`. */
45
+ bounds: [[number, number], [number, number]];
46
+ /**
47
+ * Burst-latched origin: "user" if any change since the last settle was a
48
+ * gesture — so a drag's inertia tail (MapLibre inertial moves carry no
49
+ * `originalEvent`) can't relabel the gesture mid-burst. Matches what the
50
+ * settled signals (`om-view-changed`, `onViewChange`) report.
51
+ */
52
+ origin: ViewOrigin;
53
+ }
54
+ /** `data:<layerId>` store snapshot — a version stamp, not the rows (fetch rows via `ctx.data()`). */
55
+ export interface DataVersionSnapshot {
56
+ /** Bumps on every data change for the layer (load, stream tick, poll refresh). 0 = never loaded / unknown layer. */
57
+ version: number;
58
+ /** Current row count (0 for unknown layers). */
59
+ rows: number;
60
+ }
61
+ /**
62
+ * The store-token roster — ONE source of truth shared by
63
+ * `MapController.getStore` (throws otherwise) and the React adapter's
64
+ * warn-skip, so the two can never disagree about which tokens are stores.
65
+ */
66
+ export declare function isStoreToken(token: string): boolean;
67
+ /** The per-token store table — memoized store objects (calling `get` twice for one token returns the identical store). */
68
+ export declare class TokenStoreRegistry {
69
+ private readonly entries;
70
+ /** Memoized per token — `build` is captured on first call and reused. */
71
+ get<T>(token: string, build: () => T): TokenStore<T>;
72
+ /** Marks the token dirty and notifies its subscribers. Unknown/never-requested tokens no-op (nothing to invalidate). */
73
+ invalidate(token: string): void;
74
+ /** Drop every entry and subscriber — controller teardown: a consumer that never unsubscribed must not pin the controller in memory. */
75
+ clear(): void;
76
+ }
77
+ /** Trailing debounce for the settled-camera signals — long enough to coalesce a pan/flight's per-frame churn, short enough that camera persistence feels immediate. */
78
+ export declare const VIEW_SETTLE_DEBOUNCE_MS = 150;
79
+ /**
80
+ * The settle/burst tracker behind `om-view-changed` and `onViewChange` —
81
+ * shared by both front-ends so the debounce window and the burst-origin
82
+ * rule ("user" if ANY change since the last settle was a gesture; an
83
+ * inertia tail can't relabel the gesture) can never drift apart.
84
+ */
85
+ export declare class ViewSettleTracker {
86
+ private readonly onSettle;
87
+ private timer;
88
+ private burstHadUser;
89
+ constructor(onSettle: (origin: ViewOrigin) => void);
90
+ /** Record one viewport change and (re)arm the trailing settle timer. */
91
+ bump(origin: ViewOrigin): void;
92
+ /** Burst-latched origin for snapshots taken mid-burst: the gesture wins over a trailing programmatic frame. */
93
+ latchedOrigin(lastFrameOrigin: ViewOrigin): ViewOrigin;
94
+ cancel(): void;
95
+ }