@whatsaaaa/form-builder-affects 0.1.0 → 0.1.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 +28 -136
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# @whatsaaaa/form-builder-affects
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Show, hide and clear the fields, sections and steps of JSON form definitions by conditions.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@whatsaaaa/form-builder-affects)
|
|
6
|
+
[](https://www.npmjs.com/package/@whatsaaaa/form-builder-affects)
|
|
7
|
+
[](https://github.com/whatsaaaa/form-builder/actions/workflows/ci.yml)
|
|
8
|
+
[](https://codecov.io/gh/whatsaaaa/form-builder)
|
|
9
|
+
[](https://bundlejs.com/?q=@whatsaaaa/form-builder-affects&treeshake=%5B%7Baffects%7D%5D&config=%7B%22esbuild%22:%7B%22external%22:%5B%22valibot%22,%22@whatsaaaa/form-builder-schema%22%5D%7D%7D)
|
|
10
|
+
[](https://www.npmjs.com/package/@whatsaaaa/form-builder-affects)
|
|
11
|
+
[](https://github.com/whatsaaaa/form-builder/blob/main/LICENSE)
|
|
12
|
+
|
|
13
|
+
This package is an extension for [`@whatsaaaa/form-builder-schema`](https://github.com/whatsaaaa/form-builder/tree/main/packages/schema). It adds `affects` to definitions: conditions on the values of other fields that show a field, hide it, or hide it and clear its value. A hidden field isn't validated, in every Formisch package and in a plain `v.parse`.
|
|
4
14
|
|
|
5
15
|
## Install
|
|
6
16
|
|
|
@@ -8,7 +18,7 @@ Conditions for JSON form definitions of [`@whatsaaaa/form-builder-schema`](https
|
|
|
8
18
|
pnpm add @whatsaaaa/form-builder-affects @whatsaaaa/form-builder-schema valibot
|
|
9
19
|
```
|
|
10
20
|
|
|
11
|
-
##
|
|
21
|
+
## Example
|
|
12
22
|
|
|
13
23
|
Give the schema builder the extension, and write `affects` on the fields:
|
|
14
24
|
|
|
@@ -42,146 +52,28 @@ const json: JsonForm = {
|
|
|
42
52
|
const schema = builder.build(json) // vatId is required only for a business account
|
|
43
53
|
```
|
|
44
54
|
|
|
45
|
-
With the
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
const jsonForm = useJsonForm(json, { builder, components })
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Without the extension, `affects` is an unknown key: `validate()` reports it, and `build()` throws. A definition never runs without its conditions by mistake.
|
|
52
|
-
|
|
53
|
-
## Affects
|
|
54
|
-
|
|
55
|
-
`affects` is a list of effects, each with the condition that applies it:
|
|
56
|
-
|
|
57
|
-
| Effect | While its condition is met |
|
|
58
|
-
| -------------- | ------------------------------------------------------- |
|
|
59
|
-
| `show` | the field shows. Otherwise it is hidden, like `hide` |
|
|
60
|
-
| `hide` | the field is hidden, and keeps its value |
|
|
61
|
-
| `hideAndClear` | the field is hidden, and its value cleared: it is blank |
|
|
62
|
-
|
|
63
|
-
- **Hide always wins.** A field is hidden when its `show` condition isn't met, or its `hide` or `hideAndClear` condition is. It is cleared only when its `hideAndClear` condition is met.
|
|
64
|
-
- **Each effect goes once on a field.** Join conditions with `all` or `any`. A field can have `show` with `hide` or `hideAndClear`.
|
|
65
|
-
- **Where:** any field in `fields`, at any depth, display elements, groups and arrays included, and the layout's steps, sections and display elements. Not on a row, nor on the `of` field of a list, which is the item itself: put them on the list.
|
|
66
|
-
|
|
67
|
-
## Conditions
|
|
68
|
-
|
|
69
|
-
```text
|
|
70
|
-
Condition = { "field": Path, Operator: Value }
|
|
71
|
-
| { "all": [Condition, ...] }
|
|
72
|
-
| { "any": [Condition, ...] }
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
A test reads one field, by its dot path, with exactly one operator. `all` and `any` take at least one condition, and nest to any depth.
|
|
76
|
-
|
|
77
|
-
| Operator | Value | True when the field |
|
|
78
|
-
| ----------- | --------------------------- | --------------------------------------------------------- |
|
|
79
|
-
| `equals` | a string, number or boolean | holds the value |
|
|
80
|
-
| `notEquals` | a string, number or boolean | doesn't hold the value, or is empty |
|
|
81
|
-
| `in` | a list of at least one | holds one of the values |
|
|
82
|
-
| `includes` | a string, number or boolean | is a multiselect or a list of values, and holds the value |
|
|
83
|
-
| `empty` | `true` | is empty |
|
|
84
|
-
| `notEmpty` | `true` | isn't empty |
|
|
85
|
-
|
|
86
|
-
Paths are the paths of form rules: `isBusiness`, `billing.zip`, or `lines.*.kind` for the field of the **same item** of `lines`, from a field, section or display element inside its items. A path with an index, like `lines.0.kind`, or to an item's field from outside the items, is an issue. Nested lists bind a `*` to each item the field is in: `orders.*.lines.*.sku`.
|
|
87
|
-
|
|
88
|
-
### Operators by kind
|
|
89
|
-
|
|
90
|
-
| Kind | `equals`, `notEquals`, `in` | `includes` | `empty`, `notEmpty` |
|
|
91
|
-
| ---------------------------------------------------------------------- | --------------------------- | ---------- | ------------------- |
|
|
92
|
-
| string, number, boolean, date (text, select, radio, checkbox, date...) | yes | no | yes |
|
|
93
|
-
| array: a multiselect, or a list of values with `of` | no | yes | yes |
|
|
94
|
-
| array: a list of objects with `fields`, or of files | no | no | yes |
|
|
95
|
-
| file | no | no | yes |
|
|
96
|
-
| object: a group, which is never empty | no | no | no |
|
|
97
|
-
| your own kinds | no | no | yes |
|
|
98
|
-
|
|
99
|
-
A value must be one the field can hold: its schema, rules included, takes it, like a `default`. So `{ "field": "country", "equals": "Germny" }` on a select is an issue, not a condition that is never met. A date is the ISO string the field takes: `"2026-11-09"`, `"14:30"`, `"2026-11-09T14:30"`.
|
|
100
|
-
|
|
101
|
-
- **Conditions read the input**, what the form holds now, turned the way a renderer sets it: `"5"` is `5` for a number. Normalize rules like `trim` aren't applied.
|
|
102
|
-
- **Empty** is the kind's empty, the one `required` uses: `undefined`, `''`, `[]`. An empty field never `equals` anything, and `notEquals` is true for it.
|
|
103
|
-
- **A checkbox nobody touched holds nothing**, not `false`. To show something while it isn't ticked, write `{ "notEquals": true }`, or give the checkbox `"default": false`. A required checkbox only takes `true`, so `{ "equals": false }` on one is an issue.
|
|
104
|
-
|
|
105
|
-
## What a hidden field does
|
|
106
|
-
|
|
107
|
-
- **It isn't validated:** no `required`, no rules, and no form rule that reads it, or is on it. A hidden list skips its own `required`, `minLength` and `maxLength` too. Formisch shows no error on it, and an old one goes at the next validation.
|
|
108
|
-
- **It counts as empty** in every other condition, so a chain follows: when `companyType` hides with the business account, a `vatId` shown for `companyType: "ltd"` hides too, though `companyType` still holds `"ltd"`.
|
|
109
|
-
- **A field `hideAndClear` hides is blank**: it submits `undefined`, a list `[]`, and each field of a group is blank. The adapters set it blank in the form too, with Formisch's `setInput`, as soon as it is cleared, so it is blank when it shows again. A field that starts hidden this way starts blank, without its `default`.
|
|
110
|
-
- **A field hidden otherwise keeps its value**: by `hide`, by a `show` that isn't met, or by a group, list, section or step that `hide` hides. It submits its output when its value is valid, and nothing when it isn't, so its errors, which nobody can see, never block the form.
|
|
55
|
+
With the Vue or React adapter, pass the builder to `useJsonForm`, and hidden fields, sections and steps leave the form. Without the extension, `affects` is an unknown key, so a definition never runs without its conditions by mistake.
|
|
111
56
|
|
|
112
|
-
|
|
57
|
+
## Highlights
|
|
113
58
|
|
|
114
|
-
|
|
59
|
+
- Three effects: `show`, `hide`, and `hideAndClear`, which also blanks the value
|
|
60
|
+
- Conditions on any field by its path, combined with `all` and `any`
|
|
61
|
+
- Works on fields at any depth, display elements, and the layout's sections and steps
|
|
62
|
+
- Hidden fields skip validation, so a required field only counts while it shows
|
|
63
|
+
- Checks every condition against the fields it reads, and reports loops
|
|
64
|
+
- `getVisibility` tells a renderer in any framework what to hide and clear
|
|
65
|
+
- Ships a `definition.schema.json` with `affects` for editor autocompletion
|
|
115
66
|
|
|
116
|
-
##
|
|
117
|
-
|
|
118
|
-
- **A hidden group, list, section or step hides everything in it**, and clears it when `hideAndClear` hides it. A field inside is hidden even when its own `show` is met.
|
|
119
|
-
- **A group whose fields are all hidden hides too.** The fields that count are those with a value, nested groups and lists included, and the display elements with `affects` of their own: a heading without conditions doesn't keep a group on show, but a paragraph shown by a condition does. A list never hides this way: it has a value of its own, and its add button.
|
|
120
|
-
- **A section, a row or a step of the layout whose fields are all hidden hides**, by the same rule. A field hidden in a row drops out of it, and the row's other cells keep their spans: no field moves to another row. Next and Back skip a hidden step, and the adapters leave it out of `steps.list`, unless the user is on it.
|
|
121
|
-
- **A step or a section with `affects`** hides and clears the fields placed in it. Where the layout splits a group, only the fields of the group placed in the section go with it.
|
|
122
|
-
|
|
123
|
-
## Loops
|
|
124
|
-
|
|
125
|
-
A condition can't depend on itself. These are all `cycle` issues, which name the loop:
|
|
126
|
-
|
|
127
|
-
- `a` hides with `b`, and `b` with `a`;
|
|
128
|
-
- a field that reads itself;
|
|
129
|
-
- a group that reads a field inside it, like "show `company` when `company.name` is filled": it would have two answers;
|
|
130
|
-
- a step or a section that reads a field placed in it.
|
|
131
|
-
|
|
132
|
-
The fields of a list's items are checked once, by their `*` paths.
|
|
133
|
-
|
|
134
|
-
## Checks
|
|
135
|
-
|
|
136
|
-
`validate()` checks every `affects` along with the rest of the definition:
|
|
137
|
-
|
|
138
|
-
| Problem | Code |
|
|
139
|
-
| ------------------------------------------------------------------------------------------------------------- | --------------- |
|
|
140
|
-
| `affects` isn't a list, an affect isn't an object, an unknown `effect`, no `when`, an effect twice | `invalid_shape` |
|
|
141
|
-
| A test with no operator, or with two; `all` or `any` with `field`; an empty `all`, `any` or `in`; a bad value | `invalid_shape` |
|
|
142
|
-
| `affects` on the `of` field of a list | `invalid_shape` |
|
|
143
|
-
| A key no affect or condition takes, like a misspelled operator `equal` | `unknown_key` |
|
|
144
|
-
| A path to no field with a value: a typo, a display element, an index, an item's field from outside its item | `unknown_field` |
|
|
145
|
-
| An operator the field's kind doesn't take, like `equals` on a file | `kind_mismatch` |
|
|
146
|
-
| A value the field can't hold, or an empty one | `invalid_value` |
|
|
147
|
-
| Conditions that depend on each other | `cycle` |
|
|
148
|
-
|
|
149
|
-
Each issue has its full path, such as `['fields', 3, 'affects', 0, 'when', 'all', 1, 'field']`. A field a condition reads that has issues of its own adds none.
|
|
150
|
-
|
|
151
|
-
## Rendering
|
|
152
|
-
|
|
153
|
-
The schema skips hidden fields and gives their output either way, so validation and submitting need nothing more. To render, `builder.getVisibility(json)` tells what to hide, in any framework:
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
const visibility = builder.getVisibility(json)
|
|
157
|
-
const state = visibility.of(getInput(form)) // the same object until what is hidden changes
|
|
158
|
-
|
|
159
|
-
state.isHidden(fieldNode, indexes) // a field of getFieldTree or getLayout
|
|
160
|
-
state.isStepHidden(step)
|
|
161
|
-
state.shownNodes(nodes, indexes) // the layout nodes to render
|
|
162
|
-
for (const { path, input } of state.toClear) setInput(form, { path, input }) // in an effect
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
The Vue and React adapters do all of this. [Formisch in each framework](https://github.com/whatsaaaa/form-builder/blob/main/packages/schema/docs/frameworks.md) has a renderer for Preact, Solid, Svelte, Qwik and Angular that does it too. Without the clearing effect, the output is still right, but a cleared field shows its old value when it comes back.
|
|
166
|
-
|
|
167
|
-
## JSON Schema
|
|
168
|
-
|
|
169
|
-
The package ships `definition.schema.json`, the JSON Schema of definitions for a builder with `affects()`: every key of the schema package's own, with `affects` on fields, steps and sections. Point a definition at it with `$schema`, and editors suggest effects and operators:
|
|
170
|
-
|
|
171
|
-
```json
|
|
172
|
-
{
|
|
173
|
-
"$schema": "./node_modules/@whatsaaaa/form-builder-affects/definition.schema.json",
|
|
174
|
-
"version": "1",
|
|
175
|
-
"fields": []
|
|
176
|
-
}
|
|
177
|
-
```
|
|
67
|
+
## Documentation
|
|
178
68
|
|
|
179
|
-
|
|
69
|
+
The [guide](https://github.com/whatsaaaa/form-builder/blob/main/packages/affects/docs/guide.md) covers effects, conditions and their operators, what a hidden field does, groups, sections and steps, loops, checks, rendering and TypeScript.
|
|
180
70
|
|
|
181
|
-
##
|
|
71
|
+
## Related packages
|
|
182
72
|
|
|
183
|
-
`builder
|
|
73
|
+
- [`@whatsaaaa/form-builder-schema`](https://github.com/whatsaaaa/form-builder/tree/main/packages/schema): build Formisch-ready Valibot schemas from JSON definitions
|
|
74
|
+
- [`@whatsaaaa/form-builder-vue`](https://github.com/whatsaaaa/form-builder/tree/main/frameworks/vue): render definitions with your own Vue components
|
|
75
|
+
- [`@whatsaaaa/form-builder-react`](https://github.com/whatsaaaa/form-builder/tree/main/frameworks/react): render definitions with your own React components
|
|
184
76
|
|
|
185
77
|
## License
|
|
186
78
|
|
|
187
|
-
MIT
|
|
79
|
+
[MIT](https://github.com/whatsaaaa/form-builder/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatsaaaa/form-builder-affects",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Show, hide and clear the fields, sections and steps of JSON form definitions by conditions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conditional",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@size-limit/preset-small-lib": "^14.3.0",
|
|
40
40
|
"@valibot/to-json-schema": "^1.8.0",
|
|
41
41
|
"@vitest/coverage-v8": "^5.0.5",
|
|
42
|
-
"@whatsaaaa/form-builder-schema": "^0.15.
|
|
42
|
+
"@whatsaaaa/form-builder-schema": "^0.15.1",
|
|
43
43
|
"ajv": "^8.18.0",
|
|
44
44
|
"jiti": "^2.7.0",
|
|
45
45
|
"publint": "^0.3.25",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"vitest": "^5.0.5"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@whatsaaaa/form-builder-schema": "^0.15.
|
|
53
|
+
"@whatsaaaa/form-builder-schema": "^0.15.1",
|
|
54
54
|
"valibot": "^1.3.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|