manifold 1.0.1
manifold: ^1.0.1 copied to clipboard
Edit objects with UI
Manifold #
Visual editor for immutable Dart objects generated by artifact.
Manifold uses artifact reflection metadata to build forms automatically, edit values, and emit a new object instance on every change.
Highlights #
- Auto-generates editors from
@Propertyfields. - Supports primitives:
String,int,double,bool,DateTime(nullable and non-nullable). - Supports
enumfields (nullable and non-nullable). - Supports nested artifact objects.
- Supports recursive
List,Set, andMaptrees with mixed nesting.- Example:
Map<String, List<Map<int, Map<double, ASubObject>>>>
- Example:
- Supports custom editor overrides by
Type(user overrides replace defaults). - Supports custom decorators and full decorator override.
- Supports container policy (
embedvssubScreen) for collections, maps, and sub-objects. - Supports live search filtering.
- Supports read-only/view mode.
- Includes raw model dialogs for YAML, JSON, TOML, and TOON.
Requirements #
- Flutter + Dart SDK compatible with this package (
sdk: ^3.10.7in this repo). - Models generated by
artifact/artifact_gen. - Fields you want visible in the editor must have
@Property.
Install #
dependencies:
manifold: any
artifact: any
dev_dependencies:
build_runner: any
artifact_gen: any
Then run codegen:
dart run build_runner build --delete-conflicting-outputs
Define Models #
import 'package:manifold/manifold.dart';
enum EventType { build, release, test }
@manifold
class Note {
@Property()
final String text;
const Note({this.text = ''});
}
@manifold
class Species {
@Property()
final String name;
@Property()
final double rating;
@Property()
final EventType latestEvent;
@Property()
final List<Note> notes;
@Property()
final Set<EventType> eventTypes;
@Property()
final Map<String, List<Note>> notesByCategory;
const Species({
required this.name,
required this.rating,
required this.latestEvent,
this.notes = const [],
this.eventTypes = const {},
this.notesByCategory = const {},
});
}
Quick Start #
import 'package:arcane/arcane.dart';
import 'package:my_app/gen/artifacts.gen.dart'; // registers artifact accessors
import 'package:my_app/models.dart';
import 'package:manifold/editor.dart';
class SpeciesEditorScreen extends StatelessWidget {
const SpeciesEditorScreen({super.key});
@override
Widget build(BuildContext context) {
return Screen(
child: ManifoldEditor<Species>(
edit: Species(
name: 'Pigeon',
rating: 4.2,
latestEvent: EventType.build,
),
onChanged: (species) {
print(species); // new immutable instance every edit
},
),
);
}
}
Collections And Maps #
Manifold supports add/remove/edit for recursive collections:
List<T>: reorderableSet<T>: not reorderableMap<K, V>: key/value entry editor with unique-key enforcement
Collection and map values are materialized using artifact $AT descriptors, so deep nested structures stay strongly typed.
When adding artifact values (for example List<Note> or Map<String, Note>), Manifold resolves $AClass<Note> and calls .construct().
Default add values:
String->""int->0double->0.0bool->falseDateTime->DateTime.now()enum-> first inferred enum option- artifact ->
$AClass<T>.construct() - nullable target ->
null
Read-Only Mode #
Use viewOnly: true to disable edits across the entire tree. Field controls are disabled and provider pushes are ignored.
ManifoldEditor<Species>(
viewOnly: true,
onChanged: (_) {},
)
Customization #
1) Per-field early override: propertyEditorBuilder #
Runs before default behavior. Return null to fall back.
ManifoldEditor<Species>(
propertyEditorBuilder: (ctx) {
if (ctx.field.name == 'name') {
return TextField(
placeholder: 'Species name',
onChanged: (v) => ctx.onChanged(v),
);
}
return null;
},
onChanged: (v) {},
)
2) Type override map: editorOverrides #
User map is layered on top of built-ins, so matching keys replace defaults.
ManifoldEditor<Species>(
editorOverrides: {
String: (ctx) => TextField(
placeholder: 'Custom string editor',
onChanged: (v) => ctx.onChanged(v),
),
Note: (ctx) => MyNoteEditor(
value: ctx.value as Note?,
onChanged: (v) => ctx.onChanged(v),
),
},
onChanged: (v) {},
)
ManifoldEditorOverrideContext includes:
field,propertyvalue,valueTypeonChangedcollectionElementreadOnly
3) Decorators #
Choose a built-in decorator:
import 'package:manifold/decorator/compact_property_decorator.dart';
ManifoldEditor<Species>(
decorator: const ManifoldCompactPropertyDecorator(),
onChanged: (v) {},
)
Or fully override with decoratorBuilder:
ManifoldEditor<Species>(
decoratorBuilder: (ctx) {
return Card(
titleText: ctx.label,
subtitleText: ctx.property?.description,
child: ctx.editor,
);
},
onChanged: (v) {},
)
4) Container style (embed vs subScreen) #
Use containerStyle to decide layout per field/value/type for:
getCollectionStylegetMapStylegetSubObjectStyle
class MyContainerStyle extends ManifoldContainerStyle {
const MyContainerStyle();
@override
ManifoldContainerType getCollectionStyle<M, T>({
required ManifoldEditorScope<M> scope,
required $AFld field,
required Iterable<T>? value,
}) {
if ((value?.length ?? 0) > 4) {
return ManifoldContainerType.subScreen;
}
return ManifoldContainerType.embed;
}
@override
ManifoldContainerType getMapStyle<M, K, V>({
required ManifoldEditorScope<M> scope,
required $AFld field,
required Map<K, V>? value,
}) {
return ManifoldContainerType.embed;
}
@override
ManifoldContainerType getSubObjectStyle<M, O>({
required ManifoldEditorScope<M> scope,
required $AFld field,
required O? value,
}) {
return ManifoldContainerType.subScreen;
}
}
Raw Editors #
Root overflow menu includes:
- YAML
- JSON
- TOML
- TOON
If a format cannot encode the current object graph (for example TOML with null values), that format is shown as unavailable instead of crashing the editor.
Behavior Notes #
- Only
@Propertyfields are rendered. - Live search filters the visible fields.
- Collection/map element identity is tracked to reduce focus loss and animation churn during updates/reorder/search.
- For fire_crud-style models,
documentPathis preserved across edits.
Troubleshooting #
"No artifact accessor found for type ..." #
Artifact reflection for the model type was not registered.
Checklist:
- Confirm model has
@manifold. - Run build_runner again.
- Import generated artifacts (for example
gen/artifacts.gen.dart) before openingManifoldEditor.
Minimal API Reference #
ManifoldEditor<T> primary parameters:
edit: initial object (optional)onChanged: required edited instance callbackviewOnly: disables editingeditorOverrides: type-based override mappropertyEditorBuilder: per-field early override hookdecorator: choose decorator implementationdecoratorBuilder: direct decoration overridecontainerStyle: choose embed/subScreen for collections/maps/sub-objectsinlineSubObjects: force nested object embeddingdense: force dense/compact layout instead of responsive auto mode
Example Project #
See /example for a working app with:
- nested artifact objects
- enums and doubles
- recursive lists/sets
- nested maps mixed with collections