The editor
FormKitEditor is the designer shell: a header, a searchable palette, the layout canvas, the inspector, and the Preview and Definition JSON dialogs. All state lives in a FormKitEditorController, a plain ChangeNotifier that swaps in a new, valid FormDefinition on every command.
On wide screens the palette, canvas and inspector sit side by side and the side panes can be resized. On narrow screens the palette and inspector become drawers over the canvas, toggled from the header.
FormKitEditor
FormKitEditor(
controller: controller,
subtitle: 'Quality · Forms',
status: const Chip(label: Text('Draft · v4')),
actions: [FilledButton(onPressed: publish, child: const Text('Publish'))],
extraPaletteEntries: [signatureEntry],
extraPaletteCategories: const [approvals],
extraActions: [startCollapsed],
values: valueStore,
codecs: appCodecs,
developer: kDebugMode,
)| Parameter | Type | Purpose |
|---|---|---|
controller | FormKitEditorController | Required. Owns the definition, selection and history. |
extraPaletteEntries | Iterable<FormPaletteEntry> | App elements added to the palette. |
extraPaletteCategories | Iterable<FormPaletteCategory> | App palette headings, or overrides of how a category is titled and ordered. |
extraActions | Iterable<FormEditorAction> | App commands in the selection bar, menus, shortcuts and command palette. |
subtitle | String? | A breadcrumb above the form's title. |
status | Widget? | A status chip beside the title, such as "Draft · v4". |
actions | List<Widget> | Host actions at the end of the header, such as Publish. |
values | FormValueStore? | The host's value store. The preview fills it and the header shows Captured values. Without one, the preview keeps its own. |
codecs | FormCodecRegistry? | Codecs for custom types, used by Definition JSON export and import. |
developer | bool (default true) | Offers Definition JSON (Copy JSON, Import JSON, Clear all) on the form card and in the header. Preview is always offered. |
FormValueStore is a ValueNotifier<Map<String, Object?>> with set(key, value) and reset().
Preview and Definition JSON
- Preview opens the current definition in a
FormView, filling thevaluesstore when one is given. - Definition JSON shows the encoded definition. Import JSON reads pasted JSON through
codecs; an invalid paste lists every problem and changes nothing, and a valid one replaces the definition as one undo step. Clear all empties the form and offers Undo.
Set developer: false for authors who should not see or paste raw JSON.
Controller commands
final controller = FormKitEditorController(deviationIntake);Commands that can be refused return bool. A refused command leaves the definition unchanged and puts the reason in lastRefusal. Every accepted command is one undo step, and listeners are notified.
| Area | Members |
|---|---|
| State | definition, selectedId, hovered (a ValueListenable<String?>), lastRefusal, lastNotice, canUndo, canRedo, select(id), hover(id) |
| Form and fields | setFormDetails({key, title, description}), setFieldTitle(key, title), renameField(key, newKey), updateField(key, edit), updateNode(id, edit), renameNode(id, newId), setRepeatLimits(id, {minItems, maxItems}), replaceDefinition(next), clearAll() |
| Drag and drop | evaluateDrop(payload, target), drop(payload, target) with a DropPayload (NewField, NewNode, MoveNode) and a DropTarget |
| Rows | setRowColumns(rowId, columns), moveRowBoundary(rowId, leftIndex, delta), setCellSpan(rowId, index, span), equalizeRow(rowId), unwrapRow(rowId), wrapInRow(ids) |
| Grids | resizeGridItem(gridId, childId, rect), trimGrid(gridId), tidyGrid(gridId), setGridColumns(gridId, columns), unwrapGrid(gridId) |
| Structure | duplicate(id), delete(id), wrapInGroup(ids), ungroup(id), makeRepeating(groupId), makePlainGroup(groupId), moveWithin(id, delta), moveOut(id), moveIntoNext(id) |
| History | undo(), redo(), plus commit(result) and commitEdit(result) for app commands built on the pure layout rules |
if (!controller.setCellSpan('deviation_row', 0, 8)) {
debugPrint(controller.lastRefusal); // why the spans don't fit
}
controller.setRowColumns('material_row', 6);
controller.setRepeatLimits('materials', maxItems: (value: 20));
controller.updateField(
'title',
(field) => field.copyWith(helpText: 'One line; details go in What happened.'),
);
controller.undo();The layout rules behind these commands (form_row_rules, form_grid_rules, form_tree_edits and others in vyuh_form_types) are pure functions that return a LayoutResult: Applied or Refused with a reason. commit and commitEdit apply such a result through the same history.
Undo and redo are bound to ⌘Z / ⇧⌘Z (Ctrl on Windows and Linux) on the canvas, and to buttons in the header. Dispose the controller with its owning State.
App actions
A FormEditorAction declares where it applies, why it is unavailable, and what it does. One registry of actions feeds the selection bar, the context menu, keyboard shortcuts and the command palette.
final startCollapsed = FormEditorAction(
id: 'acme.start-collapsed',
title: 'Start collapsed',
icon: Icons.unfold_less,
group: 'Group',
appliesTo: (s) => s.node is FormGroup,
availability: (s) => switch (s.node) {
FormGroup(collapsible: false) => 'Make the group collapsible first.',
FormGroup(initiallyCollapsed: true) => 'The group already starts collapsed.',
_ => null,
},
perform: (controller, s) {
controller.updateNode(
s.nodeId!,
(node) => (node as FormGroup).copyWith(initiallyCollapsed: true),
);
},
);
// FormKitEditor(controller: controller, extraActions: [startCollapsed])| Parameter | Meaning |
|---|---|
id | Stable kebab-case id. |
title, icon, group | How it is listed. Groups keep the order in which they first appear. |
appliesTo | Whether it is listed for a selection. |
availability | null when it can run; otherwise the reason it is disabled. |
perform | Runs the action with the controller and selection. |
shortcuts | Key bindings, as ShortcutActivators. |
pinned | Pinned actions come first, on the selection bar. |
destructive | Shown in the error colour. |
label | A selection-dependent title, such as "Columns: 6". |
FormEditorSelection gives the action the definition, the selected nodeId (null for the form), the node and its location. actionsFor(selection, extra:) returns the built-ins plus yours, pinned first.
Built-in action ids: duplicate, delete, wrap-in-row, wrap-in-group, ungroup, make-repeating, make-plain-group, equal-spans, row-columns, unwrap-row, tidy-placements and unwrap-to-flow, in the groups Edit, Wrap, Group, Row and Grid. The form itself has preview and definition-json in the Form group.
Palette
The palette lists FormPaletteEntrys under FormPaletteCategory headings.
const FormPaletteEntry({
required String id,
required String title,
required ElementIcon icon, // ElementIcon.glyph('✎') or ElementIcon.symbol(Icons.draw)
required FormPaletteCategory category,
required DropPayload Function(FormPaletteContext context) create,
List<String> keywords = const [],
});create returns a NewField(field) or NewNode(node) payload. Its FormPaletteContext offers nextKey(prefix) and nextId(prefix), which return names not yet used in the definition. Entry ids must be unique.
Built-in categories, in order: Layout, Text & numbers, Choice & reference, Date & time, Media & files, Computed, Content. An app category is FormPaletteCategory(id:, title:, order:); categories sort by order. The built-in entries are available as builtInPaletteEntries.
See Custom fields for a complete entry.
Validators (coming in 0.2.0)
From 0.2.0 the controller also owns the validator kinds the designer offers: FormKitEditorController(definition, extraValidators: [...]), with addValidation, replaceValidation, moveValidation, removeValidation and designIssues. See Custom validators.