Skip to content
Ooidget

Ooidget Component Format 0.1Editor's Draft

Behaviours

Behaviour source

A widget’s behaviour, when a string, is ECMAScript source of an Expression that evaluates to an object, the handler object. Each of its members that is a function is the handler for the event of that name:

({
  click(s) { if (s.toggle) return { on: !s.on } },
  drag(s0, dx, dy, ui) { return { value: Math.min(1, Math.max(0, s0.value - dy / 300)) } },
  wheel(s, notches) { return { value: Math.min(1, Math.max(0, s.value + notches / 50)) } }
})

The source is evaluated once per instance, in the sandbox. Handlers MUST be treated as pure functions of their arguments: a runtime MAY call them again with the same arguments and MAY run them on another thread.

Events

HandlerArgumentsCalled when
press(s, ui)view, pointer stateA pointer or touch goes down on the instance.
release(s, ui)view, pointer stateThe press that began on the instance ends, wherever it ends.
click(s, ui)view, pointer stateA press is released over the instance without having been a drag.
dblclick(s, ui)view, pointer stateA second click within the platform’s double-click interval.
drag(s0, dx, dy, ui, s)view as it was at the press, offsets since the press in design units, pointer state, the view nowThe pointer moves while pressed, after the platform’s drag threshold. A drag that adds up as it goes (a knob turned past half a circle) keeps its running total in state and reads it from s.
wheel(s, notches, ui)view, notches (positive: away from the user / up), pointer stateA wheel or trackpad scroll over the instance.
rotate(s, detents, ui)view, detents (positive: clockwise), pointer stateA rotary input (crown, bezel, encoder) turns while the instance has focus. Absent: wheel is called with the same value.
key(s, k)view, key eventA key is pressed while the instance has focus. k is { key, code, shift, ctrl, alt, meta } with the values of the DOM KeyboardEvent [UIEVENTS].
focus(s), blur(s)viewThe instance gains or loses focus.
context(s, ui)view, pointer stateA secondary press or long press on the instance.

A handler returns a state patch, or undefined for no change. A returned value that is not an object is ignored. Events are delivered to the topmost instance under the pointer whose behaviour has a handler for them; an instance without one lets the event pass to its parent.

Commands

A state patch MAY carry a command for the host: the members $cmd (a string) and $args (any JSON value). The runtime removes them from the patch, applies the rest, and passes the host { "method": $cmd, "params": $args, "from": path }. A host that does not know the command ignores it.

Shared behaviours

A behaviour used by several components is written once, in a file of its own in the project folder, name.behaviour.js: an ECMAScript module whose default export is a function from settings to a handler object.

detented.behaviour.js
/** A value dragged along an axis, with marks it can rest on: a knob's, a slider's, a fader's. */
export default (o) => ({
  drag(s0, dx, dy, ui) { /* … */ },
  release(s) { /* … */ },
  wheel(s, notches) { /* … */ },
});

In a bundle the same behaviour travels as a component file whose module member is the module’s source: { "about": "…", "module": "export default (o) => ({ … })" }. A behaviour with no imports MAY instead travel as "behaviour": "(o) => ({ … })", its default export as source.

A widget uses it with an object: "behaviour": { "use": "drag", "axis": "y", "steps": 11 }. The runtime calls the module’s default export with settings, the object without use, and uses the result as the widget’s handler object. A setting that is a string MAY name a view member that holds its value; this is the shared behaviour’s own convention, not the runtime’s.

Composing behaviours

A widget’s behaviour MAY be an array: several behaviours, each a source, a reference or { "use": … }, applied together. A knob that is dragged and turned by the wheel takes one of each:

"behaviour": [
  { "use": "drag", "axis": "both", "value": "volume", "steps": "ticks" },
  { "use": "wheel", "value": "volume", "steps": "ticks" }
]

The composed handler object has every handler any of them has. When several have the same handler, a runtime MUST call them in array order with the same arguments and merge the state patches they return, a later one’s members replacing an earlier one’s; a command from any of them is passed on.

Behaviours that work alike share their code as a code module they import, not as a behaviour: drag.behaviour.js and wheel.behaviour.js both import { detents } from "./detents.js".