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 ​