mobx-form 14.2.0 → 14.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -111
- package/dist/dist/mobx-form.d.ts +271 -0
- package/package.json +35 -8
- package/dist/FormModel.d.ts +0 -260
- package/dist/index.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,138 +1,207 @@
|
|
|
1
|
-
# [Mobx](https://www.npmjs.com/package/mobx)-Form
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
# mobx-form
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
[](https://www.npmjs.com/package/mobx-form)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://github.com/royriojas/mobx-form/actions/workflows/ci.yml)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- Add more unit tests
|
|
9
|
-
- Add better documentation
|
|
8
|
+
> A simple, robust, and extensible form state management helper for [MobX](https://github.com/mobxjs/mobx).
|
|
10
9
|
|
|
11
|
-
## Install
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
npm i --save mobx mobx-form
|
|
15
|
-
```
|
|
11
|
+
`mobx-form` simplifies form validation and state management in MobX-powered React applications. It provides a declarative way to define form models with synchronous and asynchronous validation, dirty tracking, and easy data serialization.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
[**View Live Demo / Storybook**](https://royriojas.github.io/mobx-form/)
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
import { createModel } from 'mobx-form';
|
|
15
|
+
## Features
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// - a `string` in which case the string message will be used as the message to show when the field has no value
|
|
38
|
-
required: 'This field is required',
|
|
39
|
-
// optional, default error message is the validaor function return just true/false
|
|
40
|
-
errorMessage:
|
|
41
|
-
// optional, if `required` is defined. Required if not
|
|
42
|
-
// the validation function it receive the current field and allFields for
|
|
43
|
-
// validations that need to take in consideration more than one in conjuction
|
|
44
|
-
//
|
|
45
|
-
// - if validation passes function must return true.
|
|
46
|
-
// - if validation does not pass the function should:
|
|
47
|
-
// - return false (default errorMessage specified in the validator will be used)
|
|
48
|
-
// - throw an error (error.message is going to be used as the errorMessage)
|
|
49
|
-
// - reject or return an object with an error field. like:
|
|
50
|
-
// ```
|
|
51
|
-
// return { error: 'some error' };
|
|
52
|
-
// return Promise.reject({ error: 'some error' }); // although this should be considered deprecated
|
|
53
|
-
// ```.
|
|
54
|
-
validator = (field, allFields, model) => {
|
|
55
|
-
// do validation
|
|
56
|
-
// return true/false
|
|
57
|
-
// throw new Error('some error');
|
|
58
|
-
// return { error: 'some error '};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
})
|
|
17
|
+
- ** declarative form definition**: Define your form structure with simple descriptors.
|
|
18
|
+
- **⚡️ Reactive**: Built on MobX for high-performance, fine-grained reactivity.
|
|
19
|
+
- **✅ Validation**:
|
|
20
|
+
- Sync and Async validators
|
|
21
|
+
- Multiple validators per field
|
|
22
|
+
- Cross-field validation (e.g., password confirmation)
|
|
23
|
+
- Custom error messages
|
|
24
|
+
- **🔍 State Tracking**:
|
|
25
|
+
- `dirty` state (modified vs initial)
|
|
26
|
+
- `interacted` state (touched)
|
|
27
|
+
- `validating` state (async loading indicators)
|
|
28
|
+
- **🛠 Utilities**:
|
|
29
|
+
- `serializedData` for easy API payloads
|
|
30
|
+
- `commit()` / `restoreInitialValues()` for transaction-like behavior
|
|
31
|
+
- `autoValidate` options
|
|
62
32
|
|
|
63
|
-
|
|
64
|
-
// To call all validators
|
|
65
|
-
await model.validate();
|
|
33
|
+
## Demo / Storybook
|
|
66
34
|
|
|
67
|
-
|
|
68
|
-
if (model.valid) {
|
|
69
|
-
// get the serialized data
|
|
70
|
-
// { fieldName: 'value set' }
|
|
71
|
-
const obj = model.serializedData;
|
|
35
|
+
This project includes a comprehensive Storybook suite demonstrating all features.
|
|
72
36
|
|
|
73
|
-
|
|
74
|
-
await xhr('/some/endpoint', { method: 'post', payload: obj });
|
|
75
|
-
};
|
|
76
|
-
};
|
|
37
|
+
To run the interactive stories locally:
|
|
77
38
|
|
|
39
|
+
```bash
|
|
40
|
+
# Install dependencies
|
|
41
|
+
npm install
|
|
78
42
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// will reset the interacted flag on the Field
|
|
82
|
-
// and reset the validation error
|
|
43
|
+
# Run Storybook
|
|
44
|
+
npm run storybook
|
|
83
45
|
```
|
|
84
46
|
|
|
85
|
-
|
|
47
|
+
[**View Live Demo**](https://royriojas.github.io/mobx-form/)
|
|
48
|
+
|
|
49
|
+
Navigate to `http://localhost:6006` to explore examples like:
|
|
50
|
+
- Simple Login Forms
|
|
51
|
+
- Async Validation (simulated API checks)
|
|
52
|
+
- Dynamic Fields
|
|
53
|
+
- Complex Validation Rules
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install --save mobx-form mobx
|
|
59
|
+
# or
|
|
60
|
+
bun add mobx-form mobx
|
|
61
|
+
```
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
> Note: `mobx` is a peer dependency.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### 1. Create a Form Model
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
89
70
|
import { createModel } from 'mobx-form';
|
|
90
71
|
|
|
91
|
-
const
|
|
72
|
+
const loginForm = createModel({
|
|
73
|
+
initialState: {
|
|
74
|
+
username: '',
|
|
75
|
+
password: ''
|
|
76
|
+
},
|
|
92
77
|
descriptors: {
|
|
93
|
-
|
|
94
|
-
required: '
|
|
78
|
+
username: {
|
|
79
|
+
required: 'Username is required',
|
|
80
|
+
autoValidate: true
|
|
95
81
|
},
|
|
96
|
-
// validator for email
|
|
97
|
-
email: {
|
|
98
|
-
// validator function
|
|
99
|
-
async validator(field) {
|
|
100
|
-
const email = trim(field.value);
|
|
101
|
-
// super simple and naive email validation
|
|
102
|
-
if (!email || !(email.indexOf('@') > 0)) {
|
|
103
|
-
throw new Error('Please provide an error message');
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
// validator for password
|
|
108
82
|
password: {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
83
|
+
required: true, // uses default required message
|
|
84
|
+
validator: (field) => {
|
|
85
|
+
if (field.value.length < 6) {
|
|
86
|
+
return { error: 'Password must be at least 6 characters' };
|
|
113
87
|
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Connect to React
|
|
95
|
+
|
|
96
|
+
Use `observer` from `mobx-react-lite` to make your component reactive.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import React from 'react';
|
|
100
|
+
import { observer } from 'mobx-react-lite';
|
|
101
|
+
|
|
102
|
+
const LoginForm = observer(({ model }) => {
|
|
103
|
+
return (
|
|
104
|
+
<form onSubmit={(e) => { e.preventDefault(); model.validate(); }}>
|
|
105
|
+
|
|
106
|
+
{/* Username Field */}
|
|
107
|
+
<div>
|
|
108
|
+
<label>Username</label>
|
|
109
|
+
<input
|
|
110
|
+
value={model.fields.username.value}
|
|
111
|
+
onChange={e => model.fields.username.setValue(e.target.value)}
|
|
112
|
+
onBlur={() => model.fields.username.markBlurredAndValidate()}
|
|
113
|
+
/>
|
|
114
|
+
<div className="error">{model.fields.username.error}</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Password Field */}
|
|
118
|
+
<div>
|
|
119
|
+
<label>Password</label>
|
|
120
|
+
<input
|
|
121
|
+
type="password"
|
|
122
|
+
value={model.fields.password.value}
|
|
123
|
+
onChange={e => model.fields.password.setValue(e.target.value)}
|
|
124
|
+
/>
|
|
125
|
+
<div className="error">{model.fields.password.error}</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Actions */}
|
|
129
|
+
<button type="submit" disabled={model.validating}>
|
|
130
|
+
{model.validating ? 'Checking...' : 'Login'}
|
|
131
|
+
</button>
|
|
132
|
+
|
|
133
|
+
{/* Form Status */}
|
|
134
|
+
<div>
|
|
135
|
+
Valid: {model.valid ? 'Yes' : 'No'} |
|
|
136
|
+
Dirty: {model.dirty ? 'Yes' : 'No'}
|
|
137
|
+
</div>
|
|
138
|
+
</form>
|
|
139
|
+
);
|
|
121
140
|
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## API Reference
|
|
144
|
+
|
|
145
|
+
### `createModel(config)`
|
|
146
|
+
|
|
147
|
+
Creates a new `FormModel` instance.
|
|
148
|
+
|
|
149
|
+
**Config Object:**
|
|
150
|
+
- `initialState`: Object containing initial values for fields.
|
|
151
|
+
- `descriptors`: Object definition validation rules and behavior for each field.
|
|
152
|
+
- `options`: `{ throwIfMissingField: boolean }` (default `true`).
|
|
153
|
+
|
|
154
|
+
### Field Descriptor Properties
|
|
122
155
|
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
| Property | Type | Description |
|
|
157
|
+
|----------|------|-------------|
|
|
158
|
+
| `required` | `boolean \| string` | Marks field as required. String serves as the error message. |
|
|
159
|
+
| `validator` | `Function \| Function[]` | Validation function(s). Return `true`, `{error: msg}`, or throw/return Error. |
|
|
160
|
+
| `autoValidate` | `boolean` | If `true`, validation runs on every change (default behavior). |
|
|
161
|
+
| `waitForBlur` | `boolean` | If `true`, validation is deferred until the field is blurred once. |
|
|
162
|
+
| `value` | `any` | Initial value (can also be set via `initialState`). |
|
|
163
|
+
| `disabled` | `boolean` | If `true`, field is skipped during validation. |
|
|
164
|
+
| `meta` | `object` | Arbitrary metadata (e.g., placeholder text, options list). |
|
|
165
|
+
| `clearErrorOnValueChange` | `boolean` | Immediately clears error when user types. |
|
|
125
166
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
167
|
+
### `FormModel` Methods & Properties
|
|
168
|
+
|
|
169
|
+
- **`model.fields`**: Access to individual field objects (e.g., `model.fields.email`).
|
|
170
|
+
- **`model.validate()`**: Triggers validation for all fields. Returns a Promise.
|
|
171
|
+
- **`model.valid`**: Boolean. `true` if all fields are valid.
|
|
172
|
+
- **`model.dirty`**: Boolean. `true` if any field value differs from initial state.
|
|
173
|
+
- **`model.serializedData`**: Returns a plain JS object with current values (trimmed strings).
|
|
174
|
+
- **`model.commit()`**: Sets current values as the new "initial" state (resets `dirty` to false).
|
|
175
|
+
- **`model.restoreInitialValues()`**: Resets all fields to their last committed values.
|
|
176
|
+
- **`model.addFields(descriptors)`**: Dynamically add new fields to the form.
|
|
177
|
+
|
|
178
|
+
## Advanced Examples
|
|
179
|
+
|
|
180
|
+
### Async Validation
|
|
181
|
+
Validators can be async functions. Use the `model.validating` or `field.validating` flags to show loaders.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
username: {
|
|
185
|
+
validator: async (field) => {
|
|
186
|
+
const isTaken = await checkApiForUser(field.value);
|
|
187
|
+
if (isTaken) throw new Error('Username already taken');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Cross-Field Validation
|
|
193
|
+
Access other fields via the `model` argument in validators.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
confirmPassword: {
|
|
197
|
+
validator: (field, allFields, model) => {
|
|
198
|
+
if (field.value !== model.fields.password.value) {
|
|
199
|
+
return { error: 'Passwords do not match' };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
129
204
|
|
|
130
|
-
|
|
131
|
-
// >>>> model.summary [ 'Name is required',
|
|
132
|
-
// 'Please provide an error message',
|
|
133
|
-
// 'Please provide your password' ]
|
|
134
|
-
// >>>> model.requiredFields [ 'name' ]
|
|
135
|
-
};
|
|
205
|
+
## License
|
|
136
206
|
|
|
137
|
-
|
|
138
|
-
```
|
|
207
|
+
MIT
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Generated by dts-bundle v0.7.3
|
|
2
|
+
// Dependencies for this module:
|
|
3
|
+
// ../lodash
|
|
4
|
+
|
|
5
|
+
declare module 'mobx-form' {
|
|
6
|
+
export * from "mobx-form/FormModel";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module 'mobx-form/FormModel' {
|
|
10
|
+
import type { DebouncedFunc } from "lodash";
|
|
11
|
+
export type Descriptors<T> = {
|
|
12
|
+
[P in keyof T]: FieldDescriptor<T[P], T>;
|
|
13
|
+
};
|
|
14
|
+
export type FormModelArgs<T> = {
|
|
15
|
+
descriptors: Partial<Descriptors<T>>;
|
|
16
|
+
initialState?: Partial<T>;
|
|
17
|
+
options?: ThrowIfMissingFieldType;
|
|
18
|
+
};
|
|
19
|
+
export type ResultObj = {
|
|
20
|
+
error: string;
|
|
21
|
+
};
|
|
22
|
+
export type ErrorLike = {
|
|
23
|
+
message: string;
|
|
24
|
+
} | Error;
|
|
25
|
+
export type ValidatorResult = boolean | ResultObj | void;
|
|
26
|
+
export type ValidateFnArgs<T, K> = {
|
|
27
|
+
field: Field<T, K>;
|
|
28
|
+
fields: FormModel<K>["fields"];
|
|
29
|
+
model: FormModel<K>;
|
|
30
|
+
value: Field<T, K>["value"];
|
|
31
|
+
};
|
|
32
|
+
export type ValidateFn<T, K> = (args: ValidateFnArgs<T, K>) => Promise<ValidatorResult> | ValidatorResult;
|
|
33
|
+
export type ResetInteractedFlagType = {
|
|
34
|
+
resetInteractedFlag?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type ThrowIfMissingFieldType = {
|
|
37
|
+
throwIfMissingField?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export interface FieldDescriptor<T, K> {
|
|
40
|
+
waitForBlur?: boolean;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
errorMessage?: string;
|
|
43
|
+
validator?: ValidateFn<T, K> | ValidateFn<T, K>[];
|
|
44
|
+
hasValue?: (value: T) => boolean;
|
|
45
|
+
value?: T;
|
|
46
|
+
required?: boolean | string;
|
|
47
|
+
autoValidate?: boolean;
|
|
48
|
+
validationDebounceThreshold?: number;
|
|
49
|
+
clearErrorOnValueChange?: boolean;
|
|
50
|
+
meta?: Record<string, any>;
|
|
51
|
+
}
|
|
52
|
+
export type CommitType = {
|
|
53
|
+
commit?: boolean;
|
|
54
|
+
};
|
|
55
|
+
export type ForceType = {
|
|
56
|
+
force?: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type SetValueFnArgs = ResetInteractedFlagType & CommitType;
|
|
59
|
+
/**
|
|
60
|
+
* Field class provides abstract the validation of a single field
|
|
61
|
+
*/
|
|
62
|
+
export class Field<T, K> {
|
|
63
|
+
_name: string;
|
|
64
|
+
meta?: Record<string, any>;
|
|
65
|
+
_model: FormModel<K>;
|
|
66
|
+
_waitForBlur?: boolean | undefined;
|
|
67
|
+
_disabled?: boolean | undefined;
|
|
68
|
+
_required?: boolean | string;
|
|
69
|
+
_validatedOnce: boolean;
|
|
70
|
+
_clearErrorOnValueChange?: boolean | undefined;
|
|
71
|
+
_hasValueFn?: (value: T) => boolean;
|
|
72
|
+
get name(): string;
|
|
73
|
+
get model(): FormModel<K>;
|
|
74
|
+
get validatedAtLeastOnce(): boolean;
|
|
75
|
+
get waitForBlur(): boolean;
|
|
76
|
+
get disabled(): boolean;
|
|
77
|
+
get required(): boolean;
|
|
78
|
+
resetInteractedFlag(): void;
|
|
79
|
+
markAsInteracted(): void;
|
|
80
|
+
resetValidatedOnce(): void;
|
|
81
|
+
get hasValue(): boolean;
|
|
82
|
+
_validationTs: number;
|
|
83
|
+
/**
|
|
84
|
+
* flag to know if a validation is in progress on this field
|
|
85
|
+
*/
|
|
86
|
+
_validating: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* field to store the initial value set on this field
|
|
89
|
+
* */
|
|
90
|
+
_initialValue?: T;
|
|
91
|
+
/**
|
|
92
|
+
* the value of the field
|
|
93
|
+
* */
|
|
94
|
+
_value?: T;
|
|
95
|
+
/**
|
|
96
|
+
* whether the user interacted with the field
|
|
97
|
+
* this means if there is any value set on the field
|
|
98
|
+
* either setting it using the `setValue` or using
|
|
99
|
+
* the setter `value`. This is useful to know if
|
|
100
|
+
* the user has interacted with teh form in any way
|
|
101
|
+
*/
|
|
102
|
+
_interacted: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* whether the field was blurred at least once
|
|
105
|
+
* usually validators should only start being applied
|
|
106
|
+
* after the first blur, otherwise they become
|
|
107
|
+
* too invasive. This flag be used to keep track of
|
|
108
|
+
* the fact that the user already blurred of a field
|
|
109
|
+
*/
|
|
110
|
+
_blurredOnce: boolean;
|
|
111
|
+
get blurred(): boolean;
|
|
112
|
+
/** the raw error in caes validator throws a real error */
|
|
113
|
+
rawError?: ErrorLike;
|
|
114
|
+
/**
|
|
115
|
+
* the error message associated with this field.
|
|
116
|
+
* This is used to indicate what error happened during
|
|
117
|
+
* the validation process
|
|
118
|
+
*/
|
|
119
|
+
get errorMessage(): string | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* whether the validation should be launch after a
|
|
122
|
+
* new value is set in the field. This is usually associated
|
|
123
|
+
* to forms that set the value on the fields after each
|
|
124
|
+
* onChange event
|
|
125
|
+
*/
|
|
126
|
+
_autoValidate: boolean;
|
|
127
|
+
get autoValidate(): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* used to keep track of the original message
|
|
130
|
+
*/
|
|
131
|
+
_originalErrorMessage?: string;
|
|
132
|
+
/**
|
|
133
|
+
* whether the field is valid or not
|
|
134
|
+
*/
|
|
135
|
+
get valid(): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* whether the user has interacted or not with the field
|
|
138
|
+
*/
|
|
139
|
+
get interacted(): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* get the value set on the field
|
|
142
|
+
*/
|
|
143
|
+
get value(): T | undefined;
|
|
144
|
+
_setValueOnly: (val?: T) => void;
|
|
145
|
+
_setValue: (val?: T) => void;
|
|
146
|
+
/**
|
|
147
|
+
* setter for the value of the field
|
|
148
|
+
*/
|
|
149
|
+
set value(val: T);
|
|
150
|
+
setValue: (value?: T, { resetInteractedFlag, commit }?: SetValueFnArgs) => void;
|
|
151
|
+
/**
|
|
152
|
+
* Restore the initial value of the field
|
|
153
|
+
*/
|
|
154
|
+
restoreInitialValue: ({ resetInteractedFlag, commit }?: SetValueFnArgs) => void;
|
|
155
|
+
get dirty(): boolean;
|
|
156
|
+
commit(): void;
|
|
157
|
+
/**
|
|
158
|
+
* clear the valid state of the field by
|
|
159
|
+
* removing the errorMessage string. A field is
|
|
160
|
+
* considered valid if the errorMessage is not empty
|
|
161
|
+
*/
|
|
162
|
+
resetError(): void;
|
|
163
|
+
clearValidation(): void;
|
|
164
|
+
/**
|
|
165
|
+
* mark the field as already blurred so validation can
|
|
166
|
+
* start to be applied to the field.
|
|
167
|
+
*/
|
|
168
|
+
markBlurredAndValidate: () => void;
|
|
169
|
+
_validateFn?: ValidateFn<T, K> | Array<ValidateFn<T, K>>;
|
|
170
|
+
_doValidate: () => Promise<ValidatorResult | undefined>;
|
|
171
|
+
setDisabled(disabled: boolean): void;
|
|
172
|
+
validate: (opts?: ForceType) => Promise<void>;
|
|
173
|
+
get originalErrorMessage(): string;
|
|
174
|
+
setValidating: (validating: boolean) => void;
|
|
175
|
+
get validating(): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* validate the field. If force is true the validation will be perform
|
|
178
|
+
* even if the field was not initially interacted or blurred
|
|
179
|
+
*
|
|
180
|
+
*/
|
|
181
|
+
_validate: ({ force }?: ForceType) => Promise<void>;
|
|
182
|
+
setRequired: (val: boolean | string) => void;
|
|
183
|
+
setErrorMessage: (msg?: string) => void;
|
|
184
|
+
setError: (error: ErrorLike) => void;
|
|
185
|
+
get error(): string | undefined;
|
|
186
|
+
_debouncedValidation?: DebouncedFunc<Field<T, K>["_validate"]>;
|
|
187
|
+
constructor(model: FormModel<K>, value: T, validatorDescriptor: FieldDescriptor<T, K>, fieldName: string);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* a helper class to generate a dynamic form
|
|
191
|
+
* provided some keys and validators descriptors
|
|
192
|
+
*
|
|
193
|
+
* @export
|
|
194
|
+
* @class FormModel
|
|
195
|
+
*/
|
|
196
|
+
export class FormModel<K> {
|
|
197
|
+
get validatedAtLeastOnce(): boolean;
|
|
198
|
+
get dataIsReady(): boolean;
|
|
199
|
+
get requiredFields(): (keyof K)[];
|
|
200
|
+
get requiredAreFilled(): boolean;
|
|
201
|
+
fields: {
|
|
202
|
+
[P in keyof K]: Field<K[P], K>;
|
|
203
|
+
};
|
|
204
|
+
_validating: boolean;
|
|
205
|
+
get valid(): boolean;
|
|
206
|
+
/**
|
|
207
|
+
* whether or not the form has been "interacted", meaning that at
|
|
208
|
+
* least a value has set on any of the fields after the model
|
|
209
|
+
* has been created
|
|
210
|
+
*/
|
|
211
|
+
get interacted(): boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Restore the initial values set at the creation time of the model
|
|
214
|
+
* */
|
|
215
|
+
restoreInitialValues(opts?: SetValueFnArgs): void;
|
|
216
|
+
commit(): void;
|
|
217
|
+
get dirty(): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Set multiple values to more than one field a time using an object
|
|
220
|
+
* where each key is the name of a field. The value will be set to each
|
|
221
|
+
* field and from that point on the values set are considered the new
|
|
222
|
+
* initial values. Validation and interacted flags are also reset if the second argument is true
|
|
223
|
+
* */
|
|
224
|
+
updateFrom(obj: Partial<K>, { resetInteractedFlag, ...opts }?: SetValueFnArgs & ThrowIfMissingFieldType): void;
|
|
225
|
+
/**
|
|
226
|
+
* return the array of errors found. The array is an Array<String>
|
|
227
|
+
* */
|
|
228
|
+
get summary(): string[];
|
|
229
|
+
setValidating: (validating: boolean) => void;
|
|
230
|
+
get validating(): boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Manually perform the form validation
|
|
233
|
+
* */
|
|
234
|
+
validate: () => Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Update the value of the field identified by the provided name.
|
|
237
|
+
* Optionally if reset is set to true, interacted and
|
|
238
|
+
* errorMessage are cleared in the Field.
|
|
239
|
+
* */
|
|
240
|
+
updateField: (name: keyof K, value?: K[keyof K], opts?: SetValueFnArgs & ThrowIfMissingFieldType) => void;
|
|
241
|
+
/**
|
|
242
|
+
* return the data as plain Javascript object (mobx magic removed from the fields)
|
|
243
|
+
* */
|
|
244
|
+
get serializedData(): K;
|
|
245
|
+
/**
|
|
246
|
+
* Creates an instance of FormModel.
|
|
247
|
+
* initialState => an object which keys are the names of the fields and the values the initial values for the form.
|
|
248
|
+
* validators => an object which keys are the names of the fields and the values are the descriptors for the validators
|
|
249
|
+
*/
|
|
250
|
+
constructor(args: FormModelArgs<K>);
|
|
251
|
+
_getField(name: keyof K, { throwIfMissingField }?: ThrowIfMissingFieldType): { [P in keyof K]: Field<K[P], K>; }[keyof K];
|
|
252
|
+
_eachField(cb: (field: Field<K[keyof K], K>) => void): void;
|
|
253
|
+
get _fieldKeys(): (keyof K)[];
|
|
254
|
+
resetInteractedFlag(): void;
|
|
255
|
+
disableFields: (fieldKeys: (keyof K)[]) => void;
|
|
256
|
+
_createField({ name, descriptor, }: {
|
|
257
|
+
name: keyof K;
|
|
258
|
+
descriptor: FieldDescriptor<K[keyof K], K>;
|
|
259
|
+
}): void;
|
|
260
|
+
addFields: (fieldsDescriptor: Partial<Descriptors<K>>) => void;
|
|
261
|
+
enableFields(fieldKeys: (keyof K)[]): void;
|
|
262
|
+
resetValidatedOnce(): void;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* return an instance of a FormModel refer to the constructor
|
|
266
|
+
*
|
|
267
|
+
*/
|
|
268
|
+
export const createModel: <T>(args: FormModelArgs<T>) => FormModel<T>;
|
|
269
|
+
export const createModelFromState: <T>(initialState?: Partial<T>, validators?: Descriptors<T>, options?: ThrowIfMissingFieldType) => FormModel<T>;
|
|
270
|
+
}
|
|
271
|
+
|
package/package.json
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-form",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.3.0",
|
|
4
4
|
"description": "A simple form helper for mobx",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
|
-
"types": "dist/
|
|
8
|
+
"types": "dist/mobx-form.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/"
|
|
11
11
|
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/mobx-form.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
12
19
|
"scripts": {
|
|
13
20
|
"lint": "eslint --cache --cache-location node_modules/.cache/ 'src/**/*.ts' 'tests/**/*.ts'",
|
|
14
21
|
"check": "npm run lint && npm run verify",
|
|
@@ -25,9 +32,12 @@
|
|
|
25
32
|
"bump-prerelease": "npm run pre-v && npm version prerelease -m 'BLD: Release v%s' && npm run post-v",
|
|
26
33
|
"prepublishOnly": "npm run build",
|
|
27
34
|
"test": "bun test",
|
|
28
|
-
"build": "rm -rf ./dist && bun bun.build.ts && npm run dts",
|
|
35
|
+
"build": "rm -rf ./dist && bun bun.build.ts && npm run dts && npm run bundle-types",
|
|
29
36
|
"dts": "tsc -p tsconfig.build.json --emitDeclarationOnly --declaration --outDir dist",
|
|
30
|
-
"
|
|
37
|
+
"bundle-types": "bash bundle-types.sh",
|
|
38
|
+
"smoke:test": "bun simple-demo.ts",
|
|
39
|
+
"storybook": "storybook dev -p 6006",
|
|
40
|
+
"build-storybook": "storybook build"
|
|
31
41
|
},
|
|
32
42
|
"repository": {
|
|
33
43
|
"type": "git",
|
|
@@ -64,17 +74,34 @@
|
|
|
64
74
|
"mobx": "^6.0.1"
|
|
65
75
|
},
|
|
66
76
|
"devDependencies": {
|
|
77
|
+
"dts-bundle": "0.7.3",
|
|
78
|
+
"@eslint/js": "^9.8.0",
|
|
79
|
+
"@storybook/addon-essentials": "^8.6.14",
|
|
80
|
+
"@storybook/addon-interactions": "^8.6.14",
|
|
81
|
+
"@storybook/addon-links": "8.6.14",
|
|
82
|
+
"@storybook/addon-storysource": "8.6.14",
|
|
83
|
+
"@storybook/addon-themes": "8.6.14",
|
|
84
|
+
"@storybook/blocks": "^8.6.14",
|
|
85
|
+
"@storybook/react": "8.6.14",
|
|
86
|
+
"@storybook/react-vite": "8.6.14",
|
|
87
|
+
"@storybook/theming": "8.6.14",
|
|
67
88
|
"@types/bun": "^1.2.20",
|
|
68
89
|
"@types/lodash": "^4.17.20",
|
|
90
|
+
"@types/react": "^19.2.14",
|
|
91
|
+
"@types/react-dom": "^19.2.3",
|
|
69
92
|
"changelogx": "^5.0.4",
|
|
70
|
-
"
|
|
93
|
+
"eslint": "9.10.0",
|
|
71
94
|
"eslint-config-prettier": "9.1.0",
|
|
72
95
|
"eslint-plugin-prettier": "5.2.1",
|
|
73
|
-
"@eslint/js": "^9.8.0",
|
|
74
|
-
"eslint": "9.10.0",
|
|
75
96
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
|
76
97
|
"eslint-plugin-react-refresh": "^0.4.9",
|
|
77
98
|
"eslint-plugin-tailwindcss": "3.17.4",
|
|
78
|
-
"
|
|
99
|
+
"mobx-react-lite": "^4.1.1",
|
|
100
|
+
"react": "^19.2.4",
|
|
101
|
+
"react-dom": "^19.2.4",
|
|
102
|
+
"storybook": "8.6.14",
|
|
103
|
+
"typescript": "5.9.3",
|
|
104
|
+
"typescript-eslint": "^8.0.0",
|
|
105
|
+
"vite": "^7.3.1"
|
|
79
106
|
}
|
|
80
107
|
}
|
package/dist/FormModel.d.ts
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import type { DebouncedFunc } from "lodash";
|
|
2
|
-
export type Descriptors<T> = {
|
|
3
|
-
[P in keyof T]: FieldDescriptor<T[P], T>;
|
|
4
|
-
};
|
|
5
|
-
export type FormModelArgs<T> = {
|
|
6
|
-
descriptors: Partial<Descriptors<T>>;
|
|
7
|
-
initialState?: Partial<T>;
|
|
8
|
-
options?: ThrowIfMissingFieldType;
|
|
9
|
-
};
|
|
10
|
-
export type ResultObj = {
|
|
11
|
-
error: string;
|
|
12
|
-
};
|
|
13
|
-
export type ErrorLike = {
|
|
14
|
-
message: string;
|
|
15
|
-
} | Error;
|
|
16
|
-
export type ValidatorResult = boolean | ResultObj | void;
|
|
17
|
-
export type ValidateFnArgs<T, K> = {
|
|
18
|
-
field: Field<T, K>;
|
|
19
|
-
fields: FormModel<K>["fields"];
|
|
20
|
-
model: FormModel<K>;
|
|
21
|
-
value: Field<T, K>["value"];
|
|
22
|
-
};
|
|
23
|
-
export type ValidateFn<T, K> = (args: ValidateFnArgs<T, K>) => Promise<ValidatorResult> | ValidatorResult;
|
|
24
|
-
export type ResetInteractedFlagType = {
|
|
25
|
-
resetInteractedFlag?: boolean;
|
|
26
|
-
};
|
|
27
|
-
export type ThrowIfMissingFieldType = {
|
|
28
|
-
throwIfMissingField?: boolean;
|
|
29
|
-
};
|
|
30
|
-
export interface FieldDescriptor<T, K> {
|
|
31
|
-
waitForBlur?: boolean;
|
|
32
|
-
disabled?: boolean;
|
|
33
|
-
errorMessage?: string;
|
|
34
|
-
validator?: ValidateFn<T, K> | ValidateFn<T, K>[];
|
|
35
|
-
hasValue?: (value: T) => boolean;
|
|
36
|
-
value?: T;
|
|
37
|
-
required?: boolean | string;
|
|
38
|
-
autoValidate?: boolean;
|
|
39
|
-
validationDebounceThreshold?: number;
|
|
40
|
-
clearErrorOnValueChange?: boolean;
|
|
41
|
-
meta?: Record<string, any>;
|
|
42
|
-
}
|
|
43
|
-
export type CommitType = {
|
|
44
|
-
commit?: boolean;
|
|
45
|
-
};
|
|
46
|
-
export type ForceType = {
|
|
47
|
-
force?: boolean;
|
|
48
|
-
};
|
|
49
|
-
export type SetValueFnArgs = ResetInteractedFlagType & CommitType;
|
|
50
|
-
/**
|
|
51
|
-
* Field class provides abstract the validation of a single field
|
|
52
|
-
*/
|
|
53
|
-
export declare class Field<T, K> {
|
|
54
|
-
_name: string;
|
|
55
|
-
meta?: Record<string, any>;
|
|
56
|
-
_model: FormModel<K>;
|
|
57
|
-
_waitForBlur?: boolean | undefined;
|
|
58
|
-
_disabled?: boolean | undefined;
|
|
59
|
-
_required?: boolean | string;
|
|
60
|
-
_validatedOnce: boolean;
|
|
61
|
-
_clearErrorOnValueChange?: boolean | undefined;
|
|
62
|
-
_hasValueFn?: (value: T) => boolean;
|
|
63
|
-
get name(): string;
|
|
64
|
-
get model(): FormModel<K>;
|
|
65
|
-
get validatedAtLeastOnce(): boolean;
|
|
66
|
-
get waitForBlur(): boolean;
|
|
67
|
-
get disabled(): boolean;
|
|
68
|
-
get required(): boolean;
|
|
69
|
-
resetInteractedFlag(): void;
|
|
70
|
-
markAsInteracted(): void;
|
|
71
|
-
resetValidatedOnce(): void;
|
|
72
|
-
get hasValue(): boolean;
|
|
73
|
-
_validationTs: number;
|
|
74
|
-
/**
|
|
75
|
-
* flag to know if a validation is in progress on this field
|
|
76
|
-
*/
|
|
77
|
-
_validating: boolean;
|
|
78
|
-
/**
|
|
79
|
-
* field to store the initial value set on this field
|
|
80
|
-
* */
|
|
81
|
-
_initialValue?: T;
|
|
82
|
-
/**
|
|
83
|
-
* the value of the field
|
|
84
|
-
* */
|
|
85
|
-
_value?: T;
|
|
86
|
-
/**
|
|
87
|
-
* whether the user interacted with the field
|
|
88
|
-
* this means if there is any value set on the field
|
|
89
|
-
* either setting it using the `setValue` or using
|
|
90
|
-
* the setter `value`. This is useful to know if
|
|
91
|
-
* the user has interacted with teh form in any way
|
|
92
|
-
*/
|
|
93
|
-
_interacted: boolean;
|
|
94
|
-
/**
|
|
95
|
-
* whether the field was blurred at least once
|
|
96
|
-
* usually validators should only start being applied
|
|
97
|
-
* after the first blur, otherwise they become
|
|
98
|
-
* too invasive. This flag be used to keep track of
|
|
99
|
-
* the fact that the user already blurred of a field
|
|
100
|
-
*/
|
|
101
|
-
_blurredOnce: boolean;
|
|
102
|
-
get blurred(): boolean;
|
|
103
|
-
/** the raw error in caes validator throws a real error */
|
|
104
|
-
rawError?: ErrorLike;
|
|
105
|
-
/**
|
|
106
|
-
* the error message associated with this field.
|
|
107
|
-
* This is used to indicate what error happened during
|
|
108
|
-
* the validation process
|
|
109
|
-
*/
|
|
110
|
-
get errorMessage(): string | undefined;
|
|
111
|
-
/**
|
|
112
|
-
* whether the validation should be launch after a
|
|
113
|
-
* new value is set in the field. This is usually associated
|
|
114
|
-
* to forms that set the value on the fields after each
|
|
115
|
-
* onChange event
|
|
116
|
-
*/
|
|
117
|
-
_autoValidate: boolean;
|
|
118
|
-
get autoValidate(): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* used to keep track of the original message
|
|
121
|
-
*/
|
|
122
|
-
_originalErrorMessage?: string;
|
|
123
|
-
/**
|
|
124
|
-
* whether the field is valid or not
|
|
125
|
-
*/
|
|
126
|
-
get valid(): boolean;
|
|
127
|
-
/**
|
|
128
|
-
* whether the user has interacted or not with the field
|
|
129
|
-
*/
|
|
130
|
-
get interacted(): boolean;
|
|
131
|
-
/**
|
|
132
|
-
* get the value set on the field
|
|
133
|
-
*/
|
|
134
|
-
get value(): T | undefined;
|
|
135
|
-
_setValueOnly: (val?: T) => void;
|
|
136
|
-
_setValue: (val?: T) => void;
|
|
137
|
-
/**
|
|
138
|
-
* setter for the value of the field
|
|
139
|
-
*/
|
|
140
|
-
set value(val: T);
|
|
141
|
-
setValue: (value?: T, { resetInteractedFlag, commit }?: SetValueFnArgs) => void;
|
|
142
|
-
/**
|
|
143
|
-
* Restore the initial value of the field
|
|
144
|
-
*/
|
|
145
|
-
restoreInitialValue: ({ resetInteractedFlag, commit }?: SetValueFnArgs) => void;
|
|
146
|
-
get dirty(): boolean;
|
|
147
|
-
commit(): void;
|
|
148
|
-
/**
|
|
149
|
-
* clear the valid state of the field by
|
|
150
|
-
* removing the errorMessage string. A field is
|
|
151
|
-
* considered valid if the errorMessage is not empty
|
|
152
|
-
*/
|
|
153
|
-
resetError(): void;
|
|
154
|
-
clearValidation(): void;
|
|
155
|
-
/**
|
|
156
|
-
* mark the field as already blurred so validation can
|
|
157
|
-
* start to be applied to the field.
|
|
158
|
-
*/
|
|
159
|
-
markBlurredAndValidate: () => void;
|
|
160
|
-
_validateFn?: ValidateFn<T, K> | Array<ValidateFn<T, K>>;
|
|
161
|
-
_doValidate: () => Promise<ValidatorResult | undefined>;
|
|
162
|
-
setDisabled(disabled: boolean): void;
|
|
163
|
-
validate: (opts?: ForceType) => Promise<void>;
|
|
164
|
-
get originalErrorMessage(): string;
|
|
165
|
-
setValidating: (validating: boolean) => void;
|
|
166
|
-
get validating(): boolean;
|
|
167
|
-
/**
|
|
168
|
-
* validate the field. If force is true the validation will be perform
|
|
169
|
-
* even if the field was not initially interacted or blurred
|
|
170
|
-
*
|
|
171
|
-
*/
|
|
172
|
-
_validate: ({ force }?: ForceType) => Promise<void>;
|
|
173
|
-
setRequired: (val: boolean | string) => void;
|
|
174
|
-
setErrorMessage: (msg?: string) => void;
|
|
175
|
-
setError: (error: ErrorLike) => void;
|
|
176
|
-
get error(): string | undefined;
|
|
177
|
-
_debouncedValidation?: DebouncedFunc<Field<T, K>["_validate"]>;
|
|
178
|
-
constructor(model: FormModel<K>, value: T, validatorDescriptor: FieldDescriptor<T, K>, fieldName: string);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* a helper class to generate a dynamic form
|
|
182
|
-
* provided some keys and validators descriptors
|
|
183
|
-
*
|
|
184
|
-
* @export
|
|
185
|
-
* @class FormModel
|
|
186
|
-
*/
|
|
187
|
-
export declare class FormModel<K> {
|
|
188
|
-
get validatedAtLeastOnce(): boolean;
|
|
189
|
-
get dataIsReady(): boolean;
|
|
190
|
-
get requiredFields(): (keyof K)[];
|
|
191
|
-
get requiredAreFilled(): boolean;
|
|
192
|
-
fields: {
|
|
193
|
-
[P in keyof K]: Field<K[P], K>;
|
|
194
|
-
};
|
|
195
|
-
_validating: boolean;
|
|
196
|
-
get valid(): boolean;
|
|
197
|
-
/**
|
|
198
|
-
* whether or not the form has been "interacted", meaning that at
|
|
199
|
-
* least a value has set on any of the fields after the model
|
|
200
|
-
* has been created
|
|
201
|
-
*/
|
|
202
|
-
get interacted(): boolean;
|
|
203
|
-
/**
|
|
204
|
-
* Restore the initial values set at the creation time of the model
|
|
205
|
-
* */
|
|
206
|
-
restoreInitialValues(opts?: SetValueFnArgs): void;
|
|
207
|
-
commit(): void;
|
|
208
|
-
get dirty(): boolean;
|
|
209
|
-
/**
|
|
210
|
-
* Set multiple values to more than one field a time using an object
|
|
211
|
-
* where each key is the name of a field. The value will be set to each
|
|
212
|
-
* field and from that point on the values set are considered the new
|
|
213
|
-
* initial values. Validation and interacted flags are also reset if the second argument is true
|
|
214
|
-
* */
|
|
215
|
-
updateFrom(obj: Partial<K>, { resetInteractedFlag, ...opts }?: SetValueFnArgs & ThrowIfMissingFieldType): void;
|
|
216
|
-
/**
|
|
217
|
-
* return the array of errors found. The array is an Array<String>
|
|
218
|
-
* */
|
|
219
|
-
get summary(): string[];
|
|
220
|
-
setValidating: (validating: boolean) => void;
|
|
221
|
-
get validating(): boolean;
|
|
222
|
-
/**
|
|
223
|
-
* Manually perform the form validation
|
|
224
|
-
* */
|
|
225
|
-
validate: () => Promise<void>;
|
|
226
|
-
/**
|
|
227
|
-
* Update the value of the field identified by the provided name.
|
|
228
|
-
* Optionally if reset is set to true, interacted and
|
|
229
|
-
* errorMessage are cleared in the Field.
|
|
230
|
-
* */
|
|
231
|
-
updateField: (name: keyof K, value?: K[keyof K], opts?: SetValueFnArgs & ThrowIfMissingFieldType) => void;
|
|
232
|
-
/**
|
|
233
|
-
* return the data as plain Javascript object (mobx magic removed from the fields)
|
|
234
|
-
* */
|
|
235
|
-
get serializedData(): K;
|
|
236
|
-
/**
|
|
237
|
-
* Creates an instance of FormModel.
|
|
238
|
-
* initialState => an object which keys are the names of the fields and the values the initial values for the form.
|
|
239
|
-
* validators => an object which keys are the names of the fields and the values are the descriptors for the validators
|
|
240
|
-
*/
|
|
241
|
-
constructor(args: FormModelArgs<K>);
|
|
242
|
-
_getField(name: keyof K, { throwIfMissingField }?: ThrowIfMissingFieldType): { [P in keyof K]: Field<K[P], K>; }[keyof K];
|
|
243
|
-
_eachField(cb: (field: Field<K[keyof K], K>) => void): void;
|
|
244
|
-
get _fieldKeys(): (keyof K)[];
|
|
245
|
-
resetInteractedFlag(): void;
|
|
246
|
-
disableFields: (fieldKeys: (keyof K)[]) => void;
|
|
247
|
-
_createField({ name, descriptor, }: {
|
|
248
|
-
name: keyof K;
|
|
249
|
-
descriptor: FieldDescriptor<K[keyof K], K>;
|
|
250
|
-
}): void;
|
|
251
|
-
addFields: (fieldsDescriptor: Partial<Descriptors<K>>) => void;
|
|
252
|
-
enableFields(fieldKeys: (keyof K)[]): void;
|
|
253
|
-
resetValidatedOnce(): void;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* return an instance of a FormModel refer to the constructor
|
|
257
|
-
*
|
|
258
|
-
*/
|
|
259
|
-
export declare const createModel: <T>(args: FormModelArgs<T>) => FormModel<T>;
|
|
260
|
-
export declare const createModelFromState: <T>(initialState?: Partial<T>, validators?: Descriptors<T>, options?: ThrowIfMissingFieldType) => FormModel<T>;
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./FormModel";
|