@dynamicforms/vue-forms 2.0.3 → 3.0.0-alpha.1

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,52 +1,57 @@
1
1
  # @dynamicforms/vue-forms
2
2
 
3
- A reactive data entry forms library for Vue.js that handles form state management without dictating your
4
- UI components.
3
+ A reactive library for Vue.js that holds forms and application state without dictating your UI components:
4
+ validation, rules between fields, transactions and lists.
5
5
 
6
6
  ## Introduction
7
7
 
8
- `@dynamicforms/vue-forms` manages form data, validation and state, and leaves rendering entirely to you.
8
+ `@dynamicforms/vue-forms` manages form data, validation and state; rendering is left to the application. The same
9
+ elements also hold application state that no screen displays, such as a shopping cart or the filters of a list view.
9
10
 
10
- What it models beyond state is the behaviour *between* fields. Visibility, enablement and values can be declared
11
- as conditions over other fields, and every change travels through an action pipeline in which each handler decides
12
- whether to pass the event on and may reshape its result. Groups and lists compose recursively, so the same
11
+ In addition to state, the library models the behaviour *between* fields. Visibility, enablement and values can be
12
+ declared as conditions over other fields, and every change passes through an action pipeline in which each handler
13
+ decides whether to pass the event on and may modify its result. Groups and lists compose recursively, so the same
13
14
  mechanism applies at every level of a nested form.
14
15
 
15
16
  ### Design Goals
16
17
 
17
- - **UI-Agnostic**: A logic layer for form state, validation and dynamic behaviour. Works with any Vue components, including your own. The few members that speak about the interface — `visibility`, `enabled`, `touched`, error render content and `Action` — are there because nearly every form needs them; [Rationale](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale#what-the-library-carries-for-the-interface) lists them with the reason for each.
18
- - **Fields that react to each other**: Conditional visibility, enablement and values are declared as statements over other fields, and an action pipeline lets a handler intercept, transform or abort an event.
19
- - **Reactive & Type-Safe**: Every member of a field, group or list is a tracked read, and a group's value type is inferred from the fields it holds, nested structures included.
20
- - **Structural serialization**: A group's value is the shape of its fields, and `Group.createFromFormData()` turns a plain object back into a form.
18
+ - **UI-Agnostic**: a logic layer for state, validation and dynamic behaviour, rendered by any Vue components,
19
+ including your own. The few members that relate to the interface are listed in
20
+ [Rationale](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale#what-the-library-carries-for-the-interface).
21
+ - **Fields that react to each other**: the behaviour between fields is part of the form definition, not wiring in
22
+ components.
23
+ - **Reactive & Type-Safe**: every member is a tracked read, and the types follow the declared structure.
24
+ - **Structural serialization**: a group's value has the shape of its fields.
25
+ - **Application state, not only forms**: the same elements hold state that no screen displays, such as a shopping
26
+ cart, the filters of a list view or the settings of an editor, with the same features a form has.
21
27
 
22
28
  ## Features
23
29
 
24
- - **UI-agnostic**: a logic layer for form state, validation and dynamic behaviour. Any Vue components render it,
25
- your own included; the few members that speak about the interface are [listed with their reasons](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale#what-the-library-carries-for-the-interface)
26
- - **Transactional**: every mutating operation is atomic — events are announced once, over the net change, and a
27
- handler that throws leaves the form exactly as it was. `transaction()` makes several writes one operation, and
28
- `tx.rollback()` withdraws one without an error
29
- - **Lists that scale**: a `List` is meant to hold thousands of rows. Writing one field of one row costs that row
30
- and the depth it sits at, a `push()` costs one row, and reading `value` or `valid` again costs nothing
31
- - **Declared once, bound per record**: the element handed to `new List(template)` is the declaration every row is
32
- built from. One validator instance and one conditional rule serve every row, and each row answers for itself
33
- - **Reactive**: every member of a field, group or list is a tracked read — assign a property directly, with no
34
- `ref` to unwrap and no computed mirror to keep in sync
35
- - **Nested structures**: fields, groups and lists compose recursively, at every level
36
- - **Event system**: every change travels through an action pipeline; a handler may pass it on, reshape it or stop it
30
+ - **Transactional**: every mutating operation is atomic. Events are announced once, over the net change, and a
31
+ handler that throws leaves the form unchanged. `transaction()` makes several writes one operation, and
32
+ `tx.rollback()` reverts one without throwing
37
33
  - **Validation**: built-in validators, custom synchronous and asynchronous rules, cross-field comparisons, and
38
- errors that carry a `code` and render as text, markdown or a component of your own
34
+ errors that contain a `code` and render as text, markdown or a component of your own
39
35
  - **Conditional logic**: visibility, enablement and values declared as statements over other fields
40
- - **Extended properties**: a field carries whatever your UI renders it with — a label, a hint, a width — declared
41
- as a second type argument, checked by the compiler and read through `extra`
42
- - **Application state, not only forms**: the same elements hold state no screen shows — a cart, the filters of a list view, editor
43
- settings — with the reactivity, transactions and validation a form has
44
- - **Plain-data views**: `view(group)` reads an element as plain properties — `form.address.city`,
45
- `v-model="form.name"` — with the element itself as `form.$`
46
- - **Access**: `'editable'`, `'readonly'`, `'disabled'` and `'disabled-null'` decide whether an element accepts input
47
- and what it contributes to the value its form sends — its own value, `null`, or nothing — and validation follows
48
- what is sent; `visibility` states how it is drawn
49
- - **TypeScript support**: full type definitions, and a group's value type inferred from the fields it holds
36
+ - **Event system**: every change passes through an action pipeline; a handler may pass it on, modify it or stop it
37
+ - **Access**: `'editable'`, `'readonly'`, `'disabled'` and `'disabled-null'` determine whether an element accepts
38
+ input and what it sends in its form's value (its own value, `null`, or nothing); validation applies to what is
39
+ sent. `visibility` determines how it is drawn
40
+ - **Application state**: a cart, the filters of a list view or editor settings built from the same elements, with
41
+ per-element tracked reads, transactions, validation and rules for what is sent
42
+ - **Plain-data views**: `view(group)` reads an element as plain properties (`form.address.city`,
43
+ `v-model="form.name"`), with the element itself as `form.$`
44
+ - **Lists that scale**: a `List` is designed to hold thousands of rows. Writing one field of one row costs that row
45
+ and the depth of its nesting, a `push()` costs one row, and reading `value` or `valid` again with no change in
46
+ between costs nothing
47
+ - **Declared once, bound per record**: a list's item template is declared once; one validator instance and one
48
+ conditional rule serve every row, and each row has its own result
49
+ - **Nested structures**: fields, groups and lists compose recursively, at every level
50
+ - **Extended properties**: a field holds the data your UI renders it with (a label, a hint, a width), declared as a
51
+ second type argument, checked by the compiler and read through `extra`
52
+ - **Serialization**: `Group.createFromFormData()` builds a form from a plain object
53
+ - **TypeScript support**: full type definitions, and a group's value type inferred from the fields it holds,
54
+ nested structures included
50
55
 
51
56
  ## Installation
52
57
 
@@ -55,38 +60,11 @@ npm install @dynamicforms/vue-forms
55
60
  ```
56
61
 
57
62
  The package is ESM-only and requires Node 22 or newer. A CommonJS consumer reaches it through `require()` of an ES
58
- module, which Node supports. Type definitions ship with the build; the stylesheet is
59
- `@dynamicforms/vue-forms/style.css`.
60
-
61
- ## Setup
62
-
63
- The library ships a Vue plugin for its global options:
64
-
65
- ```typescript
66
- import { forms } from '@dynamicforms/vue-forms';
67
-
68
- app.use(forms, { useMarkdownInValidators: false });
69
- ```
70
-
71
- The import must be a named one — the default export is a namespace of the library members, not the plugin.
72
-
73
- `useMarkdownInValidators` defaults to `true`, which means the default messages of the built-in validators are markdown
74
- (`MdString`). Rendering those requires a globally registered `vue-markdown` component; set the option to `false` if you
75
- want plain text instead.
76
-
77
- The configuration is module-global rather than per app, and `getConfig()`, `setConfig()` and the `FormsConfig` type
78
- are exported beside the plugin, so the options can be read and written where there is no app to install a plugin
79
- on.
63
+ module, which Node supports without a flag from 22.12; on 22.0–22.11 it needs `--experimental-require-module`. Type definitions ship with the build. The package ships no components and no styles.
80
64
 
81
65
  ## Basic Usage Example
82
66
 
83
- Every form element is created with the constructor: `new Field({ ... })`, `new Action({ ... })`,
84
- `new Group({ ... })`, `new List(template)`. Every read through it is tracked from that moment on, so reading
85
- `field.value` in a template tracks it and `field.value = x` re-renders — there is no `ref` to unwrap and no
86
- computed mirror to maintain. The element itself is not a Vue proxy, so watch what you read:
87
- `watch(() => field.value, cb)`.
88
-
89
- Here's a simple example of how to create and use a form with fields and groups:
67
+ Creating and using a form with fields and groups:
90
68
 
91
69
  ```typescript
92
70
  import { Field, Group } from '@dynamicforms/vue-forms';
@@ -112,61 +90,78 @@ personForm.fields.age.access = 'disabled';
112
90
  console.log(personForm.value); // { firstName: 'Jane', lastName: 'Doe', active: true }
113
91
  ```
114
92
 
115
- An `Action` is a field whose value is a label / icon pair and which can be executed:
93
+ Every element is created with its constructor: `new Field({ ... })`, `new Action({ ... })`, `new Group({ ... })`,
94
+ `new List(template)`. Every read through it is tracked, so reading `field.value` in a template tracks it and
95
+ `field.value = x` re-renders. The element itself is not a Vue proxy, so `watch()` takes a getter of the member read:
96
+ `watch(() => field.value, cb)`.
97
+
98
+ An `Action` is a command: an element with a label and an icon that can be executed and sends nothing to its
99
+ container's value. `SubmitAction` and `RejectAction` submit and reset a form, and `form.confirm()` / `form.reject()`
100
+ execute them:
116
101
 
117
102
  ```typescript
118
- import { Action, ExecuteAction } from '@dynamicforms/vue-forms';
103
+ import { Action, CommandException, Field, Group, RejectAction, SubmitAction } from '@dynamicforms/vue-forms';
119
104
 
120
- const saveAction = new Action({
121
- value: { label: 'Save' },
122
- actions: [new ExecuteAction((field, supr, params) => { console.log('saving', params); })]
105
+ const form = new Group({
106
+ name: new Field({ value: 'John' }),
107
+ save: new Action({
108
+ value: { label: 'Save', defaultConfirm: true },
109
+ actions: [new SubmitAction((action) => action.parent, (value) => api.save(value))],
110
+ }),
111
+ cancel: new Action({
112
+ value: { label: 'Cancel', defaultReject: true },
113
+ actions: [new RejectAction((action) => action.parent)],
114
+ }),
123
115
  });
124
116
 
125
- await saveAction.execute({ form: personForm }); // 'saving { form: ... }'; saveAction.busy until it settles
117
+ form.value; // { name: 'John' }: actions send nothing
118
+ form.fields.save.executable; // false while the form is invalid or a submit is running
119
+
120
+ // waits for validation, sends a valid form and rebinds the form to the result
121
+ const result = await form.confirm();
122
+ if (result instanceof CommandException) {
123
+ // the submit was refused or failed
124
+ } else if (result !== undefined) {
125
+ const { sent, received } = result;
126
+ }
126
127
  ```
127
128
 
128
- `Action` is one of the few members that speak about the interface, listed with their reasons in
129
- [Rationale](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale#what-the-library-carries-for-the-interface):
130
- it names a label and an icon because it exists as the element a form's submit and cancel hang on, and that minimal
131
- pair is what makes the concept legible. The shape is
132
- minimal because a UI library is expected to extend it — [`@dynamicforms/vuetify-inputs`](https://docs.velis.si/dynamicforms/vuetify-inputs/examples/df-actions.html)
133
- widens the value with render options and per-breakpoint variants on top of it.
129
+ `confirm()` returns `undefined` where it executes nothing, such as while `save` is not executable. The cookbook's
130
+ [Submitting](https://docs.velis.si/dynamicforms/vue-forms/guide/cookbook#submitting) recipe describes every outcome.
131
+
132
+ An `Action` carries a label and an icon because a form's submit and cancel are drawn as buttons, and it is one of
133
+ the few members that relate to the interface. A UI library extends its value:
134
+ [`@dynamicforms/vuetify-inputs`](https://docs.velis.si/dynamicforms/vuetify-inputs/examples/df-actions.html) adds
135
+ render options and per-breakpoint variants.
134
136
 
135
137
  ## Events Example
136
138
 
137
- The library provides a powerful event system for field changes and other actions:
139
+ Field changes and other events are handled by actions:
138
140
 
139
141
  ```typescript
140
- import { Field, Group, ValueChangedAction, ValidationErrorText } from '@dynamicforms/vue-forms';
141
-
142
- const emailField = new Field({ value: '' })
143
- .registerAction(new ValueChangedAction((field, supr, newValue, oldValue) => {
144
- // Custom validation on value change
145
- if (!newValue.includes('@')) {
146
- field.errors = [new ValidationErrorText('Invalid email format')];
147
- } else {
148
- field.errors = [];
149
- }
150
-
151
- // Always call supr to continue the action chain
152
- return supr(field, newValue, oldValue);
153
- }));
142
+ import { Field, Group, ValueChangedAction } from '@dynamicforms/vue-forms';
154
143
 
155
- // Or register events on a form
156
144
  const form = new Group({
157
- email: emailField,
158
- username: new Field()
159
- }).registerAction(new ValueChangedAction((field, supr, newValue, oldValue) => {
145
+ email: new Field({ value: '' }),
146
+ username: new Field({ value: '' }),
147
+ });
148
+
149
+ // Fill in the username from the email while the username field is not touched
150
+ form.fields.email.registerAction(new ValueChangedAction((field, supr, newValue, oldValue) => {
151
+ if (!form.fields.username.touched) form.fields.username.value = newValue.split('@')[0];
152
+ // calling supr passes the event on to the next handler; not calling it stops the chain
153
+ return supr(field, newValue, oldValue);
154
+ }));
155
+
156
+ // Or register events on a form
157
+ form.registerAction(new ValueChangedAction((field, supr, newValue, oldValue) => {
160
158
  console.log('Form data changed:', newValue);
161
159
  return supr(field, newValue, oldValue);
162
160
  }));
163
161
  ```
164
162
 
165
- Assigning `field.errors` directly replaces the whole array, including errors owned by validators — prefer a
166
- `Validator` (see below) when all you want is to add a validation rule.
167
-
168
- Events are announced when the operation carrying them finishes, over the net change. Wrap several writes in a
169
- `transaction()` and they announce as one:
163
+ Events are announced over the net change when the operation that contains them finishes. Several writes wrapped
164
+ in a `transaction()` are announced as one:
170
165
 
171
166
  ```typescript
172
167
  import { transaction } from '@dynamicforms/vue-forms';
@@ -178,11 +173,11 @@ transaction(() => {
178
173
  });
179
174
  ```
180
175
 
181
- A throw out of the callback rolls the whole transaction back and rethrows, so a form is never left half-applied.
176
+ An exception thrown from the callback rolls back the whole transaction and is rethrown.
182
177
 
183
178
  ## Built-in Validators
184
179
 
185
- The library provides several built-in validators for common validation scenarios:
180
+ Built-in validators for common rules:
186
181
 
187
182
  ```typescript
188
183
  import { Field, Group, Validators } from '@dynamicforms/vue-forms';
@@ -190,7 +185,7 @@ import { Field, Group, Validators } from '@dynamicforms/vue-forms';
190
185
  const validatedForm = new Group({
191
186
  // Required field
192
187
  username: new Field({
193
- validators: [new Validators.Required('Username is required')]
188
+ validators: [new Validators.Required({ code: 'username_required', detail: 'Username is required' })]
194
189
  }),
195
190
 
196
191
  // Email validation with pattern
@@ -198,7 +193,7 @@ const validatedForm = new Group({
198
193
  validators: [
199
194
  new Validators.Pattern(
200
195
  /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
201
- 'Please enter a valid email address'
196
+ { code: 'invalid_email', detail: 'Please enter a valid email address' }
202
197
  )
203
198
  ]
204
199
  }),
@@ -207,7 +202,7 @@ const validatedForm = new Group({
207
202
  age: new Field({
208
203
  value: 25,
209
204
  validators: [
210
- new Validators.ValueInRange(18, 100, 'Age must be between 18 and 100')
205
+ new Validators.ValueInRange(18, 100, { code: 'age_range', detail: 'Age must be between {minValue} and {maxValue}' })
211
206
  ]
212
207
  }),
213
208
 
@@ -221,91 +216,38 @@ const validatedForm = new Group({
221
216
  // Text length validation
222
217
  bio: new Field({
223
218
  validators: [
224
- new Validators.LengthInRange(10, 200, 'Bio must be between 10 and 200 characters')
219
+ new Validators.LengthInRange(10, 200, { code: 'bio_length', detail: 'Bio must be between 10 and 200 characters' })
225
220
  ]
226
221
  })
227
222
  });
228
223
  ```
229
224
 
230
- Validators run eagerly — a field is validated the moment it is created, so a form built from empty required fields is
231
- invalid immediately. Use `field.touched` to decide when to show the errors in the UI. `Required` trims a string
232
- before it measures it, so a value of spaces alone is no value; `new Validators.Required({ trim: false })` keeps the
233
- spaces where they are part of what the field holds.
234
-
235
- Every error a built-in validator produces carries a `code` — `required`, `pattern`, `min-length`, … — so a program
236
- reacting to one particular failure need not match message text that is translated and configurable.
237
-
238
- A validation function may return a `Promise`. `field.validating` is `true` while such a run is pending — on the
239
- field and on every container above it, so a form answers for the whole tree — and the verdict applied to the field
240
- is always the one belonging to the newest run, so a slow check cannot overwrite a faster one started after it. The
241
- function receives an `AbortSignal` as its fourth argument, which aborts the moment the run's verdict stops
242
- counting, so the request behind it can be called off. `form.busy` is the same question with the `Action.execute()`
243
- runs below it included — what a submit button binds to. A validator message given as a `Ref` or `computed` is
244
- resolved when the message is rendered, so a translated message follows a locale switch without revalidating.
245
- `field.clearValidators()` drops the validators, empties the errors and cancels whatever validation is still in
246
- flight.
247
-
248
- ## Messages Widget Component
249
-
250
- The library includes a `messages-widget` Vue component for displaying validation errors and messages:
251
-
252
- ```vue
253
- <template>
254
- <!-- Simple string message -->
255
- <messages-widget
256
- message="This is an error message"
257
- classes="text-error"
258
- />
259
-
260
- <!-- Display field validation errors -->
261
- <messages-widget
262
- v-if="emailField.errors.length > 0"
263
- :message="emailField.errors"
264
- :classes="['text-error', 'mt-2']"
265
- />
266
-
267
- <!-- Markdown message -->
268
- <messages-widget :message="markdownErrors" />
269
- </template>
270
-
271
- <script setup>
272
- import { MessagesWidget, Field, Validators, ValidationErrorRenderContent, MdString } from '@dynamicforms/vue-forms';
273
-
274
- // Example field with validation
275
- const emailField = new Field({
276
- value: '',
277
- validators: [
278
- new Validators.Pattern(
279
- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
280
- 'Please enter a valid email address'
281
- )
282
- ]
283
- });
225
+ Validators run eagerly: a field is validated when it is created, so a form built from empty required fields is
226
+ invalid immediately. `field.touched` determines when the UI shows the errors. `Required` trims a string before
227
+ measuring it, so a value of only spaces counts as empty; `new Validators.Required({ trim: false })` keeps spaces as
228
+ part of the value.
284
229
 
285
- // Markdown message support (requires VueMarkdown component)
286
- const markdownErrors = [
287
- new ValidationErrorRenderContent(
288
- new MdString('**Error**: This field contains *invalid* data.')
289
- )
290
- ];
291
- </script>
292
- ```
230
+ An error is data: a `code` (`required`, `pattern`, `min_length`, …), the `params` it failed with, and an English
231
+ `detail`. The library does not render it. The application, or a UI library such as `@dynamicforms/vuetify-inputs`,
232
+ converts it into text, translated by its code. A validator's last argument, `{ code, detail }`, replaces the
233
+ validator's default code and English detail.
293
234
 
294
- The messages widget supports:
295
- - **String messages**: Simple text messages
296
- - **ValidationError arrays**: Rich error objects with styling and components
297
- - **Markdown content**: Rich text formatting (requires VueMarkdown component)
298
- - **Custom components**: Render any Vue component as an error message
299
- - **Flexible styling**: Multiple ways to apply CSS classes
235
+ A validation function may return a `Promise`; `field.validating` is `true` while it runs, and only the newest run
236
+ determines the result. [Asynchronous validation](https://docs.velis.si/dynamicforms/vue-forms/api/validators#asynchronous-validation)
237
+ describes cancellation, `busy`, `pending` and `clearValidators()`.
300
238
 
301
239
  ## Conditional Form Behavior
302
240
 
303
- Create dynamic forms with conditional logic using Statements and Operators:
241
+ Conditional logic is declared with `Statement` and `Operator`:
304
242
 
305
243
  ```typescript
306
- import {
307
- Field, Group, Statement, Operator,
308
- ConditionalVisibilityAction, ConditionalAccessAction
244
+ import {
245
+ Field,
246
+ Group,
247
+ Statement,
248
+ Operator,
249
+ ConditionalVisibilityAction,
250
+ ConditionalAccessAction,
309
251
  } from '@dynamicforms/vue-forms';
310
252
 
311
253
  const form = new Group({
@@ -330,7 +272,7 @@ const isPerson = new Statement(form.fields.isCompany, Operator.EQUALS, false);
330
272
 
331
273
  ## Advanced Data Structures (Lists)
332
274
 
333
- Work with array data using the List component:
275
+ `List` holds array data:
334
276
 
335
277
  ```typescript
336
278
  import { Field, Group, List } from '@dynamicforms/vue-forms';
@@ -349,7 +291,7 @@ const contactsList = new List(contactTemplate);
349
291
  contactsList.push({ name: 'John Doe', email: 'john@example.com', phone: '123-456-7890' });
350
292
  contactsList.push({ name: 'Jane Doe', email: 'jane@example.com', phone: '987-654-3210' });
351
293
 
352
- // Access list items: items hands out the rows, get() answers undefined for an invalid index
294
+ // Access list items: items returns the rows, get() returns undefined for an invalid index
353
295
  console.log(contactsList.length); // 2
354
296
  const firstContact = contactsList.get(0)!;
355
297
  console.log(firstContact.fields.name.value); // 'John Doe'
@@ -362,12 +304,12 @@ contactsList.remove(1);
362
304
  ```
363
305
 
364
306
  Every list mutation is tracked, so a `v-for` over `contactsList.items` re-renders on `push()`, `insert()`,
365
- `remove()`, `pop()` and `clear()` without any extra wiring. `items` hands out a frozen array of the live rows,
366
- rebuilt once per change of the set.
307
+ `remove()`, `pop()` and `clear()` without extra wiring. `items` returns a frozen array of the live rows, rebuilt
308
+ once per change of the set of rows.
367
309
 
368
310
  ## TypeScript Support
369
311
 
370
- The library is written in TypeScript and provides full type definitions:
312
+ The library is written in TypeScript and ships full type definitions:
371
313
 
372
314
  ```typescript
373
315
  import { Field, Group, GenericFieldsInterface } from '@dynamicforms/vue-forms';
@@ -413,7 +355,7 @@ const email: string = userForm.fields.email.value;
413
355
  const age: number = userForm.fields.age.value;
414
356
  const darkMode: boolean = userForm.fields.preferences.fields.darkMode.value;
415
357
 
416
- // The serialized value is typed too, member by member. Every member is optional, because a 'disabled' member is
358
+ // The group's value is typed too, member by member. Every member is optional, because a 'disabled' member is
417
359
  // left out of the object the group builds, and nullable, because a 'disabled-null' one is sent as null
418
360
  const values = userForm.value;
419
361
  const emailFromValue: string | null | undefined = values.email;
@@ -434,13 +376,13 @@ const defaults: IFieldParams<string> = { value: '', access: 'readonly' };
434
376
  const field = new Field(defaults);
435
377
  ```
436
378
 
437
- It admits only the writable members — `value`, `originalValue`, `access`, `visibility`, `touched`, `errors`,
438
- `validators` and `actions`, listed by `IFieldConstructorParams<T>`. Derived members such as `valid` and
379
+ It accepts only the writable members (`value`, `originalValue`, `access`, `visibility`, `touched`, `errors`,
380
+ `validators` and `actions`), listed by `IFieldConstructorParams<T>`. Derived members such as `valid` and
439
381
  `isChanged` are getters, and passing one is a compile error.
440
382
 
441
- `X` is what an element carries beyond those: declare it, and the parameter object takes those properties too,
442
- `extra` reads them back and `setExtendedValues()` writes them. It is how a form built from a server's description
443
- carries the label, hint or css class a UI layer binds to its inputs:
383
+ `X` declares the additional properties an element holds: the parameter object accepts them, `extra` reads them
384
+ and `setExtendedValues()` writes them. A form built from a server's description stores in them the label, hint or
385
+ css class a UI layer binds to its inputs:
444
386
 
445
387
  ```typescript
446
388
  interface Presentation { label: string; hint?: string }
@@ -451,7 +393,7 @@ name.setExtendedValues({ hint: 'as in your passport' });
451
393
  ```
452
394
 
453
395
  `FieldBase<T>` is the abstract base of `Field`, `Action`, `Group` and `List`, and the type to use in your own
454
- signatures whenever you accept any form element:
396
+ signatures that accept any form element:
455
397
 
456
398
  ```typescript
457
399
  import { FieldBase } from '@dynamicforms/vue-forms';
@@ -463,20 +405,16 @@ function isDirty(field: FieldBase): boolean {
463
405
 
464
406
  ## Documentation
465
407
 
466
- For more detailed documentation and examples, check out the [documentation](https://docs.velis.si/dynamicforms/vue-forms).
408
+ Full documentation and examples: [documentation](https://docs.velis.si/dynamicforms/vue-forms).
467
409
 
468
- [The model](https://docs.velis.si/dynamicforms/vue-forms/guide/model) is the whole library in one page: elements,
469
- declarations, transactions, where validity comes from and how a `List` builds its rows.
410
+ [The model](https://docs.velis.si/dynamicforms/vue-forms/guide/model) describes how the library fits together:
411
+ elements, declarations, transactions, where validity comes from and how a `List` builds its rows.
412
+ [Rationale](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale) describes when the library fits, compared
413
+ with form libraries and with Pinia.
470
414
 
471
415
  Upgrading an existing project? The
472
- [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1
473
- onwards, silent breaks first, and keeps a section per release for a project crossing one.
474
-
475
- ## Conclusion
476
-
477
- `@dynamicforms/vue-forms` provides a clean, flexible approach to form management in Vue applications. By focusing on
478
- data structures and state management rather than UI components, it offers unparalleled flexibility while maintaining
479
- a simple, intuitive API.
416
+ [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) has one section per release, newest
417
+ first, from 3.0.0 back to 0.6.0, and a combined section for upgrading from a release before 0.12.
480
418
 
481
419
  ## License
482
420
 
@@ -1,2 +0,0 @@
1
- .df-messages-widget-markdown>:first-child,.df-messages-widget-markdown>:last-child{margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important;line-height:1.35em!important}
2
- /*$vite$:1*/