← All @molecule/* packages · App templates

@molecule/app-maps

Feature · maps · App (browser) · v1.0.1 · Apache-2.0

Map provider contract (markers, overlays, viewport, events) with a built-in placeholder provider — bring your own map SDK provider

npm install @molecule/app-maps

npm · Source on GitHub

How it works

@molecule/app-maps is a ready-made maps feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.

import { createSimpleMapProvider, setProvider, getProvider } from '@molecule/app-maps'

// Development placeholder (renders a grey panel, NOT a real map).
// For production, implement MapProvider against your map SDK and wire that
// provider here instead.
setProvider(createSimpleMapProvider())

const container = document.getElementById('map')
if (container) {
  const map = await getProvider().createMap({
    container, // must have an explicit CSS height
    center: { lat: 37.7749, lng: -122.4194 },
    zoom: 12,
  })
  map.addMarker({ id: 'hq', position: { lat: 37.7749, lng: -122.4194 }, title: 'HQ' })
  map.on('click', (e) => console.log('clicked', e))
}

Providers (1): @molecule/app-maps-leaflet

Works with: @molecule/app-bond, @molecule/app-i18n

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Map provider contract for molecule.dev — a unified, framework-agnostic API (create map, viewport control, markers, polylines/polygons/circles, popups, events, geocoding hooks) designed so different map SDKs can sit behind one interface.

Exports the MapProvider / MapInstance / config types, the bond-wiring helpers (provider, setProvider, getProvider, hasProvider, createMap), geometry utilities (calculateBounds, calculateCenter, calculateDistance), and createSimpleMapProvider().

IMPORTANT: for a REAL map, bond @molecule/app-maps-leaflet (Leaflet + OpenStreetMap tiles, no API key): import 'leaflet/dist/leaflet.css' once, then import { provider } from '@molecule/app-maps-leaflet' + setProvider(provider) (equivalent to bond('maps', provider)) at startup. The built-in createSimpleMapProvider() is only a PLACEHOLDER — it renders a static grey panel ("Map Placeholder", tagged data-mol-map-placeholder) instead of a map and console.warns once when it engages (markers tracked in memory but never drawn, overlays/events no-ops). Use the placeholder only in tests / before the real provider is wired. (To use a different SDK — MapLibre, Mapbox GL, Google Maps — implement MapProvider against it; but the Leaflet bond covers the common "show a map with pins" case with zero config.)

Quick Start

import { createSimpleMapProvider, setProvider, getProvider } from '@molecule/app-maps'

// Development placeholder (renders a grey panel, NOT a real map).
// For production, implement MapProvider against your map SDK and wire that
// provider here instead.
setProvider(createSimpleMapProvider())

const container = document.getElementById('map')
if (container) {
  const map = await getProvider().createMap({
    container, // must have an explicit CSS height
    center: { lat: 37.7749, lng: -122.4194 },
    zoom: 12,
  })
  map.addMarker({ id: 'hq', position: { lat: 37.7749, lng: -122.4194 }, title: 'HQ' })
  map.on('click', (e) => console.log('clicked', e))
}

Type

feature

Installation

npm install @molecule/app-maps @molecule/app-bond @molecule/app-i18n

API

Interfaces

Bounds

Geographic bounds (bounding box).

interface Bounds {
  /**
   * Northeast corner.
   */
  ne: Coordinates

  /**
   * Southwest corner.
   */
  sw: Coordinates
}

CircleConfig

Map circle overlay (center, radius in meters, fill/stroke colors).

interface CircleConfig {
  /**
   * Unique ID.
   */
  id: string

  /**
   * Center position.
   */
  center: Coordinates

  /**
   * Radius in meters.
   */
  radius: number

  /**
   * Fill color.
   */
  fillColor?: string

  /**
   * Fill opacity (0-1).
   */
  fillOpacity?: number

  /**
   * Stroke color.
   */
  strokeColor?: string

  /**
   * Stroke width.
   */
  strokeWidth?: number

  /**
   * Additional metadata.
   */
  meta?: Record<string, unknown>
}

Coordinates

Latitude/longitude pair with optional altitude.

interface Coordinates {
  /**
   * Latitude.
   */
  lat: number

  /**
   * Longitude.
   */
  lng: number

  /**
   * Altitude (optional).
   */
  alt?: number
}

MapClickEvent

Map click event data.

interface MapClickEvent {
  /**
   * Original event.
   */
  originalEvent: MouseEvent

  /**
   * Coordinates.
   */
  coordinates: Coordinates

  /**
   * Point on screen.
   */
  point: { x: number; y: number }
}

MapConfig

Map initialization options (container, center, zoom, style, interaction controls, bounds).

interface MapConfig {
  /**
   * Container element.
   */
  container: HTMLElement | string

  /**
   * Initial center.
   */
  center?: Coordinates

  /**
   * Initial zoom level.
   */
  zoom?: number

  /**
   * Min zoom level.
   */
  minZoom?: number

  /**
   * Max zoom level.
   */
  maxZoom?: number

  /**
   * Initial bearing.
   */
  bearing?: number

  /**
   * Initial pitch.
   */
  pitch?: number

  /**
   * Map style (URL or style object).
   */
  style?: string | Record<string, unknown>

  /**
   * Enable scroll zoom.
   */
  scrollZoom?: boolean

  /**
   * Enable drag pan.
   */
  dragPan?: boolean

  /**
   * Enable drag rotate.
   */
  dragRotate?: boolean

  /**
   * Enable double-click zoom.
   */
  doubleClickZoom?: boolean

  /**
   * Enable keyboard navigation.
   */
  keyboard?: boolean

  /**
   * Enable touch zoom/rotate.
   */
  touchZoomRotate?: boolean

  /**
   * Enable touch pitch.
   */
  touchPitch?: boolean

  /**
   * Max bounds (pan limits).
   */
  maxBounds?: Bounds

  /**
   * Render world copies.
   */
  renderWorldCopies?: boolean

  /**
   * Attribution control.
   */
  attributionControl?: boolean

  /**
   * Locale for controls.
   */
  locale?: string
}

MapInstance

Live map instance providing viewport control, marker/layer management, and event handling.

interface MapInstance {
  /**
   * Gets the current viewport.
   */
  getViewport(): Viewport

  /**
   * Sets the viewport.
   */
  setViewport(viewport: Partial<Viewport>, options?: { animate?: boolean; duration?: number }): void

  /**
   * Flies to a location.
   */
  flyTo(
    location: Partial<Viewport>,
    options?: { duration?: number; curve?: number; easing?: (t: number) => number },
  ): void

  /**
   * Fits the map to bounds.
   */
  fitBounds(bounds: Bounds, options?: { padding?: number; animate?: boolean }): void

  /**
   * Gets the current bounds.
   */
  getBounds(): Bounds

  /**
   * Adds a marker.
   */
  addMarker(marker: MarkerConfig): void

  /**
   * Removes a marker.
   */
  removeMarker(id: string): void

  /**
   * Updates a marker.
   */
  updateMarker(id: string, updates: Partial<MarkerConfig>): void

  /**
   * Gets all markers.
   */
  getMarkers(): MarkerConfig[]

  /**
   * Clears all markers.
   */
  clearMarkers(): void

  /**
   * Adds a polyline.
   */
  addPolyline(polyline: PolylineConfig): void

  /**
   * Removes a polyline.
   */
  removePolyline(id: string): void

  /**
   * Adds a polygon.
   */
  addPolygon(polygon: PolygonConfig): void

  /**
   * Removes a polygon.
   */
  removePolygon(id: string): void

  /**
   * Adds a circle.
   */
  addCircle(circle: CircleConfig): void

  /**
   * Removes a circle.
   */
  removeCircle(id: string): void

  /**
   * Opens a popup.
   */
  openPopup(popup: PopupConfig): void

  /**
   * Closes all popups.
   */
  closePopups(): void

  /**
   * Adds an event listener.
   */
  on<T = unknown>(event: MapEvent, handler: (data: T) => void): () => void

  /**
   * Removes an event listener.
   */
  off<T = unknown>(event: MapEvent, handler: (data: T) => void): void

  /**
   * Adds a click listener for a marker.
   */
  onMarkerClick(markerId: string, handler: (marker: MarkerConfig) => void): () => void

  /**
   * Projects coordinates to pixel position.
   */
  project(coordinates: Coordinates): { x: number; y: number }

  /**
   * Unprojects pixel position to coordinates.
   */
  unproject(point: { x: number; y: number }): Coordinates

  /**
   * Resizes the map.
   */
  resize(): void

  /**
   * Gets the map container element.
   */
  getContainer(): HTMLElement

  /**
   * Gets the canvas element.
   */
  getCanvas(): HTMLCanvasElement

  /**
   * Takes a screenshot.
   */
  getSnapshot(options?: { width?: number; height?: number }): Promise<string>

  /**
   * Gets the underlying map instance.
   */
  getInstance(): unknown

  /**
   * Destroys the map.
   */
  destroy(): void
}

MapMoveEvent

Map move event data.

interface MapMoveEvent {
  /**
   * New viewport.
   */
  viewport: Viewport

  /**
   * Original event.
   */
  originalEvent?: Event
}

MapProvider

Map provider interface.

interface MapProvider {
  /**
   * Create a new map instance with the given configuration.
   * @returns A MapInstance that can be used to control the map.
   */
  createMap(config: MapConfig): MapInstance | Promise<MapInstance>

  /**
   * Get the display name of this map provider (e.g., 'Google Maps', 'Mapbox').
   * @returns The provider name string.
   */
  getName(): string

  /**
   * Check if the map provider's SDK/library has been loaded and is ready.
   * @returns Whether the map provider is loaded and ready to create maps.
   */
  isLoaded(): boolean

  /**
   * Get the list of available map styles for this provider.
   * @returns Array of style objects with id, name, and URL.
   */
  getStyles(): { id: string; name: string; url: string }[]

  /**
   * Geocodes an address.
   */
  geocode?(address: string): Promise<Coordinates[]>

  /**
   * Reverse geocodes coordinates.
   */
  reverseGeocode?(coordinates: Coordinates): Promise<string>

  /**
   * Gets directions between points.
   */
  getDirections?(
    origin: Coordinates,
    destination: Coordinates,
    options?: { mode?: 'driving' | 'walking' | 'cycling' },
  ): Promise<{ route: Coordinates[]; duration: number; distance: number }>
}

MarkerConfig

Map marker configuration (position, icon, popup, draggable, color, metadata).

interface MarkerConfig {
  /**
   * Unique ID.
   */
  id: string

  /**
   * Position.
   */
  position: Coordinates

  /**
   * Title (tooltip on hover).
   */
  title?: string

  /**
   * Custom icon URL.
   */
  icon?: string

  /**
   * Icon size.
   */
  iconSize?: [number, number]

  /**
   * Icon anchor point.
   */
  iconAnchor?: [number, number]

  /**
   * Popup content.
   */
  popup?: string | HTMLElement

  /**
   * Draggable.
   */
  draggable?: boolean

  /**
   * Opacity (0-1).
   */
  opacity?: number

  /**
   * Color (for default markers).
   */
  color?: string

  /**
   * Additional metadata.
   */
  meta?: Record<string, unknown>
}

PolygonConfig

Map polygon overlay (closed path, fill/stroke colors, opacity).

interface PolygonConfig {
  /**
   * Unique ID.
   */
  id: string

  /**
   * Polygon coordinates.
   */
  path: Coordinates[]

  /**
   * Fill color.
   */
  fillColor?: string

  /**
   * Fill opacity (0-1).
   */
  fillOpacity?: number

  /**
   * Stroke color.
   */
  strokeColor?: string

  /**
   * Stroke width.
   */
  strokeWidth?: number

  /**
   * Stroke opacity (0-1).
   */
  strokeOpacity?: number

  /**
   * Additional metadata.
   */
  meta?: Record<string, unknown>
}

PolylineConfig

Map polyline overlay (path coordinates, stroke color/width/opacity, dash pattern).

interface PolylineConfig {
  /**
   * Unique ID.
   */
  id: string

  /**
   * Path coordinates.
   */
  path: Coordinates[]

  /**
   * Stroke color.
   */
  strokeColor?: string

  /**
   * Stroke width.
   */
  strokeWidth?: number

  /**
   * Stroke opacity (0-1).
   */
  strokeOpacity?: number

  /**
   * Dash pattern.
   */
  dashArray?: number[]

  /**
   * Additional metadata.
   */
  meta?: Record<string, unknown>
}

PopupConfig

Map popup configuration (position, HTML content, offset, close behavior, max width).

interface PopupConfig {
  /**
   * Position.
   */
  position: Coordinates

  /**
   * Content.
   */
  content: string | HTMLElement

  /**
   * Offset from position.
   */
  offset?: [number, number]

  /**
   * Close button.
   */
  closeButton?: boolean

  /**
   * Close on click.
   */
  closeOnClick?: boolean

  /**
   * Max width.
   */
  maxWidth?: number

  /**
   * Class name.
   */
  className?: string
}

Viewport

Map viewport state (center coordinates, zoom, bearing, pitch, and bounds).

interface Viewport {
  /**
   * Center coordinates.
   */
  center: Coordinates

  /**
   * Zoom level.
   */
  zoom: number

  /**
   * Bearing (rotation) in degrees.
   */
  bearing?: number

  /**
   * Pitch (tilt) in degrees.
   */
  pitch?: number
}

Types

MapEvent

Map event types.

type MapEvent =
  | 'click'
  | 'dblclick'
  | 'contextmenu'
  | 'mouseenter'
  | 'mouseleave'
  | 'mousemove'
  | 'movestart'
  | 'move'
  | 'moveend'
  | 'zoomstart'
  | 'zoom'
  | 'zoomend'
  | 'load'
  | 'idle'
  | 'resize'

Functions

calculateBounds(coordinates)

Calculate the bounding box that contains all given coordinates.

function calculateBounds(coordinates: Coordinates[]): Bounds
  • coordinates — Array of coordinates to compute bounds for.

Returns: The southwest and northeast corners of the bounding box.

calculateCenter(coordinates)

Calculate the geographic center (centroid) of multiple coordinates.

function calculateCenter(coordinates: Coordinates[]): Coordinates
  • coordinates — Array of coordinates to average.

Returns: The center point as a Coordinates object.

calculateDistance(from, to)

Calculate the Haversine distance between two geographic coordinates.

function calculateDistance(from: Coordinates, to: Coordinates): number
  • from — The starting coordinates.
  • to — The destination coordinates.

Returns: The distance in meters.

createMap(config)

Create a new map instance using the current provider.

function createMap(config: MapConfig): MapInstance | Promise<MapInstance>
  • config — Map configuration (container element, center, zoom, style, etc.).

Returns: A MapInstance for controlling the map.

createSimpleMapProvider(options)

Create the built-in PLACEHOLDER map provider. It does NOT render a real map: it draws a static grey panel that identifies itself as a placeholder and console.warns once when it first engages, because no real map SDK ships with the fleet. Markers are tracked in memory but never drawn; overlays, popups, events and projection are no-ops; getSnapshot() returns an empty string. It exists only so map-using screens render something honest until a real MapProvider (Mapbox GL / Google Maps / Leaflet / MapLibre) is implemented and bonded via setProvider().

function createSimpleMapProvider(options?: SimpleMapProviderOptions): MapProvider
  • options — Optional placeholder title and description text.

Returns: A MapProvider that renders an honest, self-identifying placeholder.

getProvider()

Get the current map provider. Falls back to the built-in placeholder provider if none has been bonded — no real map SDK ships with the fleet, so the placeholder renders a grey panel (not a map) and console.warns once when it engages rather than silently faking a working map. Use hasProvider() to detect whether a real provider was actually wired.

function getProvider(): MapProvider

Returns: The active MapProvider instance.

hasProvider()

Check if a map provider has been registered.

function hasProvider(): boolean

Returns: Whether a MapProvider has been bonded.

isWithinBounds(coordinates, bounds)

Check if a coordinate point falls within a geographic bounding box.

function isWithinBounds(coordinates: Coordinates, bounds: Bounds): boolean
  • coordinates — The point to test.
  • bounds — The bounding box with southwest and northeast corners.

Returns: Whether the point is inside the bounds.

setProvider(provider)

Set the map provider.

function setProvider(provider: MapProvider): void
  • provider — MapProvider implementation to register.

Constants

defaultStyles

Default Mapbox style URLs for common map appearances.

const defaultStyles: {
  streets: string
  outdoors: string
  light: string
  dark: string
  satellite: string
  satelliteStreets: string
  navigationDay: string
  navigationNight: string
}

provider

Default map provider — the built-in simple provider. Exported so apps can wire it with bond('maps', provider) (equivalent to setProvider), matching the convention of other bondable feature packages (e.g. @molecule/app-data-table-*). Bond a richer MapProvider instead to replace it.

const provider: MapProvider

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-bond ^1.0.1
  • @molecule/app-i18n ^1.0.1

Runtime Dependencies

  • @molecule/app-bond

  • @molecule/app-i18n

  • getProvider() falls back to the placeholder when nothing is wired — a forgotten setProvider does not throw; the placeholder renders a grey panel AND console.warns once (naming the gap and the fix) so the omission is visible, not silent. Use hasProvider() to detect real wiring.

  • The map fills 100% of its container: the container element must have an explicit height or the map/placeholder is invisible (a zero-height parent is the classic blank-screen trap).

  • When config.container is an element-id string, the element must already exist in the DOM at createMap() time or it crashes.

  • Placeholder text is localizable via @molecule/app-locales-maps (maps.placeholder.title / maps.placeholder.description) or the createSimpleMapProvider({ placeholderTitle, placeholderDescription }) options.

  • Custom providers must return the normalized MapInstance shape — never leak the underlying SDK object except through getInstance().

Translations

Translation strings are provided by @molecule/app-locales-maps.