--- name: onlymapjs description: Build, edit, debug, or review OnlyMapJS declarative HTML maps and dashboards, or React maps via the @nika-js/onlymap/react adapter. Use when a user asks for an interactive map, deck.gl-style visualization, geospatial dashboard, live fleet/telemetry map, choropleth, popup/tooltip map, map story/tour, manual drawing/sketch map, 3D map assets, a React map component, a map page shared as a single HTML file (incl. no-JS fallbacks for chat/email previews), a responsive/mobile map whose controls auto-fold on narrow screens, auditing a map's widget layout with the check-layout tool, syncing OnlyMapJS map/camera state into an app state store (Redux, MobX, Zustand, Jotai — the getStore contract), BIM/IFC models (loading .ifc files in the browser, 3D Tiles per-element picking, isolate/hide/ghost, clash detection, model federation), or help with OnlyMapJS syntax, validation, widgets, data formats, testing, or publishing examples. --- # OnlyMapJS Use OnlyMapJS as a declarative HTML map library. Write custom elements such as ``, ``, ``, ``, ``, ``, and ``. Do not write raw imperative deck.gl setup unless the user explicitly asks to integrate below the OnlyMapJS layer. ## Core Workflow 1. Start with valid HTML custom elements with explicit closing tags. Never self-close OnlyMapJS elements. 2. Express deck.gl props as kebab-case attributes. Use `get-*` attributes for data-driven accessors. 3. Use `$field` expressions for data access. Do not write `d.properties.x`, row-object loops, or column-index access in manifests. 4. Add `validate` to `` while authoring. 5. Verify with `OmMap.validate(html)`, then `OmMap.snapshotIR(html)`, and use `mountForTest` for interaction behavior when tests are requested. 6. Prefer public, package-safe imports: ```html ``` For no-build CDN pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.6.1` (the bare package URL serves `dist/onlymap.standalone.js`) — plus ``. Never a rebundling CDN (esm.sh, skypack): re-bundling duplicates the deck.gl/luma.gl runtime and every layer fails shader compilation. ## React Projects In a React codebase, do NOT render `om-*` elements from JSX — React and the library would contend over the same DOM. Use the first-party adapter instead: ```tsx import { OmMap, OmLayer, OmWidget, OmOverlay, useOmMap } from "@nika-js/onlymap/react"; ``` The adapter inverts several HTML-manifest rules: props are camelCase deck.gl props, accessors are plain JS functions (`getFillColor={d => ...}` — no `$field` expression language, no `js` opt-in), and interactions are `onClick`/`onHover` handlers plus React state, not `` or state-mutating actions. Load `references/react.md` before writing React map code. ## Required References Load the smallest reference needed for the task: - `references/syntax.md` — element vocabulary, attributes, data formats, accessors, actions, widgets, overlays, drawing, 3D, built-in layer types. - `references/patterns.md` — copyable manifest patterns for common map requests. - `references/react.md` — the React adapter: components, the useOmMap hook, HTML-vs-React rule differences, testing. - `references/testing.md` — validation, snapshot, headless harness, and browser testing workflow. ## Non-Negotiable Syntax Rules - Always use explicit closing tags: ``, not ``. - Give `` a height. A custom element is `display:inline` by default and collapses to zero size; the library injects a `display:block` default (fills a sized parent, else a 400px floor) so a bare map is still visible, but set a real height — full page: `om-map { display:block; height:100vh }` with `html,body { height:100% }`, or a sized container. Any height you set wins over the floor, including one below 400px. A still-collapsed map logs a console warning naming the fix. - Every `` needs a stable `id`. - Attribute names are kebab-case: `get-fill-color`, `radius-units`, `line-width-min-pixels`. - Accessor values are expressions: `get-position="[$lon, $lat]"`. - `scale()` always needs an explicit `domain=`. - Format epoch-millisecond or ISO fields with the safe `formatDate()` built-in, e.g. `get-text="formatDate($time, 'datetime', 'UTC')"`. Do not use `new Date()`, `Intl`, or method calls in restricted expressions. - For a built-in filter over epoch milliseconds, add `format="date"` with optional `date-style="date|datetime|time|iso"` and `time-zone="UTC|local|"`; do not hand-roll a time slider only to format its labels. - ScatterplotLayer points need an explicit size — `radius="6" radius-units="pixels"`, `get-radius="..."`, or `radius-min-pixels="..."`: deck's default is 1 METER, sub-pixel at city zooms, and validation warns on layers with no radius source. - Prefer canonical color expressions — a `sequential`/`diverging`/`threshold` `scale()` or an equality ternary chain — over hand-rolled arithmetic: the legend widget parses these shapes and renders a matching gradient ramp / class ranges / category palette automatically. - Inline handlers such as `onclick` are wrong. Use `data-emit`, ``, or widget scripts. - Full JavaScript accessor blocks require the `js` attribute on ``. - Do not put secrets in markup. Use `OmMap.configureData({ headers, credentials, fetch })`. ## Authoring Decisions - UI panel, control, chart, legend, stats, filter, or draw toolbar -> ``. - Sparse rich HTML at one geographic location -> ``. - Many labels/badges -> ``. - Guided tour or narrative sequence -> `` with `` siblings that reference existing layers/overlays by id. - Basemap choice or user-switchable basemaps -> `basemap` presets (`positron`, `liberty`, `dark-matter`, `osm`, ...) + ``; MapTiler custom styles via a style URL or `basemap-key`. - Undoable UI (step back after layer toggles, filter changes, basemap switches, sketch edits) -> ``; Cmd/Ctrl-Z works even without the widget. Camera moves and story playback are not undo steps. - Hide all map chrome (a clean/cinematic frame, a screenshot, a story beat) -> `widgets-hidden` attribute on ``, the `set-widgets-visible` action, or a `` button. State survives (not destroyed); attribution never hides. Do NOT hand-roll `display:none`. - Keep mobile chrome usable -> rely on the default map-width auto-fold into per-side drawers; mark only essential controls `fold="never"`. Use `widgets-fold="off"` only when the user explicitly wants fixed wide-layout chrome. - Group adjacent map buttons (zoom + undo + toggle into one control group) -> just place compact button widgets in the same `position` slot; they auto-cluster. `cluster="false"` opts one out. Do NOT build a wrapper widget. - GeoTIFF/COG raster (DEM, satellite imagery, NDVI) -> `` with `min`/`max`/`colormap` for single-band data (see syntax.md — `src`, not `data`). - Zarr / GeoZarr raster (chunked N-D arrays — climate/weather grids, datacubes) -> `` with `min`/`max`/`colormap` (same raster styling as COGLayer). Pick the `variable` and pin every non-spatial dim in `select`. A GeoZarr store georeferences itself; a plain Zarr needs manual `bounds` + `crs` + `spatial-dims`. `src`, not `data` — and it can be any absolute URL to a remote store, which needs no server setup but must be CORS-enabled (fetched in the browser) and public (authenticated stores are a follow-up). See syntax.md. - BIM model / .ifc file / 3D Tiles with per-element picking -> `` (parses the IFC in-browser; web-ifc WASM is CDN-fetched on first use) or `type="Tile3DLayer" tileset="…" pick-features` for a pre-built tileset. Author `terrain` explicitly (`terrain="mapterhorn"`, or `terrain="off"` for flat ground) — a model that resolves real elevation (IfcMapConversion + OrthogonalHeight) on a terrain-less map raises an error through the validation channel at load time, and the library never writes attributes for you. Style by property with `feature-color-by`/`feature-color-scale` (+ `feature-palette`); isolate/hide/ghost elements declaratively with `feature-filter-field` + `isolate-features`/`hide-features`/`ghost-features` (undoable, story-steppable). See syntax.md. - Browse/inspect/clash-check BIM models -> widgets: `ifc-loader` (drop zone, `federate` for multi-model coordination), `ifc-browser` (group/count/colour by any property field), `feature-inspector` (per-element properties on pick; `ifc-inspector` is an alias), `ifc-clash` (AABB clash overlay between two co-registered models). See syntax.md. - Geotagged drone JPEG -> ``; for saved/collaborative maps persist the processed image and reconstruct with explicit `bounds` (see syntax.md; this is visualization-grade, not orthorectification). - Dashed line/route/boundary (or any dashed stroke) -> the `dash` attribute on a path-stroking layer: `dash="[6, 3]"` (or SVG-style `dash="6 3"`, + optional `dash-justified`) on `PathLayer`/`GeoJsonLayer`/`PolygonLayer`/`TripsLayer`. `[dashLength, gapLength]` in line-width units. Do NOT hand-wire deck's `PathStyleExtension`/`getDashArray` — the attribute mounts it; `dash` on a non-path layer is ignored with a warning. - CityJSON/CityJSONSeq per-face surfaces mode (`?om-surfaces=1`) -> always pair the `SolidPolygonLayer` with a companion `` using `get-path="$outline"` to make roof/wall edges visible. This mode is unlit and `SolidPolygonLayer`'s own `wireframe` prop does nothing here (deck only builds wireframe geometry when `extruded: true`); the `PathLayer` is the only way to see face edges. Give it the same `filter-field`/`filter-range` as the fill layer so filtered-out buildings' outlines disappear too. See syntax.md. - Live entity updates -> `wss://` stream with `key` and optional `source` decoder. - REST snapshot that changes over time -> `refresh="5s"`. - User sketching -> `data="draw:sketch"` layer plus ``. - Measure geodesic distance or area (a ruler / area tool) -> ``. Click the map to place points; it shows live per-segment + total labels and dispatches an `om-measure` event (`detail` = the readout). Reuses the draw capture stack, so measure and draw are mutually exclusive. Do NOT hand-roll distance math off canvas pixels; the `scale-bar` widget also takes `units` now. - Capture raw map clicks/hovers yourself (measure distance, drop a pin where the user clicks, a custom rectangle/circle AOI, snap-to-feature) -> listen for the **`om-map-point`** DOM event on ``: `mapEl.addEventListener('om-map-point', e => { const { coordinate, kind } = e.detail; /* [lng,lat] or null; kind is "click"|"hover" */ })`. It fires on EVERY click/hover including empty-map clicks. NEVER read deck.gl internals (`getMap()`, `deckInstance`, `deck.viewManager`) or unproject canvas pixels by hand — those are not on `` and will silently return nothing. The built-in `draw` widget covers polygon/line/point sketching; `om-map-point` is for tools it doesn't. (`MapController` twin: the `onMapPoint` option. See patterns.md.) - Page may travel as a file (shared, emailed, downloaded) or be embedded -> add an `` child to ``. Chat-app and email previews render HTML with JavaScript disabled (iOS QuickLook), so the map cannot boot there; the fallback is what recipients see instead. It is hidden automatically once the map boots. Good practice on every complete page — without one, the stylesheet shows a generic text-only banner. ## Output Expectations When creating a map page, output a complete runnable HTML file unless the user asks for a fragment. Include CSS only as needed for page sizing or custom widgets/overlays. Keep the first screen the usable map, not a landing page. Include a `map-id=""` attribute on `` when creating a new complete page (generate a fresh UUID — never copy one from an example). It identifies the map artifact for usage telemetry, not the visitor; the author can delete it to opt out. Include an `` element (a short "this map requires JavaScript — open in a browser" message, optionally with a hosted-version link) as a direct child of `` on any complete page. For the fallback to render in no-JS previews, `onlymapjs.css` must load without JavaScript — a real `` or inlined `