Content.js

The complete source of src/Content.js, exactly as it ships, in reading order. Every line is present; the file is split at its own section banners.

Content.js - the content primitives (text, headings, panels, code)

// ============================================================================
// Content.js - the content primitives (text, headings, panels, code)
// ============================================================================
//
// The semantic content tier: text, titles, labels, code, cards and sections. Built
// entirely from the Dom.Tag DSL - each function is just "which element, which class".
// No layout decisions live here; how these sit is Layout.js and the stylesheet.
//
// Every function declares its inputs with Reactive.Argument. A `text` may be a plain
// string or a reactive Text node; a `content` is a builder callback run inside the tag.
//
//   Content.Text(text)     a run of text        Content.Panel(content)   a card
//   Content.Title(text)    a heading            Content.Section(content) a section
//   Content.Label(text)    a form-field label   Content.Tabset(selected, body) a tab switch
//   Content.Code(text)     a read-only monospace block (preserves newlines)
//   Content.Tabset.Tab(id, label, panel)        one entry of a Tabset (inner to Tabset)
// ----------------------------------------------------------------------------

Reactive.Module("Content", () => {

  // Text - a run of text. Renders: <span>text</span>.
  Reactive.Function("Text", () => {
    const text = Reactive.Argument(Text);
    Dom.Tag("span", () => {
      Dom.Text(text);
    });
  });

  // Title - a heading over a section or panel. Renders: <h3>text</h3>.
  Reactive.Function("Title", () => {
    const text = Reactive.Argument(Text);
    Dom.Tag("h3", () => {
      Dom.Text(text);
    });
  });

  // Label - a form-field label beside a control. Renders: <label>text</label>.
  Reactive.Function("Label", () => {
    const text = Reactive.Argument(Text);
    Dom.Tag("label", () => {
      Dom.Text(text);
    });
  });

  // Code - a read-only monospace block; <pre> preserves newlines (a GLSL signature
  // header). Distinct from the EDITABLE leaf Input.Code.Glsl. Renders: <pre class="code">.
  Reactive.Function("Code", () => {
    const text = Reactive.Argument(Text);
    Dom.Tag("pre", () => {
      Dom.Attribute("class", "code");
      Dom.Text(text);
    });
  });

  // Panel - a card that visually groups its content. Renders: <div class="panel">.
  Reactive.Function("Panel", () => {
    const content = Reactive.Argument(Reactive.Callback());
    Dom.Tag("div", () => {
      Dom.Attribute("class", "panel");
      content();
    });
  });

  // Section - groups related content (usually under a Content.Title). Renders: <section>.
  Reactive.Function("Section", () => {
    const content = Reactive.Argument(Reactive.Callback());
    Dom.Tag("section", () => {
      content();
    });
  });

  // Tabset - a tab strip over a switched panel, bound to `selected`. `body` declares the
  // entries with Content.Tabset.Tab (and may group them with Content.Section). The button
  // strip AND the panel switch are ONE declaration: each Tab emits its own control and,
  // only while it is the selected tab, its panel - so exactly one panel is ever in the tree
  // and the strip/switch can never drift (the two-parallel-lists problem is gone). How the
  // strip and the panels are arranged (strip on top or on the left, panel in the content
  // area) is the stylesheet's job, targeting .tabset / .tab / .tabpanel. Renders:
  // <div class="tabset"> button.tab* + div.tabpanel(active) </div>.
  Reactive.Function("Tabset", () => {
    const selected = Reactive.Argument(Reactive.Any);
    const body = Reactive.Argument(Reactive.Callback());

    // Tab(id, label, panel) - one entry. Defined inside Tabset so it captures `selected`
    // by closure (the settled no-ambient-context pattern, as Button.Select.Option does).
    // The control selects this tab on click and marks itself active while selected == id;
    // the panel is rendered only while active, so it is the one-of switch.
    Reactive.Function("Tab", () => {
      const id = Reactive.Argument(Reactive.Any);
      const label = Reactive.Argument(Text);
      const panel = Reactive.Argument(Reactive.Callback());
      const active = Text.Equal(selected, id);
      Button.Set(selected, id, () => {
        Dom.Attribute("class", "tab");
        Dom.Attribute("aria-selected", active);
        Content.Text(label);
      });
      Reactive.If(active, () => {
        Dom.Tag("div", () => {
          Dom.Attribute("class", "tabpanel");
          panel();
        });
      });
    });

    Dom.Tag("div", () => {
      Dom.Attribute("class", "tabset");
      body();
    });
  });

});