// PresetControlPanel: the thin, controlled editor. For the active preset it // renders that preset's `controls` component (keyed by preset id, so a switch // remounts and the ControlForms re-seed), handing it the per-layer baked // uniforms and a single shared onPatch. It never calls `kaleidoscope` itself; // the host routes onPatch into `kaleidoscope(activeId, [patch])`. import type { ReactElement } from 'react'; import type { KaleidoscopeControls, KaleidoscopePresetBook, } from '../../kaleidoscope.preset-book.types'; import { ControlScopeContext } from '../form/scope'; export type PresetControlPanelProps

= { readonly presets: P; /** The active preset id, or null when nothing is selected. */ readonly value: (keyof P & string) | null; /** Routed to the host, which applies it via `kaleidoscope(value, [patch])`. */ readonly onPatch: KaleidoscopeControls['onPatch']; /** * Per-layer uniform overrides (e.g. a persisted selection's stored patches) * merged over the preset's baked uniforms when the forms seed. Seed-time * only: the forms re-seed on a preset switch, not when this prop changes * mid-mount, so hosts restoring persisted patches should mount the panel * after hydration. */ readonly patches?: KaleidoscopeControls['uniforms']; readonly disabled?: boolean; }; export function PresetControlPanel

({ presets, value, onPatch, patches, disabled = false, }: PresetControlPanelProps

): ReactElement | null { if (value === null) return null; const preset = presets[value]; const Controls = preset?.controls; if (!Controls) return null; // Per-layer baked uniforms keyed by id, for the controls component to seed each // layer's ControlForm. Only tunable layers carry uniforms; stored overrides // merge over the baked values so restored tweaks appear in the forms. const uniforms: Record> = {}; for (const layer of preset.layers) { if ('uniforms' in layer) { uniforms[layer.id] = { ...layer.uniforms, ...patches?.[layer.id], } as Record; } } return ( ); }