@nika-js/onlymap 0.6.1 → 0.6.4

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 (63) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/README.md +28 -15
  3. package/dist/{LercDecode.es-CJnypw8j.js → LercDecode.es-D5in29tf.js} +1 -1
  4. package/dist/{basemap-DrQ0-eyR.js → basemap-C0pFT3AO.js} +13 -3
  5. package/dist/basemap.d.ts +10 -1
  6. package/dist/clip-box-controller.d.ts +94 -0
  7. package/dist/clip-box.d.ts +109 -0
  8. package/dist/data-layer.d.ts +34 -4
  9. package/dist/draw-controller.d.ts +75 -1
  10. package/dist/draw.d.ts +22 -4
  11. package/dist/elements/om-map.d.ts +20 -0
  12. package/dist/elements/om-overlay.d.ts +12 -0
  13. package/dist/geodesy.d.ts +32 -3
  14. package/dist/{geoparquet-Dix4lTNy.js → geoparquet-By98JVB0.js} +1 -1
  15. package/dist/html-data.d.ts +2 -2
  16. package/dist/{index-BQMjW5w0.js → index-Bx9GFkrn.js} +1 -1
  17. package/dist/{index-CXPaeisL.js → index-CqC4sW_k.js} +1 -1
  18. package/dist/{index-1UgNlfGR.js → index-Cxo9mCw_.js} +30947 -27785
  19. package/dist/{index-B_1PPJgC.js → index-olfncHIq.js} +1 -1
  20. package/dist/{index-CAuT5j9Y.js → index-tnlYDALL.js} +2 -2
  21. package/dist/index.d.ts +8 -3
  22. package/dist/ir-snapshot.d.ts +3 -1
  23. package/dist/layers/bim-layer.d.ts +30 -7
  24. package/dist/layers/feature-mesh-layer.d.ts +1 -1
  25. package/dist/layers/popup-layer.d.ts +13 -0
  26. package/dist/legend-spec.d.ts +1 -1
  27. package/dist/{lerc-CbTjQ7uI.js → lerc-gKDDtc69.js} +2 -2
  28. package/dist/license.d.ts +25 -4
  29. package/dist/measure-controller.d.ts +482 -4
  30. package/dist/onlymap.standalone.js +53976 -50804
  31. package/dist/onlymapjs.js +42 -40
  32. package/dist/parse-manifest.d.ts +3 -0
  33. package/dist/programmatic.d.ts +34 -5
  34. package/dist/{raster-0b0nSHUh.js → raster-Cl5m3KsC.js} +2 -2
  35. package/dist/{raster-pipeline-D8siq7-4.js → raster-pipeline-hJGxIwYx.js} +1 -1
  36. package/dist/react/om-layer.d.ts +6 -1
  37. package/dist/react.js +167 -160
  38. package/dist/region-export-controller.d.ts +37 -0
  39. package/dist/region-export.d.ts +56 -0
  40. package/dist/runtime-core.d.ts +150 -0
  41. package/dist/selection.d.ts +11 -1
  42. package/dist/site-placement.d.ts +22 -0
  43. package/dist/snapping.d.ts +88 -0
  44. package/dist/terrain-heightfield.d.ts +53 -0
  45. package/dist/terrain-sample.d.ts +30 -0
  46. package/dist/terrain.d.ts +6 -1
  47. package/dist/units.d.ts +27 -0
  48. package/dist/version.d.ts +1 -1
  49. package/dist/volumetrics-run.d.ts +13 -0
  50. package/dist/volumetrics-worker.d.ts +1 -0
  51. package/dist/volumetrics.d.ts +201 -0
  52. package/dist/{zarr-DCYro_Vs.js → zarr-hRDavRGV.js} +2 -2
  53. package/docs/3d-assets.md +49 -1
  54. package/docs/live-data.md +26 -1
  55. package/docs/react.md +9 -1
  56. package/docs/testing.md +2 -0
  57. package/llms.txt +10 -5
  58. package/onlymapjs.html-data.json +161 -2
  59. package/package.json +4 -2
  60. package/skills/onlymapjs/SKILL.md +11 -1
  61. package/skills/onlymapjs/references/react.md +2 -2
  62. package/skills/onlymapjs/references/syntax.md +20 -8
  63. package/skills/onlymapjs/references/testing.md +6 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Clip box (spec: issue #34 §"Cutting / Clipping", tier 2 — the geometric
3
+ * complement to issue #26's clip PLANE). A real oriented... well, AXIS-
4
+ * ALIGNED (v1 scope, confirmed explicitly — rotation is a documented
5
+ * follow-up, not this) 3D box: geometry outside it is discarded (or, in
6
+ * `highlight` mode, dimmed rather than removed — "non-destructive"), so a
7
+ * dense scene (a BIM model, a point cloud, a city block) can be cut open to
8
+ * see what's inside.
9
+ *
10
+ * Map-level scene state (spec: `<om-map clip-box-min="..." clip-box-max="..."
11
+ * clip-box-invert clip-box-highlight>`), same architecture as `lighting`/
12
+ * `terrain`: attribute-backed, undoable, live-editable, applied to every
13
+ * layer by default with a per-layer `clip="off"` escape hatch — see
14
+ * `applyClipBox`'s own doc comment for why that default (not opt-in) is
15
+ * the right one here specifically.
16
+ *
17
+ * Three corrections from the original design review, all reflected below:
18
+ * 1. "Outside the box" is `!inside` (De Morgan's law on the AND of 6
19
+ * half-spaces) — NOT a literal OR of 6 flipped half-space tests, which
20
+ * would (incorrectly) require re-deriving 6 new inequalities; negating
21
+ * the single boolean `inside` result is simpler and exactly equivalent.
22
+ * 2. Plane/box bounds are expressed in deck's COMMON SPACE (via
23
+ * `Layer#projectPosition`, the SAME mechanism the built-in `ClipExtension`
24
+ * uses for its own 2D bounds) — never raw lng/lat — so one authored box
25
+ * clips layers in different coordinate systems (geographic, meter-offset
26
+ * BIM placements, etc.) correctly.
27
+ * 3. Picking (including the `pickable:'3d'` depth pass) is covered for
28
+ * free: `discard` inside `fs:DECKGL_FILTER_COLOR` is the SAME shader
29
+ * stage deck's own picking pass reuses — no separate picking-specific
30
+ * code needed, confirmed by the built-in `ClipExtension` doing nothing
31
+ * extra for picking either.
32
+ */
33
+ import { LayerExtension } from "@deck.gl/core";
34
+ import type { Layer } from "@deck.gl/core";
35
+ import type { LayerIR } from "./ir";
36
+ export interface ClipBoxIR {
37
+ /** [lng, lat, elevationMeters] — one corner. Order vs. `max` doesn't matter; both get componentwise min/max'd after projecting. */
38
+ min: [number, number, number];
39
+ max: [number, number, number];
40
+ /** Show OUTSIDE the box instead of inside. */
41
+ invert: boolean;
42
+ /** Dim clipped-out geometry instead of discarding it — "non-destructive": nothing disappears, it just de-emphasizes. */
43
+ highlight: boolean;
44
+ }
45
+ /** `<om-map clip-box-min="[...]" clip-box-max="[...]" clip-box-invert clip-box-highlight>` → `ClipBoxIR`, or `null` when min/max aren't both present/valid (no box = no clipping, not a broken one). */
46
+ export declare function parseClipBoxAttrs(getAttr: (name: string) => string | null): ClipBoxIR | null;
47
+ export interface ClipBoxExtensionProps {
48
+ clipBoxMin?: [number, number, number];
49
+ clipBoxMax?: [number, number, number];
50
+ clipBoxInvert?: boolean;
51
+ clipBoxHighlight?: boolean;
52
+ }
53
+ /** Fragment-level box clip (targets extruded/mesh geometry — GeoJsonLayer extrusions, Tile3DLayer/BIMLayer triangles — not point/icon anchors, unlike the built-in ClipExtension's dual vertex/fragment modes; this library's clip-box use case is "cut into a 3D scene," not "hide points outside a rect"). */
54
+ export declare class ClipBoxExtension extends LayerExtension {
55
+ static extensionName: string;
56
+ static defaultProps: {
57
+ clipBoxMin: number[];
58
+ clipBoxMax: number[];
59
+ clipBoxInvert: boolean;
60
+ clipBoxHighlight: boolean;
61
+ };
62
+ getShaders(): {
63
+ modules: {
64
+ readonly name: "clipBox";
65
+ readonly fs: "\nlayout(std140) uniform clipBoxUniforms {\n vec3 boxMin;\n vec3 boxMax;\n float invert;\n float highlight;\n vec4 highlightTint;\n} clipBox;\n\nbool clipBox_isInside(vec3 commonPos) {\n return commonPos.x >= clipBox.boxMin.x && commonPos.x <= clipBox.boxMax.x\n && commonPos.y >= clipBox.boxMin.y && commonPos.y <= clipBox.boxMax.y\n && commonPos.z >= clipBox.boxMin.z && commonPos.z <= clipBox.boxMax.z;\n}\n";
66
+ readonly uniformTypes: {
67
+ readonly boxMin: "vec3<f32>";
68
+ readonly boxMax: "vec3<f32>";
69
+ readonly invert: "f32";
70
+ readonly highlight: "f32";
71
+ readonly highlightTint: "vec4<f32>";
72
+ };
73
+ }[];
74
+ inject: {
75
+ "vs:#decl": string;
76
+ "vs:DECKGL_FILTER_GL_POSITION": string;
77
+ "fs:#decl": string;
78
+ "fs:DECKGL_FILTER_COLOR": string;
79
+ };
80
+ };
81
+ draw(this: Layer<Required<ClipBoxExtensionProps>>): void;
82
+ }
83
+ export interface ClipBoxLayerPatch {
84
+ /**
85
+ * The deck-layer id to construct with. Suffixed `#c<generation>` while a
86
+ * box is active and this layer is opted in: adding/removing an extension
87
+ * on a LIVE (already-mounted) layer corrupts DataFilterExtension's
88
+ * attribute state for some layer types (ColumnLayer, Tile3DLayer/BIMLayer
89
+ * sublayers — empirically verified: a box present from the FIRST render
90
+ * works fine, but toggling one on live on an already-mounted filtered
91
+ * layer silently blanks it, with no console error) — the exact same
92
+ * "birth-stable extension set" issue `applyTerrain`'s own `deckId`
93
+ * suffixing already works around. A fresh id forces clean teardown +
94
+ * rebuild instead of a live prop update.
95
+ */
96
+ deckId: string;
97
+ extraProps: Record<string, unknown>;
98
+ }
99
+ /**
100
+ * Per-layer clip-box patches, PURE — mirrors `applyTerrain`'s exact shape
101
+ * (same file this gets merged alongside in runtime-core.ts's `buildLayers`).
102
+ * Default is APPLIED (every layer gets clipped once a box is configured),
103
+ * unlike terrain's per-layer OPT-IN default — the clip box's whole point is
104
+ * cutting into "the scene," and requiring every single layer to opt in
105
+ * individually would make the common case ("cut through everything, show me
106
+ * what's inside") the awkward one; `clip="off"` is the escape hatch for the
107
+ * layers that shouldn't be touched (a basemap, most often).
108
+ */
109
+ export declare function applyClipBox(irs: readonly LayerIR[], clipBox: ClipBoxIR | null, generation: number): Map<string, ClipBoxLayerPatch>;
@@ -90,6 +90,28 @@ export interface DataFormat {
90
90
  export declare function rowsToColumnar(rows: Record<string, unknown>[]): ColumnarData | undefined;
91
91
  /** `OmMap.registerFormat(format)` — plug in a parser for a format the library doesn't ship (spec: "v0.2 / Tier 1"). */
92
92
  export declare function registerFormat(format: DataFormat): void;
93
+ /** Opaque controller/element identity used to reference-count data transports. */
94
+ export type DataTransportOwner = object;
95
+ /** Start one descriptor reconciliation transaction for an owner. */
96
+ export declare function beginDataOwnerUpdate(owner: DataTransportOwner): void;
97
+ /**
98
+ * Release transports no longer named by the owner's complete descriptor
99
+ * document. Retains their rows: a layer removed or re-pointed is a routine
100
+ * edit, and re-adding it (or undoing the edit) should repaint, not blink.
101
+ */
102
+ export declare function endDataOwnerUpdate(owner: DataTransportOwner): void;
103
+ /**
104
+ * Release every fetch/poller/socket held by an owner.
105
+ *
106
+ * Defaults to a permanent teardown — dispose, unmount, an <om-map> leaving the
107
+ * document — which drops the rows too, because an owner that cannot come back
108
+ * has nothing to repaint. Pass `{ retain: true }` for a pause the owner intends
109
+ * to reverse (`MapController.suspend()`), and resuming repaints the last rows
110
+ * while the reopened transport catches up.
111
+ */
112
+ export declare function releaseDataOwner(owner: DataTransportOwner, opts?: {
113
+ retain?: boolean;
114
+ }): void;
93
115
  /**
94
116
  * A source plugin supplies domain decoding (and optionally a subscription
95
117
  * handshake); the transport (WebSocket, reconnect, flush) is generic.
@@ -128,7 +150,7 @@ export interface DataRequestConfig {
128
150
  }
129
151
  export declare function configureData(config: DataRequestConfig): void;
130
152
  /** @internal Shared request-policy chokepoint for non-row assets (ImageOverlay); not re-exported from the package root. */
131
- export declare function fetchConfiguredResource(url: string): Promise<Response>;
153
+ export declare function fetchConfiguredResource(url: string, signal?: AbortSignal): Promise<Response>;
132
154
  /** @internal Shared Content-Length license backstop for row and non-row sources. */
133
155
  export declare function assertFetchSizeWithinEntitlement(response: Response): void;
134
156
  /** `data="draw:sketch"` — the reserved scheme for a widget-fed sketch layer (never collides with http/ws/inline). */
@@ -162,9 +184,17 @@ export declare function getDrawData(url: string): LayerData;
162
184
  * call for a given URL, and invokes `onLoaded` on every resolution/flush (the
163
185
  * caller re-reconciles from there).
164
186
  */
165
- export declare function getData(url: string, onLoaded: () => void, opts?: StreamOptions): LayerData;
166
- /** Is a fetch for this URL still in flight? Feeds the `om-map-ready` signal — errored fetches count as settled (readiness must not hang on a bad URL). */
167
- export declare function isPendingData(url: string): boolean;
187
+ export declare function getData(url: string, onLoaded: () => void, opts?: StreamOptions, owner?: DataTransportOwner): LayerData;
188
+ /**
189
+ * Is a fetch for this URL still in flight? Feeds the `om-map-ready` signal —
190
+ * errored fetches count as settled (readiness must not hang on a bad URL).
191
+ *
192
+ * With an owner this asks only about that owner's OWN transports: a map must
193
+ * never wait on — or be released by — a sibling map's fetch. An owner with no
194
+ * acquired identities has nothing pending by definition, so a missing state is
195
+ * an empty set, not "match anything".
196
+ */
197
+ export declare function isPendingData(url: string, owner?: DataTransportOwner): boolean;
168
198
  /**
169
199
  * Minimal feature-shape detection (spec: "Feature Field Resolution & Data
170
200
  * Shape" — full per-layer caching/registry treatment is a later phase; this
@@ -22,6 +22,49 @@ export declare class DrawController {
22
22
  private readonly autosaveKeys;
23
23
  private readonly observers;
24
24
  private active;
25
+ /**
26
+ * rAF handle coalescing hover-driven preview updates (spec: "Cut/fill
27
+ * volume measurement" — found debugging click-reliability in standalone
28
+ * mode). `point()` used to call `renderPreview()` (which patches the
29
+ * per-frame channel and rebuilds every deck.gl layer) SYNCHRONOUSLY on
30
+ * every raw mousemove — far more often than the "per-frame" name implies,
31
+ * since mousemove fires at native pointer-event rate, not rAF rate. That
32
+ * rebuild storm intermittently corrupted deck.gl's own click/pick pipeline
33
+ * (mjolnir gesture recognition losing a click shortly after a hover-driven
34
+ * rebuild) — confirmed by disabling hover-triggered rebuilds entirely and
35
+ * watching the click-loss disappear. Coalescing to one rebuild per actual
36
+ * animation frame is what `patchAnimatedProps`'s own doc comment already
37
+ * describes as "the deck-idiomatic rAF pattern" — this was a gap between
38
+ * that intent and this call site, not a design change.
39
+ */
40
+ private hoverRaf;
41
+ /**
42
+ * The coordinate `commit()` just used to close a shape (its live cursor
43
+ * position at that moment), or null. The native `dblclick` DOM event
44
+ * (which drives `commit()`) fires as soon as the second tap's mouseup
45
+ * lands — but deck.gl's own click-gesture recognition is independently
46
+ * delayed (Hammer's Tap recognizer defers confirming a "click" until it's
47
+ * sure a second tap isn't coming, per its `requireFailure`/`interval`
48
+ * mechanics). One of the double-click's own two taps can therefore still
49
+ * arrive here as a late, ordinary "click" a few ms *after* commit() already
50
+ * ran, at ~that same coordinate — with pending now empty, session.click's
51
+ * own same-spot dedup (see `point()`'s click branch) has nothing to
52
+ * compare against, so it silently starts a fresh one-vertex shape at the
53
+ * spot the user just finished closing.
54
+ *
55
+ * Guarding on position (not a time window) is what keeps this from
56
+ * swallowing a genuinely new, deliberate click that happens to follow
57
+ * immediately after a commit — e.g. redrawing a second shape back-to-back
58
+ * in a test with no elapsed wall-clock time — since that click lands at a
59
+ * different coordinate and passes straight through. The match is a small
60
+ * PIXEL-radius tolerance (see `point()`), not exact equality: a real
61
+ * double-click's two taps are two independent physical presses and rarely
62
+ * land at the bit-identical screen pixel, let alone the bit-identical
63
+ * unprojected lng/lat — exact equality let real hand jitter slip past the
64
+ * guard even though a synthetic, pixel-perfect test wouldn't reveal that.
65
+ * Consumed (cleared) by the very next click regardless of whether it matched.
66
+ */
67
+ private lastClosePos;
25
68
  constructor(mapEl: Element, core: RuntimeCore);
26
69
  /** A draw tool is active — om-map routes points here and suppresses selection. */
27
70
  isCapturing(): boolean;
@@ -32,14 +75,45 @@ export declare class DrawController {
32
75
  * geodesic readouts + labels live off the shared draw stack. `null` unsubs.
33
76
  */
34
77
  setObserver(target: string, cb: ((session: DrawSession) => void) | null): void;
78
+ /** This map's own committed-feature count for `target` (spec: "Cut/fill volume measurement") — see DrawSession.count's doc comment for why this beats reading the shared getDrawData mirror. */
79
+ featureCount(target: string): number;
35
80
  private notify;
36
- /** Enable localStorage autosave for a target and restore any saved sketch (draw-config action). */
81
+ /** Enable localStorage autosave for a target and restore any saved sketch, and/or an auto-styled committed-geometry layer (draw-config action). */
37
82
  configure(target: string, opts: {
38
83
  autosave?: string;
84
+ fillColor?: number[];
85
+ lineColor?: number[];
39
86
  }): void;
87
+ /**
88
+ * Opt-in committed-geometry visualization (spec: issue #34's region-export
89
+ * demo surfaced this — a closed shape with no author-added layer is
90
+ * indistinguishable from "double-click didn't work," since the ONLY
91
+ * built-in visual is the in-progress preview, which clears the instant a
92
+ * shape commits). Bound directly to the SAME reactive `draw:<target>` data
93
+ * URL an author's own manual layer would use — this is pure convenience
94
+ * layered on top, not a replacement; an author who wants custom styling
95
+ * (or a non-GeoJsonLayer rendering) still adds their own layer instead,
96
+ * and both can coexist. One committed layer per TARGET (not a singleton
97
+ * like `PREVIEW_ID`), since multiple draw targets can exist on one map.
98
+ *
99
+ * `depthTest: false` (patched, not attribute-expressible — same non-
100
+ * string-prop precedent as the volume gizmo's `mesh`): a flat 2D ring has
101
+ * no z of its own, so on a page with 3D content (the exact case this was
102
+ * built for — outlining a footprint over a BIM building to export) it can
103
+ * sit at an elevation UNDER opaque building geometry, fully occluded from
104
+ * a normal camera angle — the identical depth-occlusion class already
105
+ * fixed for the measure tool's cut-prism guides. This is a SELECTION
106
+ * indicator, not real in-world geometry, so it's correct for it to always
107
+ * read on top regardless of what 3D content surrounds it.
108
+ */
109
+ private ensureCommittedLayer;
40
110
  setMode(target: string, mode: DrawMode | null): void;
41
111
  private deactivate;
42
112
  point(coord: [number, number] | null, kind: "click" | "hover"): void;
113
+ /** Drop a queued hover-coalesced render (a following immediate render already supersedes it). */
114
+ private cancelHoverRaf;
115
+ /** `CLOSE_GUARD_PIXELS` converted to real meters at the current zoom/latitude (standard Web Mercator meters-per-pixel). */
116
+ private closeGuardMeters;
43
117
  /** Close the in-progress line/polygon — from the double-click DOM event or Enter. */
44
118
  commit(): void;
45
119
  cancel(): void;
package/dist/draw.d.ts CHANGED
@@ -50,6 +50,7 @@ export interface DrawSessionOpts {
50
50
  /** Fired whenever the COMMITTED feature set changes (commit / delete / clear / seed) — D2 wires this to the store push. */
51
51
  onChange?: (fc: FeatureCollection) => void;
52
52
  }
53
+ export declare function samePoint(a: Position, b: Position): boolean;
53
54
  /**
54
55
  * One sketch session. Owns the committed features and the single in-progress
55
56
  * shape. Every mutation that changes the committed set fires `onChange` once.
@@ -74,10 +75,16 @@ export declare class DrawSession {
74
75
  /** Cursor moved — updates the rubber-band target. No committed-set change. */
75
76
  move(pos: Position): void;
76
77
  /**
77
- * Finalize the in-progress line/polygon (from double-click / Enter). A
78
- * trailing duplicate vertex the second click of a double-click — is
79
- * dropped first. No-op (shape kept pending) if there aren't enough
80
- * distinct vertices yet. Returns the committed feature or null.
78
+ * Finalize the in-progress line/polygon (from double-click / Enter). The
79
+ * two clicks that make up a double-click are individually unreliable
80
+ * deck.gl's click/dblclick gesture disambiguation can drop either or both
81
+ * of them depending on exact timing so rather than depend on them having
82
+ * landed in `pending`, the closing vertex is the live cursor position
83
+ * (already updated by the hover that preceded the double-click, via a
84
+ * separate, non-gesture-gated code path). A trailing duplicate — the
85
+ * cursor exactly repeating the last real vertex — is dropped. No-op (shape
86
+ * kept pending) if there aren't enough distinct vertices yet. Returns the
87
+ * committed feature or null.
81
88
  */
82
89
  commit(): DrawFeature | null;
83
90
  /** Esc — discard the in-progress shape, keep the tool active. */
@@ -93,6 +100,17 @@ export declare class DrawSession {
93
100
  features?: unknown;
94
101
  } | null | undefined): void;
95
102
  toFeatureCollection(): FeatureCollection;
103
+ /**
104
+ * Committed-feature count for THIS session object (spec: "Cut/fill volume
105
+ * measurement"). Distinct from reading the shared `getDrawData` store
106
+ * mirror: that mirror is a module-global `Map` keyed by URL string with no
107
+ * per-map/per-session isolation, so it can carry a PRIOR session's data
108
+ * (a different `<om-map>` sharing the same runtime-internal target id, or —
109
+ * the case that surfaced this — a freshly re-mounted map in a test) until
110
+ * this session's own first commit overwrites it. This reads `this.features`
111
+ * directly, which always starts empty for a genuinely new session.
112
+ */
113
+ count(): number;
96
114
  /** The in-progress shape for the preview layer, or null when nothing is pending. */
97
115
  preview(): DrawPreview | null;
98
116
  private finalize;
@@ -20,6 +20,7 @@
20
20
  import { HTMLElementBase } from "../env";
21
21
  import { type RuntimeContext } from "../ctx";
22
22
  import { type LightingIR } from "../scene-lighting";
23
+ import { type ClipBoxIR } from "../clip-box";
23
24
  import { type TerrainIR } from "../terrain";
24
25
  import { type WidgetSlot } from "../widget-layout";
25
26
  import { type SnapshotOptions } from "../snapshot";
@@ -44,10 +45,13 @@ export declare class OmMapElement extends HTMLElementBase {
44
45
  private observer;
45
46
  private history;
46
47
  private keydownHandler?;
48
+ private keyupHandler?;
47
49
  private dblclickHandler?;
48
50
  private contextmenuHandler?;
49
51
  private mount;
50
52
  private reconcilePending;
53
+ private readonly dataOwner;
54
+ private dataReleasePending;
51
55
  /** Decoded property tables per pick-features layer — see RuntimeCoreCallbacks.onFeatureTable. */
52
56
  private featureTables;
53
57
  private layerIRs;
@@ -257,6 +261,20 @@ export declare class OmMapElement extends HTMLElementBase {
257
261
  * Fires at pointer rate on hover (no debounce — vertex capture needs
258
262
  * every point); heavy listeners should throttle their own work. */
259
263
  private handleMapPoint;
264
+ /**
265
+ * XY snapping's snap-tip (spec: issue #34 Part A — "a snap tip shows
266
+ * which agent fired on which layer, otherwise users can't tell why a
267
+ * vertex jumped"). A SEPARATE runtime overlay from the shared
268
+ * `show-tooltip` singleton (`actions.ts`'s `getOrCreateTooltipOverlay`):
269
+ * that one is `anchor-from="selection"` (tied to `ctx.selection`) and
270
+ * shared across every `show-tooltip` behavior a page authors (this
271
+ * page's own z-tooltip, for one) — reusing it here would fight over the
272
+ * same element on every hover. This one anchors at a plain, static
273
+ * `anchor="[lng,lat]"` instead, since a snap position has nothing to do
274
+ * with whatever `ctx.selection` currently holds.
275
+ */
276
+ private ensureSnapTipOverlay;
277
+ private handleSnapPoint;
260
278
  /** Harness map-point injection (spec: "Consumer Testing Surface") — the
261
279
  * same path a real deck click/hover coordinate takes, so the om-map-point
262
280
  * event and custom capture tools are testable without a GPU. */
@@ -285,6 +303,8 @@ export declare class OmMapElement extends HTMLElementBase {
285
303
  getLightingInternal(): LightingIR | null;
286
304
  /** Resolved terrain IR (headless test inspection) — null = no surface. */
287
305
  getTerrainInternal(): TerrainIR | null;
306
+ /** Resolved clip-box IR (headless test inspection) — null = no active box. */
307
+ getClipBoxInternal(): ClipBoxIR | null;
288
308
  /**
289
309
  * Canvas-only scene snapshot (spec: "Snapshot API") — basemap + deck
290
310
  * composited at device pixels. DOM widgets/overlays/badge/attribution are
@@ -36,7 +36,19 @@ export declare class OmOverlayElement extends HTMLElementBase {
36
36
  * simply doesn't update this remembered anchor; only a matching
37
37
  * selection moves it, and only an explicit `visible="false"` hides it.
38
38
  */
39
+ /**
40
+ * A 3rd (z) component survives here specifically for the projection at
41
+ * the bottom of `updatePosition` — a selection's real elevation (a depth
42
+ * pick against terrain/3D content, see `Selection.coordinate`'s own doc
43
+ * comment) matters for WHERE on screen the anchor lands under a pitched
44
+ * camera, even though this class only ever positions in 2D. Dropping it
45
+ * (projecting at an implied z=0) put a tooltip over an elevated pick tens
46
+ * to hundreds of pixels from the actual cursor — the anchor was correct
47
+ * in lng/lat, just projected as if the picked surface were at sea level.
48
+ */
39
49
  private lastAnchor;
50
+ /** Cached [w, h] for `clip-to-map` (offsetWidth forces layout; flushes run per pointer move) — invalidated by setContent. */
51
+ private clipSizeCache;
40
52
  /** Memoized feature anchor — recomputed only when the layer's data reference or the target id changes (live layers swap data). */
41
53
  private featureAnchorCache;
42
54
  connectedCallback(): void;
package/dist/geodesy.d.ts CHANGED
@@ -23,6 +23,25 @@ export type LngLat = [number, number];
23
23
  export declare function distanceMeters(a: LngLat, b: LngLat, radius?: number): number;
24
24
  /** Total haversine length of a polyline in meters (0 for < 2 points). */
25
25
  export declare function pathLengthMeters(points: LngLat[], radius?: number): number;
26
+ export interface PathSample {
27
+ position: LngLat;
28
+ /** Cumulative distance (m) from the path's start. */
29
+ distance: number;
30
+ /** Index into the ORIGINAL input points when this sample IS one of them (see `resamplePathWithVertices`); absent for interpolated samples. */
31
+ vertexIndex?: number;
32
+ }
33
+ /**
34
+ * `count` evenly-spaced points along a multi-segment polyline, by cumulative
35
+ * distance (spec item E — elevation-profile samples). Walks the segment
36
+ * lengths to find which segment each target distance falls in, then
37
+ * `intermediate()`s within it — a straight generalization of that function
38
+ * from "interpolate one segment" to "interpolate a whole path." `count=1`
39
+ * returns just the start point; `points.length < 2` returns each input point
40
+ * at distance 0 (nothing to interpolate); a zero-length path (every point
41
+ * coincident) returns `count` copies of the start point at distance 0.
42
+ */
43
+ export declare function resamplePath(points: LngLat[], count: number): PathSample[];
44
+ export declare function resamplePathWithVertices(points: LngLat[], count: number): PathSample[];
26
45
  /**
27
46
  * Spherical polygon area in m² via the Chamberlain–Duquette line integral (the
28
47
  * exact index form Turf's `ringArea` uses, so results match Turf's fixtures).
@@ -40,8 +59,18 @@ export declare function ringPerimeterMeters(ring: LngLat[], radius?: number): nu
40
59
  */
41
60
  export declare function ringEnclosesPole(ring: LngLat[]): boolean;
42
61
  /**
43
- * Great-circle midpoint of a segment antimeridian-safe (converts to Cartesian
44
- * so a segment crossing ±180° gets its label on the shorter arc, not the middle
45
- * of the wrong way round). Returns [lng (normalized to (−180, 180]), lat].
62
+ * Point a `fraction` of the way from `a` to `b` along the great-circle path
63
+ * (spherical slerp) spec item E's evenly-spaced elevation-profile samples
64
+ * need this; only the f=0.5 special case (`midpoint`) existed before.
65
+ * Antimeridian-safe (interpolates the Cartesian unit vectors, not lng/lat
66
+ * directly, so a segment crossing ±180° doesn't wrap the wrong way).
67
+ * `f=0` returns `a`, `f=1` returns `b`. Returns [lng (normalized to
68
+ * (−180, 180]), lat].
69
+ */
70
+ export declare function intermediate(a: LngLat, b: LngLat, fraction: number): LngLat;
71
+ /**
72
+ * Great-circle midpoint of a segment — `intermediate(a, b, 0.5)`, kept as its
73
+ * own name since "the point halfway along a segment" is the common case
74
+ * (badge label anchors) and reads better than a bare 0.5 at every call site.
46
75
  */
47
76
  export declare function midpoint(a: LngLat, b: LngLat): LngLat;
@@ -1,4 +1,4 @@
1
- import { r as p, n as d } from "./index-1UgNlfGR.js";
1
+ import { r as p, n as d } from "./index-Cxo9mCw_.js";
2
2
  function g(t) {
3
3
  if (t == null) return !0;
4
4
  const e = t, n = e.id?.code;
@@ -15,9 +15,9 @@ interface TagData {
15
15
  }[];
16
16
  }
17
17
  /** Hand-authored action list — validated against hasAction() in html-data.test.ts. */
18
- export declare const BUILTIN_ACTIONS: readonly ["toggle-layer", "zoom-in", "zoom-out", "filter-layer", "highlight-feature", "fade", "pulse", "populate", "trace", "story-play", "story-pause", "story-seek", "fly-to", "zoom-to-feature", "show-overlay", "hide-overlay", "show-tooltip", "hide-tooltip", "draw-mode", "draw-commit", "draw-cancel", "draw-delete", "draw-clear", "draw-config", "draw-save", "measure-mode", "measure-clear", "measure-units", "set-basemap", "set-lighting", "set-terrain", "set-widgets-visible", "undo", "redo"];
18
+ export declare const BUILTIN_ACTIONS: readonly ["toggle-layer", "zoom-in", "zoom-out", "filter-layer", "highlight-feature", "fade", "pulse", "populate", "trace", "story-play", "story-pause", "story-seek", "fly-to", "zoom-to-feature", "show-overlay", "hide-overlay", "show-tooltip", "hide-tooltip", "draw-mode", "draw-commit", "draw-cancel", "draw-delete", "draw-clear", "draw-config", "draw-save", "export-region-3d", "measure-mode", "measure-clear", "measure-units", "set-basemap", "set-lighting", "set-terrain", "set-clip-box", "clip-box-edit", "set-widgets-visible", "undo", "redo"];
19
19
  /** Hand-authored widget types — validated against the widget registry in html-data.test.ts. */
20
- export declare const BUILTIN_WIDGETS: readonly ["legend", "layer-switcher", "zoom-controls", "scale-bar", "attribution", "filter", "vega-lite", "player", "draw", "measure", "basemap-switcher", "lighting", "undo-redo", "widgets-toggle", "ifc-browser", "ifc-legend", "feature-inspector", "ifc-inspector", "ifc-loader", "ifc-clash"];
20
+ export declare const BUILTIN_WIDGETS: readonly ["legend", "layer-switcher", "zoom-controls", "scale-bar", "attribution", "filter", "vega-lite", "dynamic-chart", "player", "draw", "measure", "basemap-switcher", "lighting", "clip-box", "undo-redo", "widgets-toggle", "ifc-browser", "ifc-legend", "feature-inspector", "ifc-inspector", "ifc-loader", "ifc-clash"];
21
21
  export declare function buildHtmlCustomData(): {
22
22
  version: number;
23
23
  tags: TagData[];
@@ -1,4 +1,4 @@
1
- import { ak as Bt, ax as Xt, ay as Yt, az as Us } from "./index-1UgNlfGR.js";
1
+ import { ak as Bt, ax as Xt, ay as Yt, az as Us } from "./index-Cxo9mCw_.js";
2
2
  import { w as gs, t as Hs, f as Ws, m as Qs } from "./mgrs-BY9bIvp4.js";
3
3
  import { c as Xs } from "./convert-arrow-schema-DrAihRf9.js";
4
4
  import { y as Ys, A as bt, a as Ks, z as Vs, R as Zs } from "./recordbatch-Bpc0uxFn.js";
@@ -1,4 +1,4 @@
1
- import { ae as _t, af as Ct } from "./index-1UgNlfGR.js";
1
+ import { ae as _t, af as Ct } from "./index-Cxo9mCw_.js";
2
2
  function Yt(e, r, t = 2, i, o = "xy") {
3
3
  const s = r && r.length, l = s ? r[0] * t : e.length;
4
4
  let c = Ut(e, 0, l, t, !0, i && i[0], o);