GeoLeaf Core API - v3.19.0
    Preparing search index...

    Interface GeoLeafUIFacade

    GeoLeaf.UI — the kernel UI façade.

    kernel/ui/ui-api.ts does const UI = _g.GeoLeaf.UI; then re-exports it: the exported value IS the namespace member. A typeof import("./api/geoleaf.ui.js").UI is therefore structurally circular — measured, tsc emits TS7022 then TS2303. Most probably the reason this member used to be declared Record<string, unknown>: the easy form does not compile.

    That is no reason to type nothing. The facade is mounted by mutation from ~10 modules; the 24 members below were read off the real assignment sites, and the named interface breaks the cycle by depending on no value.

    ⚠️ The motive that justified it here was false. It said "boot-core.ts and init-features.ts still set members outside this list". Measured: those two files set no member on GeoLeaf.UI — their only ui. occurrences are config reads (init-features.ts). The 6 genuinely missing members came from globals/globals.ui.ts and the offline-ui plugin; they are declared below, read off their assignment sites.

    What the tail cost. It made any invented member unknown — hence indistinguishable from a real untyped one. GeoLeaf.UI.toggleFilterPanel(true) was taught by the @example of api/geoleaf.ui.ts although that identifier never existed in the repo, and the three documentation gates came out green on it. Measured on 31/07/2026 (mutation GeoLeaf.UI.cetteApiNExistePas("dark") → GREEN), with the conclusion: the benefit of compiling the examples grows with the namespace's typing, and not otherwise. Tail removed, a phantom member yields TS2339, which typecheck-docs-examples.cjs knows.

    No assignment site breaks: globals.ui.ts writes through _gl.UI as Record<string, unknown>, cache-button.ts through its own cast, and ui.module.ts reads through the dynamic helper member(obj: unknown, …).

    interface GeoLeafUIFacade {
        VERSION?: string;
        BUILD?: string;
        init?: (
            options?: {
                map?: unknown;
                mapContainer?: HTMLElement;
                filterContainer?: HTMLElement;
                buttonSelector?: string;
                autoInitOnDomReady?: boolean;
                enableEventDelegation?: boolean;
                config?: unknown;
            },
        ) => unknown;
        cleanup?: () => void;
        getModuleStatus?: () => unknown;
        applyTheme?: (...args: unknown[]) => unknown;
        setTheme?: (...args: unknown[]) => unknown;
        getCurrentTheme?: () => string;
        initAutoTheme?: (...args: unknown[]) => unknown;
        initThemeToggle?: (...args: unknown[]) => unknown;
        toggleTheme?: (...args: unknown[]) => unknown;
        Notifications?: Record<string, unknown>;
        showNotification?: (...args: unknown[]) => unknown;
        showSuccess?: (...args: unknown[]) => unknown;
        showError?: (...args: unknown[]) => unknown;
        showWarning?: (...args: unknown[]) => unknown;
        showInfo?: (...args: unknown[]) => unknown;
        clearNotifications?: (...args: unknown[]) => unknown;
        notify?: {
            info?: (
                msg: string,
                opts?: number | Record<string, unknown>,
            ) => unknown;
            warn?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
            error?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
            success?: (
                msg: string,
                opts?: number | Record<string, unknown>,
            ) => unknown;
            dismiss?: (id: HTMLElement) => unknown;
        };
        initMobileToolbar?: (options: MobileToolbarOptions) => void;
        destroyMobileToolbar?: () => void;
        initDesktopPanel?: (options: DesktopPanelOptions) => void;
        activateDesktopPanel?: () => void;
        destroyDesktopPanel?: () => void;
        openPanel?: (tabId: string) => boolean;
        closePanel?: () => void;
        getOpenPanel?: () => string | null;
        registerPanelPane?: (pane: PanelPane) => void;
        openPane?: (paneId: string) => boolean;
        closePane?: () => void;
        setImmersive?: (on: boolean, options?: { fullscreen?: boolean }) => void;
        isImmersive?: () => boolean;
        CacheButton?: {
            init?: (map: unknown, cfg: Record<string, unknown>) => unknown;
            openModal?: () => unknown;
            closeModal?: () => unknown;
            Modules?: Record<string, unknown>;
        };
    }
    Index
    VERSION?: string

    Orchestrator version tag, set by ui-api.ts.

    BUILD?: string

    Build tag, set by ui-api.ts.

    init?: (
        options?: {
            map?: unknown;
            mapContainer?: HTMLElement;
            filterContainer?: HTMLElement;
            buttonSelector?: string;
            autoInitOnDomReady?: boolean;
            enableEventDelegation?: boolean;
            config?: unknown;
        },
    ) => unknown

    Boots the UI kernel (delegation, theme, containers).

    cleanup?: () => void

    Tears listeners down and resets the delegation flag.

    getModuleStatus?: () => unknown

    Availability report for the UI sub-modules.

    applyTheme?: (...args: unknown[]) => unknown

    Applies a theme by name.

    Sets the theme classes on <body> and #geoleaf-map, then dispatches geoleaf:ui-theme-changed. ⚠️ Always prefer this over touching the CSS classes directly — a manual classList.add("gl-theme-dark") skips the event, so anything listening for the change never learns of it.

    GeoLeaf.UI.applyTheme("dark");
    
    setTheme?: (...args: unknown[]) => unknown

    Alias of GeoLeafUIFacade.applyTheme — the SAME reference, by design.

    getCurrentTheme?: () => string

    The theme currently applied.

    const theme = GeoLeaf.UI.getCurrentTheme(); // "dark"
    
    initAutoTheme?: (...args: unknown[]) => unknown

    Applies the theme declared in the profile configuration. Called during boot.

    initThemeToggle?: (...args: unknown[]) => unknown

    Wires a theme-toggle button, located by the configured buttonSelector.

    toggleTheme?: (...args: unknown[]) => unknown

    Switches to the other theme and applies it.

    GeoLeaf.UI.toggleTheme(); // switches to "light"
    
    Notifications?: Record<string, unknown>

    The notification renderer, reachable through the UI façade.

    ⚠️ Typed Record<string, unknown>, so nothing here is arity-checked — the real surface is the one documented on NotificationSystem. The unknown-typed remainder only ever shrinks — never widened back.

    GeoLeaf.UI.Notifications.success("Données chargées !");
    GeoLeaf.UI.Notifications.error("Erreur réseau", 8000);
    showNotification?: (...args: unknown[]) => unknown

    Shows a toast of an explicit type. Bound form of Notifications.show.

    showSuccess?: (...args: unknown[]) => unknown

    Shows a success toast. Bound form of Notifications.success.

    showError?: (...args: unknown[]) => unknown

    Shows an error toast. Bound form of Notifications.error.

    showWarning?: (...args: unknown[]) => unknown

    Shows a warning toast. Bound form of Notifications.warning.

    showInfo?: (...args: unknown[]) => unknown

    Shows an informational toast. Bound form of Notifications.info.

    clearNotifications?: (...args: unknown[]) => unknown

    Dismisses every visible toast and empties the queue.

    notify?: {
        info?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
        warn?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
        error?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
        success?: (msg: string, opts?: number | Record<string, unknown>) => unknown;
        dismiss?: (id: HTMLElement) => unknown;
    }

    Rich toast surface — the KERNEL one, mounted by globals/globals.ui.ts.

    Distinct from GeoLeafUIFacade.Notifications: notify is the anchor-B2 adapter that reads the renderer back lazily, so a build without the toast-renderer capability degrades to a silent no-op instead of throwing.

    ⚠️ Optional chaining is not cosmetic here: without a writer the member is absent, and GeoLeaf.UI.notify.success(…) throws. It is also what avoids adding a baseline entry to typecheck-docs-examples.

    GeoLeaf.UI.notify?.success?.("Couche enregistrée");
    
    initMobileToolbar?: (options: MobileToolbarOptions) => void

    Mounts the mobile toolbar. Set by setupUIKernel() in globals/globals.ui.ts.

    Type Declaration

      • (options: MobileToolbarOptions): void
      • Initializes the mobile utility pill toolbar and sheet modal.

        Must be called after the map and .gl-main DOM are ready. Safe to call again: a toolbar already mounted in the same glMain is left alone, and one left over from a previous shell is torn down first.

        ⚠️ On the no-op branch options is NOT refreshed — a new map handle or new show* flags passed to a redundant call are ignored. initDesktopPanel behaves the same way. Pass through destroyMobileToolbar() to change them.

        Parameters

        Returns void

        GeoLeaf?.UI?.initMobileToolbar({ glMain: document.querySelector(".gl-main")! });
        
    destroyMobileToolbar?: () => void

    Tears the mobile toolbar down. Set by setupUIKernel().

    Type Declaration

      • (): void
      • Tears the mobile toolbar down: removes the four nodes it appended to glMain, disconnects its observer and drops its listeners.

        Registered as a lifecycle teardown, so Core.destroy() runs it. Also called by initMobileToolbar() when it finds a toolbar left over from a previous shell.

        ⚠️ Call it BEFORE the shell is destroyed, not after. The sheet moves shared nodes (filter panel, legend, layer manager) into its body; this function gives them back, but it can only give them back to a parent that still exists. A host that drops .gl-main first has already lost them.

        ⚠️ Proximity mode is NOT cancelled. Clearing it would mean calling back into the filter capability — a business effect on a teardown path — so the engine keeps whatever filter was applied while the pill's own flag resets. A host that tears down mid-proximity should cancel it itself first.

        Returns void

        GeoLeaf?.UI?.destroyMobileToolbar();
        
    initDesktopPanel?: (options: DesktopPanelOptions) => void

    Mounts the desktop side-panel. Set by setupUIKernel().

    Type Declaration

      • (options: DesktopPanelOptions): void
      • Builds the side panel DOM (>= 1440px). Does NOT move any element in — call activateDesktopPanel() afterwards.

        Parameters

        • options: DesktopPanelOptions

        Returns void

    activateDesktopPanel?: () => void

    Reveals the desktop side-panel. Set by setupUIKernel().

    Type Declaration

      • (): void
      • Moves the filter panel, the layer manager and the legend into their tabs of the side panel.

        Any of the three built after this call is adopted as soon as it appears, so the call need not follow Legend.init() or LayerManager.init(). A no-op before initDesktopPanel() and below the desktop breakpoint, whose crossing activates the panel on its own.

        Returns void

    destroyDesktopPanel?: () => void

    Tears the desktop side-panel down. Set by setupUIKernel().

    Type Declaration

      • (): void
      • Destroys the side panel and restores the elements to their original place.

        Returns void

    openPanel?: (tabId: string) => boolean

    Opens a side-panel tab by id, without toggling — calling it twice with the same id leaves the panel open.

    ⚠️ This is what separates it from a click on the tab, which closes an already-open tab. Set by setupUIKernel().

    Type Declaration

      • (tabId: string): boolean
      • Opens a side-panel tab by id, without toggling.

        Calling it twice with the same id leaves the panel open — that is exactly what separates it from a click on the tab, which closes an already-open tab. Use it to drive the panel from host code (a menu entry, a deep link, a workflow step).

        Parameters

        • tabId: string

          Tab to open: "filters", "layers" or "legend", depending on what the profile enabled at initDesktopPanel().

        Returns boolean

        true if the tab exists and is now open, false if the panel is not built, the desktop breakpoint is not active, or no such tab was declared.

        GeoLeaf?.UI?.openPanel("layers"); // true
        GeoLeaf?.UI?.openPanel("layers"); // true — still open, never a toggle
    GeoLeaf?.UI?.openPanel("layers"); // true
    GeoLeaf?.UI?.openPanel("layers"); // true — not a toggle
    closePanel?: () => void

    Closes whichever side-panel tab is open. A no-op when none is.

    Type Declaration

      • (): void
      • Closes whichever side-panel tab is open. A no-op when none is.

        Returns void

        GeoLeaf?.UI?.closePanel();
        GeoLeaf?.UI?.getOpenPanel(); // null
    GeoLeaf?.UI?.closePanel();
    
    getOpenPanel?: () => string | null

    The id of the open side-panel tab, or null when closed or not built.

    Type Declaration

      • (): string | null
      • The id of the currently open tab, or null when the panel is closed or not built.

        ⚠️ The open state has no dedicated variable: it lives in the DOM, as the gl-is-active class on the tab button. This reads it back rather than shadowing it, so the value cannot drift from what the user sees.

        Returns string | null

        GeoLeaf?.UI?.openPanel("legend");
        GeoLeaf?.UI?.getOpenPanel(); // "legend"
    GeoLeaf?.UI?.openPanel("legend");
    GeoLeaf?.UI?.getOpenPanel(); // "legend"
    registerPanelPane?: (pane: PanelPane) => void

    Offers a panel surface to whichever host is live — the desktop side panel above 1440px, the mobile sheet below it.

    The host ADOPTS the element named by selector: it moves the node, and puts it back where it came from on close. Register before or after boot; a registration arriving late is synced into an already-built panel.

    Type Declaration

      • (pane: PanelPane): void
      • Registers a pane, or replaces the one already under that id.

        Idempotent by id, because a module may re-register across a destroy/recreate cycle and the second registration must not double the tab.

        Parameters

        • pane: PanelPane

          The pane to offer to the hosts.

        Returns void

    GeoLeaf?.UI?.registerPanelPane({
    id: "routing",
    labelKey: "routing.toolbar.button",
    selector: ".gl-routing-panel",
    });
    openPane?: (paneId: string) => boolean

    Opens a registered pane on whichever host is live.

    ⚠️ Prefer this to GeoLeafUIFacade.openPanel from a plugin: openPanel drives the DESKTOP panel and answers false below its breakpoint, where the same content belongs in the mobile sheet. A caller reacting to a click on a feature cannot be asked to know which surface the current width implies.

    Type Declaration

      • (paneId: string): boolean
      • Opens a registered pane on whichever host is live.

        This is the entry point a plugin calls: it does not know, and must not have to know, whether the current width puts its content in the desktop panel or in the mobile sheet.

        Parameters

        • paneId: string

          The pane to show.

        Returns boolean

        true when a host displayed it, false when none could.

    GeoLeaf?.UI?.openPane("routing"); // true at any width
    
    closePane?: () => void

    Closes the pane on every live host.

    Type Declaration

      • (): void
      • Closes the pane on every live host.

        Returns void

    setImmersive?: (on: boolean, options?: { fullscreen?: boolean }) => void

    Turns the immersive UI mode on or off — strips the chrome the kernel owns, and optionally asks for browser fullscreen.

    Type Declaration

      • (on: boolean, options?: { fullscreen?: boolean }): void
      • Turns the immersive UI mode on or off.

        The mode strips the chrome the kernel owns — theme bars, filter entry point, right panel, proximity bar — so a caller that needs the whole surface for one task can have it without knowing which controls exist.

        Parameters

        • on: boolean

          Whether the document should be immersive.

        • options: { fullscreen?: boolean } = {}

          fullscreen also asks the browser for fullscreen — best-effort, a refusal never propagates, because a caller that entered immersive mode has still entered it. See the note on ownership: an exitFullscreen only happens when this module was the one that entered.

          ⚠️ The shape is inlined rather than a named exported interface: nothing outside needed to name it, and an exported type nobody imports is indistinguishable from one left behind after a deletion.

        Returns void

    GeoLeaf?.UI?.setImmersive(true, { fullscreen: true });
    
    isImmersive?: () => boolean

    Whether the document is currently in immersive mode.

    Type Declaration

      • (): boolean
      • Whether the document is currently in immersive mode.

        Read back from the DOM rather than from a variable: the class IS the state, so the answer cannot drift from what the user sees.

        Returns boolean

        true while the immersive class is set.

    CacheButton?: {
        init?: (map: unknown, cfg: Record<string, unknown>) => unknown;
        openModal?: () => unknown;
        closeModal?: () => unknown;
        Modules?: Record<string, unknown>;
    }

    Offline cache button — mounted by the offline-ui PLUGIN (ui/cache-button.ts), read by app/boot-modules/ui.module.ts.

    ⚠️ Typed STRUCTURALLY on purpose. The no-plugin-in-core rule (scripts/verify-core-standalone.cjs) forbids the core to name @geoleaf-plugins/*: a typeof import(...) is unavailable here, and that is not a shortcut but the architecture boundary. The member stays optional — a build without the plugin has no writer.