ULM
    Preparing search index...

    ULM

    Universal Layer Manager logo

    Universal Layer Manager

    License: MIT TypeScript Docs

    A state-machine-powered layer management library for map applications. Model your map contents as layers and layer groups, then control visibility, opacity, and ordering from any UI framework and any mapping library.

    API documentation →


    Package Version Description
    @ulm/core npm Core state machine library — framework and map-library agnostic
    @ulm/leaflet npm Leaflet adapter — syncs manager state to a Leaflet map

    • XState actor model: Manager, layers, and layer groups are each XState actors.
    • Layers and layer groups: Model flat lists or nested trees with optional depth control.
    • Framework-agnostic: Pure TypeScript/XState core — works with any UI rendering layer.
    • Visibility and opacity: Per-layer enable/disable and opacity that cascades through parents.
    • Time metadata: Optional LayerTimeInfo using @internationalized/date for date ranges.
    • Typed events: Strongly typed input and output events for reactive UIs.
    • Adapter pattern: Implement LayerManagerAdapter to connect any mapping library.

    npm install @ulm/core
    
    import { LayerManager } from '@ulm/core';

    interface LayerData {
    url: string;
    }

    const manager = new LayerManager<LayerData>({
    onLayerAdded(info) {
    console.log('added:', info.layerId);
    },
    onVisibilityChanged(info, visible) {
    console.log(info.layerId, 'visible:', visible);
    },
    });

    manager.addLayer({
    layerConfig: {
    layerId: 'basemap',
    layerName: 'Basemap',
    layerType: 'layer',
    parentId: null,
    layerData: { url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' },
    },
    visible: true,
    });

    manager.setVisibility('basemap', false);
    manager.destroy();

    See @ulm/core for the full API reference.

    createLayerManagerMachine is exported for direct XState usage (e.g. integrating with @xstate/react):

    import { createLayerManagerMachine } from '@ulm/core';
    import { createActor } from 'xstate';

    const actor = createActor(createLayerManagerMachine<LayerData>(), {
    input: { allowNestedGroupLayers: true },
    });
    actor.start();

    If you're using LayerManager, the raw actor is also available via manager.actor.


    LayerManager owns state; adapters subscribe to its emitted events and perform map-library side-effects (add/remove layers, sync visibility, opacity). Implement the LayerManagerAdapter interface and attach it with manager.setAdapter(adapter).

    import { LeafletLayerManagerAdapter } from '@ulm/leaflet';

    manager.setAdapter(new LeafletLayerManagerAdapter(map));
    // manager.setAdapter(null) detaches and calls adapter.unregister()

    See @ulm/leaflet for a complete example, or implement LayerManagerAdapter yourself for other mapping libraries (OpenLayers, Mapbox, etc.).


    Example Description
    examples/simple Minimal vanilla TypeScript — demonstrates LayerManager with plain DOM
    examples/leaflet React + Leaflet — @ulm/leaflet adapter, nested groups, layer list UI

    To run an example:

    npm install        # install all workspace dependencies
    npm run dev        # starts all dev servers via Turbo
    

    packages/
    core/ @ulm/corestate machine library
    leaflet/ @ulm/leafletLeaflet adapter
    examples/
    simple/ vanilla TypeScript example
    leaflet/ React + Leaflet example

    MIT