diff --git a/packages/wyatt_form_bloc/lib/src/cubit/form_cubit.dart b/packages/wyatt_form_bloc/lib/src/cubit/form_cubit.dart
new file mode 100644
index 00000000..98878f86
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/cubit/form_cubit.dart
@@ -0,0 +1,90 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/utils/set_operations.dart';
+
+part 'form_state.dart';
+
+class FormCubit extends Cubit {
+ final Future Function(FormState state)? _onSubmit;
+
+ FormCubit({
+ required FormData entries,
+ Future Function(FormState state)? onSubmit,
+ }) : _onSubmit = onSubmit,
+ super(FormState(data: entries));
+
+ void dataChanged(String field, FormInput dirtyValue) {
+ final _form = state.data.clone();
+
+ if (_form.contains(field)) {
+ _form.update(field, dirtyValue);
+ } else {
+ throw Exception('Form field $field not found');
+ }
+
+ emit(
+ state.copyWith(
+ data: _form,
+ status: FormValidator.validate(_form.inputs()),
+ ),
+ );
+ }
+
+ void updateFormData(
+ FormData data, {
+ SetOperation operation = SetOperation.replace,
+ }) {
+ FormData _form = data;
+
+ switch (operation) {
+ case SetOperation.replace:
+ _form = data;
+ break;
+ case SetOperation.difference:
+ _form = state.data.difference(data);
+ break;
+ case SetOperation.intersection:
+ _form = state.data.intersection(data);
+ break;
+ case SetOperation.union:
+ _form = state.data.union(data);
+ break;
+ }
+
+ emit(
+ state.copyWith(
+ data: _form,
+ status: FormValidator.validate(_form.inputs()),
+ ),
+ );
+ }
+
+ Future submitForm() async {
+ unawaited(
+ _onSubmit?.call(state).then((bool reemit) {
+ if (reemit) {
+ emit(state);
+ }
+ }),
+ );
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/cubit/form_state.dart b/packages/wyatt_form_bloc/lib/src/cubit/form_state.dart
new file mode 100644
index 00000000..a5c29a49
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/cubit/form_state.dart
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+part of 'form_cubit.dart';
+
+@immutable
+class FormState {
+ final FormStatus status;
+ final FormData data;
+ final String? errorMessage;
+
+ const FormState({
+ required this.data,
+ this.status = FormStatus.pure,
+ this.errorMessage,
+ });
+
+ FormState copyWith({
+ FormStatus? status,
+ FormData? data,
+ String? errorMessage,
+ }) {
+ return FormState(
+ status: status ?? this.status,
+ data: data ?? this.data,
+ errorMessage: errorMessage ?? this.errorMessage,
+ );
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+
+ return other is FormState &&
+ other.status == status &&
+ other.data == data &&
+ other.errorMessage == errorMessage;
+ }
+
+ @override
+ int get hashCode {
+ return status.hashCode ^ data.hashCode ^ errorMessage.hashCode;
+ }
+
+ @override
+ String toString() {
+ return 'FormState(status: $status, data: $data, '
+ 'errorMessage: $errorMessage)';
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form.dart b/packages/wyatt_form_bloc/lib/src/form/form.dart
new file mode 100644
index 00000000..62dfe624
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form.dart
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+export 'form_data.dart';
+export 'form_entry.dart';
+export 'form_input.dart';
+export 'form_input_status.dart';
+export 'form_status.dart';
+export 'form_validator.dart';
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_data.dart b/packages/wyatt_form_bloc/lib/src/form/form_data.dart
new file mode 100644
index 00000000..d5e40442
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_data.dart
@@ -0,0 +1,137 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:meta/meta.dart';
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/utils/list_equals.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+@immutable
+class FormData {
+ const FormData(this._entries);
+
+ FormData.empty() : this([]);
+
+ final List _entries;
+
+ List get entries => _entries;
+
+ List> inputs() {
+ return _entries
+ .map((FormEntry entry) => entry.input as FormInput)
+ .toList();
+ }
+
+ FormInput input(String field) {
+ if (contains(field)) {
+ return _entries
+ .firstWhere((FormEntry entry) => entry.field == field)
+ .input as FormInput;
+ } else {
+ throw Exception('Field $field does not exist in form');
+ }
+ }
+
+ bool contains(String field) {
+ return _entries.any((FormEntry entry) => entry.field == field);
+ }
+
+ FormData intersection(FormData other) {
+ final List entries = [];
+
+ for (final FormEntry entry in _entries) {
+ if (other.contains(entry.field)) {
+ entries.add(entry);
+ }
+ }
+
+ return FormData(entries);
+ }
+
+ FormData difference(FormData other) {
+ final List entries = [];
+
+ for (final FormEntry otherEntry in other._entries) {
+ if (!contains(otherEntry.field)) {
+ entries.add(otherEntry);
+ }
+ }
+
+ for (final FormEntry entry in _entries) {
+ if (!other.contains(entry.field)) {
+ entries.add(entry);
+ }
+ }
+
+ return FormData(entries);
+ }
+
+ FormData union(FormData other) {
+ final List entries = [];
+
+ for (final FormEntry entry in _entries) {
+ entries.add(entry);
+ }
+
+ for (final FormEntry otherEntry in other._entries) {
+ if (!contains(otherEntry.field)) {
+ entries.add(otherEntry);
+ }
+ }
+
+ return FormData(entries);
+ }
+
+ void update(String field, FormInput input) {
+ if (contains(field)) {
+ final index = _entries.indexOf(
+ _entries.firstWhere((FormEntry entry) => entry.field == field),
+ );
+ _entries[index] = _entries[index].copyWith(input: input);
+ }
+ }
+
+ FormData clone() {
+ return FormData(
+ _entries
+ .map((FormEntry entry) => entry.clone())
+ .toList(),
+ );
+ }
+
+ Map toMap() {
+ final map = {};
+ for (final entry in _entries) {
+ if (entry.export) {
+ map[entry.fieldName ?? entry.field] = entry.input.value;
+ }
+ }
+ return map;
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+
+ return other is FormData && listEquals(other._entries, _entries);
+ }
+
+ @override
+ int get hashCode => _entries.hashCode;
+
+ @override
+ String toString() => 'FormData(entries: $_entries)';
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_entry.dart b/packages/wyatt_form_bloc/lib/src/form/form_entry.dart
new file mode 100644
index 00000000..1cf3b799
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_entry.dart
@@ -0,0 +1,76 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:meta/meta.dart';
+import 'package:wyatt_form_bloc/src/form/form.dart';
+
+@immutable
+class FormEntry {
+ const FormEntry(this.field, this.input, {this.export = true, this.fieldName});
+
+ final String field;
+ final FormInput input;
+ final bool export;
+ final String? fieldName;
+
+ FormEntry copyWith({
+ String? field,
+ FormInput? input,
+ bool? export,
+ String? fieldName,
+ }) {
+ return FormEntry(
+ field ?? this.field,
+ input ?? this.input,
+ export: export ?? this.export,
+ fieldName: fieldName ?? this.fieldName,
+ );
+ }
+
+ FormEntry clone() {
+ return FormEntry(
+ field,
+ input,
+ export: export,
+ fieldName: fieldName,
+ );
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+
+ return other is FormEntry &&
+ other.field == field &&
+ other.input == input &&
+ other.export == export &&
+ other.fieldName == fieldName;
+ }
+
+ @override
+ int get hashCode {
+ return field.hashCode ^
+ input.hashCode ^
+ export.hashCode ^
+ fieldName.hashCode;
+ }
+
+ @override
+ String toString() {
+ return 'FormEntry(field: $field, input: $input, '
+ 'export: $export, fieldName: $fieldName)';
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_input.dart b/packages/wyatt_form_bloc/lib/src/form/form_input.dart
new file mode 100644
index 00000000..3759106a
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_input.dart
@@ -0,0 +1,111 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:meta/meta.dart';
+import 'package:wyatt_form_bloc/src/form/form.dart';
+
+/// {@template form_input}
+/// A [FormInput] represents the value of a single form input field.
+/// It contains information about the [FormInputStatus], [value], as well
+/// as validation status.
+///
+/// [FormInput] should be extended to define custom [FormInput] instances.
+///
+/// ```dart
+/// enum FirstNameError { empty }
+/// class FirstName extends FormInput {
+/// const FirstName.pure({String value = ''}) : super.pure(value);
+/// const FirstName.dirty({String value = ''}) : super.dirty(value);
+///
+/// @override
+/// FirstNameError? validator(String value) {
+/// return value.isEmpty ? FirstNameError.empty : null;
+/// }
+/// }
+/// ```
+/// {@endtemplate}
+@immutable
+abstract class FormInput {
+ const FormInput._(this.value, [this.pure = true]);
+
+ /// Constructor which create a `pure` [FormInput] with a given value.
+ const FormInput.pure(T value) : this._(value);
+
+ /// Constructor which create a `dirty` [FormInput] with a given value.
+ const FormInput.dirty(T value) : this._(value, false);
+
+ /// The value of the given [FormInput].
+ /// For example, if you have a `FormInput` for `FirstName`,
+ /// the value could be 'Joe'.
+ final T value;
+
+ /// If the [FormInput] is pure (has been touched/modified).
+ /// Typically when the `FormInput` is initially created,
+ /// it is created using the `FormInput.pure` constructor to
+ /// signify that the user has not modified it.
+ ///
+ /// For subsequent changes (in response to user input), the
+ /// `FormInput.dirty` constructor should be used to signify that
+ /// the `FormInput` has been manipulated.
+ final bool pure;
+
+ /// The [FormInputStatus] which can be one of the following:
+ /// * [FormInputStatus.pure]
+ /// - if the input has not been modified.
+ /// * [FormInputStatus.invalid]
+ /// - if the input has been modified and validation failed.
+ /// * [FormInputStatus.valid]
+ /// - if the input has been modified and validation succeeded.
+ FormInputStatus get status => pure
+ ? FormInputStatus.pure
+ : valid
+ ? FormInputStatus.valid
+ : FormInputStatus.invalid;
+
+ /// Returns a validation error if the [FormInput] is invalid.
+ /// Returns `null` if the [FormInput] is valid.
+ E? get error => validator(value);
+
+ /// Whether the [FormInput] value is valid according to the
+ /// overridden `validator`.
+ ///
+ /// Returns `true` if `validator` returns `null` for the
+ /// current [FormInput] value and `false` otherwise.
+ bool get valid => validator(value) == null;
+
+ /// Whether the [FormInput] value is not valid.
+ /// A value is invalid when the overridden `validator`
+ /// returns an error (non-null value).
+ bool get invalid => status == FormInputStatus.invalid;
+
+ /// A function that must return a validation error if the provided
+ /// [value] is invalid and `null` otherwise.
+ E? validator(T value);
+
+ @override
+ int get hashCode => value.hashCode ^ pure.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (other.runtimeType != runtimeType) return false;
+ return other is FormInput &&
+ other.value == value &&
+ other.pure == pure;
+ }
+
+ @override
+ String toString() => '$runtimeType($value, $pure)';
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_input_status.dart b/packages/wyatt_form_bloc/lib/src/form/form_input_status.dart
new file mode 100644
index 00000000..f8abb53e
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_input_status.dart
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/// Enum representing the status of a form input at any given point in time.
+enum FormInputStatus {
+ /// The form input has not been touched.
+ pure,
+
+ /// The form input is valid.
+ valid,
+
+ /// The form input is not valid.
+ invalid,
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_status.dart b/packages/wyatt_form_bloc/lib/src/form/form_status.dart
new file mode 100644
index 00000000..8e0709be
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_status.dart
@@ -0,0 +1,79 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/// Enum representing the status of a form at any given point in time.
+enum FormStatus {
+ /// The form has not been touched.
+ pure,
+
+ /// The form has been completely validated.
+ valid,
+
+ /// The form contains one or more invalid inputs.
+ invalid,
+
+ /// The form is in the process of being submitted.
+ submissionInProgress,
+
+ /// The form has been submitted successfully.
+ submissionSuccess,
+
+ /// The form submission failed.
+ submissionFailure,
+
+ /// The form submission has been canceled.
+ submissionCanceled
+}
+
+const Set _validatedFormStatuses = {
+ FormStatus.valid,
+ FormStatus.submissionInProgress,
+ FormStatus.submissionSuccess,
+ FormStatus.submissionFailure,
+ FormStatus.submissionCanceled,
+};
+
+/// Useful extensions on [FormStatus]
+extension FormStatusX on FormStatus {
+ /// Indicates whether the form is untouched.
+ bool get isPure => this == FormStatus.pure;
+
+ /// Indicates whether the form is completely validated.
+ bool get isValid => this == FormStatus.valid;
+
+ /// Indicates whether the form has been validated successfully.
+ /// This means the [FormStatus] is either:
+ /// * `FormStatus.valid`
+ /// * `FormStatus.submissionInProgress`
+ /// * `FormStatus.submissionSuccess`
+ /// * `FormStatus.submissionFailure`
+ bool get isValidated => _validatedFormStatuses.contains(this);
+
+ /// Indicates whether the form contains one or more invalid inputs.
+ bool get isInvalid => this == FormStatus.invalid;
+
+ /// Indicates whether the form is in the process of being submitted.
+ bool get isSubmissionInProgress => this == FormStatus.submissionInProgress;
+
+ /// Indicates whether the form has been submitted successfully.
+ bool get isSubmissionSuccess => this == FormStatus.submissionSuccess;
+
+ /// Indicates whether the form submission failed.
+ bool get isSubmissionFailure => this == FormStatus.submissionFailure;
+
+ /// Indicates whether the form submission has been canceled.
+ bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
+}
diff --git a/packages/wyatt_form_bloc/lib/src/form/form_validator.dart b/packages/wyatt_form_bloc/lib/src/form/form_validator.dart
new file mode 100644
index 00000000..07fae1ec
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/form/form_validator.dart
@@ -0,0 +1,30 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+
+/// Class which contains methods that help manipulate and manage
+/// [FormStatus] and [FormInputStatus] instances.
+class FormValidator {
+ /// Returns a [FormStatus] given a list of [FormInput].
+ static FormStatus validate(List inputs) {
+ return inputs.every((FormInput element) => element.pure)
+ ? FormStatus.pure
+ : inputs.any((FormInput input) => input.valid == false)
+ ? FormStatus.invalid
+ : FormStatus.valid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/src.dart b/packages/wyatt_form_bloc/lib/src/src.dart
new file mode 100644
index 00000000..cd8aa4f1
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/src.dart
@@ -0,0 +1,19 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+export 'form/form.dart';
+export 'utils/set_operations.dart';
+export 'validators/validators.dart';
diff --git a/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart b/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart
new file mode 100644
index 00000000..e7d1989c
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+bool listEquals(List? a, List? b) {
+ if (a == null) return b == null;
+ if (b == null || a.length != b.length) return false;
+ if (identical(a, b)) return true;
+ for (int index = 0; index < a.length; index += 1) {
+ if (a[index] != b[index]) return false;
+ }
+ return true;
+}
diff --git a/packages/wyatt_form_bloc/lib/src/utils/set_operations.dart b/packages/wyatt_form_bloc/lib/src/utils/set_operations.dart
new file mode 100644
index 00000000..09658a52
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/utils/set_operations.dart
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+enum SetOperation { replace, intersection, difference, union }
diff --git a/packages/wyatt_form_bloc/lib/src/validators/boolean.dart b/packages/wyatt_form_bloc/lib/src/validators/boolean.dart
new file mode 100644
index 00000000..6d38b598
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/boolean.dart
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template boolean}
+/// Form input for a bool input
+/// {@endtemplate}
+class Boolean extends FormInput {
+ /// {@macro boolean}
+ const Boolean.pure({bool? defaultValue = false})
+ : super.pure(defaultValue ?? false);
+
+ /// {@macro boolean}
+ const Boolean.dirty({bool value = false}) : super.dirty(value);
+
+ @override
+ ValidationError? validator(bool? value) {
+ return value != null ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart b/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart
new file mode 100644
index 00000000..99bbc9cf
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template confirmed_password}
+/// Form input for a confirmed password input.
+/// {@endtemplate}
+class ConfirmedPassword
+ extends FormInput {
+ /// {@macro confirmed_password}
+ const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
+
+ /// {@macro confirmed_password}
+ const ConfirmedPassword.dirty({required this.password, String value = ''})
+ : super.dirty(value);
+
+ /// The original password.
+ final String password;
+
+ @override
+ ValidationError? validator(String? value) {
+ return password == value ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/email.dart b/packages/wyatt_form_bloc/lib/src/validators/email.dart
new file mode 100644
index 00000000..24bbafcf
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/email.dart
@@ -0,0 +1,40 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template email}
+/// Form input for an email input.
+/// {@endtemplate}
+class Email extends FormInput {
+ /// {@macro email}
+ const Email.pure() : super.pure('');
+
+ /// {@macro email}
+ const Email.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _emailRegExp = RegExp(
+ r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
+ );
+
+ @override
+ ValidationError? validator(String? value) {
+ return _emailRegExp.hasMatch(value ?? '')
+ ? null
+ : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/iban.dart b/packages/wyatt_form_bloc/lib/src/validators/iban.dart
new file mode 100644
index 00000000..23a9d476
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/iban.dart
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template iban}
+/// Form input for an IBAN input.
+/// {@endtemplate}
+class Iban extends FormInput {
+ /// {@macro iban}
+ const Iban.pure() : super.pure('');
+
+ /// {@macro iban}
+ const Iban.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _regExp = RegExp(
+ r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$',
+ );
+
+ @override
+ ValidationError? validator(String? value) {
+ return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/name.dart b/packages/wyatt_form_bloc/lib/src/validators/name.dart
new file mode 100644
index 00000000..00dde6c6
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/name.dart
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template name}
+/// Form input for a name input.
+/// {@endtemplate}
+class Name extends FormInput {
+ /// {@macro name}
+ const Name.pure() : super.pure('');
+
+ /// {@macro name}
+ const Name.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _nameRegExp = RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$");
+
+ @override
+ ValidationError? validator(String? value) {
+ return _nameRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/password.dart b/packages/wyatt_form_bloc/lib/src/validators/password.dart
new file mode 100644
index 00000000..c755dab9
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/password.dart
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template password}
+/// Form input for a password input.
+/// {@endtemplate}
+class Password extends FormInput {
+ /// {@macro password}
+ const Password.pure() : super.pure('');
+
+ /// {@macro password}
+ const Password.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _passwordRegExp =
+ RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
+
+ @override
+ ValidationError? validator(String? value) {
+ return _passwordRegExp.hasMatch(value ?? '')
+ ? null
+ : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/phone.dart b/packages/wyatt_form_bloc/lib/src/validators/phone.dart
new file mode 100644
index 00000000..7c3ca018
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/phone.dart
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template phone}
+/// Form input for a phone input.
+/// {@endtemplate}
+class Phone extends FormInput {
+ /// {@macro phone}
+ const Phone.pure() : super.pure('');
+
+ /// {@macro phone}
+ const Phone.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _phoneRegExp =
+ RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$');
+
+ @override
+ ValidationError? validator(String? value) {
+ return _phoneRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/siren.dart b/packages/wyatt_form_bloc/lib/src/validators/siren.dart
new file mode 100644
index 00000000..5e2b2864
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/siren.dart
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template siren}
+/// Form input for a SIREN input.
+/// {@endtemplate}
+class Siren extends FormInput {
+ /// {@macro siren}
+ const Siren.pure() : super.pure('');
+
+ /// {@macro siren}
+ const Siren.dirty([String value = '']) : super.dirty(value);
+
+ static final RegExp _regExp = RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$');
+
+ @override
+ ValidationError? validator(String? value) {
+ return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/text_string.dart b/packages/wyatt_form_bloc/lib/src/validators/text_string.dart
new file mode 100644
index 00000000..137cf0cb
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/text_string.dart
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:wyatt_form_bloc/src/form/form.dart';
+import 'package:wyatt_form_bloc/src/validators/validators.dart';
+
+/// {@template text_string}
+/// Form input for a text input
+/// {@endtemplate}
+class TextString extends FormInput {
+ /// {@macro text_string}
+ const TextString.pure() : super.pure('');
+
+ /// {@macro text_string}
+ const TextString.dirty([String value = '']) : super.dirty(value);
+
+ @override
+ ValidationError? validator(String? value) {
+ return (value?.isNotEmpty ?? false)
+ ? null
+ : ValidationError.invalid;
+ }
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/validation_error.dart b/packages/wyatt_form_bloc/lib/src/validators/validation_error.dart
new file mode 100644
index 00000000..d5ae6088
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/validation_error.dart
@@ -0,0 +1,20 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+enum ValidationError {
+ /// Generic invalid error.
+ invalid
+}
diff --git a/packages/wyatt_form_bloc/lib/src/validators/validators.dart b/packages/wyatt_form_bloc/lib/src/validators/validators.dart
new file mode 100644
index 00000000..4a4a3fb5
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/src/validators/validators.dart
@@ -0,0 +1,26 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+export 'boolean.dart';
+export 'confirmed_password.dart';
+export 'email.dart';
+export 'iban.dart';
+export 'name.dart';
+export 'password.dart';
+export 'phone.dart';
+export 'siren.dart';
+export 'text_string.dart';
+export 'validation_error.dart';
diff --git a/packages/wyatt_form_bloc/lib/wyatt_form_bloc.dart b/packages/wyatt_form_bloc/lib/wyatt_form_bloc.dart
new file mode 100644
index 00000000..066e4a1f
--- /dev/null
+++ b/packages/wyatt_form_bloc/lib/wyatt_form_bloc.dart
@@ -0,0 +1,20 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/// A form library for BLoC.
+library wyatt_form_bloc;
+
+export 'src/src.dart';