You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
Buddy 5f3fd5f61a refactor: replace inheritance hierarchy with composable PROPS-table pattern
Remove LeafletElement and LeafletControl base classes. Every component
now self-describes its attributes via a PROPS table, derives observed-
Attributes from it, and generates prototype getters/setters in static {}.

Registration lifecycle is extracted into standalone helpers
(registerWithParent, createChildRegisterHandler). Common update
patterns (path style) are shared via imported helpers instead of
duplicated across 5+ components.

Components manage their own #obj private field instead of a shared
protected leafletObject. Leaflet-map-style declarative patterns are
now consistent across all 20 components.
4 months ago
src refactor: replace inheritance hierarchy with composable PROPS-table pattern 4 months ago
.gitignore feat: Initial implementation of leaflet-components 4 months ago
.prettierrc feat: Initial implementation of leaflet-components 4 months ago
CLAUDE.md feat: Initial implementation of leaflet-components 4 months ago
README.md feat: Initial implementation of leaflet-components 4 months ago
eslint.config.js feat: Initial implementation of leaflet-components 4 months ago
index.html feat: Initial implementation of leaflet-components 4 months ago
jsr.json feat: Initial implementation of leaflet-components 4 months ago
package-lock.json feat: Initial implementation of leaflet-components 4 months ago
package.json feat: Initial implementation of leaflet-components 4 months ago
tsconfig.json feat: Initial implementation of leaflet-components 4 months ago

README.md

leaflet-components

Leaflet.js as native Web Components (Custom Elements). Each HTML element maps 1:1 to a Leaflet object with reactive attribute binding.

Installation

npm install leaflet-components

Available on JSR as @buddy/leaflet-components.

Usage

Import once to register all custom elements, then use them declaratively in HTML.

<script type="module">
  import 'leaflet-components';
</script>

<leaflet-map lat="51.505" lng="-0.09" zoom="13" style="height:400px">
  <leaflet-tile-layer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution="© OpenStreetMap contributors"
  ></leaflet-tile-layer>

  <leaflet-marker lat="51.505" lng="-0.09">
    <leaflet-popup><b>Hello!</b></leaflet-popup>
    <leaflet-tooltip>Hover me</leaflet-tooltip>
  </leaflet-marker>

  <leaflet-circle lat="51.508" lng="-0.11" radius="500" color="red" fill-color="#f03" fill-opacity="0.5">
    <leaflet-popup>I am a circle</leaflet-popup>
  </leaflet-circle>

  <leaflet-polygon color="blue">
    <leaflet-line lat="51.509" lng="-0.08"></leaflet-line>
    <leaflet-line lat="51.503" lng="-0.06"></leaflet-line>
    <leaflet-line lat="51.510" lng="-0.047"></leaflet-line>
  </leaflet-polygon>

  <leaflet-control-scale position="bottomleft"></leaflet-control-scale>
</leaflet-map>

leaflet-map

The root component. All other components must be children of <leaflet-map>.

<leaflet-map lat="51.505" lng="-0.09" zoom="13" disable-scroll-wheel-zoom>

View state

These attributes stay in sync with the map as the user interacts with it — panning updates lat/lng, zooming updates zoom.

Attribute Default Description
lat 0 Center latitude
lng 0 Center longitude
zoom 2 Zoom level
min-zoom 0 Minimum zoom level
max-zoom Maximum zoom level. When unset, Leaflet uses the tile layer's own max zoom.

Interaction

Boolean options that default to true are controlled by the presence of a disable-* attribute. Handler-based options can be toggled at any time; the rest only apply at construction.

Attribute Controls Live?
disable-dragging Mouse/touch panning
disable-scroll-wheel-zoom Scroll-wheel zoom
disable-double-click-zoom Double-click zoom
disable-touch-zoom Pinch-to-zoom
disable-box-zoom Shift-drag zoom box
disable-keyboard Keyboard pan/zoom
disable-zoom-control Built-in zoom control
disable-attribution-control Built-in attribution
disable-close-popup-on-click Close popup on map click
disable-track-resize Auto-resize on window resize
disable-bounce-at-zoom-limits Bounce animation at min/max zoom
disable-tap-hold Long-press context menu (mobile)

Boolean options that default to false are enabled by adding the attribute:

Attribute Description
prefer-canvas Render vector layers on Canvas instead of SVG
world-copy-jump Pan to the original world copy when crossing the antimeridian

Animation

Attribute Default Description
disable-zoom-animation Disable CSS zoom animation
disable-fade-animation Disable tile fade-in
disable-marker-zoom-animation Disable marker zoom animation
zoom-animation-threshold 4 Max zoom delta for animated zoom

Inertia & panning

Attribute Default Description
disable-inertia Disable inertial panning
inertia-deceleration 3000 Deceleration rate (px/s²)
inertia-max-speed Infinity Maximum inertia speed (px/s)
ease-linearity 0.2 Pan easing linearity

Zoom behaviour

Attribute Default Description
zoom-snap 1 Zoom snapping interval; 0 for continuous zoom
zoom-delta 1 Zoom step per keyboard/button press
max-bounds-viscosity 0 How much the bounds resist panning past them (01)

Scroll wheel

Attribute Default Description
wheel-debounce-time 40 Debounce delay for wheel events (ms)
wheel-px-per-zoom-level 60 Pixels of scroll per zoom level

Keyboard & touch

Attribute Default Description
keyboard-pan-delta 80 Pan distance per key press (px)
tap-tolerance 15 Max touch movement to trigger a tap (px)

Rendering

Attribute Default Description
transform-3d-limit 8388608 Max CSS translate3d component value before a layer reset

Accessing the underlying map

const el = document.querySelector('leaflet-map');
el.map;          // L.Map instance (undefined before connected)
el.zoom;         // current zoom (reads live from map, falls back to attribute)
el.scrollWheelZoom = false;  // disable at runtime

All leaflet-map properties are two-way: reading returns the live map value; writing updates both the attribute and the map.


Other components

Tile layers

Element Key attributes
leaflet-tile-layer url, attribution, min-zoom, max-zoom, opacity, z-index
leaflet-tile-layer-wms url, layers, styles, format, transparent, version

Vector layers

Path style attributes (color, weight, opacity, fill, fill-color, fill-opacity, dash-array, line-cap, line-join) are shared by all vector layers.

Element Additional attributes Notes
leaflet-circle lat, lng, radius (meters)
leaflet-circle-marker lat, lng, radius (pixels)
leaflet-polyline Vertices via <leaflet-line> children
leaflet-polygon Vertices via <leaflet-line> children
leaflet-rectangle bounds (JSON)
leaflet-line lat, lng Vertex only — not rendered directly

Markers & overlays

Element Key attributes
leaflet-marker lat, lng, title, alt, draggable, opacity, z-index-offset
leaflet-image-overlay url, bounds (JSON), opacity, alt, interactive
leaflet-video-overlay url, bounds (JSON), opacity, loop, autoplay, muted
leaflet-svg-overlay bounds (JSON), opacity, interactive — wrap an <svg> element

Groups & GeoJSON

Element Key attributes
leaflet-layer-group
leaflet-feature-group
leaflet-geojson data (GeoJSON string), plus path style attributes

UI layers

Content is set via innerHTML, not attributes.

Element Key attributes
leaflet-popup lat, lng, max-width, min-width, auto-pan, close-button, auto-close
leaflet-tooltip lat, lng, direction, permanent, sticky, opacity

Controls

Element Key attributes
leaflet-control-zoom position, zoom-in-text, zoom-out-text
leaflet-control-attribution position, prefix
leaflet-control-scale position, max-width, metric, imperial

Nesting rules

  • Popup / tooltip as child of a layer → bound via bindPopup / bindTooltip.
  • Layer as child of a group → added via addLayer.
  • Any layer as child of leaflet-map → added to the map directly.
  • leaflet-polygon / leaflet-polyline take their coordinates from <leaflet-line> children, not from attributes.

Development

npm run build    # type-check + emit .d.ts via tsc, then bundle to dist/index.js via esbuild
npm run lint     # ESLint
npm run format   # Prettier
npx vite         # open index.html for a live demo