PURPOSE
Controlled React widget that edits a four-vector cosine palette (Palette4) used by the VFX workbench. Renders a live gradient preview strip plus inputs for the three RGB channels (a, b, c) and the three phase axes (d).
OWNS
- Local color-input handling (
setChannel) that converts hex toVec3and replaces the indexed channel in the palette tuple. - Local phase-input handling (
setPhase) that mutates a copy of thedvector by axis and reassembles thePalette4tuple. - A memoized CSS gradient string built from
STRIP_STOPS(32) evenly spaced samples ofevalPalette(value, t). - Inline styles for the section divider, label row, color pickers, phase number inputs, and preview strip.
READS FROM
value: Palette4prop — the current palette tuple[a, b, c, d].@engine/vfx-workbench/palette—Palette4,Vec3,vec3ToHex,hexToVec3,evalPalette.react—useMemo.
PUSHES TO
onChange(next: Palette4)prop — emits a new palette tuple whenever a color channel or phase axis changes.
DOES NOT
- Hold internal state for the palette (fully controlled by the parent via
value/onChange). - Validate or clamp phase numeric inputs beyond the native
min={0},max={1},step={0.01}attributes. - Surface errors from malformed hex input; the
setChanneltryblock silently ignores invalid hex during typing. - Render any label or title other than the static “PALETTE (cosine)” header.
- Persist the palette anywhere; persistence is the parent’s concern.
Signals
data-testid="palette-preview-strip"— the gradient preview div.data-testid="palette-color-a" | "palette-color-b" | "palette-color-c"— the three hex color inputs.data-testid="palette-phase-0" | "palette-phase-1" | "palette-phase-2"— the three phase-axis number inputs.
Entry points
- Default named export
PaletteWidget({ value, onChange }). - Mounted by parent VFX-workbench panes that hold a
Palette4and want a compact editor for it.
Pattern notes
- Fully controlled component — every keystroke / picker change calls
onChangewith a freshly assembledPalette4tuple; no internaluseState. - Channel updates use
Array.prototype.mapwith an index check to build the next tuple immutably, then cast toPalette4. - Phase updates spread the existing
dvector, mutate one axis, and reassemble the full tuple immutably. - Gradient is computed via
useMemokeyed onvalue, samplingevalPaletteat 32 stops and emitting alinear-gradient(to right, ...)CSS string. - Color inputs use
onInput(notonChange) so the parent sees updates while the user drags the native picker. - Phase inputs use
onChangewithparseFloat; non-numeric or empty input producesNaN, which is forwarded to the parent without local guarding. - Layout uses a two-column
grid(auto 1fr) with adisplay: contentswrapper to keep each label/input pair on the same row, plus a nested 3-column grid for the phase inputs. - Styling is inline only — no class names, no external CSS, no theming hooks; colors and fonts are hard-coded (
#222,#888,#aaa,#ddd,#0a0a12,Cal Sans,monospace).