// 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