@nika-js/onlymap 0.1.0

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 (77) hide show
  1. package/.vscode/onlymap.code-snippets +173 -0
  2. package/LICENSE.md +133 -0
  3. package/README.md +188 -0
  4. package/bin/onlymapjs.mjs +182 -0
  5. package/dist/Arrow.dom-7AXne1TU.js +5233 -0
  6. package/dist/actions.d.ts +30 -0
  7. package/dist/attribute-resolution.d.ts +73 -0
  8. package/dist/basemap-D95W0IIA.js +25203 -0
  9. package/dist/basemap.d.ts +72 -0
  10. package/dist/convert-arrow-schema-CvZ3cT5m.js +94 -0
  11. package/dist/csp-sandbox.d.ts +20 -0
  12. package/dist/ctx.d.ts +49 -0
  13. package/dist/d3-bundle.d.ts +218 -0
  14. package/dist/data-emit.d.ts +10 -0
  15. package/dist/data-layer.d.ts +150 -0
  16. package/dist/declarative-filter.d.ts +12 -0
  17. package/dist/dev-error-panel.d.ts +15 -0
  18. package/dist/draw-controller.d.ts +51 -0
  19. package/dist/draw.d.ts +102 -0
  20. package/dist/effects.d.ts +39 -0
  21. package/dist/elements/om-behavior.d.ts +13 -0
  22. package/dist/elements/om-layer.d.ts +11 -0
  23. package/dist/elements/om-map.d.ts +168 -0
  24. package/dist/elements/om-overlay.d.ts +82 -0
  25. package/dist/elements/om-step.d.ts +12 -0
  26. package/dist/elements/om-story.d.ts +67 -0
  27. package/dist/elements/om-widget.d.ts +29 -0
  28. package/dist/env.d.ts +41 -0
  29. package/dist/expr/cache.d.ts +28 -0
  30. package/dist/expr/compile.d.ts +27 -0
  31. package/dist/expr/errors.d.ts +10 -0
  32. package/dist/expr/full-js-block.d.ts +17 -0
  33. package/dist/expr/index.d.ts +12 -0
  34. package/dist/expr/output-type.d.ts +12 -0
  35. package/dist/expr/scale.d.ts +13 -0
  36. package/dist/expr/script-block.d.ts +16 -0
  37. package/dist/expr/whitelist.d.ts +28 -0
  38. package/dist/field-access.d.ts +24 -0
  39. package/dist/html-data.d.ts +25 -0
  40. package/dist/index-2v2NXF_n.js +435 -0
  41. package/dist/index-BFjXVHly.js +605 -0
  42. package/dist/index-C9w78NS9.js +4883 -0
  43. package/dist/index-CU-iOTdr.js +75292 -0
  44. package/dist/index-D74olQ9w.js +3907 -0
  45. package/dist/index-Do7hmHwi.js +1457 -0
  46. package/dist/index.d.ts +76 -0
  47. package/dist/ir-snapshot.d.ts +54 -0
  48. package/dist/ir.d.ts +66 -0
  49. package/dist/layer-registry.d.ts +27 -0
  50. package/dist/layers/popup-layer.d.ts +65 -0
  51. package/dist/onlymapjs.css +1 -0
  52. package/dist/onlymapjs.js +30 -0
  53. package/dist/onlymapjs.umd.cjs +8618 -0
  54. package/dist/parse-manifest.d.ts +19 -0
  55. package/dist/recordbatch-HRu0SMKp.js +4742 -0
  56. package/dist/runtime-core.d.ts +170 -0
  57. package/dist/selection.d.ts +31 -0
  58. package/dist/stats.d.ts +29 -0
  59. package/dist/table-accessors-DBjWgN0C.js +204 -0
  60. package/dist/template-interpolation.d.ts +6 -0
  61. package/dist/testing.d.ts +90 -0
  62. package/dist/timeline.d.ts +37 -0
  63. package/dist/validation.d.ts +22 -0
  64. package/dist/vega-loader.d.ts +22 -0
  65. package/dist/viewport-filter.d.ts +25 -0
  66. package/dist/widget-registry.d.ts +23 -0
  67. package/dist/widget-script.d.ts +1 -0
  68. package/dist/widgets/built-in.d.ts +1 -0
  69. package/dist/widgets/dom-utils.d.ts +4 -0
  70. package/llms.txt +43 -0
  71. package/onlymapjs.html-data.json +1688 -0
  72. package/package.json +111 -0
  73. package/skills/onlymapjs/SKILL.md +61 -0
  74. package/skills/onlymapjs/agents/openai.yaml +4 -0
  75. package/skills/onlymapjs/references/patterns.md +213 -0
  76. package/skills/onlymapjs/references/syntax.md +342 -0
  77. package/skills/onlymapjs/references/testing.md +126 -0
@@ -0,0 +1,170 @@
1
+ import type { LayerIR } from "./ir";
2
+ import { type Selection } from "./selection";
3
+ import type { ValidationEntry } from "./validation";
4
+ import type { MapViewport } from "./basemap";
5
+ /** `basemap="mapbox-*"` isn't implemented — kept maplibre-free so validation/runtime can check without loading the chunk. */
6
+ export declare function isMapboxBasemap(raw: string): boolean;
7
+ export interface InitialView {
8
+ longitude: number;
9
+ latitude: number;
10
+ zoom: number;
11
+ /** Initial camera tilt/rotation — first-class for 3D content (`<om-map pitch="55">`). */
12
+ pitch?: number;
13
+ bearing?: number;
14
+ }
15
+ interface ViewState extends InitialView {
16
+ pitch: number;
17
+ bearing: number;
18
+ }
19
+ /** Animated-camera options (spec: "Map Stories / Animation primitives"). Omit `duration` (or 0) for an instant move. */
20
+ export interface CameraOptions {
21
+ duration?: number;
22
+ /** Arc the move (zoom-out-and-in) instead of easing directly — MapLibre `flyTo` / deck `FlyToInterpolator`. */
23
+ curve?: boolean;
24
+ }
25
+ export interface RuntimeCoreCallbacks {
26
+ /** Fired whenever the view state changes (pan/zoom/pitch/bearing) — a notification, not a value. */
27
+ onViewportChange?: () => void;
28
+ onSelectionChange?: (selection: Selection | null) => void;
29
+ /** Fired once the map (deck.gl standalone, or the basemap) has finished its async init (spec: Behavior Engine "load" event). */
30
+ onLoad?: () => void;
31
+ /**
32
+ * Continuous drag-pick events (spec: Behavior Engine "drag" event) — kept
33
+ * separate from `onSelectionChange`/`ctx.selection`, which is scoped to
34
+ * discrete hover/click picks; a widget watching `selection` shouldn't
35
+ * re-render on every drag-move.
36
+ */
37
+ onDragPick?: (selection: Selection) => void;
38
+ /**
39
+ * Every click/hover's map coordinate (spec: "Manual Drawing"), fired
40
+ * ALONGSIDE onSelectionChange but INCLUDING empty-map events — which
41
+ * `toSelection` discards (no picked object, index < 0), yet sketch capture
42
+ * needs (a vertex dropped in blank space). Coordinate is null only when
43
+ * deck reports none (e.g. off-globe).
44
+ */
45
+ onMapPoint?: (coordinate: [number, number] | null, kind: "click" | "hover") => void;
46
+ /**
47
+ * Runtime error boundary (spec: "Runtime error boundary") — deck.gl-level
48
+ * failures (a crashing accessor, an incompatible prop) formatted into the
49
+ * same structured shape Manifest Validation uses, so the agent sees one
50
+ * consistent error contract regardless of origin.
51
+ */
52
+ onRuntimeError?: (entry: ValidationEntry) => void;
53
+ }
54
+ /** Headless mode config (spec: "Consumer Testing Surface") — an explicit size, since jsdom layout reports 0×0. */
55
+ export interface HeadlessOptions {
56
+ width: number;
57
+ height: number;
58
+ }
59
+ export declare class RuntimeCore {
60
+ private deck?;
61
+ private basemap?;
62
+ /**
63
+ * Which renderer owns the view. Branching happens on the MODE, not on
64
+ * `this.basemap` — in basemap mode the adapter arrives asynchronously
65
+ * (the maplibre chunk lazy-loads), and during that window camera/layer
66
+ * calls must route to the (not-yet-present) adapter, never fall through
67
+ * to a standalone Deck that doesn't exist.
68
+ */
69
+ private mode;
70
+ /** Layers reconciled while the basemap chunk is still loading — applied on adapter arrival. */
71
+ private pendingLayers?;
72
+ /** Retained descriptors — what the per-frame channel re-applies against. */
73
+ private lastIRs;
74
+ /** layerId → effect-driven plain-prop patches (the per-frame channel). */
75
+ private animatedProps;
76
+ private headless?;
77
+ private destroyed;
78
+ private viewState;
79
+ private callbacks;
80
+ constructor(parent: HTMLElement, initialView: InitialView, callbacks?: RuntimeCoreCallbacks, basemapAttr?: string, headless?: HeadlessOptions);
81
+ /**
82
+ * Rebuilds every deck.gl Layer instance from the current IR and hands the
83
+ * full array to deck.gl (or the basemap's MapboxOverlay) on every
84
+ * reconcile — the underlying layer manager's own id-keyed prop diffing
85
+ * decides what actually needs to change. Accessor props carry
86
+ * `updateTriggers` fingerprints (Layer Reconciliation) so deck.gl
87
+ * recomputes an attribute iff the accessor's source actually changed,
88
+ * rather than never (deck.gl ignores accessor function identity on its
89
+ * own) or on every pass.
90
+ */
91
+ reconcile(irs: LayerIR[]): void;
92
+ private buildLayers;
93
+ private applyLayers;
94
+ /**
95
+ * The sanctioned per-frame DOM bypass (spec: "Map Stories / per-frame
96
+ * channel"): merge plain-prop patches into a layer's descriptor and
97
+ * re-apply immediately from the retained IRs — rebuilding deck layer
98
+ * instances per frame is the deck-idiomatic rAF pattern. `null` clears a
99
+ * layer's patches. Headless stores the patches (inspectable via the map's
100
+ * internal getter) with no renderer to drive.
101
+ */
102
+ patchAnimatedProps(layerId: string, props: Record<string, unknown> | null): void;
103
+ clearAnimatedProps(): void;
104
+ getAnimatedProps(layerId: string): Record<string, unknown> | undefined;
105
+ /**
106
+ * The current resolved viewport (for `ctx.viewport`'s bounds/project) —
107
+ * always fresh, never cached. Standalone: `Deck#getViewports()` asserts
108
+ * its internal `viewManager` is set, which only happens after Deck's own
109
+ * async init — `isInitialized` guards the window between construction
110
+ * (widgets can register and request a ctx synchronously, before that init
111
+ * resolves) and the first frame. With a basemap: the MapLibre-backed
112
+ * adapter, once the basemap's style has loaded.
113
+ */
114
+ getViewport(): MapViewport | undefined;
115
+ /** Renderer init state — feeds the `om-map-ready` signal. Headless has nothing async to wait for. */
116
+ isReady(): boolean;
117
+ /**
118
+ * Synthetic pick injection (spec: "Consumer Testing Surface") — invokes
119
+ * the SAME onSelectionChange callback deck.gl's real picks arrive through,
120
+ * so flattening, columnar row materialization, widget notification,
121
+ * behavior dispatch, and the overlay flush are same-path by construction.
122
+ * Available in every mode (a browser-level test may use it too).
123
+ */
124
+ injectPick(selection: Selection | null): void;
125
+ getViewState(): Readonly<ViewState>;
126
+ /**
127
+ * Sketch capture (spec: "Manual Drawing", D4) — while a draw tool is active,
128
+ * disable the map's double-click-zoom so double-clicking to close a
129
+ * line/polygon doesn't also zoom. Honored in both mount paths. Headless is a
130
+ * no-op (no controller). Deliberately narrow: click-to-place-vertex doesn't
131
+ * fight drag-pan, so pan stays live.
132
+ */
133
+ setDrawCapture(active: boolean): void;
134
+ /**
135
+ * Used by the built-in `zoom-controls` widget's emitted zoom-in/zoom-out
136
+ * intents. Programmatic, so — unlike a user drag/scroll — standalone mode
137
+ * never goes through deck.gl's own `onViewStateChange` controller
138
+ * callback; fire `onViewportChange` directly so `viewport`-watching
139
+ * widgets (scale-bar) still pick up the change. With a basemap, MapLibre's
140
+ * own `jumpTo` already fires a `move` event (wired to `onViewportChange`
141
+ * in the constructor), so no manual notification is needed there.
142
+ */
143
+ zoomBy(delta: number): void;
144
+ /**
145
+ * Recenters (and optionally rezooms) the view — `map.flyTo(coords)`
146
+ * (spec: "Runtime Core" programmatic API) and the `zoom-to-feature`
147
+ * action's point form both go through this. Instant, not an animated
148
+ * fly, in both modes — smooth transitions are a future addition (deck.gl
149
+ * `transitions` / MapLibre's own animated `flyTo`), not implemented here.
150
+ */
151
+ flyTo(center: [number, number], zoom?: number, opts?: CameraOptions): void;
152
+ /**
153
+ * Merges a partial view state — the harness's `setView` (and the only
154
+ * path that reaches pitch/bearing, which `flyTo`/`zoomBy` don't touch).
155
+ * Standalone/headless update the tracked state directly; with a basemap,
156
+ * center/zoom delegate to the basemap's camera (which owns the view) and
157
+ * pitch/bearing are a documented not-yet gap there.
158
+ */
159
+ setViewState(partial: Partial<ViewState>, opts?: CameraOptions): void;
160
+ /**
161
+ * Recenters and rezooms to fit a `[[minLng,minLat],[maxLng,maxLat]]` box —
162
+ * the `zoom-to-feature` action's bbox form (GeoJSON polygons/lines).
163
+ * Standalone falls back to a plain `flyTo` at the box's midpoint if the
164
+ * viewport isn't initialized yet (mirrors `getViewport()`'s own guard);
165
+ * with a basemap, MapLibre's own `fitBounds` handles this directly.
166
+ */
167
+ flyToBounds(bounds: [[number, number], [number, number]], padding?: number, opts?: CameraOptions): void;
168
+ destroy(): void;
169
+ }
170
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Selection (picking) state — spec: "Runtime Context API / Selection".
3
+ * Converts a deck.gl PickingInfo into the documented, deck.gl-independent
4
+ * ctx.selection shape (HU2: ctx never leaks deck.gl internals).
5
+ */
6
+ export interface Selection {
7
+ layerId: string;
8
+ object: unknown;
9
+ /**
10
+ * The picked row's index in the layer's data. Columnar layers pick with NO
11
+ * object (deck.gl's non-iterable data path only knows the index) — the
12
+ * runtime materializes `object` from the columns at this index (om-map's
13
+ * onSelectionChange), so downstream consumers still always see an object.
14
+ */
15
+ index: number;
16
+ coordinate: [number, number] | null;
17
+ pixel: [number, number];
18
+ type: "hover" | "click";
19
+ }
20
+ interface PickingInfoLike {
21
+ layer?: {
22
+ id: string;
23
+ } | null;
24
+ object?: unknown;
25
+ index?: number;
26
+ coordinate?: number[];
27
+ x: number;
28
+ y: number;
29
+ }
30
+ export declare function toSelection(info: PickingInfoLike, type: "hover" | "click"): Selection | null;
31
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ctx.stats() — spec: "Runtime Context API / Data access". Path-resolved
3
+ * (via getField, no per-feature copying) aggregation: count/min/max/mean/
4
+ * stddev/p50/p95/histogram, matching the documented return shape exactly.
5
+ */
6
+ import type { Shape } from "./ir";
7
+ export interface HistogramBin {
8
+ x0: number;
9
+ x1: number;
10
+ count: number;
11
+ }
12
+ export interface Stats {
13
+ count: number;
14
+ min: number;
15
+ max: number;
16
+ mean: number;
17
+ stddev: number;
18
+ p50: number;
19
+ p95: number;
20
+ histogram: HistogramBin[];
21
+ }
22
+ export declare function computeStats(rows: readonly unknown[], field: string, shape: Shape): Stats;
23
+ /**
24
+ * The columnar fast path (spec: "Columnar / GeoArrow Ingestion"): stats
25
+ * straight off a column's typed array — no row objects, no field lookups,
26
+ * no materialization. `values` is consumed (sorted in place after copying
27
+ * non-finite entries out).
28
+ */
29
+ export declare function statsFromColumn(column: ArrayLike<unknown>): Stats;
@@ -0,0 +1,204 @@
1
+ function w(e) {
2
+ var a;
3
+ switch (typeof e == "object" && (e == null ? void 0 : e.shape)) {
4
+ case "array-row-table":
5
+ case "object-row-table":
6
+ return Array.isArray(e.data);
7
+ case "geojson-table":
8
+ return Array.isArray(e.features);
9
+ case "columnar-table":
10
+ return e.data && typeof e.data == "object";
11
+ case "arrow-table":
12
+ return ((a = e == null ? void 0 : e.data) == null ? void 0 : a.numRows) !== void 0;
13
+ default:
14
+ return !1;
15
+ }
16
+ }
17
+ function l(e) {
18
+ switch (e.shape) {
19
+ case "array-row-table":
20
+ case "object-row-table":
21
+ return e.data.length;
22
+ case "geojson-table":
23
+ return e.features.length;
24
+ case "arrow-table":
25
+ return e.data.numRows;
26
+ case "columnar-table":
27
+ for (const a of Object.values(e.data))
28
+ return a.length || 0;
29
+ return 0;
30
+ default:
31
+ throw new Error("table");
32
+ }
33
+ }
34
+ function g(e) {
35
+ if (e.schema)
36
+ return e.schema.fields.length;
37
+ if (l(e) === 0)
38
+ throw new Error("empty table");
39
+ switch (e.shape) {
40
+ case "array-row-table":
41
+ return e.data[0].length;
42
+ case "object-row-table":
43
+ return Object.keys(e.data[0]).length;
44
+ case "geojson-table":
45
+ return Object.keys(e.features[0]).length;
46
+ case "columnar-table":
47
+ return Object.keys(e.data).length;
48
+ case "arrow-table":
49
+ return e.data.numCols;
50
+ default:
51
+ throw new Error("table");
52
+ }
53
+ }
54
+ function j(e, r, a) {
55
+ var o;
56
+ switch (e.shape) {
57
+ case "array-row-table":
58
+ const n = m(e, a);
59
+ return e.data[r][n];
60
+ case "object-row-table":
61
+ return e.data[r][a];
62
+ case "geojson-table":
63
+ return e.features[r][a];
64
+ case "columnar-table":
65
+ return e.data[a][r];
66
+ case "arrow-table":
67
+ const c = e.data, u = c.schema.fields.findIndex((s) => s.name === a);
68
+ return (o = c.getChildAt(u)) == null ? void 0 : o.get(r);
69
+ default:
70
+ throw new Error("todo");
71
+ }
72
+ }
73
+ function y(e, r, a) {
74
+ var o;
75
+ switch (e.shape) {
76
+ case "array-row-table":
77
+ return e.data[r][a];
78
+ case "object-row-table":
79
+ const n = i(e, a);
80
+ return e.data[r][n];
81
+ case "geojson-table":
82
+ const f = i(e, a);
83
+ return e.features[r][f];
84
+ case "columnar-table":
85
+ const c = i(e, a);
86
+ return e.data[c][r];
87
+ case "arrow-table":
88
+ return (o = e.data.getChildAt(a)) == null ? void 0 : o.get(r);
89
+ default:
90
+ throw new Error("todo");
91
+ }
92
+ }
93
+ function m(e, r) {
94
+ var o;
95
+ const a = (o = e.schema) == null ? void 0 : o.fields.findIndex((n) => n.name === r);
96
+ if (a === void 0)
97
+ throw new Error(r);
98
+ return a;
99
+ }
100
+ function i(e, r) {
101
+ var o, n;
102
+ const a = (n = (o = e.schema) == null ? void 0 : o.fields[r]) == null ? void 0 : n.name;
103
+ if (!a)
104
+ throw new Error(`${r}`);
105
+ return a;
106
+ }
107
+ function T(e, r, a, o) {
108
+ switch (e.shape) {
109
+ case "object-row-table":
110
+ return o ? Object.fromEntries(Object.entries(e.data[r])) : e.data[r];
111
+ case "array-row-table":
112
+ if (e.schema) {
113
+ const s = a || {};
114
+ for (let t = 0; t < e.schema.fields.length; t++)
115
+ s[e.schema.fields[t].name] = e.data[r][t];
116
+ return s;
117
+ }
118
+ throw new Error("no schema");
119
+ case "geojson-table":
120
+ if (e.schema) {
121
+ const s = a || {};
122
+ for (let t = 0; t < e.schema.fields.length; t++)
123
+ s[e.schema.fields[t].name] = e.features[r][t];
124
+ return s;
125
+ }
126
+ throw new Error("no schema");
127
+ case "columnar-table":
128
+ if (e.schema) {
129
+ const s = a || {};
130
+ for (let t = 0; t < e.schema.fields.length; t++)
131
+ s[e.schema.fields[t].name] = e.data[e.schema.fields[t].name][r];
132
+ return s;
133
+ } else {
134
+ const s = a || {};
135
+ for (const [t, h] of Object.entries(e.data))
136
+ s[t] = h[r];
137
+ return s;
138
+ }
139
+ case "arrow-table":
140
+ const n = e.data, f = a || {}, c = n.get(r), u = n.schema;
141
+ for (let s = 0; s < u.fields.length; s++)
142
+ f[u.fields[s].name] = c == null ? void 0 : c[u.fields[s].name];
143
+ return f;
144
+ default:
145
+ throw new Error("shape");
146
+ }
147
+ }
148
+ function d(e, r, a, o) {
149
+ switch (e.shape) {
150
+ case "array-row-table":
151
+ return o ? Array.from(e.data[r]) : e.data[r];
152
+ case "object-row-table":
153
+ if (e.schema) {
154
+ const s = a || [];
155
+ for (let t = 0; t < e.schema.fields.length; t++)
156
+ s[t] = e.data[r][e.schema.fields[t].name];
157
+ return s;
158
+ }
159
+ return Object.values(e.data[r]);
160
+ case "geojson-table":
161
+ if (e.schema) {
162
+ const s = a || [];
163
+ for (let t = 0; t < e.schema.fields.length; t++)
164
+ s[t] = e.features[r][e.schema.fields[t].name];
165
+ return s;
166
+ }
167
+ return Object.values(e.features[r]);
168
+ case "columnar-table":
169
+ if (e.schema) {
170
+ const s = a || [];
171
+ for (let t = 0; t < e.schema.fields.length; t++)
172
+ s[t] = e.data[e.schema.fields[t].name][r];
173
+ return s;
174
+ } else {
175
+ const s = a || [];
176
+ let t = 0;
177
+ for (const h of Object.values(e.data))
178
+ s[t] = h[r], t++;
179
+ return s;
180
+ }
181
+ case "arrow-table":
182
+ const n = e.data, f = a || [], c = n.get(r), u = n.schema;
183
+ for (let s = 0; s < u.fields.length; s++)
184
+ f[s] = c == null ? void 0 : c[u.fields[s].name];
185
+ return f;
186
+ default:
187
+ throw new Error("shape");
188
+ }
189
+ }
190
+ function* p(e, r = []) {
191
+ const a = l(e);
192
+ for (let o = 0; o < a; o++)
193
+ yield d(e, o, r);
194
+ }
195
+ export {
196
+ g as a,
197
+ y as b,
198
+ j as c,
199
+ d,
200
+ T as e,
201
+ l as g,
202
+ w as i,
203
+ p as m
204
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Interpolates `template` against `data` (a plain object — `ctx.selection.object`,
3
+ * already shape-flattened per Feature Field Resolution). Missing fields
4
+ * interpolate to an empty string, same as a normal template-literal `undefined`.
5
+ */
6
+ export declare function interpolateTemplate(template: string, data: Record<string, unknown>): string;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Consumer test harness (spec: "Consumer Testing Surface — Headless Harness
3
+ * & Readiness"). Mounts a manifest headlessly — no Deck, no WebGL, no canvas
4
+ * — in a consumer's own jsdom/happy-dom test environment, with a real
5
+ * WebMercatorViewport doing the projection math and synthetic picks feeding
6
+ * the exact selection path deck.gl's real picks take.
7
+ *
8
+ * Assertions are deliberately plain DOM: widgets and overlays use OPEN
9
+ * shadow roots, so `el.shadowRoot.textContent` works with standard tooling —
10
+ * there is no bespoke assertion API to learn.
11
+ */
12
+ import type { OmMapElement } from "./elements/om-map";
13
+ import type { LayerIR } from "./ir";
14
+ export interface MountOptions {
15
+ /** Headless viewport size — jsdom layout reports 0×0, so size is declared, not measured. Default 800×600. */
16
+ width?: number;
17
+ height?: number;
18
+ }
19
+ export interface PickOptions {
20
+ /** The `<om-layer id>` to pick from. */
21
+ layer: string;
22
+ /** Feature id (matched the same way the highlight-feature action matches ids) — or give an explicit `index`. */
23
+ featureId?: string;
24
+ index?: number;
25
+ /**
26
+ * Pick type — drives which `<om-behavior on="...">` fires. Default
27
+ * "click". "drag" takes the drag-dispatch path (fires `on="drag"`
28
+ * behaviors, never touches ctx.selection — mirroring live semantics).
29
+ */
30
+ type?: "hover" | "click" | "drag";
31
+ }
32
+ export interface ViewOptions {
33
+ center?: [number, number];
34
+ zoom?: number;
35
+ pitch?: number;
36
+ bearing?: number;
37
+ }
38
+ export interface TestHarness {
39
+ /** The upgraded <om-map> element — query it (and shadow roots) with plain DOM. */
40
+ map: OmMapElement;
41
+ /** The wrapper div the manifest was mounted into. */
42
+ container: HTMLElement;
43
+ /** The live layer IRs — the same descriptors snapshotIR serializes. */
44
+ layers(): readonly LayerIR[];
45
+ /**
46
+ * Synthetic pick: resolves the feature, derives its coordinate through the
47
+ * layer's own compiled getPosition (columnar layers pick object-less with
48
+ * an index, exactly as deck.gl does), and routes it through the same
49
+ * selection path a real pick takes. Settles before resolving.
50
+ */
51
+ pick(opts: PickOptions): Promise<void>;
52
+ /** Ends the current selection (a hover-off) — the path that auto-hides tooltips. */
53
+ clearSelection(): Promise<void>;
54
+ /**
55
+ * Sets the camera directly (center/zoom/pitch/bearing) — everything
56
+ * viewport-derived reacts for real: `viewport`-watching widgets,
57
+ * viewport-scoped stats, overlay projection/culling. This drives the
58
+ * camera the way `flyTo` does; gesture→camera translation (deck.gl's
59
+ * controller) is deliberately not simulated.
60
+ */
61
+ setView(view: ViewOptions): Promise<void>;
62
+ /** Dispatches an action through the normal payload contract (same as ctx.emit / data-emit). Settles before resolving. */
63
+ emit(action: string, payload?: Record<string, unknown>): Promise<void>;
64
+ /** Settles pending work: microtask-batched reconciles and the rAF overlay flush. */
65
+ flush(): Promise<void>;
66
+ /**
67
+ * Deterministic story control (spec: "Map Stories"): `advance(ms)` drives
68
+ * the story's clock manually — rAF never runs for a harness-driven story,
69
+ * so "after 5s the overlay is visible" is exact, not timing-dependent.
70
+ */
71
+ story(id?: string): StoryHandle;
72
+ unmount(): void;
73
+ }
74
+ export interface StoryHandle {
75
+ play(): Promise<void>;
76
+ pause(): Promise<void>;
77
+ seek(ms: number): Promise<void>;
78
+ /** Advance the manual clock by ms (starts playback if idle). */
79
+ advance(ms: number): Promise<void>;
80
+ readonly state: string;
81
+ readonly currentTime: number;
82
+ readonly duration: number;
83
+ }
84
+ /**
85
+ * Mounts a manifest for behavioral testing and resolves once the map is
86
+ * ready (renderer up — immediate when headless — and no declared data URL
87
+ * still in flight). Forces `headless` + an explicit size onto the
88
+ * `<om-map>` before the element upgrades.
89
+ */
90
+ export declare function mountForTest(html: string, opts?: MountOptions): Promise<TestHarness>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Story timeline math (spec: "Map Stories / Clock, seek, and the scrub
3
+ * model") — a pure module: step timings in, absolute intervals out. No DOM,
4
+ * no clock, fully unit-testable.
5
+ *
6
+ * Sequencing semantics:
7
+ * - Sequential (default): a step starts when EVERYTHING before it has
8
+ * finished (max end of all prior steps) — so a short parallel step never
9
+ * pulls the next step into the middle of a longer one.
10
+ * - `parallel`: joins the PREVIOUS step's start (Animaps' "scenes" without
11
+ * a grouping element).
12
+ * - `delay` staggers either form's start.
13
+ */
14
+ export interface StepTiming {
15
+ /** How long the step's animation runs (ms). 0 = instantaneous state change. */
16
+ duration: number;
17
+ /** Stagger before the step starts (ms), applied after sequential/parallel placement. */
18
+ delay: number;
19
+ /** Start with the previous step instead of after everything so far. */
20
+ parallel: boolean;
21
+ }
22
+ export interface StepInterval {
23
+ start: number;
24
+ end: number;
25
+ }
26
+ export interface Timeline {
27
+ intervals: StepInterval[];
28
+ /** Total running time — max end across all steps. */
29
+ total: number;
30
+ }
31
+ export declare function computeTimeline(steps: readonly StepTiming[]): Timeline;
32
+ /**
33
+ * `"800"` / `"800ms"` / `"2s"` → milliseconds. The shared duration grammar
34
+ * for step timings and camera payloads (returns `fallback` on anything
35
+ * unparseable — callers decide whether to warn).
36
+ */
37
+ export declare function parseDurationMs(raw: string | number | undefined | null, fallback?: number): number;
@@ -0,0 +1,22 @@
1
+ export interface ValidationEntry {
2
+ severity: "error" | "warning";
3
+ /** e.g. "om-layer#points" — tag name, plus #id when the element has one. */
4
+ element: string;
5
+ attribute?: string;
6
+ message: string;
7
+ /** An instruction, not just a diagnosis — what makes this useful to an agent. */
8
+ fix: string;
9
+ }
10
+ export interface ValidationResult {
11
+ valid: boolean;
12
+ errors: ValidationEntry[];
13
+ warnings: ValidationEntry[];
14
+ }
15
+ /**
16
+ * Validates a manifest already parsed into a DOM tree — a live `<om-map>`
17
+ * (mounted or not) or a detached `Document`/`DocumentFragment` (what
18
+ * `validateManifestString` builds via `DOMParser`).
19
+ */
20
+ export declare function validateManifest(root: ParentNode): ValidationResult;
21
+ /** `OmMap.validate(htmlString)` — the pre-flight agent feedback loop. */
22
+ export declare function validateManifestString(htmlString: string): ValidationResult;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Vega-Lite lazy loading (spec: "Available globals in widget script blocks").
3
+ * Vega + Vega-Lite + vega-embed (~500KB) are not bundled — injected from a
4
+ * CDN on first actual use and cached thereafter. `vegaEmbedProxy` is the
5
+ * stable function reference handed to every widget script's closure; only
6
+ * *calling* it triggers the load, so passing it around before first use is free.
7
+ */
8
+ declare global {
9
+ interface Window {
10
+ vegaEmbed?: (el: Element, spec: unknown, opts?: unknown) => Promise<{
11
+ view: {
12
+ finalize(): void;
13
+ };
14
+ }>;
15
+ }
16
+ }
17
+ export declare function ensureVegaLoaded(): Promise<void>;
18
+ export declare function vegaEmbedProxy(el: Element, spec: unknown, opts?: unknown): Promise<{
19
+ view: {
20
+ finalize(): void;
21
+ };
22
+ }>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ctx.dataInViewport() — spec: "Viewport Filtering & Statistics" (#6),
3
+ * scoped pragmatically for Phase 4: a per-feature bbox test against
4
+ * `viewport.getBounds()`, using the layer's own compiled `getPosition`
5
+ * accessor for point layers or each GeoJSON feature's own geometry
6
+ * coordinates — no spatial index (kdbush/flatbush) yet, no per-layer
7
+ * `featureBounds` registry descriptor. Correct for reasonable dataset
8
+ * sizes; the optimization is deferred, not the correctness.
9
+ */
10
+ import type { MapViewport } from "./basemap";
11
+ import type { Shape, ColumnarData } from "./ir";
12
+ export declare function filterInViewport(rows: readonly unknown[], shape: Shape, viewport: MapViewport | undefined, getPosition?: (d: unknown) => number[] | undefined): unknown[];
13
+ /**
14
+ * The columnar counterpart (spec: "Columnar / GeoArrow Ingestion"). A
15
+ * columnar layer's compiled `getPosition` reads `info.data.columns[...]
16
+ * [info.index]`, never a row object — so filtering must run over INDICES.
17
+ * The (memoized) row view is taken here, not passed in, so its index
18
+ * alignment with `data` is true by construction; `rowFilter` (the
19
+ * declarative filter, applied to the materialized flat row) rides the same
20
+ * loop so a pre-pass can never break that alignment either.
21
+ */
22
+ export declare function filterColumnarInViewport(data: ColumnarData, viewport: MapViewport | undefined, getPosition: ((d: unknown, info: {
23
+ index: number;
24
+ data: ColumnarData;
25
+ }) => unknown) | undefined, rowFilter?: (row: unknown) => boolean): unknown[];
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Widget registry (spec: "Widget System — three tiers, one registry").
3
+ * Built-ins are just pre-registered entries here — not special-cased —
4
+ * symmetric with registerLayer/registerAction/registerSource.
5
+ */
6
+ import type { RuntimeContext } from "./ctx";
7
+ /** What a widget implementation gets instead of the raw OmWidgetElement (keeps this module decoupled from the elements/ layer). */
8
+ export interface WidgetHost {
9
+ root: ShadowRoot;
10
+ $(sel: string): Element | null;
11
+ $$(sel: string): NodeListOf<Element>;
12
+ getAttribute(name: string): string | null;
13
+ hasAttribute(name: string): boolean;
14
+ /** Per-instance mutable stash for state that must survive across renders (e.g. a Vega view to `.finalize()` before re-embedding). */
15
+ state: Record<string, unknown>;
16
+ }
17
+ export interface WidgetImpl {
18
+ /** Default watch tokens when the element has no `watch` attribute. */
19
+ watch?: string[];
20
+ render(ctx: RuntimeContext, host: WidgetHost): void | Promise<void>;
21
+ }
22
+ export declare function registerWidget(name: string, impl: WidgetImpl): void;
23
+ export declare function getWidget(name: string): WidgetImpl | undefined;
@@ -0,0 +1 @@
1
+ export declare function runWidgetScript(scriptText: string, thisArg: object): void;
@@ -0,0 +1 @@
1
+ export {};