Inputs

Inputs are UI elements that accept data from users and enable interaction via reactivity. They can be standard HTML elements like text boxes, sliders, and dropdowns, or custom elements like charts with interactive selection.

The view() Function

The view function is the primary way to create reactive inputs. It combines two operations:

  1. Displays the given DOM element (via display)
  2. Returns an async iterator of the element's values (via input)
Note: Dataflow's view() works like Observable Framework's but supports JSX syntax for creating elements. Compare view(<input type="text"/>) vs Observable's view(html`<input type=text>`).
const name = view(<input type="text" placeholder="Enter your name"/>);
`Hello, ${name}!`

Supported Input Types

The input function automatically handles different input types with appropriate events and value extraction:

Text Input

const textValue = view(<input type="text" value="Edit me"/>);
`Text: "${textValue}"`

Number Input

Number inputs return actual numbers, not strings:

const numValue = view(<input type="number" value="42" style="width: 80px"/>);
`Number: ${numValue} (type: ${typeof numValue})`

Range Input (Slider)

Range inputs also return numbers:

const rangeValue = view(<input type="range" min="0" max="100" value="50"/>);
`Range: ${rangeValue}`

Checkbox

Checkboxes return boolean values:

const checked = view(<input type="checkbox" id="feature"/>);

Select (Dropdown)

const color = view(<select>
    <option value="red">Red</option>
    <option value="green">Green</option>
    <option value="blue">Blue</option>
</select>);
<span style={`color: ${color}; font-weight: bold`}>Selected: {color}</span>

Multi-Select

Multi-select returns an array of selected values:

const colors = view(<select multiple style="height: 80px">
    <option value="red">Red</option>
    <option value="green">Green</option>
    <option value="blue">Blue</option>
    <option value="yellow">Yellow</option>
</select>);
<div style="font-weight: bold">
    Selected: {colors.length ? colors.map((c, i) =>
        <span>
            <span style={`color: ${c}`}>{c}</span>{i < colors.length - 1 ? ', ' : ''}
        </span>
    ) : 'none'}
</div>

Date Input

Date inputs return Date objects:

const dateValue = view(<input type="date" value="2025-01-01"/>);
`Date: ${dateValue?.toLocaleDateString() ?? 'not set'}`

Color Input

const colorValue = view(<input type="color" value="#4a90d9"/>);
<div style={`background: ${colorValue}; padding: 1em; color: white; text-shadow: 1px 1px 2px black`}>
    Color: {colorValue}
</div>

The input() Function

The input function converts a DOM element into an async iterator without displaying it. This is useful when you want to control where and when the element is displayed:

Note: Dataflow's input(element, eventType?, getValue?) adds optional parameters for custom event types and value extraction functions beyond Observable Framework's Generators.input(element).
const myInput = <input type="text" placeholder="Type here..."/>;
const myValue = input(myInput);
Enter text:
You typed:

Custom Events

By default, input listens for the appropriate event based on the input type. You can override this:

// Listen to 'change' instead of 'input' for text
input(element, 'change');

// Custom value extraction
input(element, 'input', (el) => el.value.toUpperCase());

Event Types by Input

Input Type Default Event Value Returned
text, search, email, url, telinputstring
number, rangeinputnumber
date, datetime-localinputDate
checkboxclickboolean
filechangeFile or FileList
selectinputstring
select[multiple]inputstring[]
button, submitclickvalue

The observe() Function

For more complex scenarios, observe lets you create custom async iterators from any event source. Pass an initial value as the second argument:

Note: Dataflow's observe() adds initialValue and terminate parameters beyond Observable Framework's Generators.observe(). This eliminates the need to call notify() inside the initializer for the initial value.
const windowSize = observe((notify) => {
    const handler = () => notify({
        width: window.innerWidth,
        height: window.innerHeight
    });
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
}, {width: window.innerWidth, height: window.innerHeight});
`Window size: ${windowSize.width} x ${windowSize.height}`

observe() Signature

function observe<T>(
    init: (notify: (value: T | undefined) => void) => (() => void) | void,
    initialValue?: T,
    terminate?: (value: T | undefined) => boolean
): AsyncGenerator<T>

Parameters:

Using Observable Inputs

You can also use the @observablehq/inputs library for more sophisticated input controls:

import {range, select, checkbox} from "@observablehq/inputs";

const obsSlider = view(range([0, 100], {label: "Volume", step: 1, value: 50}));
`Volume: ${obsSlider}%`

Combining Multiple Inputs

Reactive values can depend on multiple inputs. The dependent code re-runs whenever any input changes:

const w = view(<input type="range" min="50" max="300" value="150"/>);
const h = view(<input type="range" min="50" max="200" value="100"/>);
const boxColor = view(<input type="color" value="#4a90d9"/>);
<div style={`width: ${w}px; height: ${h}px; background: ${boxColor};
    display: flex; align-items: center; justify-content: center; color: white;
    text-shadow: 1px 1px 2px black`}>
    {w} x {h}
</div>