Numbers
Four ways to edit a number — or two, or four of them at once. Sliders and number
fields are built in; interval and point are heavy controls
that load on first use.
Slider
The workhorse. [value, min, max, step] as shorthand, or
{ type: "slider" } for options. Drag anywhere on the track, scrub with
arrow keys (⇧ for coarse steps), or hover the value and click to type
(double-click resets). With six or fewer stops the track snaps and shows rule
lines.
const tile = target.querySelector(".num-tile");
const panel = tweaks("Slider", {
radius: [16, 0, 66, 1], // continuous: smooth square → circle
size: [4, 2, 6, 1], // ≤6 stops: snaps, shows rule lines
});
mount.append(panel.el);
const apply = (p) => {
tile.style.borderRadius = `${p.radius}px`;
tile.style.transform = `scale(${0.55 + p.size * 0.11})`;
};
panel.on(apply);
apply(panel.params);mount is the panel's slot inside the stage; target is the demo surface it controls. In your own page you'd just document.body.append(panel.el).
Number
{ type: "number" } is a plain numeric field with a
drag-to-scrub grab handle — for values where a track makes no sense. Drag the
handle to scrub it, click the field to type. min/max
clamp; omit them for unbounded.
const card = target.querySelector(".num-rotor-card");
const panel = tweaks("Number", {
angle: { type: "number", value: 12, min: -180, max: 180, step: 1 },
});
mount.append(panel.el);
const apply = (p) => {
card.style.transform = `rotate(${p.angle}deg)`;
card.textContent = `${p.angle}°`;
};
panel.on(apply);
apply(panel.params);Interval
A dual-handle range. Shorthand [[lo, hi], min, max, step?] —
the first entry being a 2-tuple is what marks it as an interval. The value on
params is a [lo, hi] pair; here it gates which bars count
as “in range”.
const strip = target.querySelector(".num-bars");
const heights = Array.from({ length: 28 }, (_, i) => 12 + 84 * Math.abs(Math.sin(i * 0.6 + 1)));
const bars = heights.map((h) => {
const b = document.createElement("div");
b.className = "num-bar";
b.style.height = `${h}%`;
strip.append(b);
return b;
});
const panel = tweaks("Interval", {
band: [[30, 70], 0, 100, 1], // [[lo, hi], min, max, step]
});
mount.append(panel.el);
const apply = (p) => bars.forEach((b, i) =>
b.classList.toggle("num-bar-hot", heights[i] >= p.band[0] && heights[i] <= p.band[1]));
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // interval loads lazilyPoint
An n-dimensional vector — one scrubbable field per component, plus a
draggable 2D pad for the first two (on by default; pad: false opts
out). The value is a plain map of component keys. Add a third component for 3D, a
fourth for 4D; the pad stays 2D, the fields stack.
const dot = target.querySelector(".num-dot");
const panel = tweaks("Point", {
offset: { type: "point", pad: true, components: [
{ key: "x", label: "X", value: 0, min: -1, max: 1, step: 0.01 },
{ key: "y", label: "Y", value: 0, min: -1, max: 1, step: 0.01 },
] },
});
mount.append(panel.el);
const apply = (p) => {
dot.style.left = `${50 + p.offset.x * 45}%`;
dot.style.top = `${50 + p.offset.y * 45}%`;
};
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // point loads lazily