Ooidget Component Format 0.1Editor's Draft
Interface and tests
A component file MAY declare its interface: what a host may set, what it will hear, and what it may call. With
about and per-member descriptions, the interface is the component’s documentation; tools generate reference
pages and property editors from it. A component MAY also carry tests that any runtime can run.
{
"about": "A volume knob.",
"widget": { "state": { "volume": 0.5, "muted": false } },
"interface": {
"props": {
"volume": { "type": "number", "minimum": 0, "maximum": 1, "description": "Output level, linear." },
"muted": { "type": "boolean" }
},
"events": { "changed": { "description": "The volume moved.", "params": { "type": "number" } } },
"methods": {
"reset": { "description": "Back to the default level.", "source": "s => ({ volume: 0.5, muted: false })" },
"nudge": { "params": { "type": "object", "properties": { "by": { "type": "number" } }, "required": ["by"] },
"fn": "knob#nudge" },
"mute": { "description": "The behaviour's own mute handler." }
}
},
"tests": [
{ "name": "the wheel raises it", "steps": [{ "wheel": [24, 24, 1], "expect": { "volume": 0.52 } }] },
{ "name": "reset", "state": { "volume": 0.9 }, "steps": [{ "call": ["reset"], "expect": { "volume": 0.5 } }] }
]
}Props
interface.props maps root state members to a schema each, in JSON Schema 2020-12
[JSON-SCHEMA]. A prop’s default is the component’s initial
state; a runtime reports it as the schema’s default.
When a host sets state, a runtime MUST check the patch against the props, refuse a member the props do not declare,
and apply nothing of a patch with an error. It MUST check at least type, enum, const, minimum, maximum,
exclusiveMinimum, exclusiveMaximum, multipleOf, minLength, maxLength, items, minItems, maxItems,
properties, required and additionalProperties; other keywords MAY be treated as documentation. A component
without props takes any patch. State the widget’s own behaviours write is not checked.
A host’s set is not a change made inside the widget: runtimes MUST NOT report it back as one (on the web, no change
event), so a framework that passes state down and listens for changes cannot loop.
Events
interface.events documents the commands the component sends: each by its $cmd name,
with a description and the schema of its params ($args).
Methods
interface.methods names what a host may call. A method’s params is the schema its arguments MUST fit; a call whose
arguments do not fit fails without effect. The method runs:
- its
fn, a function reference, else - its
source, an ECMAScript function’s source, else - the root behaviour’s handler of the method’s name.
It is called with the root’s view and the arguments, and what it returns is taken as a handler’s return value: a state patch and, maybe, a command.
Tests
tests is an array of tests, each { name, state?, size?, steps }. A runtime runs one in a widget of its own: started
from the component’s state with state applied, laid out at size (else the component’s), and laid out again before
each step. A step does, in order, what it names, then checks what it expects:
| Member | Does |
|---|---|
set | a host’s set (a refused patch fails the test) |
apply | merges into state unchecked |
call | [name, args?]: calls a method |
down, move, up, click | [x, y]: pointer input in design units; click is move, down and up |
wheel | [x, y, notches] |
key | { key, code, shift, ctrl, alt, meta } to the focused instance |
expect | the state MUST hold these members (numbers within 1e-9) |
commands | the commands the step produced MUST match, in order |
These are the steps of the conformance suite’s scenes, so a component’s tests run anywhere the suite does. Hosts and tools SHOULD offer to run them: an editor on every save, a store before it lists a component.
In a host
| C ABI | Web | |
|---|---|---|
| describe | oo_document_describe | describe(bundle, name), el.describe() |
| set | oo_widget_set | w.set(patch), el.state = patch |
| call | oo_widget_call | w.call(name, args), el.call(name, args) |
| a child’s own state | oo_widget_write | |
| tests | oo_document_test | runTests(bundle, name), el.test() |
On the web, <oo-widget> is a standard custom element: its properties and DOM events are how React, Solid, Vue,
Svelte, Angular and plain pages all use it.
Accessibility audit
A runtime SHOULD offer an audit of a widget against WCAG 2.2 [WCAG22] for the success criteria a frame decides: 1.4.3 Contrast (Minimum), text against the solid fill under it; 2.1.1 Keyboard, operable instances are focusable; 2.4.7 Focus Visible, a focused instance draws differently; 2.5.8 Target Size (Minimum), operable instances are at least 24 × 24 design units; and 4.1.2 Name, Role, Value. Each finding names its criterion, level and instance. Passing the audit is not conformance: names that mean something, a sensible order, zoom and reflow remain a person’s and the host’s to judge.