Skip to content

Visual Editor

The vyuh_form_editor package provides a drag-and-drop form designer built on MobX and the Property System. It produces JSON compatible with vyuh_feature_forms.

Architecture

The editor consists of three main components arranged horizontally:

Visual editor three-panel layoutA resizable field palette on the left, an expandable form canvas in the center, and a resizable properties panel on the right.Field Paletteresizable source panelForm Canvasexpandable editing workspacedrop, reorder, select, validateProperties Panelresizable inspector

FormEditorStore

The MobX store manages all editor state:

dart
// Simple setup with built-in defaults
final store = FormEditorStore.withDefaults();

// Custom setup with additional field types
final store = FormEditorStore.withDefaults(
  additionalFieldTypes: [MyCustomFieldType()],
  additionalConditionTypes: [MyCustomCondition()],
  additionalActionTypes: [MyCustomAction()],
);

// Descriptor-based setup (recommended for production)
final store = FormEditorStore.fromDescriptors([
  FormEditorDescriptor.withDefaults(
    fieldTypeDescriptors: [
      ReferenceFieldDescriptor(
        providers: [myEntityProviderType()],
      ),
    ],
  ),
]);

// Custom registry for full control
final store = FormEditorStore.withRegistry(
  registry: FormEditorRegistry(
    fieldTypes: [...],
    structuralTypes: [...],
    conditionTypes: [...],
    actionTypes: [...],
  ),
);

Store API

MethodDescription
formInstanceCurrent form being edited
itemsAll items in display order
allFieldsAll fields including nested
selectedItemCurrently selected item
selectItem(item)Select an item
selectForm()Select the form itself (show form properties)
addBlock(block, container:, at:)Add a compatible field, row, section, or repeating section
removeBlock(block)Remove a block from its parent container
moveBlockWithinContainer(block, to)Reorder a block inside its current container
moveBlockToContainer(block, targetContainer:, at:)Move a block between containers
undo()Undo last action
redo()Redo last undone action
toJson()Export form as JSON
validate()Validate the form definition
isValidWhether the definition has no errors

FormEditor Widget

The main widget orchestrates all components:

dart
FormEditor(
  store: store,
  onFieldsChanged: (fields) {
    // React to field list changes
    print('Fields: ${fields.length}');
  },
  headerActions: [
    // Custom header actions (optional)
    FormEditorAction(
      label: 'Save',
      icon: Icons.save,
      onPressed: () => saveForm(store.toJson()),
    ),
  ],
)

FormEditorRegistry

The registry manages all registered types and serves as the sole compositor:

dart
final registry = store.registry;

// Access registered types
registry.getFieldTypes();               // All field types
registry.getValidationTypes();          // All validation types
registry.getConditionalRuleConditionTypes(); // All condition types
registry.getConditionalRuleActionTypes();    // All action types

// Composition: convert vyuh models to editor instances
registry.fieldFromVyuh(vyuhField);
registry.sectionFromVyuh(vyuhSection);
registry.ruleFromVyuh(vyuhRule);

// Validate registry consistency
final warnings = registry.validate();

Field Palette

The left panel shows all available field types organized by category. Users drag fields from the palette onto the canvas:

  • Structure -- Row, Section, Repeating Section
  • Basic -- TextField, SelectField, BooleanField, DateTimeField
  • Numeric -- NumberField, SliderField, RangeSliderField
  • Advanced -- PhoneNumberField, FormulaField, ReferenceField
  • Media -- ImagePickerField, FilePickerField

Form Canvas

The center panel displays the form layout with:

  • Drag-and-drop reordering
  • Row layout controls with per-field spans
  • Field cards with type icons and validation indicators
  • Section containers with collapsible headers
  • Repeating section templates
  • Multi-selection support (Ctrl/Cmd-click, Shift-click)

Properties Panel

The right panel shows configuration for the selected item:

  • Field tab -- Name, title, placeholder, help, and field-specific properties
  • Validation tab -- Add/remove/configure validations
  • Layout tab -- Layout type selection and configuration (when multiple layouts available)
  • Provider tab -- Reference-field provider configuration
  • Rules tab -- Add/remove/configure rules (condition + action pairs)

When the form itself is selected, the properties panel shows form-level settings (title, description, spacing).

Keyboard Shortcuts

The editor supports keyboard shortcuts for common operations:

  • Delete / Backspace -- Remove selected field
  • Ctrl+Z / Cmd+Z -- Undo
  • Ctrl+Shift+Z / Cmd+Shift+Z -- Redo
  • Ctrl+D / Cmd+D -- Duplicate selected field
  • Ctrl+A / Cmd+A -- Select all
  • Escape -- Clear selection

Import/Export

Export to JSON

dart
final json = store.toJson();
// json is a Map<String, dynamic> compatible with vyuh_feature_forms

Import from JSON

dart
// Parse existing form JSON with vyuh_feature_forms, then compose editor blocks
final formInstance = registry.formType.fromVyuh(existingForm);
for (final block in existingForm.items) {
  final item = switch (block) {
    FormField f => registry.fieldFromVyuh(f),
    FormRowBlock r => registry.rowFromVyuh(r),
    FormSection s => registry.sectionFromVyuh(s),
    RepeatingSection r => registry.repeatingSectionFromVyuh(r),
    _ => null,
  };
  if (item != null) formInstance.addBlock(item);
}
store.importFormCommand(formInstance);

Validation

The store provides form definition validation:

dart
final errors = store.validate();
for (final error in errors) {
  print('${error.type}: ${error.propertyLabel} - ${error.errorDetail}');
  if (error.fieldTitle != null) {
    print('  Field: ${error.fieldTitle}');
  }
}

Validation checks:

  • Form must have at least one field
  • Field names must be unique
  • Required properties must have values
  • Property validators (e.g., identifier format for field names)

FormEditorDescriptor

The declarative way to configure the editor:

dart
final descriptor = FormEditorDescriptor.withDefaults(
  fieldTypeDescriptors: [
    // Customize specific field types
    ReferenceFieldDescriptor(
      providers: [
        myEntityProviderType(['course', 'instructor']),
      ],
    ),
  ],
);

final store = FormEditorStore.fromDescriptors([descriptor]);

ReferenceFieldDescriptor.providers accepts editor ProviderType instances. The built-in reference field registers the static provider by default; app or entity packages can contribute additional provider factories without subclassing ProviderType.

Multiple descriptors can be merged -- each feature contributes its own, and the store builds the unified registry.

Next Steps