Add a colour-wheel widget to the dashboard

A custom hue ring — a conic gradient, not a canvas — with saturation and
brightness sliders beside or under it depending on the tile's shape, sized
for a wall panel and reachable from a keyboard. It publishes [h, s, v] by
default, which is what the reference installation's DMX encoders read, and
`format` switches that to [r, g, b] or "#rrggbb".

`usePublish` moves to its own module so a widget in a file of its own can
reach it without importing `widgets.tsx` back.
This commit is contained in:
2026-08-22 12:50:59 +02:00
parent 3cc2d353cd
commit 9927577cec
13 changed files with 804 additions and 101 deletions
+37
View File
@@ -303,6 +303,43 @@ def test_a_querying_chart_asks_with_a_record_and_draws_a_series():
WidgetDef(id="c", type="chart", config=query_chart(request_dtype="json"))
def test_a_colour_widget_publishes_the_shape_its_format_names():
"""The format picks the payload, so the binding is held to that one."""
wheel = WidgetDef(
id="lamp",
type="color",
config={"target": "a.color", "dtype": "list", "format": "hsv"},
)
assert wheel.target == "a.color"
# It publishes rather than reads: nothing is drawn from a message.
assert wheel.messages == []
# Hex is the same widget speaking a string, and rgb is still a list.
WidgetDef(
id="l", type="color", config={"target": "a.c", "dtype": "str", "format": "hex"}
)
WidgetDef(
id="l", type="color", config={"target": "a.c", "dtype": "list", "format": "rgb"}
)
# Nothing recorded binds anything, as everywhere else.
WidgetDef(id="l", type="color", config={"target": "a.c"})
with pytest.raises(ValueError):
WidgetDef(
id="l",
type="color",
config={"target": "a.c", "dtype": "str", "format": "hsv"},
)
with pytest.raises(ValueError):
WidgetDef(
id="l",
type="color",
config={"target": "a.c", "dtype": "list", "format": "hex"},
)
with pytest.raises(ValueError):
WidgetDef(id="l", type="color", config={"target": "a.c", "dtype": "float"})
def test_a_querying_chart_keeps_no_ring():
"""The answer carries its own past; a ring would store it twice."""
widget = WidgetDef(