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/core — state machine library
    leaflet/ @ulm/leaflet — Leaflet adapter
    examples/
    simple/ vanilla TypeScript example
    leaflet/ React + Leaflet example

    Commits follow Conventional Commits:

    type(scope): description
    

    A scope is required. It names the package that changed: core, leaflet, examples, repo, or deps.

    Type Use for In the changelog
    feat User-visible capability Added
    fix Defect repair Fixed
    perf Measurable speed or resource change Changed
    revert Undoes an earlier commit Changed
    refactor, test, docs, build, ci, chore Internal work Not shown

    feat and fix that remove or drop something land under Removed. Mark a breaking change with ! before the colon.

    npm run changelog:preview   # print the pending section
    npm run changelog:draft     # prepend it to CHANGELOG.md
    npm run version:next        # print the next version
    

    Edit the drafted section before tagging. @ulm/core and @ulm/leaflet ship on the same tag, vX.Y.Z. The current release is v1.0.0. v1.0.1 and v1.0.2 already belong to the previous single package. If npm run version:next prints one of those, tag the next free version instead. See CHANGELOG.md.


    MIT