diff --git a/lib/ui/input_widgets_test.go b/lib/ui/input_widgets_test.go index bc458fbc..6f30a87d 100644 --- a/lib/ui/input_widgets_test.go +++ b/lib/ui/input_widgets_test.go @@ -179,6 +179,16 @@ func TestInputFloatWidgets(t *testing.T) { mustMatch(t, `^$`, got) } +// TestRangeRendersExplicitBounds guards the documented workaround for the browser +// range defaults: min, max and step passed as params render verbatim after the +// value, letting a caller widen a domain the default 0..100 would otherwise clamp. +func TestRangeRendersExplicitBounds(t *testing.T) { + _, rq := newCoreRequest(t) + sf := newTestSetter(150.0) + _, got := renderUI(t, rq, NewRange(sf), `min="0"`, `max="200"`, `step="0.5"`) + mustMatch(t, `^$`, got) +} + // TestInputFloat_ReconcilesConvertedValues verifies that number and range inputs // receive the canonical server value when a numeric adapter truncates or rounds // an input to the value that was already stored. diff --git a/lib/ui/range.go b/lib/ui/range.go index ab728014..753163f8 100644 --- a/lib/ui/range.go +++ b/lib/ui/range.go @@ -19,6 +19,13 @@ type Range struct{ InputFloat } // rendering or wire representation, so rendering, updating, or receiving one from // the browser cancels the [jaws.Request] with a cause wrapping // [jaws.ErrValueNotFinite]. +// +// The widget emits no min, max, or step attribute of its own, so the browser +// applies the HTML range defaults min="0", max="100", step="1". A bound value +// outside that domain, or off the step grid, is silently clamped and rounded by +// the browser, and the adjusted value is echoed back through the setter on the +// next input event. Supply explicit min, max, and step attributes as render +// params when the defaults do not cover the bound value's domain. func NewRange(g bind.Setter[float64]) *Range { return &Range{InputFloat{Setter: g}} } // JawsRender renders ui as an HTML range input. @@ -28,7 +35,13 @@ func (u *Range) JawsRender(elem *jaws.Element, w io.Writer, params []any) error // Range renders an HTML range input. // -// See [NewRange] for how a non-finite bound value is handled. +// Supply min, max, and step attributes in params when the browser defaults +// (min="0", max="100", step="1") do not cover the bound value's domain; a value +// outside it is silently clamped and rounded by the browser. For example: +// +// rw.Range(&v, `min="0"`, `max="200"`, `step="0.5"`) +// +// See [NewRange] for details and for how a non-finite bound value is handled. func (rw RequestWriter) Range(value any, params ...any) error { return rw.NewUI(NewRange(bind.MakeSetterFloat64(value)), params...) }