@dynamicforms/vue-forms 0.12.0 → 0.16.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,10 +1,10 @@
1
1
  {
2
2
  "name": "@dynamicforms/vue-forms",
3
3
  "private": false,
4
- "version": "0.12.0",
4
+ "version": "0.16.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
- "author": "Jure Erznožnik",
7
+ "author": "Jure Erzno\u017enik",
8
8
  "files": [
9
9
  "dist/*"
10
10
  ],
@@ -61,23 +61,35 @@
61
61
  "vue": "^3.5.2"
62
62
  },
63
63
  "devDependencies": {
64
+ "@eslint/js": "^10.0.1",
64
65
  "@types/lodash-es": "^4.17.12",
65
66
  "@types/markdown-it-attrs": "^4.1.3",
66
- "@types/node": "^24.5.2",
67
+ "@types/node": "^26.2.0",
68
+ "@typescript-eslint/eslint-plugin": "^8.67.0",
69
+ "@typescript-eslint/parser": "^8.67.0",
67
70
  "@vitejs/plugin-vue": "^6",
68
71
  "@vitest/coverage-v8": "^3",
69
72
  "@vue/test-utils": "^2.2.4",
70
73
  "@vue/tsconfig": "^0.7.0",
71
74
  "baseline-browser-mapping": "^2.8.31",
72
- "eslint-config-velis": "^2.0.11",
75
+ "eslint": "^10.4.0",
76
+ "eslint-config-prettier": "^10.1.8",
77
+ "eslint-config-velis": "^3.0.0",
78
+ "eslint-import-resolver-typescript": "^4.4.5",
79
+ "eslint-plugin-import-x": "^4.17.1",
80
+ "eslint-plugin-prettier": "^5.5.6",
81
+ "eslint-plugin-unicorn": "^73.0.0",
82
+ "eslint-plugin-vue": "^10.10.0",
73
83
  "jsdom": "^26.0.0",
74
84
  "markdown-it-attrs": "^4.3.1",
85
+ "prettier": "^3.9.6",
75
86
  "rollup-plugin-visualizer": "^5.14.0",
76
87
  "typescript": "^5",
77
88
  "vite": "^7",
78
89
  "vite-plugin-dts": "^4",
79
90
  "vite-plugin-eslint": "^1.8.1",
80
91
  "vitest": "^3",
92
+ "vue-eslint-parser": "^10.4.1",
81
93
  "vue-markdown-render": "^2.3.0",
82
94
  "vue-tsc": "^2"
83
95
  }
package/readme.md CHANGED
@@ -22,19 +22,27 @@ mechanism applies at every level of a nested form.
22
22
 
23
23
  ## Features
24
24
 
25
- - **UI-agnostic**: Works with any Vue UI components or your custom ones
25
+ - **UI-agnostic**: a logic layer for form state, validation and dynamic behaviour. Any Vue components render it,
26
+ your own included
27
+ - **Transactional**: every mutating operation is atomic — events are announced once, over the net change, and a
28
+ handler that throws leaves the form exactly as it was. `transaction()` makes several writes one operation, and
29
+ `tx.rollback()` withdraws one without an error
30
+ - **Lists that scale**: a `List` is meant to hold thousands of rows. Writing one field of one row costs that row
31
+ and the depth it sits at, a `push()` costs one row, and reading `value` or `valid` again costs nothing
32
+ - **Declared once, bound per record**: the `Group` handed to `new List(template)` is the declaration every row is
33
+ built from. One validator instance and one conditional rule serve every row, and each row answers for itself
26
34
  - **Reactive**: every member of a field, group or list is a tracked read — assign a property directly, with no
27
35
  `ref` to unwrap and no computed mirror to keep in sync
28
- - **Nested structures**: Support for complex data with nested fields and groups
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
32
- - **TypeScript support**: Full type definitions for excellent developer experience
36
+ - **Nested structures**: fields, groups and lists compose recursively, at every level
37
+ - **Event system**: every change travels through an action pipeline; a handler may pass it on, reshape it or stop it
38
+ - **Validation**: built-in validators, custom synchronous and asynchronous rules, cross-field comparisons, and
39
+ errors that carry a `code` and render as text, markdown or a component of your own
40
+ - **Conditional logic**: visibility, enablement and values declared as statements over other fields
41
+ - **Extended properties**: a field carries whatever your UI renders it with — a label, a hint, a width — declared
42
+ as a second type argument, checked by the compiler and read through `extra`
43
+ - **Display modes**: control field visibility with different display modes (Full, Hidden, Invisible, Suppress)
44
+ - **TypeScript support**: full type definitions, and a group's value type inferred from the fields it holds
33
45
  - **Lightweight**: `vue` (^3.5.2) is the only peer dependency and `lodash-es` the only runtime one
34
- - **Field types**: Core field types (Field, Action, Group, List) to represent any data structure
35
- - **Validation**: Comprehensive validation system with built-in validators and extensible error handling
36
- - **Conditional logic**: Dynamic form behavior based on field values and conditions
37
- - **Display modes**: Control field visibility with different display modes (Full, Hidden, Invisible, Suppress)
38
46
 
39
47
  ## Installation
40
48
 
@@ -63,6 +71,10 @@ The import must be a named one — the default export is a namespace of the libr
63
71
  (`MdString`). Rendering those requires a globally registered `vue-markdown` component; set the option to `false` if you
64
72
  want plain text instead.
65
73
 
74
+ The configuration is module-global rather than per app, and `getConfig()`, `setConfig()` and the `FormsConfig` type
75
+ are exported beside the plugin, so the options can be read and written where there is no app to install a plugin
76
+ on.
77
+
66
78
  ## Basic Usage Example
67
79
 
68
80
  Every form element is created with the constructor: `new Field({ ... })`, `new Action({ ... })`,
@@ -211,13 +223,22 @@ const validatedForm = new Group({
211
223
  ```
212
224
 
213
225
  Validators run eagerly — a field is validated the moment it is created, so a form built from empty required fields is
214
- invalid immediately. Use `field.touched` to decide when to show the errors in the UI.
215
-
216
- A validation function may return a `Promise`. `field.validating` is `true` while such a run is pending, and the
217
- verdict applied to the field is always the one belonging to the newest run, so a slow check cannot overwrite a faster
218
- one started after it. A validator message given as a `Ref` or `computed` is resolved when the message is rendered, so
219
- a translated message follows a locale switch without revalidating. `field.clearValidators()` drops the validators,
220
- empties the errors and cancels whatever validation is still in flight.
226
+ invalid immediately. Use `field.touched` to decide when to show the errors in the UI. `Required` trims a string
227
+ before it measures it, so a value of spaces alone is no value; `new Validators.Required({ trim: false })` keeps the
228
+ spaces where they are part of what the field holds.
229
+
230
+ Every error a built-in validator produces carries a `code` — `required`, `pattern`, `min-length`, … — so a program
231
+ reacting to one particular failure need not match message text that is translated and configurable.
232
+
233
+ A validation function may return a `Promise`. `field.validating` is `true` while such a run is pending — on the
234
+ field and on every container above it, so a form answers for the whole tree — and the verdict applied to the field
235
+ is always the one belonging to the newest run, so a slow check cannot overwrite a faster one started after it. The
236
+ function receives an `AbortSignal` as its fourth argument, which aborts the moment the run's verdict stops
237
+ counting, so the request behind it can be called off. `form.busy` is the same question with the `Action.execute()`
238
+ runs below it included — what a submit button binds to. A validator message given as a `Ref` or `computed` is
239
+ resolved when the message is rendered, so a translated message follows a locale switch without revalidating.
240
+ `field.clearValidators()` drops the validators, empties the errors and cancels whatever validation is still in
241
+ flight.
221
242
 
222
243
  ## Messages Widget Component
223
244
 
@@ -326,7 +347,8 @@ const contactsList = new List(contactTemplate);
326
347
  contactsList.push({ name: 'John Doe', email: 'john@example.com', phone: '123-456-7890' });
327
348
  contactsList.push({ name: 'Jane Doe', email: 'jane@example.com', phone: '987-654-3210' });
328
349
 
329
- // Access list items: get() returns undefined for an invalid index
350
+ // Access list items: items hands out the rows, get() answers undefined for an invalid index
351
+ console.log(contactsList.length); // 2
330
352
  const firstContact = contactsList.get(0)!;
331
353
  console.log(firstContact.fields.name.value); // 'John Doe'
332
354
 
@@ -337,8 +359,9 @@ firstContact.fields.email.value = 'john.doe@example.com';
337
359
  contactsList.remove(1);
338
360
  ```
339
361
 
340
- Every list mutation is tracked, so a `v-for` over `contactsList.value` re-renders on `push()`, `insert()`,
341
- `remove()`, `pop()` and `clear()` without any extra wiring.
362
+ Every list mutation is tracked, so a `v-for` over `contactsList.items` re-renders on `push()`, `insert()`,
363
+ `remove()`, `pop()` and `clear()` without any extra wiring. `items` hands out a frozen array of the live rows,
364
+ rebuilt once per change of the set.
342
365
 
343
366
  ## TypeScript Support
344
367
 
@@ -388,10 +411,11 @@ const email: string = userForm.fields.email.value;
388
411
  const age: number = userForm.fields.age.value;
389
412
  const darkMode: boolean = userForm.fields.preferences.fields.darkMode.value;
390
413
 
391
- // The serialized value is typed too, member by member
414
+ // The serialized value is typed too, member by member. Every member is optional, because a disabled field is
415
+ // left out of the object the group builds
392
416
  const values = userForm.value!;
393
- const emailFromValue: string = values.email;
394
- const prefs: { darkMode: boolean; notifications: boolean } | null = values.preferences;
417
+ const emailFromValue: string | undefined = values.email;
418
+ const prefs: { darkMode?: boolean; notifications?: boolean } | null | undefined = values.preferences;
395
419
 
396
420
  // Type safety prevents errors
397
421
  // userForm.fields.age.value = 'not a number'; // Error: Type 'string' is not assignable to type 'number'
@@ -443,8 +467,8 @@ For more detailed documentation and examples, check out the [documentation](http
443
467
  declarations, transactions, where validity comes from and how a `List` builds its rows.
444
468
 
445
469
  Upgrading an existing project? The
446
- [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1 to
447
- 0.10.x, silent breaks first, and keeps a section per release for a project crossing one.
470
+ [migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1
471
+ onwards, silent breaks first, and keeps a section per release for a project crossing one.
448
472
 
449
473
  ## Conclusion
450
474