Controls
A spec can declare controls: a slider, a toggle or a select. The library draws the inputs next to the picture and redraws when one changes. Inputs are ordinary labelled form controls, so they work with a keyboard, and every change is announced to a screen reader once the reader stops moving.
{
"version": 1,
"seed": 7,
"canvas": { "width": 640, "height": 400 },
"controls": [
{ "id": "dots", "type": "slider", "label": "Stars", "min": 10, "max": 600, "step": 10, "value": 200 },
{ "id": "horizon", "type": "toggle", "label": "Show the horizon", "value": true },
{ "id": "tint", "type": "select", "label": "Star colour", "options": ["accent", "muted", "fg"], "value": "muted" }
],
"elements": [
{ "type": "random-dots", "count": { "control": "dots" }, "fill": { "control": "tint" }, "radius": [0.6, 1.8] },
{ "type": "line", "points": [[0, 0.8], [1, 0.8]], "stroke": "accent", "weight": 1.5, "when": "horizon" }
]
}
| Type | Fields | Value |
|---|---|---|
slider | min, max, step, value | A number from min to max, snapped to the step. |
toggle | value | true or false. |
select | options (2 to 12 strings), value | One of the options. |
Every control has an id (lowercase letters, digits and hyphens), a label of up to 80 characters, and a type from slider, toggle, select. A spec can hold up to 12.
A field follows a control by naming it. The control's whole range must fit the field, so a coordinate can only follow a slider that runs inside 0 to 1. The validator says so when it does not.
| Field | Follows |
|---|---|
x, y, w, h, r | a slider inside 0 to 1 (r on a scatter is pixels, up to 100) |
weight | a slider inside 0 to 64 |
count | a whole-number slider with a minimum of 1 |
seed | a whole-number slider from 0 to 4294967295 |
fill, stroke | a select whose options are all theme tokens |
theme | a select whose options are light and dark |
when on an element | a toggle: the element is hidden while it is off |
Pass a shareKey to mount (or a share-key attribute) and the values that differ from the defaults are written to the address bar as one parameter, p5loom-<key>. Opening that link draws the same picture. A hand-edited value is checked against the control's range and step, so a link can never set a value the spec would refuse.
import { mount } from "p5loom";
const handle = mount(element, spec, {
p5,
shareKey: "revenue", // optional: put the values in the address bar
onChange: (values, id) => save(values), // optional
});
handle.set("dots", 400); // moves the slider, redraws, announces, updates the link
handle.values(); // { dots: 400, horizon: true, tint: "muted" }
handle.plan(); // the drawing plan for the current values
handle.destroy();
Each change also dispatches a p5loom-change event on the picture, with the values as its detail.
The picture is a function of the spec and the control values, and nothing else. Values are snapped to the step and clamped to the range. Two readers with the same spec and values see the same picture, and an AI client can reproduce any state a person left the page in.