@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,342 @@
1
+ # OnlyMapJS Syntax Reference
2
+
3
+ This reference is public-safe and self-contained. Use it when authoring or reviewing OnlyMapJS manifests.
4
+
5
+ ## Import Patterns
6
+
7
+ Vite/npm project:
8
+
9
+ ```html
10
+ <script type="module">
11
+ import "@nika-js/onlymap";
12
+ import "onlymapjs/onlymapjs.css";
13
+ </script>
14
+ ```
15
+
16
+ Static CDN page:
17
+
18
+ ```html
19
+ <script type="module" src="https://esm.sh/@nika-js/onlymap@0.1.0"></script>
20
+ ```
21
+
22
+ If using MapLibre basemaps from a built package, include the CSS import or stylesheet. Standalone `basemap="none"` maps do not need the CSS.
23
+
24
+ ## Elements
25
+
26
+ ### `<om-map>`
27
+
28
+ Root element. Children are layers, widgets, overlays, behaviors, and stories.
29
+
30
+ Common attributes:
31
+
32
+ - `center="[lng, lat]"`
33
+ - `zoom="11"`
34
+ - `pitch="55"`
35
+ - `bearing="20"`
36
+ - `basemap="maplibre"` or `basemap="none"` or a MapLibre style URL
37
+ - `validate` to show live validation errors during authoring
38
+ - `headless width="800" height="600"` for test harness use
39
+
40
+ Example:
41
+
42
+ ```html
43
+ <om-map center="[-122.42, 37.77]" zoom="11" basemap="maplibre" validate>
44
+ ...
45
+ </om-map>
46
+ ```
47
+
48
+ ### `<om-layer>`
49
+
50
+ Declares a deck.gl layer. Required: `id`, `type`.
51
+
52
+ Core attributes:
53
+
54
+ - `id="quakes"` — stable identity for behaviors, widgets, tests, stories.
55
+ - `type="ScatterplotLayer"` — any bundled layer type.
56
+ - `data="./points.json"` — URL, stream, draw store, or omit for inline JSON.
57
+ - `label="Earthquakes"` and `color="#b30000"` — legend metadata.
58
+ - `pickable` — enable click/hover behaviors.
59
+ - `visible="false"` or `opacity="0"` — initial visibility/opacity.
60
+
61
+ Accessors:
62
+
63
+ ```html
64
+ get-position="[$lon, $lat]"
65
+ get-radius="$population * 0.001"
66
+ get-fill-color="$value > 100 ? [255,0,0] : [0,128,255]"
67
+ get-fill-color="scale($depth, sequential, ['#ffffcc','#800026'], domain=[0,700])"
68
+ ```
69
+
70
+ `$field` works on flat rows, GeoJSON properties, columnar JSON, CSV/TSV columns, and Arrow point columns.
71
+
72
+ Smart shorthands:
73
+
74
+ - `color="#1f9e89"` sets a constant `getFillColor` when no explicit `get-fill-color` exists, and always feeds the legend swatch.
75
+ - `radius="6"` sets a constant `getRadius` when no explicit `get-radius` exists.
76
+
77
+ Transitions:
78
+
79
+ ```html
80
+ transition="get-fill-color 800ms, get-radius 400ms"
81
+ ```
82
+
83
+ Filtering:
84
+
85
+ ```html
86
+ filter-field="magnitude" filter-range="[4, 10]"
87
+ ```
88
+
89
+ ### Built-In Layer Types
90
+
91
+ Use the `type` value exactly:
92
+
93
+ `A5Layer`, `ArcLayer`, `BitmapLayer`, `ColumnLayer`, `ContourLayer`, `GeoJsonLayer`, `GeohashLayer`, `GreatCircleLayer`, `GridCellLayer`, `GridLayer`, `H3ClusterLayer`, `H3HexagonLayer`, `HeatmapLayer`, `HexagonLayer`, `IconLayer`, `LineLayer`, `MVTLayer`, `PathLayer`, `PointCloudLayer`, `PolygonLayer`, `PopupLayer`, `QuadkeyLayer`, `S2Layer`, `ScatterplotLayer`, `ScenegraphLayer`, `ScreenGridLayer`, `SimpleMeshLayer`, `SolidPolygonLayer`, `TerrainLayer`, `TextLayer`, `Tile3DLayer`, `TileLayer`, `TripsLayer`.
94
+
95
+ Common choices:
96
+
97
+ - Points: `ScatterplotLayer`, `IconLayer`, `TextLayer`, `PopupLayer`.
98
+ - Lines/routes: `PathLayer`, `LineLayer`, `ArcLayer`, `TripsLayer`.
99
+ - Polygons/choropleths: `GeoJsonLayer`, `PolygonLayer`.
100
+ - Aggregation: `HeatmapLayer`, `HexagonLayer`, `GridLayer`, `ScreenGridLayer`.
101
+ - Tiles: `TileLayer`, `MVTLayer`, `Tile3DLayer`.
102
+ - 3D models: `ScenegraphLayer`, `SimpleMeshLayer`, `PointCloudLayer`, `Tile3DLayer`.
103
+
104
+ ### Data Sources
105
+
106
+ | Source | Manifest | Notes |
107
+ |---|---|---|
108
+ | JSON / GeoJSON URL | `data="./points.json"` | Arrays or FeatureCollections. |
109
+ | Inline JSON | child `<script type="application/json">` | Good for tests/demos. |
110
+ | Columnar JSON | `{"columns": {"lon": [...], "lat": [...]}}` | Fast point path. |
111
+ | CSV / TSV | `data="./quakes.csv"` | Parsed to typed columns. |
112
+ | Arrow / GeoArrow IPC | `data="./big.arrow"` | Points stay columnar; lines/polygons become GeoJSON features. |
113
+ | Shapefile | `data="./countries.shp"` | Loads sidecars and joins `.dbf` attributes. |
114
+ | KML | `data="./tour.kml"` | Placemarks become GeoJSON features. |
115
+ | WebSocket | `data="wss://feed" key="id" flush="250ms" source="decoder"` | Upsert-by-key stream. |
116
+ | Polling | `data="/api/fleet.json" refresh="5s"` | Snapshot replace. |
117
+ | Draw store | `data="draw:sketch"` | Written by draw widget. |
118
+
119
+ Authenticated fetches:
120
+
121
+ ```js
122
+ import { OmMap } from "@nika-js/onlymap";
123
+ OmMap.configureData({ headers: { Authorization: `Bearer ${token}` } });
124
+ ```
125
+
126
+ Custom format:
127
+
128
+ ```js
129
+ OmMap.registerFormat({
130
+ match: (url, contentType) => url.endsWith(".custom"),
131
+ parse: async (res, url) => /* return rows or columnar data */
132
+ });
133
+ ```
134
+
135
+ Custom stream decoder:
136
+
137
+ ```js
138
+ OmMap.registerSource("fleet", {
139
+ onOpen: (send) => send(JSON.stringify({ subscribe: "vehicles" })),
140
+ decode: (msg) => msg.type === "position" ? { id: msg.id, lon: msg.lon, lat: msg.lat } : null
141
+ });
142
+ ```
143
+
144
+ ### Accessor Blocks
145
+
146
+ Restricted expression block:
147
+
148
+ ```html
149
+ <script type="om/accessors">
150
+ export const getPosition = d => [$lon, $lat];
151
+ export const getRadius = d => Math.max(2, $magnitude * 3);
152
+ </script>
153
+ ```
154
+
155
+ Full JavaScript block:
156
+
157
+ ```html
158
+ <om-layer id="routes" type="PathLayer" js>
159
+ <script type="om/accessors">
160
+ const speeds = { slow: [80, 120, 255], fast: [255, 80, 80] };
161
+ export const getColor = d => speeds[d.speedClass] ?? [180, 180, 180];
162
+ </script>
163
+ </om-layer>
164
+ ```
165
+
166
+ Do not use full-JS blocks on columnar/Arrow layers.
167
+
168
+ ### `<om-widget>`
169
+
170
+ Built-ins:
171
+
172
+ - `legend`
173
+ - `layer-switcher`
174
+ - `zoom-controls`
175
+ - `scale-bar`
176
+ - `attribution`
177
+ - `filter`
178
+ - `draw`
179
+ - `vega-lite`
180
+ - `player`
181
+
182
+ Positions: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
183
+
184
+ Examples:
185
+
186
+ ```html
187
+ <om-widget type="legend" position="bottom-right" title="Layers" interactive></om-widget>
188
+ <om-widget type="filter" layer="quakes" field="magnitude" position="top-left"></om-widget>
189
+ <om-widget type="draw" target="sketch" modes="point line polygon" save="both"></om-widget>
190
+ ```
191
+
192
+ Custom widget:
193
+
194
+ ```html
195
+ <om-widget position="top-left">
196
+ <div id="out"></div>
197
+ <script type="om/widget">
198
+ this.watch = ["data:quakes", "viewport", "selection"];
199
+ this.render = (ctx) => {
200
+ const s = ctx.stats("quakes", "magnitude", { scope: "viewport" });
201
+ this.$("#out").textContent = `${s.count} quakes`;
202
+ };
203
+ </script>
204
+ </om-widget>
205
+ ```
206
+
207
+ Widget context:
208
+
209
+ - `ctx.layers`
210
+ - `ctx.data(id)`
211
+ - `ctx.dataInViewport(id)`
212
+ - `ctx.stats(id, field, { scope: "viewport" })`
213
+ - `ctx.selection`
214
+ - `ctx.viewport`
215
+ - `ctx.emit(action, payload)`
216
+
217
+ Use `this.$()` and `this.root`; widgets render in shadow DOM.
218
+
219
+ ### `<om-overlay>`
220
+
221
+ Rich sparse HTML anchored to the map.
222
+
223
+ Anchors:
224
+
225
+ - `anchor="[lng, lat]"`
226
+ - `anchor-from="selection"`
227
+ - `anchor-layer="regions" anchor-feature-id="mission"`
228
+
229
+ Templates:
230
+
231
+ - `{{field}}` HTML-escaped interpolation.
232
+ - `{{{field}}}` raw HTML; avoid unless trusted.
233
+
234
+ Example:
235
+
236
+ ```html
237
+ <om-overlay id="detail" anchor-from="selection" visible="false">
238
+ <div><b>{{place}}</b> M {{magnitude}}</div>
239
+ </om-overlay>
240
+ <om-behavior on="click" layer="quakes" action="show-overlay" target="detail"></om-behavior>
241
+ ```
242
+
243
+ ### `<om-behavior>`
244
+
245
+ Declarative event to action binding.
246
+
247
+ Events: `click`, `hover`, `drag`, `load`, `data-loaded`.
248
+
249
+ Common built-in actions:
250
+
251
+ - `show-overlay`, `hide-overlay`
252
+ - `show-tooltip`, `hide-tooltip`
253
+ - `toggle-layer`
254
+ - `highlight-feature`
255
+ - `zoom-to-feature`
256
+ - `filter-layer`
257
+ - `zoom-in`, `zoom-out`
258
+ - `fly-to`
259
+ - story actions: `story-play`, `story-pause`, `story-seek`
260
+ - effect actions: `fade`, `pulse`, `trace`, `populate`
261
+ - draw actions: `draw-mode`, `draw-commit`, `draw-cancel`, `draw-delete`, `draw-clear`, `draw-config`, `draw-save`
262
+
263
+ Payload attributes are kebab-case and become camelCase payload keys.
264
+
265
+ Example:
266
+
267
+ ```html
268
+ <om-behavior on="click" layer="regions" action="zoom-to-feature" duration="1200ms"></om-behavior>
269
+ ```
270
+
271
+ ### Stories
272
+
273
+ Use `<om-story>` with `<om-step>` children. Stories are siblings of layers/overlays, not containers.
274
+
275
+ ```html
276
+ <om-story id="tour" interrupt="pause">
277
+ <om-step duration="2s" action="fly-to" center="[-122.44, 37.78]" zoom="12" curve></om-step>
278
+ <om-step duration="1s" action="show-overlay" target="intro" parallel></om-step>
279
+ <om-step duration="1500ms" fade layer="regions"></om-step>
280
+ <om-step duration="2s" trace layer="regions" feature-id="mission"></om-step>
281
+ </om-story>
282
+ <om-widget type="player" story="tour" position="bottom-left"></om-widget>
283
+ ```
284
+
285
+ Story timing attributes:
286
+
287
+ - `duration`
288
+ - `delay`
289
+ - `parallel`
290
+
291
+ Use declarative payloads for scrub-safe state: `visible="true"` when toggling, explicit `filter-range`, explicit camera target.
292
+
293
+ ### Manual Drawing
294
+
295
+ Use a normal GeoJSON layer bound to a draw store plus a draw widget:
296
+
297
+ ```html
298
+ <om-layer id="sketch" type="GeoJsonLayer" data="draw:sketch"
299
+ get-fill-color="[80, 140, 255, 90]"
300
+ get-line-color="[40, 90, 220]"
301
+ point-radius-min-pixels="6"
302
+ line-width-min-pixels="3"
303
+ stroked filled pickable></om-layer>
304
+ <om-widget type="draw" target="sketch" position="top-left"
305
+ modes="point line polygon" save="both"
306
+ autosave="my-sketch"></om-widget>
307
+ ```
308
+
309
+ The draw widget supports points, lines, polygons, delete-last, clear, save, and autosave. Lines/polygons close with double-click or Enter; Escape cancels the in-progress shape.
310
+
311
+ ### 3D
312
+
313
+ ScenegraphLayer places GLB/glTF models at coordinates:
314
+
315
+ ```html
316
+ <om-layer id="vehicles" type="ScenegraphLayer"
317
+ scenegraph="./truck.glb"
318
+ data="./vehicles.json"
319
+ get-position="[$lon, $lat]"
320
+ get-orientation="[0, $heading, 90]"
321
+ get-scale="[1, 1, 1]"
322
+ lighting="pbr" pickable></om-layer>
323
+ ```
324
+
325
+ Roll `90` stands Y-up glTF assets upright. Use `Tile3DLayer` for large 3D Tiles datasets.
326
+
327
+ 3D Tiles roots use `tileset`, not `data`, because OnlyMapJS reserves `data` for parsed datasets:
328
+
329
+ ```html
330
+ <om-layer id="city" type="Tile3DLayer"
331
+ tileset="https://example.com/tileset.json"></om-layer>
332
+ ```
333
+
334
+ For 3D Tiles LOD/refinement experiments, use:
335
+
336
+ ```html
337
+ <om-layer id="city" type="Tile3DLayer"
338
+ tileset="https://example.com/tileset.json"
339
+ maximum-screen-space-error="2"
340
+ maximum-memory-usage="256"
341
+ view-distance-scale="0.85"></om-layer>
342
+ ```
@@ -0,0 +1,126 @@
1
+ # OnlyMapJS Validation And Testing
2
+
3
+ Use this reference when a user asks to debug, validate, review, or test an OnlyMapJS page.
4
+
5
+ ## Static Validation
6
+
7
+ Use `OmMap.validate(htmlString)` before finalizing agent-written maps.
8
+
9
+ ```ts
10
+ import { OmMap } from "@nika-js/onlymap";
11
+
12
+ const result = OmMap.validate(html);
13
+ if (!result.valid) {
14
+ console.log(result.errors);
15
+ }
16
+ ```
17
+
18
+ Errors and warnings have:
19
+
20
+ ```ts
21
+ {
22
+ severity: "error" | "warning",
23
+ element: "om-layer#quakes",
24
+ attribute: "get-position",
25
+ message: "...",
26
+ fix: "..."
27
+ }
28
+ ```
29
+
30
+ Treat `fix` as the direct instruction to apply.
31
+
32
+ Common validation failures:
33
+
34
+ - Self-closing custom elements cause bad DOM nesting. Use explicit closing tags.
35
+ - Missing layer `id`.
36
+ - Unknown layer `type`.
37
+ - Missing required accessor such as `get-position`.
38
+ - `scale()` missing explicit `domain=`.
39
+ - Unknown attributes from camelCase or typos.
40
+ - `js` full-JS accessor block on columnar/Arrow data.
41
+ - `filter-field` without `filter-range`.
42
+ - Inline handlers in widget/overlay content.
43
+ - Story steps containing child elements.
44
+
45
+ ## Snapshot IR
46
+
47
+ Use `OmMap.snapshotIR(html)` to inspect what a manifest means without WebGL or network.
48
+
49
+ ```ts
50
+ expect(OmMap.snapshotIR(html)).toMatchSnapshot();
51
+ ```
52
+
53
+ The snapshot contains resolved layer descriptors. Accessors appear as behavioral fingerprints, so expression changes show up in diffs without serializing functions.
54
+
55
+ ## Headless Behavior Harness
56
+
57
+ Use `mountForTest` for most interaction tests.
58
+
59
+ ```ts
60
+ // @vitest-environment happy-dom
61
+ import "@nika-js/onlymap";
62
+ import { mountForTest } from "@nika-js/onlymap";
63
+
64
+ const h = await mountForTest(`
65
+ <om-map center="[-122.42, 37.77]" zoom="11">
66
+ <om-layer id="points" type="ScatterplotLayer" get-position="[$lon, $lat]" pickable>
67
+ <script type="application/json">
68
+ [{ "id": "p1", "name": "A", "lon": -122.42, "lat": 37.77 }]
69
+ </script>
70
+ </om-layer>
71
+ <om-overlay id="detail" anchor-from="selection" visible="false">
72
+ <div>{{name}}</div>
73
+ </om-overlay>
74
+ <om-behavior on="click" layer="points" action="show-overlay" target="detail"></om-behavior>
75
+ </om-map>
76
+ `);
77
+
78
+ await h.pick({ layer: "points", featureId: "p1" });
79
+ expect(h.map.querySelector("#detail").shadowRoot.textContent).toContain("A");
80
+ ```
81
+
82
+ Harness operations:
83
+
84
+ - `h.pick({ layer, featureId | index, type })`
85
+ - `h.clearSelection()`
86
+ - `h.emit(action, payload)`
87
+ - `h.setView({ center, zoom, pitch, bearing })`
88
+ - `h.layers()`
89
+ - `h.story(id).advance(ms)`
90
+ - `h.story(id).seek(ms)`
91
+ - `h.unmount()`
92
+
93
+ Use `vi.stubGlobal("fetch", ...)` for URL data and `vi.stubGlobal("WebSocket", ...)` for streams.
94
+
95
+ ## Browser/E2E Testing
96
+
97
+ Use Playwright only for pixels, real GPU picking, basemap composition, or asset rendering.
98
+
99
+ Wait for readiness:
100
+
101
+ ```ts
102
+ await page.evaluate(() => document.querySelector("om-map").ready);
103
+ ```
104
+
105
+ Project data coordinates to click pixels:
106
+
107
+ ```ts
108
+ const p = await page.evaluate(() => document.querySelector("om-map").projectInternal([-122.42, 37.77]));
109
+ const box = await page.locator("om-map").boundingBox();
110
+ await page.mouse.click(box.x + p[0], box.y + p[1]);
111
+ ```
112
+
113
+ Do not use sleeps. Wait for `ready`, DOM state, screenshots, or `expect.poll`.
114
+
115
+ ## Debugging Checklist
116
+
117
+ 1. Is every OnlyMapJS custom element explicitly closed?
118
+ 2. Does every layer have `id` and a valid `type`?
119
+ 3. Are attributes kebab-case?
120
+ 4. Is the data shape compatible with the accessor? Prefer `$field`.
121
+ 5. Does every `scale()` include `domain=`?
122
+ 6. Are behaviors pointing at existing `layer`/`target` ids?
123
+ 7. Is `pickable` present where click/hover behaviors are expected?
124
+ 8. Are credentials configured through `OmMap.configureData`, not markup?
125
+ 9. Is the relevant CSS imported when using MapLibre basemaps?
126
+ 10. Is a story step setting state rather than toggling ambiguous state?