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();
    });
  });

});