Docs / Enterprise
SenseEcrfDesignerComponent Preview
The clinical form (eCRF) designer from @artificialsenselabs/enterprise/ecrf, a secondary entry point of the ELv2 enterprise package (paid tier). It reads and writes a plain, versioned SenseEcrfDefinition and persists nothing itself — the host owns storage.
Live demo: run nx serve showcase and open /enterprise/ecrf-designer (port 4202).
Status
Preview. The pure-TypeScript model and rule engine, the element registry, the form renderer and the designer core (palette, canvas, undo/redo and live preview) and the property inspector are in. The host ports (drafts, presets, definition policy, locale packs) and JSON import / export are in too. The API may change until the 1.0 sign-off (ADR-0061 §6).
Installation
Unlike the main enterprise surface, /ecrf builds on @artificialsenselabs/ui. It is an optional peer of the enterprise package: install it only if you import /ecrf.
npm install @artificialsenselabs/enterprise @artificialsenselabs/ui @artificialsenselabs/tokensimport { SenseEcrfDesignerComponent } from '@artificialsenselabs/enterprise/ecrf';
<sense-ecrf-designer [(definition)]="definition" />Renderer
<sense-ecrf-renderer> draws a SenseEcrfDefinition. mode is preview (interactive, no submit; the designer's preview), fill (interactive, with a Submit button and an error summary) or readonly (answers as text). values is a two-way model keyed by field key and is the single source of truth.
- Hidden, disabled and required follow the definition's conditions, evaluated by the same pure rule engine the designer uses.
- Errors appear once a field is touched or the form is submitted. A failed submit moves focus to the error summary, and each entry moves focus to its field.
submitRequestedfires only infillmode and only when valid, carrying the values of visible fields.- Every control is a
@artificialsenselabs/uicomponent. Strings live underenterprise.ecrf.*: override any of them withSENSE_ECRF_I18N_OVERRIDESor ui'sSenseI18nService.extend(). - Theme with the
--sense-ecrf-*slots on:host(gap, column width, summary colours).
Live demo: /enterprise/ecrf-renderer in the showcase (port 4202).
<sense-ecrf-renderer
[definition]="definition"
mode="fill"
[(values)]="values"
(submitRequested)="save($event)"
/> Generated from libs/enterprise/ecrf/src/lib/sense-ecrf-renderer/sense-ecrf-renderer.component.ts — run pnpm run docs:api to refresh.
| Input | Type | Default | Description |
|---|---|---|---|
definition | SenseEcrfDefinition | SENSE_ECRF_EMPTY_DEFINITION | The form to render. |
mode | SenseEcrfRendererMode | 'preview' | `preview` (interactive, no submit), `fill` (interactive + submit + summary), `readonly`. |
values | SenseEcrfFormValues | {} | The answers, keyed by field `key`. Two-way bindable. |
showSubmit | boolean | true | Show the Submit button in `fill` mode. Turn off to drive submission with `submit()`. |
| Output | Type | Description |
|---|---|---|
valueChange | SenseEcrfValueChange | A respondent changed a field. |
submitRequested | SenseEcrfFormValues | `fill` mode, submit pressed and the form is valid. Carries the values of visible fields. |
validityChange | boolean | The form's validity changed (also emitted once with the initial validity). |
Element registry
Element types are registered on the multi-provider token SENSE_ECRF_ELEMENTS. The built-in 11 fields and the columns layout are always present; provideSenseEcrfElement(descriptor) adds a custom type (or replaces a built-in one) with no fork. A descriptor carries the type, its i18n label key, an icon, a create factory, a renderer component and a property schema for the inspector. A renderer component declares the inputs of SenseEcrfFieldRendererInputs and the outputs valueChange and touched.
Designer
<sense-ecrf-designer> is a toolbar (undo, redo, design / preview, problem count, publish), an element palette and a design canvas. definition and mode are two-way models; changed fires on every edit, undo and redo, publishRequested only when the definition has no problems, and inspectRequested on Enter or F2 over an element.
- State is a command stack. The component-scoped
SenseEcrfDesignerStorekeeps the definition, the selection and the undo history as signals. Every edit is a model command, so undo and redo replay commands; a command that changes nothing never enters the history (the stack keepsSENSE_ECRF_HISTORY_LIMITedits).markSaved()tells the designer the host has persisted the definition. - Reports, never repairs. The problem count is
validateDefinitionof the current definition. Publish with problems opens the problem list and emits nothing. - Drag and drop uses CDK drag-drop: drag from the palette to copy, reorder, or drop into and out of columns. Lists refuse what the model's nesting limits forbid.
- Every pointer operation has a keyboard equivalent. The canvas is an ARIA tree with a roving tabindex: Arrow Up / Down and Home / End select; Alt + Arrow Up / Down reorder; Alt + Arrow Right / Left move into or out of an adjacent columns element (mirrored in RTL); Delete, Ctrl/Cmd + D, Ctrl/Cmd + Z and Shift + Ctrl/Cmd + Z delete, duplicate, undo and redo. Enter / Space on a palette item inserts it after the selection. Each structural change is announced in an
aria-liveregion. - Icons. The palette draws the icon each element type names. Register the glyphs you use with
provideSenseIcons(...).
Live demo: /enterprise/ecrf-designer in the showcase (port 4202).
Inspector
<sense-ecrf-inspector> edits the selected element in a panel beside the canvas (a bottom sheet below 64rem). One schema-driven editor replaces the old library's 12 per-element dialogs: each registry descriptor's properties list says which control edits which property (text, textarea, number, boolean, key, select, choice-list, bounds) and in which section (General, Options, Validation, Conditions, Appearance), so a custom element type gets an inspector with no extra code.
- Forms. Typed Reactive Forms (the ui controls are
ControlValueAccessors) bridged to the store: an edit dispatchesstore.update, and rapid edits of one field are one undo step (SENSE_ECRF_COALESCE_WINDOW_MS). - Field key. Checked against the model's key pattern and for uniqueness; a key that fails is shown as an error and not saved. With
[keyLocked]="(element) => published"on the designer a key is read-only and says it is permanent once published. Never locked by default. - Options. Add, remove and reorder choices (drag, the Move buttons, or Alt + Arrow Up / Down), flag duplicate or empty values, paste several (
value,labelorlabelper line) and choose the default selection. - Validation rules. required, requireChecked (checkboxes only), regex, range, length, dependency, comparison and conditionalRequired, each with a message override. A regex tester runs the pattern through the model's capped
testRegex: a pattern over 500 characters, a sample over 200, or a pattern that repeats a repeat such as(a+)+is refused, never run. - Conditions. Show, hide, enable or require while a nested all / any / not expression over the other fields holds. The value editor adapts to the compared field's type, and a dependency loop found by the model is shown where you edit.
- Live feedback. A preview of just the selected element sits on top, followed by the element's problems from
validateDefinition(they report, nothing is repaired). - Focus. Enter or F2 on a canvas element moves focus to the label in the panel; Escape returns it to that element.
Host ports
The designer persists nothing itself. Everything it could store or decide is a port: a DI token whose default is inert, so nothing outlives a page load and nothing is blocked unless the host provides it. Every port method may return its value or a promise of it, and a failed write is reported as a value ({ ok: false, reason: 'quota' | 'unavailable' | 'error' }), never thrown.
SENSE_ECRF_DRAFT_STORE(load,save,remove,list). Used only while thedraftIdinput is set: the designer autosaves after a short pause, offers "Restore draft" when a newer draft than the supplied definition exists (definitionSavedAtsays how new the definition is), and removes the draft once the host callsmarkSaved(). Default: in memory.provideSenseEcrfLocalStorageDrafts({ keyPrefix })is the opt-in adapter: SSR-safe, quota reported, corrupt JSON read as no draft.SENSE_ECRF_PRESET_STORE(list,load,save,remove). "Save as preset" on the element toolbar stores a named copy of an element; the palette's Presets group inserts a copy with new ids and a unique key. Default: in memory;provideSenseEcrfLocalStoragePresets()is opt-in.SENSE_ECRF_DEFINITION_POLICY(check,isKeyLocked), fed by thepolicyContextinput. Policy problems join the problem count, are listed with the wording the policy names (an i18n key plus params) and block publishing; a locked key is read-only in the inspector. Reported, never repaired. ThekeyLockedinput overrides the policy. Default: permissive.provideSenseEcrfMessages(pack)takes a complete translation typed on every message key, so a pack with a missing key does not compile (satisfies SenseEcrfMessages). Only English ships.
The package README has the interfaces and a small host provider example, which is what a product implements when it adopts the designer.
Import and export
The toolbar's File menu exports the definition as JSON (named after its title) and imports one back. The pure helpers serializeDefinition and parseDefinition are public: parsing rejects unsafe keys such as __proto__, then migrates and validates, and reports problems instead of repairing them. An import replaces the definition as one undoable step. In preview mode a toggle draws the preview in the page's theme or forced light or dark.
Custom element types
Element types live in a DI registry, so a clinical type the library does not ship (a signature, a vital sign) is added without a fork. Register a descriptor with provideSenseEcrfElement: its icon, a factory for a fresh valid element, a property schema (the inspector renders it) and a renderer component that follows the SenseEcrfFieldRenderer contract (the inputs of SenseEcrfFieldRendererInputs and the valueChange / touched outputs). Registering a type that exists replaces it.
providers: [
provideSenseEcrfElement({
type: 'signature',
kind: 'field',
labelKey: 'acme.signature',
icon: 'type',
renderer: SignatureFieldComponent,
properties: [{ name: 'label', kind: 'text', labelKey: 'acme.label', section: 'general', required: true }],
create: (seed) => ({ ...seed, type: 'signature', required: false, disabled: false,
readOnly: false, validation: [], conditions: [] }),
}),
]SenseEcrfFieldElement.type accepts a registered custom string without a cast. validateDefinition(definition, { knownTypes }) accepts those types (the designer passes the registry's) and still fails closed with element-type-unknown on a type nobody registered. A custom type is opaque to the model: key, label, rules and conditions are still checked, choices and bounds are the type's own business.
Definition format
A SenseEcrfDefinition is plain, method-free JSON: schemaVersion, metadata (title, description) and elements in document order. A field carries id (designer identity), type, a permanent key (dot-separated identifiers, unique), label, flags, optional choices / bounds / default, a validation list and a conditions list. A columns element holds up to four columns (nesting depth 2) of child elements.
- Configuration is data. Rules (
required,requireChecked,regex,range,length,dependency,comparison,conditionalRequired) and conditions (show/hide/enable/requireoverall/any/notexpressions) are closed, declarative unions. Nothing in the entry point evaluates authored content as code: noFunction, noeval, no dynamicimport(). - Versioning.
schemaVersionis 1. A change to the format bumps it and ships amigrateDefinitionstep from the previous version;parseDefinitionruns migration, then validation, and reports problems without repairing anything. A version newer than the library knows is refused (version-unsupported), never guessed at. - Required versus requireChecked.
requiredmeans answered: an unticked checkbox isfalse, andfalseis an answer, so it satisfiesrequired. That is right for a yes/no question and wrong for a confirmation ("I confirm consent was obtained"). The opt-in{ rule: 'requireChecked' }rule passes only when the value is exactlytrue;undefined,nullandfalseall fail. It is offered for checkbox fields only,validateDefinitionreports it on any other type (rule-not-applicable) and never repairs it, and when both are present it reports a single error. A condition with therequireeffect makes a fieldrequired, notrequireChecked. For a yes/no that must be answered, prefer a Yes/No radio group or button group: an unticked box cannot tell "No" from "not answered" in a regulated record. A definition that usesrequireCheckedhas no trialpath FormSchema v1 equivalent. - Renaming a key. Changing a field's key in the inspector is one undoable
renameKeystep that also rewrites every condition and rule that referred to it (createRenameKeyCommandbuilds the same command for hosts).
Keyboard and accessibility
The canvas is an ARIA tree with a roving tabindex; every pointer operation has a keyboard equivalent and each structural change is announced in a live region.
- Arrow Up / Arrow Down, Home / End: move through elements.
- Enter or F2: open the inspector on the element; Escape returns to the canvas.
- Alt + Arrow Up / Down: move within the list.
- Alt + Arrow Left / Right: move into or out of a columns element (mirrored in right-to-left).
- Delete, Ctrl/Cmd + D: delete, duplicate.
- Ctrl/Cmd + Z, + Shift + Z or + Y: undo, redo.
Layer-3 axe (WCAG 2.2 AA) covers the designer, renderer and inspector demo routes in the default and dark themes, plus the 320px reflow check. The palette inserts at the selection from the keyboard, so drag is never required.
Relationship to the form builder
This is the enterprise form designer. SenseFormBuilderDesignerComponent is deprecated in its favour (still working; removal not before /ecrf 1.0). The runtime SenseFormBuilderComponent and SenseSchemaRenderer are not deprecated. See libs/enterprise/migration.md.
Designer API
Generated from libs/enterprise/ecrf/src/lib/sense-ecrf-designer/sense-ecrf-designer.component.ts — run pnpm run docs:api to refresh.
| Input | Type | Default | Description |
|---|---|---|---|
definition | SenseEcrfDefinition | SENSE_ECRF_EMPTY_DEFINITION | The form definition being designed. Two-way bindable; the designer never persists it. |
mode | SenseEcrfDesignerMode | 'design' | `design` edits the form; `preview` renders it live as a respondent sees it. Two-way bindable. |
keyLocked | ((element: SenseEcrfFieldElement) => boolean) | null | null | Override for "this key is permanent". By default the designer asks the host's `SENSE_ECRF_DEFINITION_POLICY` (`isKeyLocked`); set this input to decide yourself and the policy is not asked. A locked field key is read-only in the inspector, with the reason shown. |
draftId | string | null | null | Autosave the work in progress under this id, through `SENSE_ECRF_DRAFT_STORE`, and offer a newer stored draft for restoring. `null` (the default) turns drafts off. The designer never touches a store the host did not provide. |
definitionSavedAt | number | null | null | When the supplied `definition` was last saved (epoch milliseconds). A stored draft saved at or before this moment is not newer, so it is not offered. Omit it and any draft that differs is. |
policyContext | SenseEcrfPolicyContext | {} | What the host's policy needs to know (typically the last published definition). |
| Output | Type | Description |
|---|---|---|
changed | SenseEcrfDefinition | Every edit, undo and redo emits the new definition. Not emitted when the host sets `definition`. |
publishRequested | SenseEcrfDefinition | Publish was pressed and the definition has no problems. With problems it opens the problem list instead. |
inspectRequested | string | Enter or F2 on an element: edit it. Carries the element id. |