s&box/field-guide
the symptom, in your words

"Building a draggable slider with click-to-jump and drag-to-scrub"

✓ verified on engine 26.07lane: Building UIposted
▸ SYMPTOM

You need a slider that supports both click-to-jump (click anywhere on the track to set the value) and drag-to-scrub (hold and drag to continuously update) in a PanelComponent, but there's no built-in drag helper you can drop into a RenderFragment.

▸ CAUSE

The engine's UI system doesn't expose a high-level drag API for Razor panels. MousePanelEvent has no .Type or event-name field, so you can't distinguish mousedown from mousemove by inspecting the event object itself. You need a different approach to branch between jump and scrub behavior.

▸ FIX

The engine ships the exact pattern in its own SliderControl.razor (and ColorHueControl.cs) under addons/base/code/UI/Controls/. The approach:

Value calculation: frac = Clamp(LocalPosition.x / This.Box.Rect.Width, 0, 1) — mouse position relative to the track panel, normalized to 0–1.

Jump vs scrub branching: since MousePanelEvent has no event-type field, pass a bool per binding:

  • onmousedown → always act (this is the jump)
  • onmousemove → act only if This.PseudoClass.HasFlag(PseudoClass.Active) (true while pressed — this is the scrub)

Snap to step: MathF.Round(v / step) * step or the engine's float.SnapToGrid(step).

Key SCSS rules for the track:

snippet
.track {
  pointer-events: all;
  height: 14px;  /* tall enough to be grabbable */
}

.fill {
  pointer-events: none;  /* events always target the track */
}

Setting pointer-events: none on the fill ensures events always target the track, keeping LocalPosition track-relative. Also drop any transition: width on the fill or scrub will visually lag behind the cursor.

A bare click automatically works as jump-to-position with this design — no special handling needed.

▸ WHY IT WORKS

By routing all pointer events to the track (via pointer-events: none on the fill), LocalPosition is always relative to the track panel. The PseudoClass.Active flag — which is true while the mouse button is held down — provides the scrub gate without needing to identify event types. This matches the engine's own slider implementation and avoids inventing a custom drag state machine.

Verified on engine 26.07 — seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?