4.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm run build # tsc emits individual ESM modules to dist/ (no bundling)
npm run typecheck # tsc --noEmit
npm run lint # oxlint over src/
npm run format # Prettier formatting
Linting runs on oxlint, not ESLint/@typescript-eslint. This project pins typescript@^7.0.2, and @typescript-eslint has no released version that supports it (peer range caps at <6.1.0, and even loading @typescript-eslint/parser crashes against TS 7's package shape). oxlint has its own parser and doesn't touch the typescript package, so it works regardless of TS version — the tradeoff is no type-aware rules (no no-floating-promises, no-unnecessary-condition, etc.). Revisit once @typescript-eslint supports TS 7.
There are no tests in this project. To preview components locally, open index.html in a browser with any static-file server.
Architecture
This library wraps Leaflet.js as native Web Components (Custom Elements). Each HTML element maps 1:1 to a Leaflet object.
WithProps mixin: src/core/with-props.ts
The WithProps(Base, PROPS) mixin factory replaces the old LeafletElement base class. Each component defines a PROPS table (a const record mapping kebab-case attribute names to PropDef descriptors), then extends WithProps(HTMLElement, PROPS). The mixin handles:
observedAttributesgetter derived from the PROPS table keys.definePropAccessors— property getters/setters on the prototype that sync attributes.- On connect,
initOptions()builds a Leaflet options object from current attributes + PROPS defaults, then callscreateLeafletObject(). - On attribute change,
updateLeafletObject()by default dispatches to the matching Leaflet setter (e.g.setOpacity,setRadius). Components override this for custom attribute handling (e.g. lat/lng pairs). - Every Leaflet event the created object fires is re-emitted on the element as
leaflet:<type>(e.g.leaflet:zoomend,leaflet:dragend), carrying the original Leaflet event object asevent.detail. This is generic and automatic:WithPropswraps the object's ownfire()method, so no per-component or per-event-type registration is needed. Not bubbling — Leaflet already propagates layer events up to the map, so<leaflet-map>would otherwise see each one twice.
leaflet-map: src/components/leaflet-map.ts
LeafletMap extends HTMLElement directly and uses Shadow DOM. It is the root of the component tree and terminates all bubbling leaflet-register events by calling layer.addTo(this.map). Uses a ResizeObserver on the host element to call map.invalidateSize() automatically.
Child component pattern
All non-map components extend WithProps(HTMLElement, PROPS). To add a new component:
- Define a
PROPS = {...}const table mapping kebab-case attributes toPropDefentries. - Declare
class LeafletFoo extends WithProps(HTMLElement, PROPS)implementingcreateLeafletObject(): L.Layer. - Override
updateLeafletObject(name, val)only if the default setter-based update won't work (common for coordinate pairs). - Call
customElements.define('leaflet-foo', LeafletFoo)at the bottom. - Export from
src/index.ts.
Special cases
leaflet-polygonuses<leaflet-line>children for vertices. The polygon collects lat/lng from childleaflet-lineelements rather than having them as direct attributes.leaflet-popup/leaflet-tooltip: content comes frominnerHTML, not attributes.leaflet-popupwatches for DOM mutations to keep Leaflet in sync.leaflet-layer-group/leaflet-feature-group: passthrough containers built withWithProps({})(an empty props table) — they have no options of their own, but still get the standard lifecycle, child registration, andleaflet:event forwarding for free. Children register themselves into them via the standard bubble mechanism.
Output
tsc compiles src/ → dist/ as individual ESM modules (.js + .d.ts + .d.ts.map) — no bundling step. Consumers import dist/index.js (or any individual module) directly; there is no CJS or UMD build. Leaflet is always external (never bundled). Imports within source use .ts extensions; rewriteRelativeImportExtensions in tsconfig strips them to .js in the tsc output (tsconfig.json:16).