FS FormScript
Package@formscript/render-dom
Specification5.1, 7.12, 13, 13.4, 13.5, 13.6, 15.2, 15.3
Depends ona11y, ast, engine, render, validation

@formscript/render-dom

The DOM binding of FormScript render intents, per §15.2 and §15.3 of the specification.

@formscript/render computes which widget a field needs; this package turns that intent into a live HTML form. It is a host-side concern: FormScript emits intents, never markup (§15.2), so the markup lives here.

Rendering a form

ts
import { renderForm } from '@formscript/render-dom';

const form = renderForm({
  container: document.getElementById('live')!,
  document: parsed.document,
  engine,
});

form.render();
form.submit();

renderForm replaces the content of the container with a <form>, wires every control to the engine, and re-renders when the engine's state changes. The engine keeps the values, the validity and the conditions; the renderer only presents them.

The widgets

Intent Widget
RenderTextInput <input type=text>
RenderNumberInput <input type=number>
RenderChoice a radio group, or a <select> above six options (§7.12)
RenderMultiSelect a checkbox group, or a <select multiple> above six options
RenderDate <input type=date>, or datetime-local for a datetime
RenderToggle <input type=checkbox>
RenderFile <input type=file>
RenderSignature a <canvas>, or a text control when the host has no canvas
RenderLocation a pair of number controls and a geolocation button
RenderGroup <fieldset> with a <legend> (§13.6)
RenderRepeat a repeated <fieldset> with add and remove buttons
RenderProgress <progress>

Every intent of §15.2 has a widget, as Spec-15.2 requires. A field whose declaration selects no intent is not dropped silently: the renderer says so in the form.

The obligations

  • A labelled control for every active field (Spec-15.4). A field with no label shows its name. A group, a repeat and a radio group carry their label in a <legend>, so the label is never a floating text.
  • The field state is exposed (Spec-15.4, §13). The control carries aria-required, aria-invalid and aria-describedby; the hint, the help and the error message are the elements it points at.
  • A value never loses its label (Spec-15.4). The label is outside the control, so no value is ever rendered without one.
  • Focus and the caret survive a state propagation (Spec-15.5). The renderer reconciles by key: an element whose key survives is updated in place, so the person keeps typing in the same control. The caret is restored when the text changes under it.
  • A failed submit renders the summary of §13.4: every failing field is listed at the top of the form, the summary takes the focus, each entry links to its field, and the same message is rendered inline at the field. A re-failing submission returns the focus to the summary (§13.5).

Judgement calls

The specification does not say how a browser renderer binds these intents to a DOM. The choices this package makes are recorded here.

  • The identity of a control is its path. The id of a control is the path the engine addresses the field by, joined with dots: household.0.income. A declared id attribute is the field's identity in the document; the DOM needs one that is unique per repeat instance, so the path is used.
  • A choice above six options becomes a select (§7.12), a radio group below it. The threshold is the one @formscript/a11y uses for the accessible tree, so the DOM and the accessibility tree agree.
  • A blank option is offered in a select, and choosing it clears the field when the field's type is a string. An enum cannot hold the empty string (Spec-4.24), so choosing the blank option in an enum leaves the field as it was.
  • A file field holds the name of the chosen file. The specification gives a file field an unconstrained domain (§5.1) and does not define the shape of the value; the name is the only part a browser exposes without an upload.
  • A signature field holds a PNG data URL, which is what a canvas produces. A host with no canvas gets a text control instead.
  • A location field holds { latitude, longitude }, with either part null when it was not given.
  • A when gate removes the field from the form (Spec-8.3). The renderer reads the applicability the engine holds, so a field that comes back keeps its place in the document order.

Public API

12 exported symbols.

createWidget re-export
displayText re-export
ElementSpec interface

A keyed description of one element the renderer owns.

FormWidget interface

The control a field renders as.

KEY_ATTRIBUTE const

The attribute that carries an element's reconciliation key.

reconcile function

Reconciles the children of `parent` with `specs`. An element whose key survives is reused and updated in place, so the focus and the caret of the person typi…

RenderedForm interface
renderForm function

Renders a FormScript document as a live form.

RenderFormOptions interface
ResolvedOption interface

An option of a choice set, with the label a person reads.

setControlValue function

Sets a control's value, keeping the caret where it was when the text changed.

WidgetContext interface

Everything a widget builder needs.