react-luna-form 0.0.83 → 0.0.84
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 +140 -0
- package/dist/client/cjs/index.js +1 -1
- package/dist/client/esm/index.js +1 -1
- package/dist/server/cjs/index.js +1 -1
- package/dist/server/esm/index.js +1 -1
- package/dist/types/luna-core/util/build.d.ts +1 -1
- package/dist/types/luna-core/util/schema.d.ts +4 -4
- package/dist/types/luna-core/util/style.d.ts +1 -5
- package/docs/events/change.md +45 -2
- package/docs/fields/data-source.md +113 -0
- package/docs/fields/input.md +1 -1
- package/docs/fields/list.md +5 -2
- package/docs/fields/select.md +49 -1
- package/docs/forms/submit.md +129 -0
- package/docs/index.md +9 -3
- package/docs/interpolation/format-filters.md +4 -0
- package/docs/interpolation/overview.md +179 -0
- package/docs/setup.md +65 -14
- package/docs/structure/definition.md +99 -0
- package/docs/structure/layout.md +189 -0
- package/docs/structure/sections.md +187 -0
- package/docs/validation/overview.md +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Sections
|
|
2
|
+
|
|
3
|
+
`sections` is the top level of a form. Every other page describes what goes
|
|
4
|
+
inside one; this page describes the section itself.
|
|
5
|
+
|
|
6
|
+
A section is a **visual grouping and nothing else**. It draws a legend, sets the
|
|
7
|
+
spacing around its fields and can collapse them out of the way, but it does not
|
|
8
|
+
scope names: every field in the form shares one flat namespace, whichever
|
|
9
|
+
section it was declared in. Two fields called `email` in two different sections
|
|
10
|
+
are one field with two renderings, and
|
|
11
|
+
[`buildFormSchema`](../validation/overview.md#headless-validation) produces one
|
|
12
|
+
key for them.
|
|
13
|
+
|
|
14
|
+
## The shape
|
|
15
|
+
|
|
16
|
+
| Key | Type | Does |
|
|
17
|
+
| ---------------------- | ------------------ | --------------------------------------------- |
|
|
18
|
+
| `fields` | `Fields` | what the section renders |
|
|
19
|
+
| `title` | `string` | the legend |
|
|
20
|
+
| `description` | `string` | help text under the legend |
|
|
21
|
+
| `id` | `string \| number` | the `id` attribute of the rendered `fieldset` |
|
|
22
|
+
| `order` | `number` | position among the other sections |
|
|
23
|
+
| `advanced.compact` | `boolean` | tighter spacing between this section's fields |
|
|
24
|
+
| `advanced.collapsible` | `boolean` | render the section closed, behind a toggle |
|
|
25
|
+
| `advanced.separator` | `boolean` | draw a rule after the section |
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"sections": [
|
|
30
|
+
{
|
|
31
|
+
"id": "payment",
|
|
32
|
+
"title": "Payment Method",
|
|
33
|
+
"description": "All transactions are secure and encrypted",
|
|
34
|
+
"advanced": { "separator": true },
|
|
35
|
+
"fields": [{ "name": "name", "type": "input/text", "label": "Name" }]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## A section with no title and no description draws nothing
|
|
42
|
+
|
|
43
|
+
There is no empty legend and no wrapper to style around it: the section's fields
|
|
44
|
+
join the flow of the form as if they had been declared without one. This is the
|
|
45
|
+
form to reach for when you want the grouping only for `compact` spacing or for
|
|
46
|
+
`order`.
|
|
47
|
+
|
|
48
|
+
Declaring `advanced.collapsible` on such a section does nothing either — with no
|
|
49
|
+
title there is nothing to click, so the fields render open.
|
|
50
|
+
|
|
51
|
+
## Title and description are translated, not interpolated
|
|
52
|
+
|
|
53
|
+
Both are resolved against the [`translations`](../fields/list.md#localization)
|
|
54
|
+
dictionary and both render `[text](url)` markdown links. Neither is passed
|
|
55
|
+
through interpolation: a `{context.name}` in a section title stays on screen as
|
|
56
|
+
the literal text `{context.name}`.
|
|
57
|
+
|
|
58
|
+
This is the one place the rule differs. A field's `label` and `description`
|
|
59
|
+
_are_ interpolated, and so is a [column's description](layout.md#description).
|
|
60
|
+
If a section heading has to carry a value, put the value in a field description
|
|
61
|
+
or in a column instead.
|
|
62
|
+
|
|
63
|
+
## `advanced.compact`
|
|
64
|
+
|
|
65
|
+
Spacing between the fields of this section, tight instead of loose. It is
|
|
66
|
+
declared per section:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"title": "Connection",
|
|
71
|
+
"advanced": { "compact": true },
|
|
72
|
+
"fields": [{ "name": "host", "type": "input/text" }]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A section that declares nothing inherits `style.compact` from
|
|
77
|
+
[`defineConfig`](../setup.md#style-alert-env), so the form-wide default holds
|
|
78
|
+
and a section only has to speak up to differ from it — including to opt out,
|
|
79
|
+
with `"compact": false` under a compact form.
|
|
80
|
+
|
|
81
|
+
## `advanced.separator`
|
|
82
|
+
|
|
83
|
+
Draws a horizontal rule **after** the section, between it and whatever follows.
|
|
84
|
+
On the last section it draws a trailing rule, which is rarely what you want.
|
|
85
|
+
|
|
86
|
+
## `advanced.collapsible`
|
|
87
|
+
|
|
88
|
+
Renders the section closed, with its title as the toggle:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"title": "Advanced options",
|
|
93
|
+
"advanced": { "collapsible": true },
|
|
94
|
+
"fields": [{ "name": "timeout", "type": "input/number" }]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Three things to know before using it:
|
|
99
|
+
|
|
100
|
+
- **It always starts closed.** There is no key that opens it, and reopening the
|
|
101
|
+
form does not remember that the user had expanded it.
|
|
102
|
+
- **The fields inside stay mounted.** Collapsing hides them; it does not unmount
|
|
103
|
+
them. They keep their values, they still validate, and they are still
|
|
104
|
+
submitted. That is the opposite of what a [`state` action](../events/change.md#what-clearing-a-hidden-target-means)
|
|
105
|
+
does when it hides a field, which unmounts it and clears the value.
|
|
106
|
+
- **The description moves inside.** It renders with the fields rather than under
|
|
107
|
+
the title, so it is not visible while the section is closed.
|
|
108
|
+
|
|
109
|
+
A collapsible section also renders no step number, whatever `advanced.step` says.
|
|
110
|
+
|
|
111
|
+
## `advanced.step` numbers the sections
|
|
112
|
+
|
|
113
|
+
Declared on the **form**, not on a section, it puts a numbered badge beside each
|
|
114
|
+
section's legend, counting the sections in the order they render:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Form advanced={{ step: true }} sections={sections} config={config} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It is a numbering, not a wizard: every section is on screen at once, and the
|
|
121
|
+
library ships nothing that shows them one at a time. The badge appears only on
|
|
122
|
+
sections that draw a legend — a section with no title and no description has
|
|
123
|
+
nowhere to put it, and a collapsible one does not take it.
|
|
124
|
+
|
|
125
|
+
## `order`, and what disappears
|
|
126
|
+
|
|
127
|
+
`sections` renders in array order unless a section declares `order`. Sections
|
|
128
|
+
carrying one sort ascending; sections without one keep their relative order and
|
|
129
|
+
follow the rest. The same rule applies to the entries inside a section, so a
|
|
130
|
+
field and a column can be interleaved by `order` — see
|
|
131
|
+
[layout.md](layout.md#ordering-fields-and-columns).
|
|
132
|
+
|
|
133
|
+
Two kinds of section never render at all:
|
|
134
|
+
|
|
135
|
+
- **One with no fields.** `"fields": []` is dropped before rendering, legend
|
|
136
|
+
included.
|
|
137
|
+
- **One whose fields are all hidden.** Hidden by `hidden: true` in the JSON or
|
|
138
|
+
by a [`state` action](../events/change.md#2-state-action-stateevent) at run
|
|
139
|
+
time, it makes no difference: when nothing inside a section is visible the
|
|
140
|
+
section goes with it, and it comes back when one of them is shown again. A
|
|
141
|
+
column counts as hidden when every field in it is.
|
|
142
|
+
|
|
143
|
+
A section has **no visibility flag of its own**. Writing `"hidden": true` on a
|
|
144
|
+
section is accepted and ignored; hide its fields instead, or leave the section
|
|
145
|
+
out of the array.
|
|
146
|
+
|
|
147
|
+
## `id`
|
|
148
|
+
|
|
149
|
+
Lands on the rendered `<fieldset>` as its `id` attribute, for an anchor link or
|
|
150
|
+
a test selector. It is not a name and nothing resolves against it: fields are
|
|
151
|
+
addressed by their own `name` from anywhere in the form.
|
|
152
|
+
|
|
153
|
+
## Complete example
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"advanced": { "step": true },
|
|
158
|
+
"sections": [
|
|
159
|
+
{
|
|
160
|
+
"id": "contact",
|
|
161
|
+
"title": "Contact",
|
|
162
|
+
"description": "How we reach you about this order",
|
|
163
|
+
"advanced": { "separator": true },
|
|
164
|
+
"fields": [
|
|
165
|
+
{ "name": "email", "type": "input/email", "label": "Email" },
|
|
166
|
+
{ "name": "phone", "type": "input/tel", "label": "Phone" }
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"title": "Delivery",
|
|
171
|
+
"advanced": { "compact": true },
|
|
172
|
+
"fields": [
|
|
173
|
+
{ "name": "street", "type": "input/text", "label": "Street" },
|
|
174
|
+
{ "name": "city", "type": "input/text", "label": "City" }
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"title": "Advanced options",
|
|
179
|
+
"advanced": { "collapsible": true },
|
|
180
|
+
"order": 99,
|
|
181
|
+
"fields": [
|
|
182
|
+
{ "name": "notes", "type": "textarea", "label": "Delivery notes" }
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
```
|
|
@@ -251,7 +251,7 @@ if (!result.success) {
|
|
|
251
251
|
}
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
- **`buildFormSchema(sections, translations?, definition?)`** — walks the form tree (sections → columns → lists → fields) and returns a Zod schema. Lists become arrays of objects, so nested `requiredWhen`/`rules` resolve in **item scope**. `translations` maps messages to localized strings; `definition` resolves `$ref`
|
|
254
|
+
- **`buildFormSchema(sections, translations?, definition?)`** — walks the form tree (sections → columns → lists → fields) and returns a Zod schema. Lists become arrays of objects, so nested `requiredWhen`/`rules` resolve in **item scope**. `translations` maps messages to localized strings; `definition` resolves [`$ref` entries](../structure/definition.md), and has to be the same one the form renders with.
|
|
255
255
|
- **`collectIssues(error)`** — flattens a `ZodError` into `{ path, message }` entries with **dotted paths** (e.g. `rules.0.value`), including nested list issues.
|
|
256
256
|
|
|
257
257
|
### What each path validates
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-luna-form",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
4
4
|
"description": "A React library for building forms based on JSON",
|
|
5
5
|
"main": "./dist/client/cjs/index.js",
|
|
6
6
|
"module": "./dist/client/esm/index.js",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/react": "19.2.18",
|
|
34
|
-
"@types/react-dom": "19.2.
|
|
34
|
+
"@types/react-dom": "19.2.5",
|
|
35
35
|
"eslint-plugin-react": "7.37.5",
|
|
36
36
|
"eslint-plugin-react-hooks": "7.1.1",
|
|
37
|
-
"eslint-plugin-react-refresh": "0.5.
|
|
38
|
-
"@luna-form/core": "0.0.
|
|
37
|
+
"eslint-plugin-react-refresh": "0.5.5",
|
|
38
|
+
"@luna-form/core": "0.0.84"
|
|
39
39
|
},
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"repository": {
|