Architecture
The Vyuh Form system follows a layered architecture with a clear split between runtime and editor concerns. Both packages share the same schema types but manage state independently.
Package Split
Layered Architecture
Descriptor System
Forms use the Vyuh content extension system for registration. Every component type has a TypeDescriptor that enables JSON deserialization and runtime discovery.
Feature Registration
The feature.dart file registers everything through a single FeatureDescriptor:
final feature = FeatureDescriptor(
name: 'forms',
title: 'Forms',
extensions: [
ContentExtensionDescriptor(
contents: [
// Form descriptor with validations, conditions, actions
FormDescriptor(
validations: [
RequiredValidation.typeDescriptor,
EmailValidation.typeDescriptor,
// ... 15 validation types
],
conditions: [
BooleanCondition.typeDescriptor,
StringCondition.typeDescriptor,
// ... Empty, Numeric, Date, and Compound conditions
],
actions: [
VisibilityAction.typeDescriptor,
EnabledAction.typeDescriptor,
// ... Focus, SetValue, Compound, and Validation actions
],
),
// StepForm descriptor with layout variants
StepFormDescriptor(
layouts: [
DefaultStepFormLayout.typeDescriptor,
VerticalStepperStepFormLayout.typeDescriptor,
SingleStepLayout.typeDescriptor,
],
),
// Field descriptors (one per field type)
TextFieldDescriptor(),
SelectFieldDescriptor(layouts: [...]),
BooleanFieldDescriptor(layouts: [...]),
// ... all 12 field types
],
contentBuilders: [
Form.contentBuilder,
FormRowBlock.contentBuilder,
FormSection.contentBuilder,
RepeatingSection.contentBuilder,
StepForm.contentBuilder,
TextField.contentBuilder,
// ... all field content builders
],
),
],
);FormDescriptor
FormDescriptor extends ContentDescriptor and carries the registration lists for cross-cutting concerns:
class FormDescriptor extends ContentDescriptor {
final List<TypeDescriptor<ValidationConfiguration>>? validations;
final List<TypeDescriptor<AsyncValidationConfiguration>>? asyncValidations;
final List<TypeDescriptor<FormAsyncValidationConfiguration>>? formAsyncValidations;
final List<TypeDescriptor<RuleCondition>>? conditions;
final List<TypeDescriptor<RuleAction>>? actions;
final List<TypeDescriptor<FormFieldRule>>? rules;
}The FormContentBuilder collects all descriptors during init() and registers them into the content type system, enabling JSON deserialization of any registered type.
FormGroup as Single Source of Truth
The Form class creates a single FormGroup that owns all field state:
Key design decisions:
- Flat structure -- Fields inside
FormSectionare registered at the top level of theFormGroup. Sections are visual only. - Rows are structural --
FormRowBlockcreates no control; it wraps fields in{field, span}row items so layout stays separate from field state. - Repeating sections use arrays --
RepeatingSectionregisters aFormArraywhere each instance is aFormGroupbuilt from the template. - Bridged validators --
ValidationConfigurationinstances are bridged to reactive_formsValidatorinstances at construction time. - Dynamic validators -- Fields with
ValidationActionrules get aDynamicValidatorthat can activate/deactivate validators at runtime. - Context-aware validators -- Fields with
DateDependencyValidation(or similar) get cross-field subscriptions that re-validate when dependent fields change. - MobX bridge -- The form's
validobservable is updated viaformGroup.valueChanges, enabling MobXObserverwidgets to react to validity changes.
Editor Architecture
The visual editor (vyuh_form_editor) uses a different state model:
// Store manages editor state with MobX
final store = FormEditorStore.withDefaults();
// Registry holds all registered types
final registry = store.registry;
registry.fieldTypes; // All available field types
registry.validationTypes; // All validation types
registry.ruleConditionTypes; // All condition types
registry.ruleActionTypes; // All action types
// The editor widget composes three panels
FormEditor(
store: store,
onFieldsChanged: (fields) { /* ... */ },
)The FormEditorRegistry is the sole compositor -- types never resolve other types. All cross-type composition (adding rules to fields, resolving conditions/actions in rules, mapping validations) flows through the registry's composition methods.
The editor model is block-based. FormInstance, SectionInstance, RepeatingSectionInstance, and RowInstance all use BlockContainer to declare which blocks they accept:
| Container | Accepted blocks |
|---|---|
FormInstance | fields, rows, sections, repeating sections |
SectionInstance | fields and rows |
RepeatingSectionInstance | fields and rows |
RowInstance | fields only |
This is why drag/drop, palette tap-to-add, import, and command-history operations can share the same addBlock, removeBlock, and move commands.
Editor Data Flow
The editor outputs JSON compatible with vyuh_feature_forms. The JSON can be stored in a CMS, file, or database, then deserialized at runtime into a live Form with full validation and rules.
Next Steps
- Form Model -- Form, FormRowBlock, FormSection, RepeatingSection, StepForm in detail
- Field Types -- All 12 field types
- Validation -- The validation pipeline