@dynamicforms/vue-forms 0.6.0 → 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.0",
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",
@@ -29,6 +29,7 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "build": "vite build && node -e \"require('fs').copyFileSync('dist/index.d.ts', 'dist/index.d.cts')\"",
32
+ "prepack": "npm run build",
32
33
  "test": "vitest run --coverage",
33
34
  "lint": "eslint src --fix && vue-tsc --noEmit",
34
35
  "lint:ci": "eslint src && vue-tsc --noEmit",
@@ -52,13 +53,19 @@
52
53
  "homepage": "https://docs.velis.si/dynamicforms/vue-forms",
53
54
  "repository": {
54
55
  "type": "git",
55
- "url": "git+https://github.com/velis74/dynamicforms-vue-forms.git"
56
+ "url": "git+https://github.com/dynamicforms/vue-forms.git"
56
57
  },
57
58
  "bugs": {
58
- "url": "https://github.com/velis74/dynamicforms-vue-forms/issues"
59
+ "url": "https://github.com/dynamicforms/vue-forms/issues"
60
+ },
61
+ "engines": {
62
+ "node": ">=18"
63
+ },
64
+ "dependencies": {
65
+ "lodash": "^4.17.21",
66
+ "lodash-es": "^4.17.21"
59
67
  },
60
68
  "peerDependencies": {
61
- "lodash-es": "^4.17.12",
62
69
  "vue": "^3.4"
63
70
  },
64
71
  "devDependencies": {
package/readme.md CHANGED
@@ -16,19 +16,22 @@ 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
- - **Lightweight**: Depends on Vue and lodash-es, and nothing else.
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
- - **Lightweight**: depends only on `vue` (^3.4) and `lodash-es` (^4.17)
33
+ - **Lightweight**: `vue` (^3.4) is the only peer dependency; the runtime dependencies are `lodash` and `lodash-es`,
34
+ one for each of the two builds
32
35
  - **Field types**: Core field types (Field, Action, Group, List) to represent any data structure
33
36
  - **Validation**: Comprehensive validation system with built-in validators and extensible error handling
34
37
  - **Conditional logic**: Dynamic form behavior based on field values and conditions
@@ -40,6 +43,11 @@ mechanism applies at every level of a nested form.
40
43
  npm install @dynamicforms/vue-forms
41
44
  ```
42
45
 
46
+ The package ships two builds of the same source: an ESM build that imports `lodash-es` and a CJS/UMD build that
47
+ requires `lodash`. Both lodash packages are declared as dependencies and install with the package, so an `import` and
48
+ a `require()` both resolve without further setup. Node 18 or newer is required, and `vue` (^3.4) is the only peer
49
+ dependency. Type definitions ship for both entry points; the stylesheet is `@dynamicforms/vue-forms/style.css`.
50
+
43
51
  ## Setup
44
52
 
45
53
  The library ships a Vue plugin for its global options:
@@ -59,9 +67,10 @@ want plain text instead.
59
67
  ## Basic Usage Example
60
68
 
61
69
  Every form element is created with the constructor: `new Field({ ... })`, `new Action({ ... })`,
62
- `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
63
71
  `field.value` in a template tracks it and `field.value = x` re-renders — there is no `ref` to unwrap and no
64
- 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)`.
65
74
 
66
75
  Here's a simple example of how to create and use a form with fields and groups:
67
76
 
@@ -99,9 +108,14 @@ const saveAction = new Action({
99
108
  actions: [new ExecuteAction((field, supr, params) => { console.log('saving', params); })]
100
109
  });
101
110
 
102
- saveAction.execute({ form: personForm }); // 'saving { form: ... }'
111
+ await saveAction.execute({ form: personForm }); // 'saving { form: ... }'; saveAction.busy until it settles
103
112
  ```
104
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
+
105
119
  ## Events Example
106
120
 
107
121
  The library provides a powerful event system for field changes and other actions:
@@ -135,6 +149,21 @@ const form = new Group({
135
149
  Assigning `field.errors` directly replaces the whole array, including errors owned by validators — prefer a
136
150
  `Validator` (see below) when all you want is to add a validation rule.
137
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
+
138
167
  ## Built-in Validators
139
168
 
140
169
  The library provides several built-in validators for common validation scenarios:
@@ -185,6 +214,12 @@ const validatedForm = new Group({
185
214
  Validators run eagerly — a field is validated the moment it is created, so a form built from empty required fields is
186
215
  invalid immediately. Use `field.touched` to decide when to show the errors in the UI.
187
216
 
217
+ A validation function may return a `Promise`. `field.validating` is `true` while such a run is pending, and the
218
+ verdict applied to the field is always the one belonging to the newest run, so a slow check cannot overwrite a faster
219
+ one started after it. A validator message given as a `Ref` or `computed` is resolved when the message is rendered, so
220
+ a translated message follows a locale switch without revalidating. `field.clearValidators()` drops the validators,
221
+ empties the errors and cancels whatever validation is still in flight.
222
+
188
223
  ## Messages Widget Component
189
224
 
190
225
  The library includes a `messages-widget` Vue component for displaying validation errors and messages:
@@ -365,18 +400,30 @@ const prefs: { darkMode: boolean; notifications: boolean } | null = values.prefe
365
400
 
366
401
  The value shape is derived from the fields map by the exported `FieldsToValues<T>`, with `GroupValue<T>` and
367
402
  `GroupValueInput<T>` as the group's read and write types, and `ListValue` for lists. Constructor parameters have
368
- 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:
369
404
 
370
405
  ```typescript
371
- import { Field, IFieldConstructorParams } from '@dynamicforms/vue-forms';
406
+ import { Field, IFieldParams } from '@dynamicforms/vue-forms';
372
407
 
373
- const defaults: Partial<IFieldConstructorParams<string>> = { value: '', enabled: false };
408
+ const defaults: IFieldParams<string> = { value: '', enabled: false };
374
409
  const field = new Field(defaults);
375
410
  ```
376
411
 
377
412
  It admits only the writable members — `value`, `originalValue`, `enabled`, `visibility`, `touched`, `errors`,
378
- `validators` and `actions`. Derived members such as `valid` and `isChanged` are getters, and passing one is a
379
- 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
+ ```
380
427
 
381
428
  `FieldBase<T>` is the abstract base of `Field`, `Action`, `Group` and `List`, and the type to use in your own
382
429
  signatures whenever you accept any form element:
@@ -393,9 +440,12 @@ function isDirty(field: FieldBase): boolean {
393
440
 
394
441
  For more detailed documentation and examples, check out the [documentation](https://docs.velis.si/dynamicforms/vue-forms).
395
442
 
396
- Upgrading from 0.5.x? The
397
- [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) lists every breaking change with
398
- 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.
399
449
 
400
450
  ## Conclusion
401
451