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 CHANGED
@@ -1,138 +1,207 @@
1
- # [Mobx](https://www.npmjs.com/package/mobx)-Form
2
1
 
3
- Simple helper for state management
2
+ # mobx-form
4
3
 
5
- TODO:
4
+ [![NPM Version](https://img.shields.io/npm/v/mobx-form.svg)](https://www.npmjs.com/package/mobx-form)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![CI](https://github.com/royriojas/mobx-form/actions/workflows/ci.yml/badge.svg)](https://github.com/royriojas/mobx-form/actions/workflows/ci.yml)
6
7
 
7
- - Add more examples
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
- ```bash
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
- ## Usage
13
+ [**View Live Demo / Storybook**](https://royriojas.github.io/mobx-form/)
18
14
 
19
- ```javascript
20
- import { createModel } from 'mobx-form';
15
+ ## Features
21
16
 
22
- const model = createModel( {
23
- // the field descriptor
24
- // when calling model.validate(); all the validators are executed in parallel
25
- fieldName1: {
26
- // whether validation will happen automatically after first update
27
- autoValidate: true,
28
- // whether validation should wait until the field is blurred at least once
29
- // (meaning the blur event happened at least once in that field).
30
- //
31
- // This requires that the component calls `markBlurredAndValidate()` after
32
- // the blur even is raised on the field
33
- waitForBlur: false,
34
- // Very useful flag to just make a field required or not without requiring a validator
35
- // the `required` property can be:
36
- // - a boolean, in which case the error message shown will be `Required`
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
- performValidation = async () => {
64
- // To call all validators
65
- await model.validate();
33
+ ## Demo / Storybook
66
34
 
67
- // To check if valid (after awaiting the validate call)
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
- // do something with the serializedData
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
- // to update values in the form.
80
- model.updateFrom({ fieldName1: 'new value' }, /* reset = true */); // by default setting values using this method
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
- ## Example:
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
- ```js
88
- import trim from 'jq-trim';
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 model = createModel({
72
+ const loginForm = createModel({
73
+ initialState: {
74
+ username: '',
75
+ password: ''
76
+ },
92
77
  descriptors: {
93
- name: {
94
- required: 'Name is 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
- // validator function
110
- async validator(field) {
111
- if (!trim(field.value)) {
112
- throw new Error('Please provide your password');
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
- initialState: {
118
- email: '',
119
- password: '',
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
- const main = async () => {
124
- await model.validate();
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
- console.log('>>>> model.valid', model.valid);
127
- console.log('>>>> model.summary', model.summary);
128
- console.log('>>>> model.requiredFields', model.requiredFields);
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
- // >>>> model.valid false
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
- main().catch(err => console.error('>>> error', err));
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.2.0",
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/index.d.ts",
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
- "smoke:test": "bun simple-demo.ts"
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
- "typescript": "5.9.2",
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
- "typescript-eslint": "^8.0.0"
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
  }
@@ -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";