adaptform 2.0.5 → 2.2.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/CHANGELOG.md +151 -2
- package/CHANGELOG.ru.md +200 -0
- package/LICENSE.md +3 -1
- package/LICENSE.ru.md +25 -0
- package/README.md +299 -297
- package/README.ru.md +422 -0
- package/dist/Form-uHUaTUZS.d.cts +387 -0
- package/dist/Form-uHUaTUZS.d.ts +387 -0
- package/dist/index.cjs +864 -283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.mjs +863 -284
- package/dist/index.mjs.map +1 -1
- package/dist/luxon.d.cts +1 -1
- package/dist/luxon.d.ts +1 -1
- package/package.json +28 -9
- package/dist/Form-Be3I6LX0.d.cts +0 -293
- package/dist/Form-Be3I6LX0.d.ts +0 -293
package/README.ru.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# AdaptForm
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [Русский](README.ru.md)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/adaptform)
|
|
6
|
+
[](https://www.npmjs.com/package/adaptform)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
**[Открыть интерактивное демо](https://arhimen.github.io/AdaptForm/?lang=ru)** · **[История изменений](https://github.com/ArhiMEN/AdaptForm/blob/master/CHANGELOG.ru.md)** · **[Исходный код демо](https://github.com/ArhiMEN/AdaptForm/tree/master/demo)**
|
|
11
|
+
|
|
12
|
+
Для сопровождающих: [настройка автоматической публикации в npm](docs/NPM_PUBLISH.ru.md).
|
|
13
|
+
|
|
14
|
+
AdaptForm — независимый от UI-фреймворков headless-движок форм с class-based API и приоритетом TypeScript, вдохновлённый Django Forms и Pydantic. Формы и поля управляют значениями, приведением типов, валидацией, ошибками, baseline, подписками и наблюдаемым состоянием без зависимости от UI-компонентов.
|
|
15
|
+
|
|
16
|
+
## Возможности
|
|
17
|
+
|
|
18
|
+
- Модели форм на основе классов с выводом типов значений в TypeScript.
|
|
19
|
+
- Проверки отдельных полей и зависимостей между полями.
|
|
20
|
+
- Условная обязательность полей и пользовательские синхронные валидаторы.
|
|
21
|
+
- Типы полей для строк, целых и десятичных чисел, логических значений и JavaScript Date.
|
|
22
|
+
- Динамические массивы полей и массивы вложенных форм.
|
|
23
|
+
- Плагины преобразования ввода, включая `MaskPlugin`.
|
|
24
|
+
- Сопоставление серверных ошибок через `ErrorsSchema`.
|
|
25
|
+
- Сериализация в JSON и `FormData`.
|
|
26
|
+
- Необязательная интеграция с Luxon через точку входа `adaptform/luxon`.
|
|
27
|
+
- Независимое от фреймворков ядро без обязательных зависимостей времени выполнения.
|
|
28
|
+
- Исходные очищенные значения с `isDirty` и `commitBaseline()` у полей, форм и коллекций.
|
|
29
|
+
- Стабильные неизменяемые снимки `getSnapshot()`, подписки и отписка через `subscribe()`.
|
|
30
|
+
- Вложенный синхронный `Form.batch()` и распространение обновлений вложенных форм и коллекций.
|
|
31
|
+
|
|
32
|
+
## Установка
|
|
33
|
+
|
|
34
|
+
Установите опубликованный npm-пакет:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install adaptform
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Интеграция с Luxon необязательна:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install luxon
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
[Интерактивное демо](https://arhimen.github.io/AdaptForm/?lang=ru) использует исходный код репозитория и автоматически публикуется из `v2.1.0` и `master`. **Новые headless API в кандидатных исходниках 2.2.0 ещё не опубликованы в npm.** Доступные для установки версии проверяйте на [npm](https://www.npmjs.com/package/adaptform).
|
|
47
|
+
|
|
48
|
+
Демо поддерживает английский и русский языки с сохранением выбора: [English](https://arhimen.github.io/AdaptForm/?lang=en) · [Русский](https://arhimen.github.io/AdaptForm/?lang=ru). Переключение языка сохраняет данные формы и состояние валидации.
|
|
49
|
+
|
|
50
|
+
## Быстрый старт
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { BooleanType, Field, Form, StringType } from 'adaptform'
|
|
54
|
+
|
|
55
|
+
class RegistrationForm extends Form {
|
|
56
|
+
name = new Field(StringType, null, {
|
|
57
|
+
isRequired: true,
|
|
58
|
+
minLength: 2,
|
|
59
|
+
maxLength: 50,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
email = new Field(StringType, null, {
|
|
63
|
+
isRequired: true,
|
|
64
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
acceptedTerms = new Field(BooleanType, false, {
|
|
68
|
+
mustBeTrue: true,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const form = new RegistrationForm()
|
|
73
|
+
form.fields = {
|
|
74
|
+
name: 'Alex Morgan',
|
|
75
|
+
email: 'alex@example.com',
|
|
76
|
+
acceptedTerms: true,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (form.isValid) {
|
|
80
|
+
console.log(form.fieldsValue)
|
|
81
|
+
console.log(form.toJSON())
|
|
82
|
+
} else {
|
|
83
|
+
console.log(form.allErrors)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`form.fields` присваивает переданные значения и проверяет полностью заполненную форму. Поля, отсутствующие в присваивании, сохраняют прежние значения.
|
|
88
|
+
|
|
89
|
+
## Типы полей и параметры
|
|
90
|
+
|
|
91
|
+
Все типы полей создаются через `new Field(TypeConstructor, defaultValue?, options?)`.
|
|
92
|
+
|
|
93
|
+
### StringType
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const username = new Field(StringType, null, {
|
|
97
|
+
isRequired: true,
|
|
98
|
+
minLength: 3,
|
|
99
|
+
maxLength: 32,
|
|
100
|
+
pattern: /^[a-z0-9_]+$/i,
|
|
101
|
+
messages: {
|
|
102
|
+
isRequired: 'Enter a username.',
|
|
103
|
+
minLength: 'Use at least 3 characters.',
|
|
104
|
+
pattern: 'Use only letters, numbers, and underscores.',
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Поддерживаемые ограничения: `isRequired`, `minLength`, `maxLength` и `pattern`.
|
|
110
|
+
|
|
111
|
+
### NumberType
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const age = new Field(NumberType, null, {
|
|
115
|
+
isRequired: true,
|
|
116
|
+
ge: 18,
|
|
117
|
+
le: 120,
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Поддерживаемые ограничения: `gt`, `ge`, `lt` и `le`. Значения должны быть конечными целыми числами; пустые строки и неконечные числа отклоняются.
|
|
122
|
+
|
|
123
|
+
### DecimalType
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const price = new Field(DecimalType, null, {
|
|
127
|
+
decimalPlaces: 2,
|
|
128
|
+
maxDigits: 8,
|
|
129
|
+
ge: 0,
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Поддерживаются `gt`, `ge`, `lt`, `le`, `decimalPlaces` и `maxDigits`. Числовые значения с избыточной точностью отклоняются без скрытого округления. В строковых значениях десятичным разделителем может быть запятая или точка. Незавершённые значения вроде `"123."` сохраняются при редактировании, но не проходят явную проверку; перед отправкой вызывайте `Form.checkValid()`. Метод `toJSON()` не выполняет валидацию автоматически.
|
|
134
|
+
|
|
135
|
+
### BooleanType
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const accepted = new Field(BooleanType, false, {
|
|
139
|
+
mustBeTrue: true,
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Поддерживаются `mustBeTrue` и `mustBeFalse`.
|
|
144
|
+
|
|
145
|
+
### DateType
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const birthday = new Field(DateType, null, {
|
|
149
|
+
isDatetime: false,
|
|
150
|
+
minDate: new Date('1900-01-01T00:00:00.000Z'),
|
|
151
|
+
maxDate: new Date(),
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Поддерживаются `minDate`, `maxDate` и `isDatetime`. При `isDatetime: false` сравниваются календарные даты; при `true` — временные метки. Некорректные даты отклоняются.
|
|
156
|
+
|
|
157
|
+
### Пользовательская валидация и сообщения
|
|
158
|
+
|
|
159
|
+
Параметры поля поддерживают `isRequired`, `messages` и синхронную функцию `validate(value, form)`. Пользовательский валидатор возвращает `true` при успехе, `false` для настроенного сообщения валидации или строку с конкретной ошибкой.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
class PasswordForm extends Form {
|
|
163
|
+
password = new Field(StringType, null, { minLength: 8 })
|
|
164
|
+
|
|
165
|
+
confirmation = new Field(StringType, null, {
|
|
166
|
+
isRequired: true,
|
|
167
|
+
validate: (value, form) =>
|
|
168
|
+
value === form?.password.valueClear || 'Passwords do not match.',
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Валидаторы синхронные; возврат Promise не поддерживается. Для асинхронных проверок, например уникальности на сервере, вызовите сервис отдельно и примените ответ через API ошибок формы.
|
|
174
|
+
|
|
175
|
+
## Жизненный цикл валидации и состояние
|
|
176
|
+
|
|
177
|
+
- Значения по умолчанию начинают со состояния «не изменено».
|
|
178
|
+
- Пустые строки и строки только из пробелов считаются пустыми значениями.
|
|
179
|
+
- Глобальный и липкий флаги регулярных выражений не влияют на повторные проверки строк.
|
|
180
|
+
- `form.checkValid()` проверяет каждое поле и возвращает `false`, если хотя бы одно поле некорректно или остались общие серверные ошибки.
|
|
181
|
+
- Чтение `isValid` не запускает валидаторы. Поля без ненулевого значения по умолчанию начинают с `isValid === null`; ненулевые значения конструктора проходят приведение и валидацию, но остаются неизменёнными по состоянию. После `reset()` корректность поля не определена до следующей проверки.
|
|
182
|
+
- Массовое присваивание запускает проверку после установки значений непосредственных полей, поэтому правила условной обязательности видят полное состояние формы.
|
|
183
|
+
- Если зависимые поля меняются по отдельности, после изменений вызовите `form.checkValid()` для пересчёта зависимых правил.
|
|
184
|
+
- `field.setRawValue(value, false)` и `form.setFields(data, false)` позволяют отложить валидацию. Прежде чем полагаться на корректность формы, вызовите `checkValid()`.
|
|
185
|
+
- `reset()` восстанавливает значения по умолчанию, очищает ошибки и оставляет корректность неопределённой до следующей проверки. Корректность коллекции после сброса равна false до её повторной проверки.
|
|
186
|
+
- Добавление, удаление и очистка элементов проверяют коллекции вне `Form.batch()`; внутри batch автоматическая валидация откладывается. Некорректные индексы удаления игнорируются.
|
|
187
|
+
|
|
188
|
+
## Динамические коллекции
|
|
189
|
+
|
|
190
|
+
### ArrayField
|
|
191
|
+
|
|
192
|
+
Используйте `ArrayField` для динамического списка однотипных полей.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { ArrayField, Field, StringType } from 'adaptform'
|
|
196
|
+
|
|
197
|
+
const tags = new ArrayField(
|
|
198
|
+
(value?: string) => new Field(StringType, value, {
|
|
199
|
+
isRequired: true,
|
|
200
|
+
minLength: 2,
|
|
201
|
+
}),
|
|
202
|
+
{ minItems: 1, maxItems: 5 },
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
tags.add('typescript')
|
|
206
|
+
tags.add('forms')
|
|
207
|
+
console.log(tags.itemsValue)
|
|
208
|
+
console.log(tags.isValid)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Параметры включают `isRequired`, `minItems`, `maxItems`, `messages` и синхронную функцию `validate(values, form)`. `itemsValue` возвращает массив, элементы которого могут быть `null`.
|
|
212
|
+
|
|
213
|
+
### ArrayFormField
|
|
214
|
+
|
|
215
|
+
Используйте `ArrayFormField` для списка экземпляров вложенных форм.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { ArrayFormField, Field, Form, StringType } from 'adaptform'
|
|
219
|
+
|
|
220
|
+
class AddressForm extends Form {
|
|
221
|
+
city = new Field(StringType, null, { isRequired: true })
|
|
222
|
+
postalCode = new Field(StringType, null, { isRequired: true })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const addresses = new ArrayFormField(AddressForm, {
|
|
226
|
+
minForms: 1,
|
|
227
|
+
maxForms: 3,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
addresses.add({ city: 'Berlin', postalCode: '10115' })
|
|
231
|
+
console.log(addresses.formsFieldsValue)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Параметры включают `isRequired`, `minForms`, `maxForms`, `messages` и синхронную функцию `validate(forms, form)`.
|
|
235
|
+
|
|
236
|
+
## Маски ввода
|
|
237
|
+
|
|
238
|
+
Плагины преобразуют ввод до приведения типов и валидации. Встроенный `MaskPlugin` поддерживает постоянные символы маски и позиции для цифр или символов по пользовательскому шаблону.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { Field, MaskPlugin, StringType } from 'adaptform'
|
|
242
|
+
|
|
243
|
+
const phone = new Field(StringType, null, {
|
|
244
|
+
plugins: [
|
|
245
|
+
new MaskPlugin({
|
|
246
|
+
maskFormat: '+1 (___) ___-____',
|
|
247
|
+
maskPlaceholder: '_',
|
|
248
|
+
digitPattern: /\d/,
|
|
249
|
+
}),
|
|
250
|
+
],
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
phone.rawValue = '4155550134'
|
|
254
|
+
console.log(phone.rawValue)
|
|
255
|
+
console.log(phone.valueClear)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Серверные ошибки и ErrorsSchema
|
|
259
|
+
|
|
260
|
+
`ErrorsSchema` сопоставляет ключи ошибок сервера с полями формы. По умолчанию для ключей без соответствия сохраняются исходные имена.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { ErrorsSchema, Form, Field, StringType } from 'adaptform'
|
|
264
|
+
|
|
265
|
+
class AccountForm extends Form {
|
|
266
|
+
email = new Field(StringType)
|
|
267
|
+
username = new Field(StringType)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const form = new AccountForm()
|
|
271
|
+
form.errorsShema = new ErrorsSchema()
|
|
272
|
+
.staticKey('email_address', 'email')
|
|
273
|
+
.staticKey('user_name', 'username')
|
|
274
|
+
.fallback(key => key)
|
|
275
|
+
|
|
276
|
+
form.errors = {
|
|
277
|
+
email_address: 'This email is already registered.',
|
|
278
|
+
user_name: 'This username is unavailable.',
|
|
279
|
+
request_id: 'The request could not be completed.',
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(form.allErrors)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Доступные методы схемы:
|
|
286
|
+
|
|
287
|
+
- `staticKey(errorKey, fieldName)` сопоставляет точный ключ.
|
|
288
|
+
- `dynamicKey(pattern, mapping, options?)` сопоставляет ключи по регулярному выражению с необязательным приоритетом.
|
|
289
|
+
- `fallback(handler)` обрабатывает ключи, не подошедшие под настроенные правила.
|
|
290
|
+
- `transformValue(transformer)` нормализует значения серверных ошибок до сопоставления.
|
|
291
|
+
- `nest(prefix)` создаёт схему для вложенных путей полей.
|
|
292
|
+
- `clone()` и `merge(other)` позволяют составлять схемы.
|
|
293
|
+
- `ErrorsSchema.fromObject(mapping)` создаёт схему из таблицы соответствий ключей и полей.
|
|
294
|
+
- `ErrorsSchema.forArray(itemSchema, arrayFieldName, indexExtractor?)` сопоставляет ошибки элементов массива, включая индексы в скобках и через точку.
|
|
295
|
+
|
|
296
|
+
Присваивание нового объекта `form.errors` очищает предыдущие ошибки перед применением нового ответа. Неизвестные ключи полей становятся общими ошибками формы.
|
|
297
|
+
|
|
298
|
+
## Сериализация
|
|
299
|
+
|
|
300
|
+
### JSON
|
|
301
|
+
|
|
302
|
+
`toJSON()` возвращает JSON-совместимый объект. Экземпляры JavaScript `Date` рекурсивно преобразуются в строки ISO 8601, включая даты внутри массивов и обычных объектов.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const payload = form.toJSON()
|
|
306
|
+
await fetch('/api/profile', {
|
|
307
|
+
method: 'POST',
|
|
308
|
+
headers: { 'Content-Type': 'application/json' },
|
|
309
|
+
body: JSON.stringify(payload),
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### FormData
|
|
314
|
+
|
|
315
|
+
`toFormData()` пропускает `null` и `undefined`, добавляет скалярные значения как строки, сериализует даты в ISO 8601, сохраняет значения `Blob` и `File`, а массивы и вложенные объекты кодирует в JSON.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const body = form.toFormData()
|
|
319
|
+
await fetch('/api/profile', {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
body,
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Не задавайте multipart-заголовок `Content-Type` вручную: браузер сам добавит разделитель boundary.
|
|
326
|
+
|
|
327
|
+
## Необязательная интеграция с Luxon
|
|
328
|
+
|
|
329
|
+
Установите Luxon отдельно, затем импортируйте дополнительный тип даты из выделенной точки входа:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npm install luxon
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { Field } from 'adaptform'
|
|
337
|
+
import { DateLuxonType } from 'adaptform/luxon'
|
|
338
|
+
import { DateTime } from 'luxon'
|
|
339
|
+
|
|
340
|
+
const date = new Field(DateLuxonType, null, {
|
|
341
|
+
isDatetime: false,
|
|
342
|
+
minDate: DateTime.fromISO('1900-01-01'),
|
|
343
|
+
maxDate: DateTime.now(),
|
|
344
|
+
inputFormat: 'dd.MM.yyyy',
|
|
345
|
+
})
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Обзор API
|
|
349
|
+
|
|
350
|
+
| Класс | Основной API |
|
|
351
|
+
| --- | --- |
|
|
352
|
+
| `Form` | `fields`, `setFields()`, `fieldsValue`, `checkValid()`, `isValid`, `isDirty`, `commitBaseline()`, `subscribe()`, `getSnapshot()`, `batch()`, `allErrors`, `globalErrors`, `errors`, `setGlobalError()`, `clearGlobalErrors()`, `reset()`, `toJSON()`, `toFormData()` |
|
|
353
|
+
| `Field` | `rawValue`, `setRawValue()`, `valueClear`, `errors`, `error`, `isValid`, `isTouched`, `isDirty`, `commitBaseline()`, `subscribe()`, `getSnapshot()`, `isEmpty`, `checkValid()`, `reset()` |
|
|
354
|
+
| `ArrayField` | `items`, `itemsValue`, `add()`, `remove()`, `clear()`, `isDirty`, `commitBaseline()`, `subscribe()`, `getSnapshot()`, `checkValid()`, `reset()` |
|
|
355
|
+
| `ArrayFormField` | `forms`, `formsFieldsValue`, `add()`, `remove()`, `clear()`, `isDirty`, `commitBaseline()`, `subscribe()`, `getSnapshot()`, `checkValid()`, `reset()` |
|
|
356
|
+
| `ErrorsSchema` | `staticKey()`, `dynamicKey()`, `fallback()`, `transformValue()`, `parseErrors()`, `parseErrorsDetailed()`, `nest()`, `clone()`, `merge()`, `forArray()`, `fromObject()` |
|
|
357
|
+
|
|
358
|
+
## Разработка
|
|
359
|
+
|
|
360
|
+
Требуются Node.js 22 (используется в CI) и npm.
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npm ci
|
|
364
|
+
npm run lint
|
|
365
|
+
npm test
|
|
366
|
+
npm run build
|
|
367
|
+
npx tsc --noEmit -p tsconfig.json
|
|
368
|
+
npm run check:demo
|
|
369
|
+
npm run build:demo
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Для локального запуска интерактивного демо:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npm run dev -- --config demo/vite.config.ts
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Демо собирается GitHub Actions из `v2.1.0` и `master`. GitHub Pages разрешает обеим веткам публиковать страницу по одному адресу; см. [Демо и GitHub Pages](https://github.com/ArhiMEN/AdaptForm/blob/master/docs/DEMO.ru.md).
|
|
379
|
+
|
|
380
|
+
## Ссылки
|
|
381
|
+
|
|
382
|
+
- **[Интерактивное демо](https://arhimen.github.io/AdaptForm/?lang=ru)**
|
|
383
|
+
- [Репозиторий GitHub](https://github.com/ArhiMEN/AdaptForm)
|
|
384
|
+
- [Пакет npm](https://www.npmjs.com/package/adaptform)
|
|
385
|
+
- [Задачи и ошибки](https://github.com/ArhiMEN/AdaptForm/issues)
|
|
386
|
+
- [История изменений](https://github.com/ArhiMEN/AdaptForm/blob/master/CHANGELOG.ru.md)
|
|
387
|
+
- [Лицензия](https://github.com/ArhiMEN/AdaptForm/blob/master/LICENSE.ru.md)
|
|
388
|
+
|
|
389
|
+
## Лицензия
|
|
390
|
+
|
|
391
|
+
MIT © Stanislav Orlov
|
|
392
|
+
|
|
393
|
+
## Headless-состояния форм (кандидат исходников 2.2.0)
|
|
394
|
+
|
|
395
|
+
HF-005–HF-010 прошли независимое ревью и объединены. **Кандидат исходников — 2.2.0**, а публикация в npm — отдельный этап, поэтому новые API могут отсутствовать в пакете, установленном через `npm install adaptform`.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import {Field, Form, NumberType, StringType} from 'adaptform'
|
|
399
|
+
|
|
400
|
+
class SettingsForm extends Form {
|
|
401
|
+
displayName = new Field(StringType, 'Alex')
|
|
402
|
+
notifications = new Field(NumberType, 1)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const form = new SettingsForm()
|
|
406
|
+
const unsubscribe = form.subscribe(() => {
|
|
407
|
+
const snapshot = form.getSnapshot()
|
|
408
|
+
console.log(snapshot.isDirty, snapshot.isValid, snapshot.fieldsValue)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
form.batch(() => {
|
|
412
|
+
form.displayName.rawValue = 'Alexandra'
|
|
413
|
+
form.notifications.rawValue = 2
|
|
414
|
+
}) // One coherent notification after final automatic validation
|
|
415
|
+
|
|
416
|
+
form.commitBaseline() // Accept current cleaned values; isDirty becomes false
|
|
417
|
+
unsubscribe() // Release the listener
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
`isTouched` сохраняет прежнюю семантику. `commitBaseline()` меняет точку сравнения dirty, **не** значения конструктора, возвращаемые `reset()`; после сброса `isDirty` иногда остаётся `true`. Snapshots — независимые неизменяемые представления, а не ссылки на изменяемые данные. `toJSON()` по-прежнему не выполняет валидацию.
|
|
421
|
+
|
|
422
|
+
Автоматическая проверка внутри `Form.batch()` откладывается, явные `checkValid()` выполняются сразу. Batch только синхронный; при исключении выполненные изменения не откатываются. Прямые мутации старых публичных массивов и произвольная замена свойств формы не создают автоматических уведомлений. Подробности: [использование и ограничения](docs/ai/HEADLESS_STATE_USAGE.ru.md), [совместимость с 2.1.1](docs/ai/HEADLESS_STATE_CONTRACT.ru.md#совместимость-с-211).
|