DOCS / UI REFERENCE
Events & gestures
The RootView hit-tests every mouse event into the shadow tree with DOM-style bubbling, maintains hover chains, and applies hover:/active: style layers natively — no JS round-trip for a hover repaint.
Pointer events
| PROP | PAYLOAD | NOTES |
|---|---|---|
onClick | — | Fires on release over the pressed node. |
onDoubleClick | — | The DAW reset gesture — built-in controls use it for reset-to-default. |
onWheel | {dy} | Controls get first refusal on the wheel; scroll containers keep it otherwise. dy is the notch fraction (~0.1/notch). |
onMouseEnter / onMouseLeave | — | Hover chain, parent-to-child, like the DOM. |
onMouseDown / onMouseUp | — | Drive active: styles. |
onDragStart / onDrag / onDragEnd | {dx, dy, x, y} | Deltas from the drag origin plus the live pointer position, in root coordinates — everything a knob or an XY pad needs. |
Drag gestures
The built-in knob maps vertical drag to value with dragToValue(startValue, dy). Rolling your own control is a few lines:
function Fader({ value, onChange }) {
const start = useRef(0);
return (
<View
className="w-10 h-40 bg-zinc-900 rounded-lg cursor-pointer"
onDragStart={() => { start.current = value; }}
onDrag={(e) => onChange(clamp01(start.current - e.dy / 160))}
>
<View
className="absolute left-0 right-0 bottom-0 bg-lime-400 rounded-lg"
style={{ height: `${value * 100}%` }}
/>
</View>
);
}Scroll containers
Give a View overflow-y-scroll and a bounded height: children lay out at full size, the mouse wheel scrolls, the painter clips and draws a thumb. Set the scrollTop prop to reset the offset programmatically (say, when switching tabs).
<View className="flex-1 overflow-y-scroll gap-2 p-3" scrollTop={0}>
{items.map((item) => <Row key={item.id} item={item} />)}
</View>Layout feedback — onLayout
Layout happens in C++ (Yoga), so JS normally never knows where anything landed. onLayout closes the loop: it fires with the node’s root-space rect (scroll-adjusted) whenever layout moves or resizes it — and only when the rect actually changes. This is the foundation for menus, tooltips, and popovers.
import { useLayoutRect, useOverlay } from "@vsreact/core";
function InfoTip({ children }) {
const [rect, onLayout] = useLayoutRect();
const overlay = useOverlay();
return (
<View
onLayout={onLayout}
onMouseEnter={() => rect && overlay.show(
<View className="absolute rounded-lg bg-zinc-800 px-3 py-2"
style={{ left: rect.x, top: rect.y + rect.height + 4 }}>
<Text className="text-[11]">{children}</Text>
</View>
)}
onMouseLeave={() => overlay.hide()}
>
<Text className="text-faint">?</Text>
</View>
);
}useLayoutRect()—[rect, onLayout]sugar for capturing the rect in state.useOverlay()— a slot in the overlay layer, whichrender()mounts after your app automatically so overlay content paints on top and receives input first.show(node)/hide(); unmount cleans up.- The built-in
<Select>is built entirely from these parts.
Cursors
Cursors come from classes — cursor-pointer, cursor-text, cursor-default — applied per-node by the hit-tester as the mouse moves.