import { useLayoutEffect, useMemo, useState, type ReactNode } from 'react' import { LegendContext, createLegendStore, getLegendStore, hasLegendStore, registerLegendStore, setLegendStoreEntry, unregisterLegendStore, } from '../stores' import type { LegendGroupInput, LegendLayerInput, LegendStoreApi, } from '../stores' import { LegendConfigContext, type LegendConfig } from './legend-config-context' import { DEFAULT_LEGEND_LABELS, type LegendLabels } from './labels' export interface LegendProviderProps { /** Unique id for this legend panel — keys its store. */ id: string /** * Layer definitions. Interactive fields (visible/opacity/…) are seeded. * Pass stable references — `layers`, `groups`, and each layer's `variables` * should be memoized or module constants (same contract as widgets-v2 * `data`); the store bails out of unchanged syncs by reference. */ layers: LegendLayerInput[] /** Optional group definitions for grouping layers. */ groups?: LegendGroupInput[] /** Override any subset of the legend's user-facing strings. */ labels?: Partial /** * When `true` (the default), the per-panel store survives unmount so * visibility/opacity/order are preserved across remounts — the common case * for a legend that toggles in and out of view. Consumers must call * `deleteLegendStore(id)` when the legend is permanently removed. Set to * `false` to tear the store down on unmount. */ keepAlive?: boolean children: ReactNode } /** * Legend panel shell. Creates a per-panel Zustand store keyed by `id`, syncs * the consumer's `layers`/`groups` definitions onto it (preserving interactive * state), and exposes the `id` via `LegendContext` so descendants resolve their * store with `useLegendId()`. Labels are surfaced via `LegendConfigContext`. * * Modeled on the widgets-v2 `Widget.Provider`. * * @experimental This API is new and may change in a future release. */ export function LegendProvider({ id, layers, groups, labels, keepAlive = true, children, }: LegendProviderProps) { // Lazy init — reuse an existing keepAlive store, otherwise create one and // synchronously place it in the registry so descendants can call // useLegendStore(id, ...) on first render. const [store] = useState(() => { if (hasLegendStore(id)) return getLegendStore(id) const created = createLegendStore(id, { layers, groups }) setLegendStoreEntry(id, created) return created }) useLayoutEffect(() => { registerLegendStore(id, store) return () => { unregisterLegendStore(id, { keepAlive }) } }, [id, store, keepAlive]) // Sync definitions in. The store's `_sync` preserves interactive state of // known layers/groups and bails out (no state change) when the merged result // is reference-identical, so stable props are a no-op. useLayoutEffect(() => { store.getState()._sync(layers, groups ?? []) }, [store, layers, groups]) const config = useMemo( () => ({ labels: labels ? { ...DEFAULT_LEGEND_LABELS, ...labels } : DEFAULT_LEGEND_LABELS, }), [labels], ) return ( {children} ) }