adaptform 2.0.5 → 2.1.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,420 +1,388 @@
1
1
  # AdaptForm
2
2
 
3
+ [English](README.md) · [Русский](README.ru.md)
4
+
3
5
  [![npm version](https://img.shields.io/npm/v/adaptform.svg)](https://www.npmjs.com/package/adaptform)
4
6
  [![npm downloads](https://img.shields.io/npm/dm/adaptform.svg)](https://www.npmjs.com/package/adaptform)
5
- [![bundle size](https://img.shields.io/bundlephobia/minzip/adaptform)](https://bundlephobia.com/package/adaptform)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
9
 
9
- AdaptForm is a TypeScript-first form modeling framework inspired by Django Forms and Pydantic.
10
-
11
- Instead of describing validation schemas, you describe forms as classes with typed fields. Each field is responsible for
12
- its own state lifecycle, validation, transformation pipeline, and serialization logic.
10
+ **[Open the interactive demo](https://arhimen.github.io/AdaptForm/)** · **[Read the 2.1.0 changelog](https://github.com/ArhiMEN/AdaptForm/blob/master/CHANGELOG.md)** · **[Browse the demo source](https://github.com/ArhiMEN/AdaptForm/tree/master/demo)**
13
11
 
14
- ## AdaptForm provides:
12
+ Maintainers: [automatic npm publication setup](docs/NPM_PUBLISH.md).
15
13
 
16
- - Typed form models
17
- - Field-level validation
18
- - Cross-field validation
19
- - Input transformation plugins
20
- - Nested forms and arrays
21
- - Automatic TypeScript inference
22
- - Framework-agnostic integration
14
+ AdaptForm is a TypeScript-first form modeling library inspired by Django Forms and Pydantic. Define forms as classes with typed fields. Each field owns its value, casting, validation, transformation plugins, errors, and state.
23
15
 
24
16
  ## Features
25
17
 
26
- - Form-as-a-domain-model architecture
27
- - Field-level state + validation ownership
28
- - Cross-field validation support
29
- - Plugin-based transformation pipeline
30
- - Strong TypeScript inference
31
- - Nested forms & collections
32
- - Built-in serialization (JSON / FormData)
33
- - Optional date engine (Luxon)
34
- - Framework-agnostic design
35
- - Zero-core-dependencies
36
-
37
- ## Philosophy
38
-
39
- Most form libraries treat forms as validation schemas:
40
-
41
- ```TypeScript
42
- const schema = z.object({
43
- login: z.string(),
44
- password: z.string(),
45
- })
46
- ````
47
-
48
- AdaptForm treats forms as stateful domain models:
49
-
50
- ```TypeScript
51
- class LoginForm extends Form {
52
- login = new Field(StringType)
53
- password = new Field(StringType)
54
- }
55
- ```
56
-
57
- field is not just a validator — it is a state container that owns:
58
-
59
- - value
60
- - validation
61
- - errors
62
- - transformation
63
- - plugins
64
- - type casting
65
-
66
- The form itself becomes a single source of truth.
67
-
68
- Simple Vue:
69
-
70
- ## Without AdaptForm
71
-
72
- A typical Vue form consists of multiple disconnected parts:
73
-
74
- - reactive state
75
- - validation schema
76
- - error store
77
- - input masks
78
- - submission mapping
79
-
80
- ## With AdaptForm
81
-
82
- Everything is unified into a single form model:
83
-
84
- ```TypeScript
85
- class RegistrationForm extends Form {
86
- phone = new Field(StringType)
87
- }
88
- ````
18
+ - Class-based form models with inferred TypeScript value types.
19
+ - Field-level and cross-field validation.
20
+ - Conditional required rules and custom synchronous validators.
21
+ - String, number, decimal, boolean, and JavaScript Date field types.
22
+ - Dynamic arrays of fields and arrays of nested forms.
23
+ - Input transformation plugins, including `MaskPlugin`.
24
+ - Server-error mapping with `ErrorsSchema`.
25
+ - JSON and `FormData` serialization.
26
+ - Optional Luxon integration through the `adaptform/luxon` entry point.
27
+ - Framework-agnostic core with no required runtime dependencies.
89
28
 
90
29
  ## Installation
91
30
 
31
+ Install the published npm package:
32
+
92
33
  ```bash
93
34
  npm install adaptform
94
35
  ```
95
36
 
96
- ## Optional: Luxon integration
37
+ Luxon integration is optional:
97
38
 
98
- ```Bash
39
+ ```bash
99
40
  npm install luxon
100
41
  ```
101
42
 
102
- ## Quick Start
43
+ The [interactive demo](https://arhimen.github.io/AdaptForm/) runs from repository source, with automatic deployment from `v2.1.0` and `master`. Version `2.1.0` is prepared in the repository; npm continues to serve the last published version until the maintainer publishes the release.
103
44
 
104
- ```TypeScript
105
- import {Form, Field, StringType, NumberType} from 'adaptform';
45
+ The demo supports English and Russian with a persistent language selector: [English](https://arhimen.github.io/AdaptForm/?lang=en) · [Русский](https://arhimen.github.io/AdaptForm/?lang=ru). Switching languages preserves form data and validation state.
106
46
 
107
- class LoginForm extends Form {
108
- login = new Field(StringType, null, {maxLength: 50});
109
- password = new Field(StringType, null, {minLength: 8});
110
- remember = new Field(BooleanType, false);
111
- }
47
+ ## Quick start
48
+
49
+ ```typescript
50
+ import { BooleanType, Field, Form, StringType } from 'adaptform'
51
+
52
+ class RegistrationForm extends Form {
53
+ name = new Field(StringType, null, {
54
+ isRequired: true,
55
+ minLength: 2,
56
+ maxLength: 50,
57
+ })
112
58
 
113
- const form = new LoginForm();
59
+ email = new Field(StringType, null, {
60
+ isRequired: true,
61
+ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
62
+ })
114
63
 
64
+ acceptedTerms = new Field(BooleanType, false, {
65
+ mustBeTrue: true,
66
+ })
67
+ }
68
+
69
+ const form = new RegistrationForm()
115
70
  form.fields = {
116
- login: 'john_doe',
117
- password: 'securepass123'
118
- };
71
+ name: 'Alex Morgan',
72
+ email: 'alex@example.com',
73
+ acceptedTerms: true,
74
+ }
119
75
 
120
- console.log(form.isValid); // true
121
- console.log(form.fieldsValue); // { login: 'john_doe', password: 'securepass123', remember: false }
76
+ if (form.isValid) {
77
+ console.log(form.fieldsValue)
78
+ console.log(form.toJSON())
79
+ } else {
80
+ console.log(form.allErrors)
81
+ }
122
82
  ```
123
83
 
124
- ## Usage Examples
84
+ `form.fields` assigns the supplied values and validates the completed form. Values for fields omitted from the assignment are left unchanged.
125
85
 
126
- ## Field Types
86
+ ## Field types and options
127
87
 
128
- ```TypeScript
129
- import {Field, StringType, NumberType, BooleanType, DecimalType, DateType} from 'adaptform';
88
+ All field types are constructed using `new Field(TypeConstructor, defaultValue?, options?)`.
130
89
 
131
- // String with validation
132
- const name = new Field(StringType, null, {
133
- minLength: 2,
134
- maxLength: 50,
135
- pattern: /^[a-zA-Z]+$/
136
- });
90
+ ### StringType
137
91
 
138
- // Number with range
139
- const age = new Field(NumberType, null, {
140
- gt: 0, // greater than
141
- lt: 120 // less than
142
- });
143
-
144
- // Boolean with requirement
145
- const agreed = new Field(BooleanType, false, {
146
- mustBeTrue: true,
92
+ ```typescript
93
+ const username = new Field(StringType, null, {
94
+ isRequired: true,
95
+ minLength: 3,
96
+ maxLength: 32,
97
+ pattern: /^[a-z0-9_]+$/i,
147
98
  messages: {
148
- mustBeTrue: 'You must agree to the terms'
149
- }
150
- });
151
- ```
152
-
153
- ## Custom Validation
154
-
155
- ```TypeScript
156
- const email = new Field(StringType, null, {
157
- validate: (value) => {
158
- return value.includes('@') || 'Invalid email format';
159
- }
160
- });
161
-
162
- // Async validation with form context
163
- const passwordConfirmation = new Field(StringType, null, {
164
- validate: (value, form) => {
165
- return value === form?.password?.valueClear || 'Passwords do not match';
166
- }
167
- });
99
+ isRequired: 'Enter a username.',
100
+ minLength: 'Use at least 3 characters.',
101
+ pattern: 'Use only letters, numbers, and underscores.',
102
+ },
103
+ })
168
104
  ```
169
105
 
170
- ## Conditional Required Fields
106
+ Supported constraints: `isRequired`, `minLength`, `maxLength`, and `pattern`.
171
107
 
172
- ```TypeScript
173
- class SignUpForm extends Form {
174
- country = new Field(StringType, null);
108
+ ### NumberType
175
109
 
176
- state = new Field(StringType, null, {
177
- isRequired: (form) => form?.country?.valueClear === 'USA',
178
- messages: {
179
- isRequired: 'State is required for USA residents'
180
- }
181
- });
182
- }
110
+ ```typescript
111
+ const age = new Field(NumberType, null, {
112
+ isRequired: true,
113
+ ge: 18,
114
+ le: 120,
115
+ })
183
116
  ```
184
117
 
185
- ## Input Masks
118
+ Supported constraints: `gt`, `ge`, `lt`, and `le`. Values must be finite integers; blank strings and non-finite numbers are rejected.
186
119
 
187
- ```TypeScript
188
- import {Field, StringType, MaskPlugin} from 'adaptform';
120
+ ### DecimalType
189
121
 
190
- const phone = new Field(StringType, null, {
191
- plugins: [
192
- new MaskPlugin({
193
- maskFormat: '+7 (___) ___-__-__',
194
- maskPlaceholder: '_',
195
- digitPattern: /\d/
196
- })
197
- ]
198
- });
199
-
200
- phone.rawValue = '9991234567';
201
- console.log(phone.valueClear); // '+7 (999) 123-45-67'
122
+ ```typescript
123
+ const price = new Field(DecimalType, null, {
124
+ decimalPlaces: 2,
125
+ maxDigits: 8,
126
+ ge: 0,
127
+ })
202
128
  ```
203
129
 
204
- ## Nested Forms
205
-
206
- ```TypeScript
207
- import {ArrayField, Field, NumberType} from 'adaptform';
130
+ Supports `gt`, `ge`, `lt`, `le`, `decimalPlaces`, and `maxDigits`. Numeric inputs with excess precision are rejected rather than silently rounded. String inputs may use a comma or a period as the decimal separator.
208
131
 
209
- const gameIds = new ArrayField(
210
- (value?: number) => new Field(NumberType, value, {gt: 0}),
211
- {minItems: 1}
212
- );
132
+ ### BooleanType
213
133
 
214
- gameIds.itemsValue = [1, 2, 3];
215
- gameIds.add(4);
216
- console.log(gameIds.itemsValue); // [1, 2, 3, 4]
134
+ ```typescript
135
+ const accepted = new Field(BooleanType, false, {
136
+ mustBeTrue: true,
137
+ })
217
138
  ```
218
139
 
219
- ## Luxon Date Integration
220
-
221
- ```Bash
222
- npm install luxon
223
- ```
140
+ Supports `mustBeTrue` and `mustBeFalse`.
224
141
 
225
- ```TypeScript
226
- import {DateLuxonType} from 'adaptform/luxon';
227
- import {DateTime} from 'luxon';
142
+ ### DateType
228
143
 
229
- const birthdate = new Field(DateLuxonType, null, {
144
+ ```typescript
145
+ const birthday = new Field(DateType, null, {
230
146
  isDatetime: false,
231
- minDate: DateTime.fromISO('1900-01-01'),
232
- maxDate: DateTime.now(),
233
- inputFormat: 'dd.MM.yyyy',
234
- messages: {
235
- minDate: 'Date must be after 1900',
236
- maxDate: 'Date cannot be in the future'
237
- }
238
- });
239
-
240
- birthdate.rawValue = '15.06.1990';
241
- console.log(birthdate.valueClear); // Luxon DateTime object
147
+ minDate: new Date('1900-01-01T00:00:00.000Z'),
148
+ maxDate: new Date(),
149
+ })
242
150
  ```
243
151
 
244
- ## Form Submission
152
+ Supports `minDate`, `maxDate`, and `isDatetime`. When `isDatetime` is false, comparisons use calendar dates; when true, timestamps are compared. Invalid dates are rejected.
245
153
 
246
- ```TypeScript
247
- class ContactForm extends Form {
248
- name = new Field(StringType, null, {maxLength: 100});
249
- email = new Field(StringType, null, {
250
- pattern: /^[^@]+@[^@.]+\.[^@.]+$/
251
- });
252
- message = new Field(StringType, null, {maxLength: 1000});
253
- }
154
+ ### Custom validation and messages
254
155
 
255
- const form = new ContactForm();
256
- form.fields = {
257
- name: 'John',
258
- email: 'john@example.com',
259
- message: 'Hello!'
260
- };
261
-
262
- if (form.checkValid()) {
263
- // Submit form
264
- const formData = form.toFormData();
265
- const json = form.toJSON();
266
-
267
- await fetch('/api/contact', {
268
- method: 'POST',
269
- body: formData
270
- });
271
- } else {
272
- console.log(form.allErrors);
273
- // {
274
- // name: [],
275
- // email: [],
276
- // message: []
277
- // }
156
+ Field options support `isRequired`, `messages`, and a synchronous `validate(value, form)` callback. A custom validator returns `true` for success, `false` for the configured validation message, or a string for a specific error.
157
+
158
+ ```typescript
159
+ class PasswordForm extends Form {
160
+ password = new Field(StringType, null, { minLength: 8 })
161
+
162
+ confirmation = new Field(StringType, null, {
163
+ isRequired: true,
164
+ validate: (value, form) =>
165
+ value === form?.password.valueClear || 'Passwords do not match.',
166
+ })
278
167
  }
279
168
  ```
280
169
 
281
- ## Global Errors
170
+ Validators are synchronous; returning a Promise is not supported. For asynchronous checks such as server-side uniqueness, call the service separately and apply the response through the form's error API.
282
171
 
283
- ```TypeScript
284
- class LoginForm extends Form {
285
- login = new Field(StringType, null);
286
- password = new Field(StringType, null);
287
- }
172
+ ## Validation lifecycle and state
288
173
 
289
- const form = new LoginForm();
174
+ - Constructor defaults begin untouched.
175
+ - Empty strings and whitespace-only strings count as empty values.
176
+ - Global and sticky regular-expression flags do not make repeated string validation stateful.
177
+ - `form.checkValid()` validates every field and returns `false` when a field is invalid or global server errors remain.
178
+ - Reading `isValid` does not run validators. Fields without a non-null default start with `isValid === null`; non-null constructor defaults pass through casting and validation but still start untouched. After `reset()`, field validity is unset until validation runs again.
179
+ - Bulk assignment validates after direct field values have been assigned, so conditional-required rules can inspect the complete form state.
180
+ - If you change dependent fields one at a time, call `form.checkValid()` after the changes to recompute cross-field rules.
181
+ - `field.setRawValue(value, false)` and `form.setFields(data, false)` allow advanced callers to defer validation. Call `checkValid()` before relying on the validity state.
182
+ - `reset()` restores defaults, clears errors, and leaves validity unset until the next validation. Collection validity is false after reset until the collection is checked again.
183
+ - Adding, removing, or clearing collection items refreshes collection validation. Invalid removal indices are ignored.
290
184
 
291
- // Add server-side errors
292
- form.errors = {
293
- login: 'Account not found',
294
- password: 'Invalid password'
295
- };
185
+ ## Dynamic collections
296
186
 
297
- // Or set global error
298
- form.setGlobalError('auth', 'Authentication failed');
299
- ```
187
+ ### ArrayField
188
+
189
+ Use `ArrayField` for a dynamic list of similarly typed fields.
300
190
 
301
- ## Plugin System
191
+ ```typescript
192
+ import { ArrayField, Field, StringType } from 'adaptform'
302
193
 
303
- ### Built-in Plugins
194
+ const tags = new ArrayField(
195
+ (value?: string) => new Field(StringType, value, {
196
+ isRequired: true,
197
+ minLength: 2,
198
+ }),
199
+ { minItems: 1, maxItems: 5 },
200
+ )
304
201
 
305
- * **MaskPlugin** - Input masking (phone, date, credit card)
202
+ tags.add('typescript')
203
+ tags.add('forms')
204
+ console.log(tags.itemsValue)
205
+ console.log(tags.isValid)
206
+ ```
306
207
 
307
- * **FieldPlugin** - Base plugin for custom extensions
208
+ Options include `isRequired`, `minItems`, `maxItems`, `messages`, and a synchronous `validate(values, form)` callback. `itemsValue` returns an array whose entries may be `null`.
308
209
 
309
- ### Creating Custom Plugins
210
+ ### ArrayFormField
310
211
 
311
- ```TypeScript
312
- import {BasePlugin} from 'adaptform';
313
- import type {FieldPlugin} from 'adaptform';
212
+ Use `ArrayFormField` for a list of nested form instances.
314
213
 
315
- class UppercasePlugin extends BasePlugin implements FieldPlugin<string> {
316
- toRawValue(value: string): string {
317
- return value?.toUpperCase() || '';
318
- }
214
+ ```typescript
215
+ import { ArrayFormField, Field, Form, StringType } from 'adaptform'
319
216
 
320
- toValueClear(value: string): string {
321
- return value?.toLowerCase() || '';
322
- }
217
+ class AddressForm extends Form {
218
+ city = new Field(StringType, null, { isRequired: true })
219
+ postalCode = new Field(StringType, null, { isRequired: true })
323
220
  }
324
221
 
325
- const field = new Field(StringType, null, {
326
- plugins: [new UppercasePlugin()]
327
- });
328
- ```
222
+ const addresses = new ArrayFormField(AddressForm, {
223
+ minForms: 1,
224
+ maxForms: 3,
225
+ })
329
226
 
330
- ## API Reference
227
+ addresses.add({ city: 'Berlin', postalCode: '10115' })
228
+ console.log(addresses.formsFieldsValue)
229
+ ```
331
230
 
332
- ### Form
231
+ Options include `isRequired`, `minForms`, `maxForms`, `messages`, and a synchronous `validate(forms, form)` callback.
333
232
 
334
- * `checkValid(form?)` - Validate all fields
233
+ ## Input masks
335
234
 
336
- * `fieldsValue` - Get all field values
235
+ Plugins transform input before it is cast and validated. The built-in `MaskPlugin` supports fixed mask characters and digit or custom pattern placeholders.
337
236
 
338
- * `allErrors` - Get all validation errors
237
+ ```typescript
238
+ import { Field, MaskPlugin, StringType } from 'adaptform'
339
239
 
340
- * `globalErrors` - Get global errors
240
+ const phone = new Field(StringType, null, {
241
+ plugins: [
242
+ new MaskPlugin({
243
+ maskFormat: '+1 (___) ___-____',
244
+ maskPlaceholder: '_',
245
+ digitPattern: /\d/,
246
+ }),
247
+ ],
248
+ })
341
249
 
342
- * `toFormData()` - Convert to FormData
250
+ phone.rawValue = '4155550134'
251
+ console.log(phone.rawValue)
252
+ console.log(phone.valueClear)
253
+ ```
343
254
 
344
- * `toJSON()` - Convert to JSON
255
+ ## Server-side errors with ErrorsSchema
345
256
 
346
- * `reset()` - Reset all fields
257
+ `ErrorsSchema` maps error keys returned by a backend to form fields. By default, unmatched keys fall back to their original names.
347
258
 
348
- ### Field
259
+ ```typescript
260
+ import { ErrorsSchema, Form, Field, StringType } from 'adaptform'
349
261
 
350
- * `rawValue` - Set raw input value
262
+ class AccountForm extends Form {
263
+ email = new Field(StringType)
264
+ username = new Field(StringType)
265
+ }
351
266
 
352
- * `valueClear` - Get cleaned/validated value
267
+ const form = new AccountForm()
268
+ form.errorsShema = new ErrorsSchema()
269
+ .staticKey('email_address', 'email')
270
+ .staticKey('user_name', 'username')
271
+ .fallback(key => key)
353
272
 
354
- * `isValid` - Check if field is valid
273
+ form.errors = {
274
+ email_address: 'This email is already registered.',
275
+ user_name: 'This username is unavailable.',
276
+ request_id: 'The request could not be completed.',
277
+ }
355
278
 
356
- * `error` - Get first error message
279
+ console.log(form.allErrors)
280
+ ```
357
281
 
358
- * `errors` - Get all error messages
282
+ Available schema methods:
359
283
 
360
- * `isTouched` - Check if field was modified
284
+ - `staticKey(errorKey, fieldName)` maps an exact key.
285
+ - `dynamicKey(pattern, mapping, options?)` maps keys using a regular expression and optional priority.
286
+ - `fallback(handler)` handles keys that do not match configured rules.
287
+ - `transformValue(transformer)` normalizes server error values before mapping.
288
+ - `nest(prefix)` creates a schema for nested field paths.
289
+ - `clone()` and `merge(other)` compose schemas.
290
+ - `ErrorsSchema.fromObject(mapping)` creates a schema from a key-to-field map.
291
+ - `ErrorsSchema.forArray(itemSchema, arrayFieldName, indexExtractor?)` maps array-item errors, including bracket and dotted index notation.
361
292
 
362
- * `isEmpty` - Check if field is empty
293
+ Assigning a new `form.errors` object clears previously applied errors before applying the new payload. Unknown field keys become global errors.
363
294
 
364
- * `reset()` - Reset to default value
295
+ ## Serialization
365
296
 
366
- ### ArrayField
297
+ ### JSON
367
298
 
368
- * `add(value?)` - Add new field
299
+ `toJSON()` returns a JSON-compatible object. JavaScript `Date` instances are recursively converted to ISO 8601 strings, including dates nested in arrays and plain objects.
369
300
 
370
- * `remove(index)` - Remove field at index
301
+ ```typescript
302
+ const payload = form.toJSON()
303
+ await fetch('/api/profile', {
304
+ method: 'POST',
305
+ headers: { 'Content-Type': 'application/json' },
306
+ body: JSON.stringify(payload),
307
+ })
308
+ ```
371
309
 
372
- * `clear()` - Remove all fields
310
+ ### FormData
373
311
 
374
- * `itemsValue` - Get array of values
312
+ `toFormData()` omits `null` and `undefined` values, appends scalar values as strings, serializes dates as ISO 8601 strings, preserves `Blob` and `File` values, and JSON-encodes arrays and nested objects.
375
313
 
376
- * `items` - Get array of Field instances
314
+ ```typescript
315
+ const body = form.toFormData()
316
+ await fetch('/api/profile', {
317
+ method: 'POST',
318
+ body,
319
+ })
320
+ ```
377
321
 
378
- ### ArrayFormField
322
+ Do not set the multipart `Content-Type` header manually; the browser supplies the boundary.
379
323
 
380
- * `add(data?)` - Add new form
324
+ ## Optional Luxon integration
381
325
 
382
- * `remove(index)` - Remove form at index
326
+ Install Luxon separately, then import the additional date type from the dedicated subpath:
383
327
 
384
- * `clear()` - Remove all forms
328
+ ```bash
329
+ npm install luxon
330
+ ```
385
331
 
386
- * `forms` - Get array of Form instances
332
+ ```typescript
333
+ import { Field } from 'adaptform'
334
+ import { DateLuxonType } from 'adaptform/luxon'
335
+ import { DateTime } from 'luxon'
387
336
 
388
- * `formsFieldsValue` - Get array of form values
337
+ const date = new Field(DateLuxonType, null, {
338
+ isDatetime: false,
339
+ minDate: DateTime.fromISO('1900-01-01'),
340
+ maxDate: DateTime.now(),
341
+ inputFormat: 'dd.MM.yyyy',
342
+ })
343
+ ```
389
344
 
390
- ## TypeScript Support
345
+ ## API overview
391
346
 
392
- ```TypeScript
393
- import {Form, Field, StringType, NumberType} from 'adaptform';
347
+ | Class | Main API |
348
+ | --- | --- |
349
+ | `Form` | `fields`, `setFields()`, `fieldsValue`, `checkValid()`, `isValid`, `allErrors`, `globalErrors`, `errors`, `setGlobalError()`, `clearGlobalErrors()`, `reset()`, `toJSON()`, `toFormData()` |
350
+ | `Field` | `rawValue`, `setRawValue()`, `valueClear`, `errors`, `error`, `isValid`, `isTouched`, `isEmpty`, `checkValid()`, `reset()` |
351
+ | `ArrayField` | `items`, `itemsValue`, `add()`, `remove()`, `clear()`, `checkValid()`, `reset()` |
352
+ | `ArrayFormField` | `forms`, `formsFieldsValue`, `add()`, `remove()`, `clear()`, `checkValid()`, `reset()` |
353
+ | `ErrorsSchema` | `staticKey()`, `dynamicKey()`, `fallback()`, `transformValue()`, `parseErrors()`, `parseErrorsDetailed()`, `nest()`, `clone()`, `merge()`, `forArray()`, `fromObject()` |
394
354
 
395
- class UserForm extends Form {
396
- name = new Field(StringType, null);
397
- age = new Field(NumberType, null);
398
- }
355
+ ## Development
399
356
 
400
- const form = new UserForm();
357
+ Requirements: Node.js 22 (used by CI) and npm.
401
358
 
402
- // Full type inference
403
- type UserData = typeof form.fieldsValue;
404
- // { name: string | null; age: number | null; }
359
+ ```bash
360
+ npm ci
361
+ npm run lint
362
+ npm test
363
+ npm run build
364
+ npx tsc --noEmit -p tsconfig.json
365
+ npm run check:demo
366
+ npm run build:demo
405
367
  ```
406
368
 
407
- ## License
369
+ To run the interactive demo locally:
370
+
371
+ ```bash
372
+ npm run dev -- --config demo/vite.config.ts
373
+ ```
408
374
 
409
- MIT © [Stanislav Orlov](https://github.com/ArhiMEN)
375
+ The demo is built by GitHub Actions from `v2.1.0` and `master`. GitHub Pages is configured to allow both branches to publish to the same demo URL; see [Demo and GitHub Pages notes](https://github.com/ArhiMEN/AdaptForm/blob/master/docs/DEMO.md).
410
376
 
411
377
  ## Links
412
378
 
413
- * [GitHub Repository](https://github.com/ArhiMEN/adaptform)
414
- * [npm Package](https://www.npmjs.com/package/adaptform)
415
- * [Bug Reports](https://github.com/ArhiMEN/adaptform/issues)
416
- * [Changelog](https://github.com/ArhiMEN/adaptform/blob/master/CHANGELOG.md)
379
+ - **[Interactive demo](https://arhimen.github.io/AdaptForm/)**
380
+ - [GitHub repository](https://github.com/ArhiMEN/AdaptForm)
381
+ - [npm package](https://www.npmjs.com/package/adaptform)
382
+ - [Issue tracker](https://github.com/ArhiMEN/AdaptForm/issues)
383
+ - [Changelog](https://github.com/ArhiMEN/AdaptForm/blob/master/CHANGELOG.md)
384
+ - [License](https://github.com/ArhiMEN/AdaptForm/blob/master/LICENSE.md)
417
385
 
418
- ## Support
386
+ ## License
419
387
 
420
- If you find this project useful, please give it a star on GitHub!
388
+ MIT © Stanislav Orlov