Button.js
The complete source of src/Button.js, exactly as it ships, in reading order. Every line is present; the file is split at its own section banners.
Button.js - buttons that write to state on click
// ============================================================================
// Button.js - buttons that write to state on click
// ============================================================================
//
// A button is a click wired to a state write, built on the Dom.Tag DSL. Each function
// declares its inputs with Reactive.Argument.
//
// Button.Set(settable, newValue, content) click: settable <- newValue
// Button.Integer.Increment(value, content) click: value <- value + 1
// Button.Integer.Decrement(value, content) click: value <- value - 1
// Button.Select(selected, body) a one-of-many group bound to `selected`
// Button.Select.Option(value, content) one option in that group
//
// There is NO separate "command"/action button. An action (Save, Undo, ...) is just a
// Button.Set that writes a value into a Reactive.Reverse sink; the reverse fires and does the
// work. A reverse holds no persisted value, so the signal is momentary by construction: it
// reads back as its reversible default next run, with nothing to clear.
//
// Button.Select is the one-of-many group. Its `Option` is an INNER pseudo-module function
// defined inside Select's body, so it captures `selected` by ordinary closure (no ambient
// context): an Option sets `selected` to its value on click and marks itself active while
// selected == value. That is the settled replacement for the old Context.Provide/Use.
// ----------------------------------------------------------------------------
Reactive.Module("Button", () => {
// Set - a button that writes a fixed `newValue` into `settable` when clicked. `content`
// builds the face. Renders: <button> ...content... </button>, click -> settable <- newValue.
Reactive.Function("Set", () => {
const settable = Reactive.Argument(Reactive.Any);
const newValue = Reactive.Argument(Reactive.Any);
const content = Reactive.Argument(Reactive.Callback());
Dom.Tag("button", () => {
Dom.Event.Set("click", settable, newValue);
content();
});
});
// Integer - buttons that step a whole-number node by one.
Reactive.Module("Integer", () => {
// Increment - add one to `value` when clicked. `content` builds the face. The stepped
// value is a reactive derivation of `value`, so clicking Sets `value <- value + 1`.
Reactive.Function("Increment", () => {
const value = Reactive.Argument(Maths.Integer);
const content = Reactive.Argument(Reactive.Callback());
const next = Reactive.Calculate(() => Reactive.Value(value) + 1);
Button.Set(value, next, content);
});
// Decrement - subtract one from `value` when clicked.
Reactive.Function("Decrement", () => {
const value = Reactive.Argument(Maths.Integer);
const content = Reactive.Argument(Reactive.Callback());
const next = Reactive.Calculate(() => Reactive.Value(value) - 1);
Button.Set(value, next, content);
});
});
// Select - a one-of-many button group bound to `selected`. `body` declares the options
// (Button.Select.Option) and any Content.Title separators. Option is defined inside here
// so it captures `selected` by closure. Renders: <div class="select"> ...body... </div>.
Reactive.Function("Select", () => {
const selected = Reactive.Argument(Reactive.Any);
const body = Reactive.Argument(Reactive.Callback());
// Option - one button in this Select. Captures the enclosing `selected`: sets it to
// `value` on click; marks itself active (aria-selected) while selected == value.
Reactive.Function("Option", () => {
const value = Reactive.Argument(Reactive.Any);
const content = Reactive.Argument(Reactive.Callback());
const active = Text.Equal(selected, value);
Button.Set(selected, value, () => {
Dom.Attribute("aria-selected", active);
content();
});
});
Dom.Tag("div", () => {
Dom.Attribute("class", "select");
body();
});
});
});