Compare commits
4 Commits
9203df94a0
...
94b0ff01ad
Author | SHA1 | Date | |
---|---|---|---|
94b0ff01ad | |||
741f05e1be | |||
d0f1d07363 | |||
08ac6d8fda |
@ -30,21 +30,25 @@ Form Bloc for Dart & Flutter.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Form
|
- Form
|
||||||
* FormInput: *atom of a form*
|
* FormInputValidator:
|
||||||
- Store data
|
- Store data
|
||||||
- Validate this data
|
- Validate this data
|
||||||
* FormEntry: *shell of this atom*
|
* FormInputMetadata:
|
||||||
|
- Store infos and options about an input.
|
||||||
|
* FormEntry:
|
||||||
- Associate a key to an input
|
- Associate a key to an input
|
||||||
- Configure form attribute (exportation, name...)
|
- Configure form attribute (exportation, name...)
|
||||||
* FormData: *collection of entries*
|
* FormInput:
|
||||||
- Contain all entries
|
- Associaite a key, to a validator and metadata.
|
||||||
|
* FormData: *collection of inputs*
|
||||||
|
- Contain all inputs
|
||||||
- Basic set operation
|
- Basic set operation
|
||||||
|
|
||||||
- FormDataCubit
|
- FormDataCubit
|
||||||
* Data management behind a form.
|
* Data management behind a form.
|
||||||
- Use entries to pass a FormData object
|
- Use entries to pass a FormData object
|
||||||
- You can use several pre configured FormInput for validation
|
- You can use several pre configured `FormInputValidator` for validation
|
||||||
- You can use updateFormData() to change FormData and validators during runtime (intersection, union, difference or replace)
|
- You can use updateFormData() to change `FormData` during runtime (intersection, union, difference or replace)
|
||||||
|
|
||||||
- Consistent
|
- Consistent
|
||||||
* Every class have same naming convention
|
* Every class have same naming convention
|
||||||
@ -59,4 +63,120 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
todo
|
Firstly, you have to create your form inputs.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
static List<FormInput> getNormalEntries() {
|
||||||
|
return const [
|
||||||
|
FormInput(formFieldName, Name.pure()),
|
||||||
|
FormInput(formFieldEmail, Email.pure()),
|
||||||
|
FormInput(formFieldPhone, Phone.pure()),
|
||||||
|
FormInput(formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||||
|
FormInput(formFieldRadio, TextString.pure()),
|
||||||
|
FormInput(formFieldPro, Boolean.pure(), metadata: FormInputMetadata<void>(name: 'business')),
|
||||||
|
FormInput(formFieldHidden, Boolean.pure(), metadata: FormInputMetadata<void>(export: false)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FormInput> getBusinessEntries() {
|
||||||
|
const entries = [
|
||||||
|
FormInput(formFieldSiren, Siren.pure()),
|
||||||
|
FormInput(formFieldIban, Iban.pure()),
|
||||||
|
];
|
||||||
|
return getNormalEntries() + entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormData getNormalFormData() {
|
||||||
|
return FormData(getNormalEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormData getProFormData() {
|
||||||
|
return FormData(getBusinessEntries());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Let's create functions to enable dynamic changes in runtime.
|
||||||
|
|
||||||
|
> When creating metadata with `FormInputMetadata<T>`, `T` is the type of `extra` object.
|
||||||
|
|
||||||
|
Then a `FormData` is a collection of inputs. You can `validate`, `contains`, `intersection`, `difference`, `union`, or `clone` this data.
|
||||||
|
|
||||||
|
After that, you have to extends `FormDataCubit`.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomFormCubit extends FormDataCubit {
|
||||||
|
CustomFormCubit({required FormData inputs}) : super(inputs: inputs);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> submitForm() {
|
||||||
|
log(state.data.toMap().toString());
|
||||||
|
// Handle all your logic here.
|
||||||
|
emit(...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> The submited form is in `state.data` !
|
||||||
|
|
||||||
|
Don't forget to create and provide it !
|
||||||
|
|
||||||
|
```dart
|
||||||
|
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());
|
||||||
|
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => _formCubit,
|
||||||
|
child: const WidgetTree(),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now create the first form input !
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _EmailInput extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return TextField(
|
||||||
|
onChanged: (email) => context
|
||||||
|
.read<FormDataCubit>()
|
||||||
|
.dataChanged(formFieldEmail, Email.dirty(email)),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'email',
|
||||||
|
helperText: '',
|
||||||
|
errorText: state.data.isNotValid(formFieldEmail)
|
||||||
|
? 'invalid email'
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can create the trigger
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _SignUpButton extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
buildWhen: (previous, current) => previous.status != current.status,
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.status.isSubmissionInProgress
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: state.status.isValidated
|
||||||
|
? () => context.read<FormDataCubit>().submitForm()
|
||||||
|
: null,
|
||||||
|
child: const Text('SIGN UP'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> With `state.status` you can access the status of the form to check if there is an error or if it's in submission.
|
@ -1,10 +1,30 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 5464c5bac742001448fe4fc0597be939379f88ea
|
revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: web
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
@ -16,31 +16,37 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:form_bloc_example/app/metadata.dart';
|
||||||
import 'package:form_bloc_example/constants.dart';
|
import 'package:form_bloc_example/constants.dart';
|
||||||
import 'package:form_bloc_example/cubit/custom_form_cubit.dart';
|
import 'package:form_bloc_example/cubit/custom_form_cubit.dart';
|
||||||
import 'package:form_bloc_example/sign_up/sign_up_page.dart';
|
import 'package:form_bloc_example/sign_up/sign_up_page.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
const blue = Metadata(category: Category.perso);
|
||||||
|
const red = Metadata(category: Category.business);
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class App extends StatelessWidget {
|
||||||
const App({Key? key}) : super(key: key);
|
const App({Key? key}) : super(key: key);
|
||||||
|
|
||||||
static List<FormEntry> getNormalEntries() {
|
static List<FormInput> getNormalEntries() {
|
||||||
return const [
|
return const [
|
||||||
FormEntry(formFieldName, Name.pure()),
|
FormInput(formFieldName, Name.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
|
||||||
FormEntry(formFieldEmail, Email.pure()),
|
FormInput(formFieldEmail, Email.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
|
||||||
FormEntry(formFieldPhone, Phone.pure()),
|
FormInput(formFieldPhone, Phone.pure(), metadata: FormInputMetadata<Metadata>(extra: red)),
|
||||||
FormEntry(
|
FormInput(
|
||||||
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||||
FormEntry(formFieldRadio, TextString.pure()),
|
FormInput(formFieldRadio, TextString.pure()),
|
||||||
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
|
FormInput(formFieldPro, Boolean.pure(),
|
||||||
FormEntry(formFieldHidden, Boolean.pure(), export: false),
|
metadata: FormInputMetadata<Metadata>(name: 'business')),
|
||||||
|
FormInput(formFieldHidden, Boolean.pure(),
|
||||||
|
metadata: FormInputMetadata<Metadata>(export: false, extra: blue)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<FormEntry> getBusinessEntries() {
|
static List<FormInput> getBusinessEntries() {
|
||||||
const entries = [
|
const entries = [
|
||||||
FormEntry(formFieldSiren, Siren.pure()),
|
FormInput(formFieldSiren, Siren.pure()),
|
||||||
FormEntry(formFieldIban, Iban.pure()),
|
FormInput(formFieldIban, Iban.pure()),
|
||||||
];
|
];
|
||||||
return getNormalEntries() + entries;
|
return getNormalEntries() + entries;
|
||||||
}
|
}
|
||||||
@ -55,7 +61,7 @@ class App extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData());
|
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => _formCubit,
|
create: (context) => _formCubit,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@ -14,9 +15,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
part of 'custom_form_cubit.dart';
|
enum Category {
|
||||||
|
perso,
|
||||||
|
business
|
||||||
|
}
|
||||||
|
|
||||||
@immutable
|
class Metadata {
|
||||||
abstract class CustomFormState {}
|
final Category category;
|
||||||
|
|
||||||
class CustomFormInitial extends CustomFormState {}
|
const Metadata({
|
||||||
|
required this.category,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return category.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,10 @@
|
|||||||
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
part 'custom_form_state.dart';
|
|
||||||
|
|
||||||
class CustomFormCubit extends FormDataCubit {
|
class CustomFormCubit extends FormDataCubit {
|
||||||
CustomFormCubit({required FormData entries}) : super(entries: entries);
|
CustomFormCubit({required FormData inputs}) : super(inputs: inputs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitForm() {
|
Future<void> submitForm() {
|
||||||
|
@ -19,35 +19,83 @@ import 'dart:developer';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:form_bloc_example/app/app.dart';
|
import 'package:form_bloc_example/app/app.dart';
|
||||||
|
import 'package:form_bloc_example/app/metadata.dart';
|
||||||
import 'package:form_bloc_example/constants.dart';
|
import 'package:form_bloc_example/constants.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
class CategoryIndicator extends StatelessWidget {
|
||||||
|
const CategoryIndicator(
|
||||||
|
{Key? key, required this.field, required this.builder})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final String field;
|
||||||
|
final Function(BuildContext context, FormDataState state) builder;
|
||||||
|
|
||||||
|
Color computeColor(Metadata meta) {
|
||||||
|
switch (meta.category) {
|
||||||
|
case Category.perso:
|
||||||
|
return Colors.blue;
|
||||||
|
case Category.business:
|
||||||
|
return Colors.red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: ((context, state) {
|
||||||
|
final meta = state.data.metadataOf(field).extra as Metadata;
|
||||||
|
final color = computeColor(meta);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 8,
|
||||||
|
width: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20,),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - 45,
|
||||||
|
child: builder(context, state),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _NameInput extends StatelessWidget {
|
class _NameInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
builder: (context, state) {
|
field: formFieldName,
|
||||||
return TextField(
|
builder: (context, state) {
|
||||||
onChanged: (name) => context
|
return TextField(
|
||||||
.read<FormDataCubit>()
|
onChanged: (name) => context
|
||||||
.dataChanged(formFieldName, Name.dirty(name)),
|
.read<FormDataCubit>()
|
||||||
keyboardType: TextInputType.name,
|
.dataChanged(formFieldName, Name.dirty(name)),
|
||||||
decoration: InputDecoration(
|
keyboardType: TextInputType.name,
|
||||||
labelText: 'name',
|
decoration: InputDecoration(
|
||||||
helperText: '',
|
labelText: 'name',
|
||||||
errorText:
|
helperText: '',
|
||||||
state.data.input(formFieldName).invalid ? 'invalid name' : null,
|
errorText:
|
||||||
),
|
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
class _EmailInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldEmail,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => context
|
onChanged: (email) => context
|
||||||
@ -57,9 +105,8 @@ class _EmailInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'email',
|
labelText: 'email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldEmail).invalid
|
errorText:
|
||||||
? 'invalid email'
|
state.data.isNotValid(formFieldEmail) ? 'invalid email' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -70,7 +117,8 @@ class _EmailInput extends StatelessWidget {
|
|||||||
class _PhoneInput extends StatelessWidget {
|
class _PhoneInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldPhone,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (phone) => context
|
onChanged: (phone) => context
|
||||||
@ -80,9 +128,8 @@ class _PhoneInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'phone',
|
labelText: 'phone',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldPhone).invalid
|
errorText:
|
||||||
? 'invalid phone'
|
state.data.isNotValid(formFieldPhone) ? 'invalid phone' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -93,7 +140,8 @@ class _PhoneInput extends StatelessWidget {
|
|||||||
class _SirenInput extends StatelessWidget {
|
class _SirenInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldName,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (siren) => context
|
onChanged: (siren) => context
|
||||||
@ -103,9 +151,8 @@ class _SirenInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'siren',
|
labelText: 'siren',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldSiren).invalid
|
errorText:
|
||||||
? 'invalid SIREN'
|
state.data.isNotValid(formFieldSiren) ? 'invalid SIREN' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -127,7 +174,7 @@ class _IbanInput extends StatelessWidget {
|
|||||||
labelText: 'iban',
|
labelText: 'iban',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText:
|
errorText:
|
||||||
state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null,
|
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -141,7 +188,7 @@ class _CheckListInput extends StatelessWidget {
|
|||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _input =
|
final _input =
|
||||||
state.data.input<List<String>>(formFieldList) as ListOption<String>;
|
state.data.validatorOf<ListOption<String>>(formFieldList);
|
||||||
final _options = _input.value;
|
final _options = _input.value;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@ -192,8 +239,7 @@ class _RadioListInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _input =
|
final _input = state.data.validatorOf<TextString>(formFieldRadio);
|
||||||
state.data.input<String>(formFieldRadio) as TextString;
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -249,7 +295,7 @@ class _CheckHiddenInput extends StatelessWidget {
|
|||||||
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: state.data.input<bool>(formFieldHidden).value,
|
value: state.data.validatorOf<Boolean>(formFieldHidden).value,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final value = v!; // state is false, so value can't be null
|
final value = v!; // state is false, so value can't be null
|
||||||
|
|
||||||
@ -272,7 +318,7 @@ class _CheckIsProInput extends StatelessWidget {
|
|||||||
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: state.data.input<bool>(formFieldPro).value,
|
value: state.data.validatorOf<Boolean>(formFieldPro).value,
|
||||||
onChanged: (isPro) {
|
onChanged: (isPro) {
|
||||||
final value = isPro!; // state is false, so value can't be null
|
final value = isPro!; // state is false, so value can't be null
|
||||||
|
|
||||||
@ -370,7 +416,7 @@ class SignUpForm extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
BlocBuilder<FormDataCubit, FormDataState>(
|
BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.data.input<bool>(formFieldPro).value) {
|
if (state.data.validatorOf<Boolean>(formFieldPro).value) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
_SirenInput(),
|
_SirenInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.16.2 <3.0.0"
|
sdk: ">=2.17.2 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
BIN
packages/wyatt_form_bloc/example/web/favicon.png
Normal file
BIN
packages/wyatt_form_bloc/example/web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-192.png
Normal file
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-512.png
Normal file
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-maskable-192.png
Normal file
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-maskable-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-maskable-512.png
Normal file
BIN
packages/wyatt_form_bloc/example/web/icons/Icon-maskable-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
58
packages/wyatt_form_bloc/example/web/index.html
Normal file
58
packages/wyatt_form_bloc/example/web/index.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="example">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>example</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
}
|
||||||
|
}).then(function(engineInitializer) {
|
||||||
|
return engineInitializer.initializeEngine();
|
||||||
|
}).then(function(appRunner) {
|
||||||
|
return appRunner.runApp();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
packages/wyatt_form_bloc/example/web/manifest.json
Normal file
35
packages/wyatt_form_bloc/example/web/manifest.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "example",
|
||||||
|
"short_name": "example",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
import 'package:wyatt_form_bloc/src/form/form.dart';
|
||||||
@ -24,19 +25,19 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
part 'form_data_state.dart';
|
part 'form_data_state.dart';
|
||||||
|
|
||||||
abstract class FormDataCubit extends Cubit<FormDataState> {
|
abstract class FormDataCubit extends Cubit<FormDataState> {
|
||||||
FormDataCubit({required FormData entries})
|
FormDataCubit({required FormData inputs})
|
||||||
: super(FormDataState(data: entries));
|
: super(FormDataState(data: inputs));
|
||||||
|
|
||||||
/// Change value of a field.
|
/// Change value of a field.
|
||||||
///
|
///
|
||||||
/// Inputs:
|
/// Inputs:
|
||||||
/// - `field`: The key of the field to change.
|
/// - `field`: The key of the field to change.
|
||||||
/// - `dirtyValue`: The new value of the field.
|
/// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator)
|
||||||
void dataChanged(String field, FormInput dirtyValue) {
|
void dataChanged(String field, FormInputValidator dirtyValue) {
|
||||||
final _form = state.data.clone();
|
final _form = state.data.clone();
|
||||||
|
|
||||||
if (_form.contains(field)) {
|
if (_form.contains(field)) {
|
||||||
_form.update(field, dirtyValue);
|
_form.updateValidator(field, dirtyValue);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Form field $field not found');
|
throw Exception('Form field $field not found');
|
||||||
}
|
}
|
||||||
@ -44,7 +45,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: _form.selfValidate(),
|
status: _form.validate(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: _form.selfValidate(),
|
status: _form.validate(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
part of 'form_data_cubit.dart';
|
part of 'form_data_cubit.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class FormDataState {
|
class FormDataState extends Equatable {
|
||||||
final FormStatus status;
|
final FormStatus status;
|
||||||
final FormData data;
|
final FormData data;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
@ -41,23 +41,8 @@ class FormDataState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool? get stringify => true;
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormDataState &&
|
|
||||||
other.status == status &&
|
|
||||||
other.data == data &&
|
|
||||||
other.errorMessage == errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
List<Object?> get props => [status, data, errorMessage];
|
||||||
return status.hashCode ^ data.hashCode ^ errorMessage.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'FormDataState(status: $status, data: $data, '
|
|
||||||
'errorMessage: $errorMessage)';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_form_bloc/src/form/form.dart';
|
||||||
|
|
||||||
|
const Set<FormStatus> _validatedFormStatuses = <FormStatus>{
|
||||||
|
FormStatus.valid,
|
||||||
|
FormStatus.submissionInProgress,
|
||||||
|
FormStatus.submissionSuccess,
|
||||||
|
FormStatus.submissionFailure,
|
||||||
|
FormStatus.submissionCanceled,
|
||||||
|
};
|
||||||
|
|
||||||
/// Enum representing the status of a form at any given point in time.
|
/// Enum representing the status of a form at any given point in time.
|
||||||
enum FormStatus {
|
enum FormStatus {
|
||||||
/// The form has not been touched.
|
/// The form has not been touched.
|
||||||
@ -35,19 +45,8 @@ enum FormStatus {
|
|||||||
submissionFailure,
|
submissionFailure,
|
||||||
|
|
||||||
/// The form submission has been canceled.
|
/// The form submission has been canceled.
|
||||||
submissionCanceled
|
submissionCanceled;
|
||||||
}
|
|
||||||
|
|
||||||
const Set<FormStatus> _validatedFormStatuses = <FormStatus>{
|
|
||||||
FormStatus.valid,
|
|
||||||
FormStatus.submissionInProgress,
|
|
||||||
FormStatus.submissionSuccess,
|
|
||||||
FormStatus.submissionFailure,
|
|
||||||
FormStatus.submissionCanceled,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Useful extensions on [FormStatus]
|
|
||||||
extension FormStatusX on FormStatus {
|
|
||||||
/// Indicates whether the form is untouched.
|
/// Indicates whether the form is untouched.
|
||||||
bool get isPure => this == FormStatus.pure;
|
bool get isPure => this == FormStatus.pure;
|
||||||
|
|
||||||
@ -76,4 +75,13 @@ extension FormStatusX on FormStatus {
|
|||||||
|
|
||||||
/// Indicates whether the form submission has been canceled.
|
/// Indicates whether the form submission has been canceled.
|
||||||
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
|
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
|
||||||
|
|
||||||
|
/// Validate a list of inputs
|
||||||
|
static FormStatus validate(List<FormInput> inputs) {
|
||||||
|
return inputs.every((FormInput input) => input.validator.pure)
|
||||||
|
? FormStatus.pure
|
||||||
|
: inputs.any((FormInput input) => input.validator.valid == false)
|
||||||
|
? FormStatus.invalid
|
||||||
|
: FormStatus.valid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export 'form_data.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
export 'form_entry.dart';
|
import 'package:meta/meta.dart';
|
||||||
export 'form_input.dart';
|
import 'package:wyatt_form_bloc/src/enums/form_input_status.dart';
|
||||||
|
import 'package:wyatt_form_bloc/src/enums/form_status.dart';
|
||||||
|
|
||||||
|
part 'form_input.dart';
|
||||||
|
part 'form_input_metadata.dart';
|
||||||
|
part 'form_input_validator.dart';
|
||||||
|
part 'form_data.dart';
|
||||||
|
@ -14,133 +14,164 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
part of 'form.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/utils/list_equals.dart';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class FormData {
|
class FormData extends Equatable {
|
||||||
const FormData(this._entries);
|
final List<FormInput> _inputs;
|
||||||
|
|
||||||
FormData.empty() : this(<FormEntry>[]);
|
const FormData(this._inputs);
|
||||||
|
const FormData.empty() : this(const <FormInput>[]);
|
||||||
|
|
||||||
final List<FormEntry> _entries;
|
/// Returns the input for the associated key
|
||||||
|
FormInput inputByKey(String key) {
|
||||||
List<FormEntry> get entries => _entries;
|
|
||||||
|
|
||||||
List<FormInput<T, ValidationError>> inputs<T>() {
|
|
||||||
return _entries
|
|
||||||
.map((FormEntry entry) => entry.input as FormInput<T, ValidationError>)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
static FormStatus validate(List<FormInput> inputs) {
|
|
||||||
return inputs.every((FormInput element) => element.pure)
|
|
||||||
? FormStatus.pure
|
|
||||||
: inputs.any((FormInput input) => input.valid == false)
|
|
||||||
? FormStatus.invalid
|
|
||||||
: FormStatus.valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormStatus selfValidate() {
|
|
||||||
return validate(inputs<dynamic>());
|
|
||||||
}
|
|
||||||
|
|
||||||
FormInput<T, ValidationError> input<T>(String key) {
|
|
||||||
if (contains(key)) {
|
if (contains(key)) {
|
||||||
return _entries.firstWhere((FormEntry entry) => entry.key == key).input
|
return _inputs.firstWhere((FormInput input) => input.key == key);
|
||||||
as FormInput<T, ValidationError>;
|
|
||||||
} else {
|
} else {
|
||||||
throw Exception('FormInput with key `$key` does not exist in form');
|
throw Exception('FormInput with key `$key` does not exist in form');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(String key) {
|
/// Updates a input (perform a replace at index).
|
||||||
return _entries.any((FormEntry entry) => entry.key == key);
|
void updateInput(String key, FormInput input) {
|
||||||
}
|
|
||||||
|
|
||||||
FormData intersection(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
if (other.contains(entry.key)) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormData difference(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry otherEntry in other._entries) {
|
|
||||||
if (!contains(otherEntry.key)) {
|
|
||||||
entries.add(otherEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
if (!other.contains(entry.key)) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormData union(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final FormEntry otherEntry in other._entries) {
|
|
||||||
if (!contains(otherEntry.key)) {
|
|
||||||
entries.add(otherEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(String key, FormInput input) {
|
|
||||||
if (contains(key)) {
|
if (contains(key)) {
|
||||||
final index = _entries.indexOf(
|
final index = _inputs.indexOf(
|
||||||
_entries.firstWhere((FormEntry entry) => entry.key == key),
|
inputByKey(key),
|
||||||
);
|
);
|
||||||
_entries[index] = _entries[index].copyWith(input: input);
|
_inputs[index] = input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates validator of a given input. (perform copyWith)
|
||||||
|
void updateValidator(String key, FormInputValidator dirtyValue) {
|
||||||
|
if (contains(key)) {
|
||||||
|
final index = _inputs.indexOf(
|
||||||
|
inputByKey(key),
|
||||||
|
);
|
||||||
|
_inputs[index] = _inputs[index].copyWith(validator: dirtyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates metadata of a given input. (perform copyWith)
|
||||||
|
void updateMetadata(String key, FormInputMetadata meta) {
|
||||||
|
if (contains(key)) {
|
||||||
|
final index = _inputs.indexOf(
|
||||||
|
inputByKey(key),
|
||||||
|
);
|
||||||
|
_inputs[index] = _inputs[index].copyWith(metadata: meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [FormInputValidator] represents the value of a single form input field.
|
||||||
|
/// It contains information about the [FormInputStatus], value, as well
|
||||||
|
/// as validation status.
|
||||||
|
T validatorOf<T>(String key) {
|
||||||
|
return inputByKey(key).validator as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a validation error if the [FormInputValidator] is invalid.
|
||||||
|
/// Returns null if the [FormInputValidator] is valid.
|
||||||
|
E? errorOf<V, E>(String key) {
|
||||||
|
return (inputByKey(key).validator as FormInputValidator<V, E>).error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of the associated [FormInputValidator]. For example,
|
||||||
|
/// if you have a FormInputValidator for FirstName, the value could be 'Joe'.
|
||||||
|
V valueOf<V, E>(String key) {
|
||||||
|
return (inputByKey(key).validator as FormInputValidator<V, E>).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the [FormInputValidator] is not valid.
|
||||||
|
/// Same as `E? errorOf<V, E>(String key) != null`
|
||||||
|
bool isNotValid(String key) {
|
||||||
|
return !inputByKey(key).validator.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata associated. With `M` the type of extra data.
|
||||||
|
FormInputMetadata<M> metadataOf<M>(String key) {
|
||||||
|
return inputByKey(key).metadata as FormInputMetadata<M>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate self inputs.
|
||||||
|
FormStatus validate() {
|
||||||
|
return FormStatus.validate(_inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this contains an input with the given key.
|
||||||
|
bool contains(String key) {
|
||||||
|
return _inputs.any((FormInput input) => input.key == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an intersection set operation and returns newly created [FormData]
|
||||||
|
FormData intersection(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
if (other.contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a difference set operation and returns newly created [FormData]
|
||||||
|
FormData difference(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in other._inputs) {
|
||||||
|
if (!contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
if (!other.contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an union set operation and returns newly created [FormData]
|
||||||
|
FormData union(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final FormInput i in other._inputs) {
|
||||||
|
if (!contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deeply copy this.
|
||||||
FormData clone() {
|
FormData clone() {
|
||||||
return FormData(
|
return FormData(
|
||||||
_entries.map((FormEntry entry) => entry.clone()).toList(),
|
_inputs.map((FormInput input) => input.clone()).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Export this to [Map] format.
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
final map = <String, dynamic>{};
|
final map = <String, dynamic>{};
|
||||||
for (final entry in _entries) {
|
for (final input in _inputs) {
|
||||||
if (entry.export) {
|
if (input.metadata.export) {
|
||||||
map[entry.name] = entry.input.value;
|
map[input.name] = input.validator.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool? get stringify => true;
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormData && listEquals(other._entries, _entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => _entries.hashCode;
|
List<Object?> get props => _inputs;
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'FormData(entries: $_entries)';
|
|
||||||
}
|
}
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class FormEntry {
|
|
||||||
const FormEntry(this.key, this.input, {this.export = true, String? name})
|
|
||||||
: _field = name ?? key;
|
|
||||||
|
|
||||||
final String key;
|
|
||||||
final FormInput input;
|
|
||||||
final bool export;
|
|
||||||
final String _field;
|
|
||||||
|
|
||||||
String get name => _field;
|
|
||||||
|
|
||||||
FormEntry copyWith({
|
|
||||||
String? key,
|
|
||||||
FormInput? input,
|
|
||||||
bool? export,
|
|
||||||
String? name,
|
|
||||||
}) {
|
|
||||||
return FormEntry(
|
|
||||||
key ?? this.key,
|
|
||||||
input ?? this.input,
|
|
||||||
export: export ?? this.export,
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormEntry clone() {
|
|
||||||
return FormEntry(
|
|
||||||
key,
|
|
||||||
input,
|
|
||||||
export: export,
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormEntry &&
|
|
||||||
other.key == key &&
|
|
||||||
other.input == input &&
|
|
||||||
other.export == export &&
|
|
||||||
other.name == name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
return key.hashCode ^
|
|
||||||
input.hashCode ^
|
|
||||||
export.hashCode ^
|
|
||||||
name.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'FormEntry(key: $key, input: $input, '
|
|
||||||
'export: $export, name: $name)';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@ -14,98 +15,45 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
part of 'form.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
|
||||||
|
|
||||||
/// {@template form_input}
|
class FormInput extends Equatable {
|
||||||
/// A [FormInput] represents the value of a single form input field.
|
final String key;
|
||||||
/// It contains information about the [FormInputStatus], [value], as well
|
final FormInputValidator validator;
|
||||||
/// as validation status.
|
final FormInputMetadata metadata;
|
||||||
///
|
|
||||||
/// [FormInput] should be extended to define custom [FormInput] instances.
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// enum FirstNameError { empty }
|
|
||||||
/// class FirstName extends FormInput<String, FirstNameError> {
|
|
||||||
/// 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<T, E> {
|
|
||||||
const FormInput._(this.value, [this.pure = true]);
|
|
||||||
|
|
||||||
/// Constructor which create a `pure` [FormInput] with a given value.
|
String get name => metadata._name ?? key;
|
||||||
const FormInput.pure(T value) : this._(value);
|
|
||||||
|
|
||||||
/// Constructor which create a `dirty` [FormInput] with a given value.
|
const FormInput(
|
||||||
const FormInput.dirty(T value) : this._(value, false);
|
this.key,
|
||||||
|
this.validator, {
|
||||||
/// The value of the given [FormInput].
|
// ignore: avoid_redundant_argument_values
|
||||||
/// For example, if you have a `FormInput` for `FirstName`,
|
this.metadata = const FormInputMetadata<void>(export: true),
|
||||||
/// 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
|
@override
|
||||||
int get hashCode => value.hashCode ^ pure.hashCode;
|
bool? get stringify => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
List<Object?> get props => [key, validator, metadata];
|
||||||
if (other.runtimeType != runtimeType) return false;
|
|
||||||
return other is FormInput<T, E> &&
|
FormInput copyWith({
|
||||||
other.value == value &&
|
String? key,
|
||||||
other.pure == pure;
|
FormInputValidator? validator,
|
||||||
|
FormInputMetadata? metadata,
|
||||||
|
}) {
|
||||||
|
return FormInput(
|
||||||
|
key ?? this.key,
|
||||||
|
validator ?? this.validator,
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
FormInput clone() {
|
||||||
String toString() => 'FormInput<$runtimeType>(value: $value, pure: $pure)';
|
return copyWith(
|
||||||
|
key: key,
|
||||||
|
validator: validator,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
part of 'form.dart';
|
||||||
|
|
||||||
|
class FormInputMetadata<T> extends Equatable {
|
||||||
|
final bool export;
|
||||||
|
final String? _name;
|
||||||
|
final T? extra;
|
||||||
|
|
||||||
|
const FormInputMetadata({
|
||||||
|
this.export = true,
|
||||||
|
this.extra,
|
||||||
|
String? name,
|
||||||
|
}) : _name = name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [export, _name, extra];
|
||||||
|
|
||||||
|
FormInputMetadata<T> copyWith({
|
||||||
|
bool? export,
|
||||||
|
String? name,
|
||||||
|
T? extra,
|
||||||
|
}) {
|
||||||
|
return FormInputMetadata<T>(
|
||||||
|
export: export ?? this.export,
|
||||||
|
name: name ?? _name,
|
||||||
|
extra: extra ?? this.extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FormInputMetadata<T> clone() {
|
||||||
|
return copyWith(
|
||||||
|
export: export,
|
||||||
|
name: _name,
|
||||||
|
extra: extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
104
packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart
Normal file
104
packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
part of 'form.dart';
|
||||||
|
|
||||||
|
/// {@template form_input}
|
||||||
|
/// A [FormInputValidator] represents the value of a single form input field.
|
||||||
|
/// It contains information about the [FormInputStatus], [value], as well
|
||||||
|
/// as validation status.
|
||||||
|
///
|
||||||
|
/// [FormInputValidator] should be extended to define custom
|
||||||
|
/// [FormInputValidator] instances.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// enum FirstNameError { empty }
|
||||||
|
/// class FirstName extends FormInputValidator<String, FirstNameError> {
|
||||||
|
/// 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 FormInputValidator<V, E> extends Equatable {
|
||||||
|
const FormInputValidator._(this.value, [this.pure = true]);
|
||||||
|
|
||||||
|
/// Constructor which create a `pure` [FormInputValidator] with a given value.
|
||||||
|
const FormInputValidator.pure(V value) : this._(value);
|
||||||
|
|
||||||
|
/// Constructor which create a `dirty` [FormInputValidator] with a
|
||||||
|
/// given value.
|
||||||
|
const FormInputValidator.dirty(V value) : this._(value, false);
|
||||||
|
|
||||||
|
/// The value of the given [FormInputValidator].
|
||||||
|
/// For example, if you have a `FormInputValidator` for `FirstName`,
|
||||||
|
/// the value could be 'Joe'.
|
||||||
|
final V value;
|
||||||
|
|
||||||
|
/// If the [FormInputValidator] is pure (has been touched/modified).
|
||||||
|
/// Typically when the `FormInputValidator` is initially created,
|
||||||
|
/// it is created using the `FormInputValidator.pure` constructor to
|
||||||
|
/// signify that the user has not modified it.
|
||||||
|
///
|
||||||
|
/// For subsequent changes (in response to user input), the
|
||||||
|
/// `FormInputValidator.dirty` constructor should be used to signify that
|
||||||
|
/// the `FormInputValidator` 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 [FormInputValidator] is invalid.
|
||||||
|
/// Returns `null` if the [FormInputValidator] is valid.
|
||||||
|
E? get error => validator(value);
|
||||||
|
|
||||||
|
/// Whether the [FormInputValidator] value is valid according to the
|
||||||
|
/// overridden `validator`.
|
||||||
|
///
|
||||||
|
/// Returns `true` if `validator` returns `null` for the
|
||||||
|
/// current [FormInputValidator] value and `false` otherwise.
|
||||||
|
bool get valid => validator(value) == null;
|
||||||
|
|
||||||
|
/// Whether the [FormInputValidator] 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(V value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [value, pure];
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
bool listEquals<T>(List<T>? a, List<T>? 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;
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template boolean}
|
/// {@template boolean}
|
||||||
/// Form input for a bool input
|
/// Form input for a bool input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Boolean extends FormInput<bool, ValidationError> {
|
class Boolean extends FormInputValidator<bool, ValidationError> {
|
||||||
/// {@macro boolean}
|
/// {@macro boolean}
|
||||||
const Boolean.pure({bool? defaultValue = false})
|
const Boolean.pure({bool? defaultValue = false})
|
||||||
: super.pure(defaultValue ?? false);
|
: super.pure(defaultValue ?? false);
|
||||||
|
@ -21,7 +21,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// Form input for a confirmed password input.
|
/// Form input for a confirmed password input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ConfirmedPassword
|
class ConfirmedPassword
|
||||||
extends FormInput<String, ValidationError> {
|
extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro confirmed_password}
|
/// {@macro confirmed_password}
|
||||||
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
|
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template email}
|
/// {@template email}
|
||||||
/// Form input for an email input.
|
/// Form input for an email input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Email extends FormInput<String, ValidationError> {
|
class Email extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro email}
|
/// {@macro email}
|
||||||
const Email.pure() : super.pure('');
|
const Email.pure() : super.pure('');
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class Email extends FormInput<String, ValidationError> {
|
|||||||
const Email.dirty([String value = '']) : super.dirty(value);
|
const Email.dirty([String value = '']) : super.dirty(value);
|
||||||
|
|
||||||
static final RegExp _emailRegExp = RegExp(
|
static final RegExp _emailRegExp = RegExp(
|
||||||
r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
|
r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+',
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template iban}
|
/// {@template iban}
|
||||||
/// Form input for an IBAN input.
|
/// Form input for an IBAN input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Iban extends FormInput<String, ValidationError> {
|
class Iban extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro iban}
|
/// {@macro iban}
|
||||||
const Iban.pure() : super.pure('');
|
const Iban.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template list_option}
|
/// {@template list_option}
|
||||||
/// Form input for a list input
|
/// Form input for a list input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ListOption<T> extends FormInput<List<T>, ValidationError> {
|
class ListOption<T> extends FormInputValidator<List<T>, ValidationError> {
|
||||||
/// {@macro list_option}
|
/// {@macro list_option}
|
||||||
const ListOption.pure({List<T>? defaultValue})
|
const ListOption.pure({List<T>? defaultValue})
|
||||||
: super.pure(defaultValue ?? const []);
|
: super.pure(defaultValue ?? const []);
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template name}
|
/// {@template name}
|
||||||
/// Form input for a name input.
|
/// Form input for a name input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Name extends FormInput<String, ValidationError> {
|
class Name extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro name}
|
/// {@macro name}
|
||||||
const Name.pure() : super.pure('');
|
const Name.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template password}
|
/// {@template password}
|
||||||
/// Form input for a password input.
|
/// Form input for a password input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Password extends FormInput<String, ValidationError> {
|
class Password extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro password}
|
/// {@macro password}
|
||||||
const Password.pure() : super.pure('');
|
const Password.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template phone}
|
/// {@template phone}
|
||||||
/// Form input for a phone input.
|
/// Form input for a phone input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Phone extends FormInput<String, ValidationError> {
|
class Phone extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro phone}
|
/// {@macro phone}
|
||||||
const Phone.pure() : super.pure('');
|
const Phone.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template siren}
|
/// {@template siren}
|
||||||
/// Form input for a SIREN input.
|
/// Form input for a SIREN input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Siren extends FormInput<String, ValidationError> {
|
class Siren extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro siren}
|
/// {@macro siren}
|
||||||
const Siren.pure() : super.pure('');
|
const Siren.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template text_string}
|
/// {@template text_string}
|
||||||
/// Form input for a text input
|
/// Form input for a text input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class TextString extends FormInput<String, ValidationError> {
|
class TextString extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro text_string}
|
/// {@macro text_string}
|
||||||
const TextString.pure() : super.pure('');
|
const TextString.pure() : super.pure('');
|
||||||
|
|
||||||
|
@ -4,14 +4,15 @@ repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/mas
|
|||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.16.2 <3.0.0'
|
sdk: '>=2.17.2 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bloc: ^8.0.3
|
bloc: ^8.0.3
|
||||||
|
equatable: ^2.0.3
|
||||||
meta: ^1.7.0
|
meta: ^1.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.21.0
|
test: ^1.21.4
|
||||||
|
|
||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user