@dynamicforms/vue-forms 0.6.1 → 0.11.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dynamicforms/vue-forms",
3
3
  "private": false,
4
- "version": "0.6.1",
4
+ "version": "0.11.0",
5
5
  "type": "module",
6
6
  "description": "A lightweight Vue.js library for form state management with built-in validation, conditional logic, nested structures, and a rich event system. UI-agnostic and TypeScript-first.",
7
7
  "author": "Jure Erznožnik",
package/readme.md CHANGED
@@ -16,17 +16,19 @@ mechanism applies at every level of a nested form.
16
16
 
17
17
  - **UI-Agnostic**: A logic layer for form state, validation and dynamic behaviour. Works with any Vue components, including your own.
18
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**: Fields, groups and lists are Vue reactive objects, and a group's value type is inferred from the fields it holds, nested structures included.
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
20
  - **Structural serialization**: A group's value is the shape of its fields, and `Group.createFromFormData()` turns a plain object back into a form.
21
21
  - **Lightweight**: `vue` (^3.4) is the only peer dependency, and `lodash` and `lodash-es` — one per build — the only runtime ones.
22
22
 
23
23
  ## Features
24
24
 
25
25
  - **UI-agnostic**: Works with any Vue UI components or your custom ones
26
- - **Reactive**: Fields, groups and lists are Vue reactive objects — read and assign their properties directly, with
27
- no `ref` to unwrap and no computed mirror to keep in sync
26
+ - **Reactive**: every member of a field, group or list is a tracked read — assign a property directly, with no
27
+ `ref` to unwrap and no computed mirror to keep in sync
28
28
  - **Nested structures**: Support for complex data with nested fields and groups
29
29
  - **Event system**: Rich event handling for field changes, validation, and more
30
+ - **Transactional**: every mutating operation is atomic — events are announced once, over the net change, and a
31
+ handler that throws leaves the form exactly as it was
30
32
  - **TypeScript support**: Full type definitions for excellent developer experience
31
33
  - **Lightweight**: `vue` (^3.4) is the only peer dependency; the runtime dependencies are `lodash` and `lodash-es`,
32
34
  one for each of the two builds
@@ -65,9 +67,10 @@ want plain text instead.
65
67
  ## Basic Usage Example
66
68
 
67
69
  Every form element is created with the constructor: `new Field({ ... })`, `new Action({ ... })`,
68
- `new Group({ ... })`, `new List(template)`. The instance is a Vue reactive object from that moment on, so reading
70
+ `new Group({ ... })`, `new List(template)`. Every read through it is tracked from that moment on, so reading
69
71
  `field.value` in a template tracks it and `field.value = x` re-renders — there is no `ref` to unwrap and no
70
- computed mirror to maintain.
72
+ computed mirror to maintain. The element itself is not a Vue proxy, so watch what you read:
73
+ `watch(() => field.value, cb)`.
71
74
 
72
75
  Here's a simple example of how to create and use a form with fields and groups:
73
76
 
@@ -105,9 +108,14 @@ const saveAction = new Action({
105
108
  actions: [new ExecuteAction((field, supr, params) => { console.log('saving', params); })]
106
109
  });
107
110
 
108
- saveAction.execute({ form: personForm }); // 'saving { form: ... }'
111
+ await saveAction.execute({ form: personForm }); // 'saving { form: ... }'; saveAction.busy until it settles
109
112
  ```
110
113
 
114
+ `Action` is the one deliberate exception to "UI-agnostic": it names a label and an icon because it exists as the
115
+ element a form's submit and cancel hang on, and that minimal pair is what makes the concept legible. The shape is
116
+ minimal because a UI library is expected to extend it — [`@dynamicforms/vuetify-inputs`](https://docs.velis.si/dynamicforms/vuetify-inputs/examples/df-actions.html)
117
+ widens the value with render options and per-breakpoint variants on top of it.
118
+
111
119
  ## Events Example
112
120
 
113
121
  The library provides a powerful event system for field changes and other actions:
@@ -141,6 +149,21 @@ const form = new Group({
141
149
  Assigning `field.errors` directly replaces the whole array, including errors owned by validators — prefer a
142
150
  `Validator` (see below) when all you want is to add a validation rule.
143
151
 
152
+ Events are announced when the operation carrying them finishes, over the net change. Wrap several writes in a
153
+ `transaction()` and they announce as one:
154
+
155
+ ```typescript
156
+ import { transaction } from '@dynamicforms/vue-forms';
157
+
158
+ // one ValueChangedAction on the form, not two
159
+ transaction(() => {
160
+ form.fields.email.value = 'janez@example.com';
161
+ form.fields.username.value = 'janez';
162
+ });
163
+ ```
164
+
165
+ A throw out of the callback rolls the whole transaction back and rethrows, so a form is never left half-applied.
166
+
144
167
  ## Built-in Validators
145
168
 
146
169
  The library provides several built-in validators for common validation scenarios:
@@ -377,18 +400,30 @@ const prefs: { darkMode: boolean; notifications: boolean } | null = values.prefe
377
400
 
378
401
  The value shape is derived from the fields map by the exported `FieldsToValues<T>`, with `GroupValue<T>` and
379
402
  `GroupValueInput<T>` as the group's read and write types, and `ListValue` for lists. Constructor parameters have
380
- their own exported type, `IFieldConstructorParams<T>`, shared by all four element classes and by `clone()`:
403
+ their own exported type, `IFieldParams<T, X>`, shared by all four element classes:
381
404
 
382
405
  ```typescript
383
- import { Field, IFieldConstructorParams } from '@dynamicforms/vue-forms';
406
+ import { Field, IFieldParams } from '@dynamicforms/vue-forms';
384
407
 
385
- const defaults: Partial<IFieldConstructorParams<string>> = { value: '', enabled: false };
408
+ const defaults: IFieldParams<string> = { value: '', enabled: false };
386
409
  const field = new Field(defaults);
387
410
  ```
388
411
 
389
412
  It admits only the writable members — `value`, `originalValue`, `enabled`, `visibility`, `touched`, `errors`,
390
- `validators` and `actions`. Derived members such as `valid` and `isChanged` are getters, and passing one is a
391
- compile error.
413
+ `validators` and `actions`, listed by `IFieldConstructorParams<T>`. Derived members such as `valid` and
414
+ `isChanged` are getters, and passing one is a compile error.
415
+
416
+ `X` is what an element carries beyond those: declare it, and the parameter object takes those properties too,
417
+ `extra` reads them back and `setExtendedValues()` writes them. It is how a form built from a server's description
418
+ carries the label, hint or css class a UI layer binds to its inputs:
419
+
420
+ ```typescript
421
+ interface Presentation { label: string; hint?: string }
422
+
423
+ const name = new Field<string, Presentation>({ value: 'John', label: 'First name' });
424
+ name.extra.label; // 'First name', and a template reading it re-renders when it is written
425
+ name.setExtendedValues({ hint: 'as in your passport' });
426
+ ```
392
427
 
393
428
  `FieldBase<T>` is the abstract base of `Field`, `Action`, `Group` and `List`, and the type to use in your own
394
429
  signatures whenever you accept any form element:
@@ -405,9 +440,12 @@ function isDirty(field: FieldBase): boolean {
405
440
 
406
441
  For more detailed documentation and examples, check out the [documentation](https://docs.velis.si/dynamicforms/vue-forms).
407
442
 
408
- Upgrading from 0.5.x? The
409
- [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) lists every breaking change with
410
- before/after code.
443
+ [The model](https://docs.velis.si/dynamicforms/vue-forms/guide/model) is the whole library in one page: elements,
444
+ declarations, transactions, where validity comes from and how a `List` builds its rows.
445
+
446
+ Upgrading an existing project? The
447
+ [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1 to
448
+ 0.10.x, silent breaks first, and keeps a section per release for a project crossing one.
411
449
 
412
450
  ## Conclusion
413
451