diff --git a/README.md b/README.md
deleted file mode 100644
index ade77f9..0000000
--- a/README.md
+++ /dev/null
@@ -1,216 +0,0 @@
-
-
-
-
-Developed with π by [netglade][netglade_link]
-
-[![ci][ci_badge]][ci_badge_link]
-[![glade_forms][glade_forms_pub_badge]][glade_forms_pub_badge_link]
-[![license: MIT][license_badge]][license_badge_link]
-[![style: netglade analysis][style_badge]][style_badge_link]
-[![Discord][discord_badge]][discord_badge_link]
-
----
-
-A universal way to define form validators with support of translations.
-
-- [π What is this?](#-what-is-this)
-- [π Getting started](#-getting-started)
- - [GladeInput](#gladeinput)
- - [Defining input](#defining-input)
- - [StringToValueConverter (valueConverter)](#stringtovalueconverter-valueconverter)
- - [StringInput](#stringinput)
- - [Dependencies](#dependencies)
- - [π Adding translation support](#-adding-translation-support)
- - [GladeModel](#glademodel)
- - [GladeFormBuilder and GladeFormProvider](#gladeformbuilder-and-gladeformprovider)
- - [π¨ Debugging validators](#-debugging-validators)
-- [π Contributing](#-contributing)
-
-## π What is this?
-
-Glade forms offer unified way to define reusable form input
-with support of fluent API to define input's validators and with support of translation on top of that.
-
-**TBA DEMO SITE**
-
-
-## π Getting started
-
-Define you model and inputs:
-
-```dart
-class _Model extends GladeModel {
- late StringInput name;
- late GladeInput age;
- late StringInput email;
-
- @override
- List> get inputs => [name, age, email];
-
- _Model() {
- name = StringInput.required();
- age = GladeInput.intInput(value: 0);
- email = StringInput.create((validator) => (validator..isEmail()).build());
- }
-}
-
-```
-
-and wire-it up with Form
-
-```dart
-GladeFormBuilder(
- create: (context) => _Model(),
- builder: (context, model) => Form(
- autovalidateMode: AutovalidateMode.onUserInteraction,
- child: Column(
- children: [
- TextFormField(
- initialValue: model.name.value,
- validator: model.name.formFieldInputValidator,
- onChanged: (v) => model.stringFieldUpdateInput(model.name, v),
- decoration: const InputDecoration(labelText: 'Name'),
- ),
- TextFormField(
- initialValue: model.age.stringValue,
- validator: model.age.formFieldInputValidator,
- onChanged: (v) => model.stringFieldUpdateInput(model.age, v),
- decoration: const InputDecoration(labelText: 'Age'),
- ),
- TextFormField(
- initialValue: model.email.value,
- validator: model.email.formFieldInputValidator,
- onChanged: (v) => model.stringFieldUpdateInput(model.email, v),
- decoration: const InputDecoration(labelText: 'Email'),
- ),
- const SizedBox(height: 10),
- ElevatedButton(onPressed: model.isValid ? () {} : null, child: const Text('Save')),
- ],
- ),
- ),
-)
-```
-
-See DEMO site for more, complex, examples.
-
-### GladeInput
-Each form's input is represented by instance of `GladeInput` where `T` is value held by input.
-For simplicity we will interchange `input` and `GladeInput`.
-
-Every input is *dirty* or *pure* based on if value was updated (or not, yet).
-
-On each input we can defined
- - *validator* - Input's value must satistfy validation to be *valid* input.
- - *translateError* - If there are validation errors, function for error translations can be provided.
- - *inputKey* - For debug purposes and dependencies, each input can have unique name for simple identification.
- - *dependencies* - Each input can depend on another inputs for validation.
- - *valueConverter* - If input is used by TextField and `T` is not a `String`, value converter should be provided.
- - *valueComparator* - Sometimes it is handy to provied `initialValue` which will be never updated after input is mutated. `valueComparator` should be provided to compare `initialValue` and `value` if `T` is not comparable type by default.
- - *defaultTranslation* - If error's translations are simple, the default translation settings can be set instead of custom `translateError` method.
-
-#### Defining input
-Most of the time, input is created with `.create()` factory with defined validation, translation and other properties.
-
-Validation is defined through part methods on ValidatorFactory such as `notNull()`, `satisfy()` and other parts.
-
-Each validation rule defines
- - *value validation*, e.g `notNull()` defines that value can not be null. `satisfy()` defines predicate which has to be true to be valid etc.
- - **devErrorMessage** - message which will be displayed if no translation is not provided.
- - **key** - Validation error's identification. Usable for translation.
-
-
-This example defines validation that `int` value has to be greater or equal to 18.
-
-```dart
- ageInput = GladeInput.create(
- validator: (v) => (v
- ..notNull()
- ..satisfy(
- (value, extra, dependencies) {
- return value >= 18;
- },
- devError: (_, __) => 'Value must be greater or equal to 18',
- key: _ErrorKeys.ageRestriction,
- ))
- .build(),
- value: 0,
- valueConverter: GladeTypeConverters.intConverter,
- );
-```
-
-Order of validation parts matter. By default first failing part stops validation. Pass `stopOnFirstError: false` on `.build()` to validate all parts at once.
-
-#### StringToValueConverter (valueConverter)
-As noted before, if `T` is not a String, a converter from String to `T` has to be provided.
-
-GladeForms provides some predefined converters such as `IntConverter` and more. See `GladeTypeConverters` for more.
-
-
-#### StringInput
-StringInput is specialized variant of GladeInput which has aditional, string focused, validations such as `isEmail` or `isUrl`.
-
-#### Dependencies
-Input can have dependencies on another inputs to allow dependendent validation.
-`inputKey` should be assigned for each input to allow dependency work.
-
-In validation (or translation if needed) just call `dependencies.byKey()` to get dependendent input.
-
-
-### π Adding translation support
-
-Each validation error (and conversion error if any) can be translated. Provide `translateError` fucntion which accepts
-
-- `error` - Error to translate
-- `key` - Error's identification if any
-- `devMessage` - Provided `devError` from validator
-- `dependencies` - Input's dependencies
-
-Age example translation
-```dart
-
-translateError: (error, key, devMessage, {required dependencies}) {
- if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr();
-
- if (error.isConversionError) return LocaleKeys.ageRestriction_ageFormat.tr();
-
- return devMessage;
-},
-
-```
-
-### GladeModel
-GladeModel is base class for Form's model which holds all inputs together.
-
-For updating concrete input, call `updateInput` or `stringFieldUpdateInput` methods to update its value. GladeModel is ChangeNotifier so all dependant widgets will be rebuilt.
-
-### GladeFormBuilder and GladeFormProvider
-GladeFormProvider is predefined widget to provide GladeFormModel to widget's subtreee.
-
-Similarly GladeFormBuilder allows to listen to Model's changes and rebuilts its child.
-
-### π¨ Debugging validators
-
-There are some getter and methods on GladeInput / GladeModel which can be used for debugging.
-
-Use `model.formattedValidationErrors` to get all input's error formatted for simple debugging.
-
-There is also `GladeModelDebugInfo` widget which displays table of all model's inputs and their properties such as `isValid` or `validation error`.
-
-
-
-## π Contributing
-
-Your contributions are always welcome! Feel free to open pull request.
-
-[netglade_link]: https://netglade.com/en
-[ci_badge]: https://github.com/netglade/glade_forms/actions/workflows/ci.yaml/badge.svg
-[ci_badge_link]: https://github.com/netglade/glade_forms/actions
-[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
-[license_badge_link]: https://opensource.org/licenses/MIT
-[style_badge]: https://img.shields.io/badge/style-netglade_analysis-26D07C.svg
-[style_badge_link]: https://pub.dev/packages/netglade_analysis
-[glade_forms_pub_badge]: https://img.shields.io/pub/v/glade_forms.svg
-[glade_forms_pub_badge_link]: https://pub.dartlang.org/packages/glade_forms
-[discord_badge]: https://img.shields.io/discord/1091460081054400532.svg?logo=discord&color=blue
-[discord_badge_link]: https://discord.gg/sJfBBuDZy4
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 120000
index 0000000..70e7736
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+glade_forms/README.md
\ No newline at end of file
diff --git a/examples/gallery/README.md b/examples/gallery/README.md
index 2b3fce4..ade77f9 100644
--- a/examples/gallery/README.md
+++ b/examples/gallery/README.md
@@ -1,16 +1,216 @@
-# example
+
+
+
-A new Flutter project.
+Developed with π by [netglade][netglade_link]
-## Getting Started
+[![ci][ci_badge]][ci_badge_link]
+[![glade_forms][glade_forms_pub_badge]][glade_forms_pub_badge_link]
+[![license: MIT][license_badge]][license_badge_link]
+[![style: netglade analysis][style_badge]][style_badge_link]
+[![Discord][discord_badge]][discord_badge_link]
-This project is a starting point for a Flutter application.
+---
-A few resources to get you started if this is your first Flutter project:
+A universal way to define form validators with support of translations.
-- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+- [π What is this?](#-what-is-this)
+- [π Getting started](#-getting-started)
+ - [GladeInput](#gladeinput)
+ - [Defining input](#defining-input)
+ - [StringToValueConverter (valueConverter)](#stringtovalueconverter-valueconverter)
+ - [StringInput](#stringinput)
+ - [Dependencies](#dependencies)
+ - [π Adding translation support](#-adding-translation-support)
+ - [GladeModel](#glademodel)
+ - [GladeFormBuilder and GladeFormProvider](#gladeformbuilder-and-gladeformprovider)
+ - [π¨ Debugging validators](#-debugging-validators)
+- [π Contributing](#-contributing)
-For help getting started with Flutter development, view the
-[online documentation](https://docs.flutter.dev/), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+## π What is this?
+
+Glade forms offer unified way to define reusable form input
+with support of fluent API to define input's validators and with support of translation on top of that.
+
+**TBA DEMO SITE**
+
+
+## π Getting started
+
+Define you model and inputs:
+
+```dart
+class _Model extends GladeModel {
+ late StringInput name;
+ late GladeInput age;
+ late StringInput email;
+
+ @override
+ List> get inputs => [name, age, email];
+
+ _Model() {
+ name = StringInput.required();
+ age = GladeInput.intInput(value: 0);
+ email = StringInput.create((validator) => (validator..isEmail()).build());
+ }
+}
+
+```
+
+and wire-it up with Form
+
+```dart
+GladeFormBuilder(
+ create: (context) => _Model(),
+ builder: (context, model) => Form(
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ child: Column(
+ children: [
+ TextFormField(
+ initialValue: model.name.value,
+ validator: model.name.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.name, v),
+ decoration: const InputDecoration(labelText: 'Name'),
+ ),
+ TextFormField(
+ initialValue: model.age.stringValue,
+ validator: model.age.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.age, v),
+ decoration: const InputDecoration(labelText: 'Age'),
+ ),
+ TextFormField(
+ initialValue: model.email.value,
+ validator: model.email.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.email, v),
+ decoration: const InputDecoration(labelText: 'Email'),
+ ),
+ const SizedBox(height: 10),
+ ElevatedButton(onPressed: model.isValid ? () {} : null, child: const Text('Save')),
+ ],
+ ),
+ ),
+)
+```
+
+See DEMO site for more, complex, examples.
+
+### GladeInput
+Each form's input is represented by instance of `GladeInput` where `T` is value held by input.
+For simplicity we will interchange `input` and `GladeInput`.
+
+Every input is *dirty* or *pure* based on if value was updated (or not, yet).
+
+On each input we can defined
+ - *validator* - Input's value must satistfy validation to be *valid* input.
+ - *translateError* - If there are validation errors, function for error translations can be provided.
+ - *inputKey* - For debug purposes and dependencies, each input can have unique name for simple identification.
+ - *dependencies* - Each input can depend on another inputs for validation.
+ - *valueConverter* - If input is used by TextField and `T` is not a `String`, value converter should be provided.
+ - *valueComparator* - Sometimes it is handy to provied `initialValue` which will be never updated after input is mutated. `valueComparator` should be provided to compare `initialValue` and `value` if `T` is not comparable type by default.
+ - *defaultTranslation* - If error's translations are simple, the default translation settings can be set instead of custom `translateError` method.
+
+#### Defining input
+Most of the time, input is created with `.create()` factory with defined validation, translation and other properties.
+
+Validation is defined through part methods on ValidatorFactory such as `notNull()`, `satisfy()` and other parts.
+
+Each validation rule defines
+ - *value validation*, e.g `notNull()` defines that value can not be null. `satisfy()` defines predicate which has to be true to be valid etc.
+ - **devErrorMessage** - message which will be displayed if no translation is not provided.
+ - **key** - Validation error's identification. Usable for translation.
+
+
+This example defines validation that `int` value has to be greater or equal to 18.
+
+```dart
+ ageInput = GladeInput.create(
+ validator: (v) => (v
+ ..notNull()
+ ..satisfy(
+ (value, extra, dependencies) {
+ return value >= 18;
+ },
+ devError: (_, __) => 'Value must be greater or equal to 18',
+ key: _ErrorKeys.ageRestriction,
+ ))
+ .build(),
+ value: 0,
+ valueConverter: GladeTypeConverters.intConverter,
+ );
+```
+
+Order of validation parts matter. By default first failing part stops validation. Pass `stopOnFirstError: false` on `.build()` to validate all parts at once.
+
+#### StringToValueConverter (valueConverter)
+As noted before, if `T` is not a String, a converter from String to `T` has to be provided.
+
+GladeForms provides some predefined converters such as `IntConverter` and more. See `GladeTypeConverters` for more.
+
+
+#### StringInput
+StringInput is specialized variant of GladeInput which has aditional, string focused, validations such as `isEmail` or `isUrl`.
+
+#### Dependencies
+Input can have dependencies on another inputs to allow dependendent validation.
+`inputKey` should be assigned for each input to allow dependency work.
+
+In validation (or translation if needed) just call `dependencies.byKey()` to get dependendent input.
+
+
+### π Adding translation support
+
+Each validation error (and conversion error if any) can be translated. Provide `translateError` fucntion which accepts
+
+- `error` - Error to translate
+- `key` - Error's identification if any
+- `devMessage` - Provided `devError` from validator
+- `dependencies` - Input's dependencies
+
+Age example translation
+```dart
+
+translateError: (error, key, devMessage, {required dependencies}) {
+ if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr();
+
+ if (error.isConversionError) return LocaleKeys.ageRestriction_ageFormat.tr();
+
+ return devMessage;
+},
+
+```
+
+### GladeModel
+GladeModel is base class for Form's model which holds all inputs together.
+
+For updating concrete input, call `updateInput` or `stringFieldUpdateInput` methods to update its value. GladeModel is ChangeNotifier so all dependant widgets will be rebuilt.
+
+### GladeFormBuilder and GladeFormProvider
+GladeFormProvider is predefined widget to provide GladeFormModel to widget's subtreee.
+
+Similarly GladeFormBuilder allows to listen to Model's changes and rebuilts its child.
+
+### π¨ Debugging validators
+
+There are some getter and methods on GladeInput / GladeModel which can be used for debugging.
+
+Use `model.formattedValidationErrors` to get all input's error formatted for simple debugging.
+
+There is also `GladeModelDebugInfo` widget which displays table of all model's inputs and their properties such as `isValid` or `validation error`.
+
+
+
+## π Contributing
+
+Your contributions are always welcome! Feel free to open pull request.
+
+[netglade_link]: https://netglade.com/en
+[ci_badge]: https://github.com/netglade/glade_forms/actions/workflows/ci.yaml/badge.svg
+[ci_badge_link]: https://github.com/netglade/glade_forms/actions
+[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[license_badge_link]: https://opensource.org/licenses/MIT
+[style_badge]: https://img.shields.io/badge/style-netglade_analysis-26D07C.svg
+[style_badge_link]: https://pub.dev/packages/netglade_analysis
+[glade_forms_pub_badge]: https://img.shields.io/pub/v/glade_forms.svg
+[glade_forms_pub_badge_link]: https://pub.dartlang.org/packages/glade_forms
+[discord_badge]: https://img.shields.io/discord/1091460081054400532.svg?logo=discord&color=blue
+[discord_badge_link]: https://discord.gg/sJfBBuDZy4
\ No newline at end of file
diff --git a/examples/gallery/lib/main.dart b/examples/gallery/lib/main.dart
index e43807c..6dab352 100644
--- a/examples/gallery/lib/main.dart
+++ b/examples/gallery/lib/main.dart
@@ -2,11 +2,11 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
-import 'package:glade_forms_example/generated/locale_loader.g.dart';
-import 'package:glade_forms_example/localization_addon_custom.dart';
-import 'package:glade_forms_example/usecases/age_restricted_example.dart';
-import 'package:glade_forms_example/usecases/complex_object_mapping_example.dart';
-import 'package:glade_forms_example/usecases/quickstart_example.dart';
+import 'package:glade_forms_gallery/generated/locale_loader.g.dart';
+import 'package:glade_forms_gallery/localization_addon_custom.dart';
+import 'package:glade_forms_gallery/usecases/age_restricted_example.dart';
+import 'package:glade_forms_gallery/usecases/complex_object_mapping_example.dart';
+import 'package:glade_forms_gallery/usecases/quickstart_example.dart';
import 'package:widgetbook/widgetbook.dart';
// ignore: prefer-static-class, ok for now
diff --git a/examples/gallery/lib/usecases/age_restricted_example.dart b/examples/gallery/lib/usecases/age_restricted_example.dart
index 7da320c..5a72897 100644
--- a/examples/gallery/lib/usecases/age_restricted_example.dart
+++ b/examples/gallery/lib/usecases/age_restricted_example.dart
@@ -3,8 +3,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:glade_forms/glade_forms.dart';
-import 'package:glade_forms_example/generated/locale_keys.g.dart';
-import 'package:glade_forms_example/shared/usecase_container.dart';
+import 'package:glade_forms_gallery/generated/locale_keys.g.dart';
+import 'package:glade_forms_gallery/shared/usecase_container.dart';
class _ErrorKeys {
static const String ageRestriction = 'age-restriction';
diff --git a/examples/gallery/lib/usecases/complex_object_mapping_example.dart b/examples/gallery/lib/usecases/complex_object_mapping_example.dart
index a83486b..d5b7c64 100644
--- a/examples/gallery/lib/usecases/complex_object_mapping_example.dart
+++ b/examples/gallery/lib/usecases/complex_object_mapping_example.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:glade_forms/glade_forms.dart';
-import 'package:glade_forms_example/shared/usecase_container.dart';
+import 'package:glade_forms_gallery/shared/usecase_container.dart';
class _Item {
final int id;
diff --git a/examples/gallery/lib/usecases/quickstart_example.dart b/examples/gallery/lib/usecases/quickstart_example.dart
index 0eade64..c319a51 100644
--- a/examples/gallery/lib/usecases/quickstart_example.dart
+++ b/examples/gallery/lib/usecases/quickstart_example.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:glade_forms/glade_forms.dart';
-import 'package:glade_forms_example/shared/usecase_container.dart';
+import 'package:glade_forms_gallery/shared/usecase_container.dart';
class _Model extends GladeModel {
late StringInput name;
diff --git a/examples/gallery/pubspec.yaml b/examples/gallery/pubspec.yaml
index c20f27f..50d9064 100644
--- a/examples/gallery/pubspec.yaml
+++ b/examples/gallery/pubspec.yaml
@@ -1,6 +1,6 @@
-name: glade_forms_example
+name: glade_forms_gallery
description: Glade Forms - Interactive example
-version: 0.0.1
+version: 1.0.0
publish_to: none
environment:
@@ -13,8 +13,7 @@ dependencies:
flutter_highlighter: ^0.1.1
flutter_hooks: ^0.20.1
flutter_markdown: ^0.6.17+3
- glade_forms:
- path: ../../glade_forms
+ glade_forms: ^1.0.0
provider: ^6.0.2
widgetbook: ^3.3.0
diff --git a/glade_forms/README.md b/glade_forms/README.md
new file mode 100644
index 0000000..bd15722
--- /dev/null
+++ b/glade_forms/README.md
@@ -0,0 +1,222 @@
+
+
+
+
+Developed with π by [netglade][netglade_link]
+
+[![ci][ci_badge]][ci_badge_link]
+[![glade_forms][glade_forms_pub_badge]][glade_forms_pub_badge_link]
+[![license: MIT][license_badge]][license_badge_link]
+[![style: netglade analysis][style_badge]][style_badge_link]
+[![Discord][discord_badge]][discord_badge_link]
+
+---
+
+A universal way to define form validators with support of translations.
+
+- [π What is this?](#-what-is-this)
+- [π Getting started](#-getting-started)
+ - [GladeInput](#gladeinput)
+ - [Defining input](#defining-input)
+ - [StringToValueConverter (valueConverter)](#stringtovalueconverter-valueconverter)
+ - [StringInput](#stringinput)
+ - [Dependencies](#dependencies)
+ - [π Adding translation support](#-adding-translation-support)
+ - [GladeModel](#glademodel)
+ - [GladeFormBuilder and GladeFormProvider](#gladeformbuilder-and-gladeformprovider)
+ - [π¨ Debugging validators](#-debugging-validators)
+- [π Contributing](#-contributing)
+
+## π What is this?
+
+Glade forms offer unified way to define reusable form input
+with support of fluent API to define input's validators and with support of translation on top of that.
+
+**TBA DEMO SITE**
+
+
+## π Getting started
+
+Define you model and inputs:
+
+```dart
+class _Model extends GladeModel {
+ late StringInput name;
+ late GladeInput age;
+ late StringInput email;
+
+ @override
+ List> get inputs => [name, age, email];
+
+ _Model() {
+ name = StringInput.required();
+ age = GladeInput.intInput(value: 0);
+ email = StringInput.create((validator) => (validator..isEmail()).build());
+ }
+}
+
+```
+
+and wire-it up with Form
+
+```dart
+GladeFormBuilder(
+ create: (context) => _Model(),
+ builder: (context, model) => Form(
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ child: Column(
+ children: [
+ TextFormField(
+ initialValue: model.name.value,
+ validator: model.name.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.name, v),
+ decoration: const InputDecoration(labelText: 'Name'),
+ ),
+ TextFormField(
+ initialValue: model.age.stringValue,
+ validator: model.age.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.age, v),
+ decoration: const InputDecoration(labelText: 'Age'),
+ ),
+ TextFormField(
+ initialValue: model.email.value,
+ validator: model.email.formFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.email, v),
+ decoration: const InputDecoration(labelText: 'Email'),
+ ),
+ const SizedBox(height: 10),
+ ElevatedButton(onPressed: model.isValid ? () {} : null, child: const Text('Save')),
+ ],
+ ),
+ ),
+)
+```
+
+See DEMO site for more, complex, examples.
+
+### GladeInput
+Each form's input is represented by instance of `GladeInput` where `T` is value held by input.
+For simplicity we will interchange `input` and `GladeInput`.
+
+Every input is *dirty* or *pure* based on if value was updated (or not, yet).
+
+On each input we can defined
+ - *validator* - Input's value must satistfy validation to be *valid* input.
+ - *translateError* - If there are validation errors, function for error translations can be provided.
+ - *inputKey* - For debug purposes and dependencies, each input can have unique name for simple identification.
+ - *dependencies* - Each input can depend on another inputs for validation.
+ - *valueConverter* - If input is used by TextField and `T` is not a `String`, value converter should be provided.
+ - *valueComparator* - Sometimes it is handy to provied `initialValue` which will be never updated after input is mutated. `valueComparator` should be provided to compare `initialValue` and `value` if `T` is not comparable type by default.
+ - *defaultTranslation* - If error's translations are simple, the default translation settings can be set instead of custom `translateError` method.
+
+#### Defining input
+Most of the time, input is created with `.create()` factory with defined validation, translation and other properties.
+
+Validation is defined through part methods on ValidatorFactory such as `notNull()`, `satisfy()` and other parts.
+
+Each validation rule defines
+ - *value validation*, e.g `notNull()` defines that value can not be null. `satisfy()` defines predicate which has to be true to be valid etc.
+ - **devErrorMessage** - message which will be displayed if no translation is not provided.
+ - **key** - Validation error's identification. Usable for translation.
+
+
+This example defines validation that `int` value has to be greater or equal to 18.
+
+```dart
+ ageInput = GladeInput.create(
+ validator: (v) => (v
+ ..notNull()
+ ..satisfy(
+ (value, extra, dependencies) {
+ return value >= 18;
+ },
+ devError: (_, __) => 'Value must be greater or equal to 18',
+ key: _ErrorKeys.ageRestriction,
+ ))
+ .build(),
+ value: 0,
+ valueConverter: GladeTypeConverters.intConverter,
+ );
+```
+
+Order of validation parts matter. By default first failing part stops validation. Pass `stopOnFirstError: false` on `.build()` to validate all parts at once.
+
+#### StringToValueConverter (valueConverter)
+As noted before, if `T` is not a String, a converter from String to `T` has to be provided.
+
+GladeForms provides some predefined converters such as `IntConverter` and more. See `GladeTypeConverters` for more.
+
+
+#### StringInput
+StringInput is specialized variant of GladeInput which has additional, string related, validations such as `isEmail`, `isUrl`, `maxLength` and more.
+
+#### Dependencies
+Input can have dependencies on another inputs to allow dependendent validation.
+`inputKey` should be assigned for each input to allow dependency work.
+
+In validation (or translation if needed) just call `dependencies.byKey()` to get dependendent input.
+
+
+### π Adding translation support
+
+Each validation error (and conversion error if any) can be translated. Provide `translateError` fucntion which accepts
+
+- `error` - Error to translate
+- `key` - Error's identification if any
+- `devMessage` - Provided `devError` from validator
+- `dependencies` - Input's dependencies
+
+Age example translation
+```dart
+
+translateError: (error, key, devMessage, {required dependencies}) {
+ if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr();
+
+ if (error.isConversionError) return LocaleKeys.ageRestriction_ageFormat.tr();
+
+ return devMessage;
+},
+
+```
+
+### GladeModel
+GladeModel is base class for Form's model which holds all inputs together.
+
+For updating concrete input, call `updateInput` or `stringFieldUpdateInput` methods to update its value. GladeModel is ChangeNotifier so all dependant widgets will be rebuilt.
+
+### GladeFormBuilder and GladeFormProvider
+GladeFormProvider is predefined widget to provide GladeFormModel to widget's subtreee.
+
+Similarly GladeFormBuilder allows to listen to Model's changes and rebuilts its child.
+
+### π¨ Debugging validators
+
+There are some getter and methods on GladeInput / GladeModel which can be used for debugging.
+
+Use `model.formattedValidationErrors` to get all input's error formatted for simple debugging.
+
+There is also `GladeModelDebugInfo` widget which displays table of all model's inputs and their properties such as `isValid` or `validation error`.
+
+### Using validators without GladeInput
+Is is possible to use GladeValidator without associated GladeInputs.
+Just create instance of ``
+
+```dart
+
+```
+
+## π Contributing
+
+Your contributions are always welcome! Feel free to open pull request.
+
+[netglade_link]: https://netglade.com/en
+[ci_badge]: https://github.com/netglade/glade_forms/actions/workflows/ci.yaml/badge.svg
+[ci_badge_link]: https://github.com/netglade/glade_forms/actions
+[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[license_badge_link]: https://opensource.org/licenses/MIT
+[style_badge]: https://img.shields.io/badge/style-netglade_analysis-26D07C.svg
+[style_badge_link]: https://pub.dev/packages/netglade_analysis
+[glade_forms_pub_badge]: https://img.shields.io/pub/v/glade_forms.svg
+[glade_forms_pub_badge_link]: https://pub.dartlang.org/packages/glade_forms
+[discord_badge]: https://img.shields.io/discord/1091460081054400532.svg?logo=discord&color=blue
+[discord_badge_link]: https://discord.gg/sJfBBuDZy4
\ No newline at end of file
diff --git a/glade_forms/example/lib/example.dart b/glade_forms/example/lib/example.dart
new file mode 100644
index 0000000..7e2aff4
--- /dev/null
+++ b/glade_forms/example/lib/example.dart
@@ -0,0 +1,61 @@
+import 'package:flutter/material.dart';
+import 'package:glade_forms/glade_forms.dart';
+
+class _Model extends GladeModel {
+ late StringInput name;
+ late GladeInput age;
+ late StringInput email;
+
+ @override
+ List> get inputs => [name, age, email];
+
+ _Model() {
+ name = StringInput.required();
+ age = GladeInput.intInput(value: 0);
+ email = StringInput.create(validator: (validator) => (validator..isEmail()).build());
+ }
+}
+
+class Example extends StatelessWidget {
+ const Example({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return GladeFormBuilder(
+ create: (context) => _Model(),
+ builder: (context, model, _) => Padding(
+ padding: const EdgeInsets.all(32),
+ child: Form(
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ child: Column(
+ children: [
+ TextFormField(
+ initialValue: model.name.value,
+ validator: model.name.textFormFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.name, v),
+ decoration: const InputDecoration(labelText: 'Name'),
+ ),
+ TextFormField(
+ initialValue: model.age.stringValue,
+ validator: model.age.textFormFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.age, v),
+ decoration: const InputDecoration(labelText: 'Age'),
+ ),
+ TextFormField(
+ initialValue: model.email.value,
+ validator: model.email.textFormFieldInputValidator,
+ onChanged: (v) => model.stringFieldUpdateInput(model.email, v),
+ decoration: const InputDecoration(labelText: 'Email'),
+ ),
+ const SizedBox(height: 10),
+ ElevatedButton(
+ onPressed: model.isValid ? () {} : null,
+ child: const Text('Save'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/glade_forms/example/pubspec.yaml b/glade_forms/example/pubspec.yaml
new file mode 100644
index 0000000..05a73b0
--- /dev/null
+++ b/glade_forms/example/pubspec.yaml
@@ -0,0 +1,10 @@
+name: glade_forms_example
+description: Example project how to use GladeForms
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ">=2.18.7 <4.0.0"
+
+dependencies:
+ glade_forms: ^1.0.0
diff --git a/glade_forms/lib/src/core/glade_error_keys.dart b/glade_forms/lib/src/core/glade_error_keys.dart
index 4c53ea3..cc8d25f 100644
--- a/glade_forms/lib/src/core/glade_error_keys.dart
+++ b/glade_forms/lib/src/core/glade_error_keys.dart
@@ -1,7 +1,11 @@
class GladeErrorKeys {
+ static const String conversionError = 'value-cant-be-converted';
static const String stringEmpty = 'string-empty-error';
static const String stringNotUrl = 'string-not-url';
static const String stringNotEmail = 'string-not-email';
+ static const String stringPatternMatch = 'string-pattern-match';
+ static const String stringMinLength = 'string-min-length';
+ static const String stringMaxLength = 'string-max-length';
+ static const String stringExactLength = 'string-exact-length';
static const String valueIsNull = 'value-is-null';
- static const String conversionError = 'value-cant-be-converted';
}
diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart
index 243f7bc..e1150d2 100644
--- a/glade_forms/lib/src/core/glade_input.dart
+++ b/glade_forms/lib/src/core/glade_input.dart
@@ -6,14 +6,14 @@ import 'package:glade_forms/src/core/input_dependencies.dart';
import 'package:glade_forms/src/core/string_to_type_converter.dart';
import 'package:glade_forms/src/core/type_helper.dart';
import 'package:glade_forms/src/validator/validator.dart';
+import 'package:glade_forms/src/validator/validator_result.dart';
typedef ValueComparator = bool Function(T? initial, T? value);
-typedef ValidatorFactory = ValidatorInstance Function(GenericValidator v);
+typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v);
class GladeInput extends ChangeNotifier {
+ /// Compares initial and current value.
@protected
-
- /// Compares.
final ValueComparator? valueComparator;
@protected
@@ -51,7 +51,7 @@ class GladeInput extends ChangeNotifier {
/// Input's value was not changed.
bool get isPure => _isPure;
- ValidatorErrors? get error => _validator(value);
+ ValidatorResult? get validatorError => _validator(value);
/// [value] is equal to [initialValue].
///
@@ -114,7 +114,7 @@ class GladeInput extends ChangeNotifier {
valueComparator: valueComparator,
stringTovalueConverter: valueConverter,
dependenciesFactory: dependencies ?? () => [],
- validatorInstance: validatorInstance ?? GenericValidator().build(),
+ validatorInstance: validatorInstance ?? GladeValidator().build(),
translateError: translateError,
defaultTranslations: defaultTranslations,
);
@@ -137,7 +137,7 @@ class GladeInput extends ChangeNotifier {
valueComparator: valueComparator,
stringTovalueConverter: valueConverter,
dependenciesFactory: dependencies ?? () => [],
- validatorInstance: validatorInstance ?? GenericValidator().build(),
+ validatorInstance: validatorInstance ?? GladeValidator().build(),
translateError: translateError,
defaultTranslations: defaultTranslations,
);
@@ -158,7 +158,7 @@ class GladeInput extends ChangeNotifier {
StringToTypeConverter? valueConverter,
InputDependenciesFactory? dependencies,
}) {
- final validatorInstance = validator?.call(GenericValidator()) ?? GenericValidator().build();
+ final validatorInstance = validator?.call(GladeValidator()) ?? GladeValidator().build();
return pure
? GladeInput.pure(
@@ -210,7 +210,7 @@ class GladeInput extends ChangeNotifier {
dependencies: dependencies,
);
- // Predefined GenericInput with predefined `notNull` validation.
+ /// Predefined GenericInput with predefined `notNull` validation.
///
/// In case of need of any aditional validation use [GladeInput.create] directly.
factory GladeInput.required({
@@ -283,11 +283,12 @@ class GladeInput extends ChangeNotifier {
GladeInput asPure(T value) => copyWith(isPure: true, value: value);
- ValidatorErrors? validate() => _validator(value);
+ ValidatorResult? validate() => _validator(value);
- String? translate({String delimiter = '.'}) => _translate(delimiter: delimiter, customError: error);
+ String? translate({String delimiter = '.'}) => _translate(delimiter: delimiter, customError: validatorError);
- String errorFormatted({String delimiter = '|'}) => error?.errors.map((e) => e.toString()).join(delimiter) ?? '';
+ String errorFormatted({String delimiter = '|'}) =>
+ validatorError?.errors.map((e) => e.toString()).join(delimiter) ?? '';
/// Shorthand validator for TextFieldForm inputs.
///
@@ -308,7 +309,7 @@ class GladeInput extends ChangeNotifier {
} on ConvertError catch (formatError) {
return formatError.error != null
? _translate(delimiter: delimiter, customError: formatError)
- : formatError.devError(value, extra: error);
+ : formatError.devError(value, extra: validatorError);
}
}
@@ -370,11 +371,11 @@ class GladeInput extends ChangeNotifier {
/// Translates input's errors (validation or conversion).
String? _translate({String delimiter = '.', Object? customError}) {
- final err = customError ?? error;
+ final err = customError ?? validatorError;
if (err == null) return null;
- if (err is ValidatorErrors) {
+ if (err is ValidatorResult) {
return _translateGenericErrors(err, delimiter);
}
@@ -396,7 +397,7 @@ class GladeInput extends ChangeNotifier {
return err.toString();
}
- ValidatorErrors? _validator(T value) {
+ ValidatorResult? _validator(T value) {
final result = validatorInstance.validate(value);
if (result.isValid) return null;
@@ -404,7 +405,7 @@ class GladeInput extends ChangeNotifier {
return result;
}
- String _translateGenericErrors(ValidatorErrors inputErrors, String delimiter) {
+ String _translateGenericErrors(ValidatorResult inputErrors, String delimiter) {
final translateErrorTmp = translateError;
final defaultTranslationsTmp = this.defaultTranslations;
diff --git a/glade_forms/lib/src/core/string_to_type_converter.dart b/glade_forms/lib/src/core/string_to_type_converter.dart
index 1462b05..7aa66ef 100644
--- a/glade_forms/lib/src/core/string_to_type_converter.dart
+++ b/glade_forms/lib/src/core/string_to_type_converter.dart
@@ -38,8 +38,9 @@ class StringToTypeConverter {
} on ConvertError {
// If _cantConvert were used -> we already thrown an Error.
rethrow;
- // ignore: avoid_catches_without_on_clauses, has to be generic to catch everything
- } catch (e) {
+ }
+ // ignore: avoid_catches_without_on_clauses, has to be generic to catch everything
+ catch (e) {
// ignore: avoid-throw-in-catch-block, this method should throw custom exception
throw onError(input, e);
}
diff --git a/glade_forms/lib/src/model/glade_form_mixin.dart b/glade_forms/lib/src/model/glade_form_mixin.dart
index 4a0d236..423b583 100644
--- a/glade_forms/lib/src/model/glade_form_mixin.dart
+++ b/glade_forms/lib/src/model/glade_form_mixin.dart
@@ -17,12 +17,12 @@ mixin GladeFormMixin {
String get formattedValidationErrors => inputs.map((e) {
if (e.hasConversionError) return '${e.inputKey ?? e.runtimeType} - CONVERSION ERROR';
- if (e.error?.errors.isNotEmpty ?? false) {
+ if (e.validatorError?.errors.isNotEmpty ?? false) {
return '${e.inputKey ?? e.runtimeType} - ${e.errorFormatted()}';
}
return '${e.inputKey ?? e.runtimeType} - VALID';
}).join('\n');
- List