@dynamicforms/vue-forms 1.0.0 → 2.0.2

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": "1.0.0",
4
+ "version": "2.0.2",
5
5
  "type": "module",
6
6
  "description": "A 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
@@ -14,7 +14,7 @@ mechanism applies at every level of a nested form.
14
14
 
15
15
  ### Design Goals
16
16
 
17
- - **UI-Agnostic**: A logic layer for form state, validation and dynamic behaviour. Works with any Vue components, including your own.
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
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
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.
@@ -22,13 +22,13 @@ mechanism applies at every level of a nested form.
22
22
  ## Features
23
23
 
24
24
  - **UI-agnostic**: a logic layer for form state, validation and dynamic behaviour. Any Vue components render it,
25
- your own included
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
26
  - **Transactional**: every mutating operation is atomic — events are announced once, over the net change, and a
27
27
  handler that throws leaves the form exactly as it was. `transaction()` makes several writes one operation, and
28
28
  `tx.rollback()` withdraws one without an error
29
29
  - **Lists that scale**: a `List` is meant to hold thousands of rows. Writing one field of one row costs that row
30
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 `Group` handed to `new List(template)` is the declaration every row is
31
+ - **Declared once, bound per record**: the element handed to `new List(template)` is the declaration every row is
32
32
  built from. One validator instance and one conditional rule serve every row, and each row answers for itself
33
33
  - **Reactive**: every member of a field, group or list is a tracked read — assign a property directly, with no
34
34
  `ref` to unwrap and no computed mirror to keep in sync
@@ -39,7 +39,13 @@ mechanism applies at every level of a nested form.
39
39
  - **Conditional logic**: visibility, enablement and values declared as statements over other fields
40
40
  - **Extended properties**: a field carries whatever your UI renders it with — a label, a hint, a width — declared
41
41
  as a second type argument, checked by the compiler and read through `extra`
42
- - **Display modes**: control field visibility with different display modes (Full, Hidden, Invisible, Suppress)
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
43
49
  - **TypeScript support**: full type definitions, and a group's value type inferred from the fields it holds
44
50
 
45
51
  ## Installation
@@ -101,9 +107,9 @@ console.log(personForm.value); // { firstName: 'John', lastName: 'Doe', age: 30
101
107
  personForm.fields.firstName.value = 'Jane';
102
108
 
103
109
  // Disable a field
104
- personForm.fields.age.enabled = false;
110
+ personForm.fields.age.access = 'disabled';
105
111
 
106
- // Form serializes only enabled fields
112
+ // The form leaves a disabled field out of what it sends
107
113
  console.log(personForm.value); // { firstName: 'Jane', lastName: 'Doe', active: true }
108
114
  ```
109
115
 
@@ -120,8 +126,10 @@ const saveAction = new Action({
120
126
  await saveAction.execute({ form: personForm }); // 'saving { form: ... }'; saveAction.busy until it settles
121
127
  ```
122
128
 
123
- `Action` is the one deliberate exception to "UI-agnostic": it names a label and an icon because it exists as the
124
- element a form's submit and cancel hang on, and that minimal pair is what makes the concept legible. The shape is
129
+ `Action` is one of the few members that speak about the interface, listed with their reasons in
130
+ [Rationale](https://docs.velis.si/dynamicforms/vue-forms/guide/rationale#what-the-library-carries-for-the-interface):
131
+ it names a label and an icon because it exists as the element a form's submit and cancel hang on, and that minimal
132
+ pair is what makes the concept legible. The shape is
125
133
  minimal because a UI library is expected to extend it — [`@dynamicforms/vuetify-inputs`](https://docs.velis.si/dynamicforms/vuetify-inputs/examples/df-actions.html)
126
134
  widens the value with render options and per-breakpoint variants on top of it.
127
135
 
@@ -298,7 +306,7 @@ Create dynamic forms with conditional logic using Statements and Operators:
298
306
  ```typescript
299
307
  import {
300
308
  Field, Group, Statement, Operator,
301
- ConditionalVisibilityAction, ConditionalEnabledAction
309
+ ConditionalVisibilityAction, ConditionalAccessAction
302
310
  } from '@dynamicforms/vue-forms';
303
311
 
304
312
  const form = new Group({
@@ -308,20 +316,17 @@ const form = new Group({
308
316
  lastName: new Field()
309
317
  });
310
318
 
311
- // Show company name field only when isCompany is true
312
- const showCompanyNameStatement = new Statement(form.fields.isCompany, Operator.EQUALS, true);
313
- form.fields.companyName.registerAction(
314
- new ConditionalVisibilityAction(showCompanyNameStatement)
315
- );
316
-
317
- // Show personal name fields only when isCompany is false
318
- const showPersonalFieldsStatement = new Statement(form.fields.isCompany, Operator.EQUALS, false);
319
- form.fields.firstName.registerAction(
320
- new ConditionalVisibilityAction(showPersonalFieldsStatement)
321
- );
322
- form.fields.lastName.registerAction(
323
- new ConditionalVisibilityAction(showPersonalFieldsStatement)
324
- );
319
+ // Show and send the company name only when isCompany is true
320
+ const isCompany = new Statement(form.fields.isCompany, Operator.EQUALS, true);
321
+ form.fields.companyName.registerAction(new ConditionalVisibilityAction(isCompany));
322
+ form.fields.companyName.registerAction(new ConditionalAccessAction(isCompany));
323
+
324
+ // Show and send the personal name fields only when isCompany is false
325
+ const isPerson = new Statement(form.fields.isCompany, Operator.EQUALS, false);
326
+ [form.fields.firstName, form.fields.lastName].forEach((field) => {
327
+ field.registerAction(new ConditionalVisibilityAction(isPerson));
328
+ field.registerAction(new ConditionalAccessAction(isPerson));
329
+ });
325
330
  ```
326
331
 
327
332
  ## Advanced Data Structures (Lists)
@@ -409,28 +414,28 @@ const email: string = userForm.fields.email.value;
409
414
  const age: number = userForm.fields.age.value;
410
415
  const darkMode: boolean = userForm.fields.preferences.fields.darkMode.value;
411
416
 
412
- // The serialized value is typed too, member by member. Every member is optional, because a disabled field is
413
- // left out of the object the group builds
414
- const values = userForm.value!;
415
- const emailFromValue: string | undefined = values.email;
416
- const prefs: { darkMode?: boolean; notifications?: boolean } | null | undefined = values.preferences;
417
+ // The serialized value is typed too, member by member. Every member is optional, because a 'disabled' member is
418
+ // left out of the object the group builds, and nullable, because a 'disabled-null' one is sent as null
419
+ const values = userForm.value;
420
+ const emailFromValue: string | null | undefined = values.email;
421
+ const prefs: { darkMode?: boolean | null; notifications?: boolean | null } | null | undefined = values.preferences;
417
422
 
418
423
  // Type safety prevents errors
419
424
  // userForm.fields.age.value = 'not a number'; // Error: Type 'string' is not assignable to type 'number'
420
425
  ```
421
426
 
422
427
  The value shape is derived from the fields map by the exported `FieldsToValues<T>`, with `GroupValue<T>` and
423
- `GroupValueInput<T>` as the group's read and write types, and `ListValue` for lists. Constructor parameters have
428
+ `GroupValueInput<T>` as the group's read and write types, and `ListValue<R>` for lists. Constructor parameters have
424
429
  their own exported type, `IFieldParams<T, X>`, shared by all four element classes:
425
430
 
426
431
  ```typescript
427
432
  import { Field, IFieldParams } from '@dynamicforms/vue-forms';
428
433
 
429
- const defaults: IFieldParams<string> = { value: '', enabled: false };
434
+ const defaults: IFieldParams<string> = { value: '', access: 'readonly' };
430
435
  const field = new Field(defaults);
431
436
  ```
432
437
 
433
- It admits only the writable members — `value`, `originalValue`, `enabled`, `visibility`, `touched`, `errors`,
438
+ It admits only the writable members — `value`, `originalValue`, `access`, `visibility`, `touched`, `errors`,
434
439
  `validators` and `actions`, listed by `IFieldConstructorParams<T>`. Derived members such as `valid` and
435
440
  `isChanged` are getters, and passing one is a compile error.
436
441