react-luna-form 0.0.81 → 0.0.83
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/dist/client/cjs/index.js +1 -1
- package/dist/client/esm/index.js +1 -1
- package/dist/config/cjs/index.js +1 -1
- package/dist/config/esm/index.js +1 -1
- package/dist/schema/cjs/index.js +1 -1
- package/dist/schema/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/helper/input.d.ts +1 -9
- package/dist/types/luna-core/type.d.ts +5 -9
- package/dist/types/luna-core/util/attributes.d.ts +0 -1
- package/dist/types/luna-core/util/constant.d.ts +0 -6
- package/dist/types/luna-core/util/extract.d.ts +4 -2
- package/dist/types/luna-core/util/is-type.d.ts +2 -1
- package/dist/types/luna-core/util/schema.d.ts +0 -4
- package/dist/types/luna-react/src/client/component/input-selectable.d.ts +2 -2
- package/dist/types/luna-react/src/client/component/input-strategies.d.ts +19 -4
- package/dist/types/luna-react/src/client/hook/use-input-core.d.ts +3 -3
- package/docs/events/change.md +415 -0
- package/docs/fields/custom-inputs.md +128 -0
- package/docs/fields/input.md +175 -0
- package/docs/fields/list.md +230 -0
- package/docs/fields/select.md +194 -0
- package/docs/fields/specialized-selectors.md +175 -0
- package/docs/index.md +53 -0
- package/docs/interpolation/format-filters.md +128 -0
- package/docs/setup.md +165 -0
- package/docs/validation/overview.md +318 -0
- package/package.json +4 -3
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Input Fields (`input/*`, `textarea`, `checkbox`)
|
|
2
|
+
|
|
3
|
+
The `input/*` field types in Luna Form map to standard HTML `<input>` elements. These fields are used for capturing text, numbers, dates, times, and other generic data values from the user.
|
|
4
|
+
|
|
5
|
+
## Supported Types
|
|
6
|
+
|
|
7
|
+
- `input`: Same as `input/text`. The rendered `type` is taken from the part after the last `/`, and a bare `input` falls back to `text`.
|
|
8
|
+
- `input/text`: Standard text input (`<input type="text">`).
|
|
9
|
+
- `input/email`: Email address input with built-in format validation (`<input type="email">`).
|
|
10
|
+
- `input/password`: Password input with text masked (`<input type="password">`).
|
|
11
|
+
- `input/number`: Numeric input (`<input type="number">`).
|
|
12
|
+
- `input/tel`: Telephone number input (`<input type="tel">`).
|
|
13
|
+
- `input/date`: Date picker component.
|
|
14
|
+
- `input/time`: Time picker component.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Properties
|
|
19
|
+
|
|
20
|
+
Luna Form's input fields inherit the standard logical properties available to all fields:
|
|
21
|
+
|
|
22
|
+
- **`name`** _(string, required)_: The unique identifier for the field in the form output.
|
|
23
|
+
- **`type`** _(string, required)_: Evaluates the type of input to render (e.g., `"input/text"`, `"input/password"`).
|
|
24
|
+
- **`label`** _(string, optional)_: The human-readable label shown to the user.
|
|
25
|
+
- **`defaultValue`** _(any, optional)_: The initial value of the field when the form is mounted and no external value is provided.
|
|
26
|
+
- **`description`** _(string | object, optional)_: Help text or extra context for the form field. Supports **Markdown** (specifically links).
|
|
27
|
+
- Can be a direct string.
|
|
28
|
+
- Or an object payload: `{ title: string, message: string, collapsed?: boolean }`.
|
|
29
|
+
- **`placeholder`** _(string, optional)_: The temporary placeholder text shown when the input is empty.
|
|
30
|
+
- **`required`** _(boolean, optional)_: If `true`, standard browser/HTML5 validation makes the field mandatory.
|
|
31
|
+
- **`disabled`** _(boolean, optional)_: If `true`, the field is non-interactive and blocked from events.
|
|
32
|
+
- **`readonly`** _(boolean, optional)_: If `true`, the field's value is locked and cannot be modified by the user.
|
|
33
|
+
- **`hidden`** _(boolean, optional)_: If `true`, the field is hidden from the user interface.
|
|
34
|
+
- **`order`** _(number, optional)_: A numeric order determining the field's position relative to adjacent fields.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Advanced Configuration (`advanced` object)
|
|
39
|
+
|
|
40
|
+
The `advanced` property dictates finer HTML details, interactive structures, and native HTML attributes.
|
|
41
|
+
|
|
42
|
+
### Common Options
|
|
43
|
+
|
|
44
|
+
- **`autocomplete`** _(string)_: Standard HTML autocomplete attribute (e.g., `"username"`, `"off"`).
|
|
45
|
+
- **`cols`** _(number)_: Layout column span for CSS grid alignment.
|
|
46
|
+
- **`horizontal`** _(boolean)_: Overrides configuration layout to render label and input horizontally.
|
|
47
|
+
- **`reverse`** _(boolean)_: If `true`, reverses the flex direction.
|
|
48
|
+
- **`aria`** _(object)_: Map of specific ARIA attributes for screen readers (e.g., `{ "aria-label": "Custom label" }`).
|
|
49
|
+
- **`data`** _(object)_: Map of specific HTML data attributes (e.g., `{ "data-test-id": "my-field" }`).
|
|
50
|
+
- **`transient`** _(boolean)_: If `true`, the field fires its change events and keeps nothing of its own — it is neither stored nor reported to the consumer. For a control whose whole job is to write to _another_ field. See [Transient controls](../events/change.md#transient-controls).
|
|
51
|
+
|
|
52
|
+
### Text & Numeric Options (`input/text`, `input/email`, `input/tel`, `input/password`, `input/number`)
|
|
53
|
+
|
|
54
|
+
These basic field types support extra manipulation properties inside the `advanced` block:
|
|
55
|
+
|
|
56
|
+
- **`length`** _({ min?: number, max?: number })_: Applies HTML structural limits (`minlength` / `maxlength`, or `min` / `max` depending on the input type).
|
|
57
|
+
- **`transform`** _(string | string[])_: Safely intercepts user inputs and manipulates content dynamically. Options include:
|
|
58
|
+
- `"lowercase"`
|
|
59
|
+
- `"uppercase"`
|
|
60
|
+
- `"remove-space"`
|
|
61
|
+
- `"remove-accent"`
|
|
62
|
+
|
|
63
|
+
### Temporal Options (`input/date`, `input/time`)
|
|
64
|
+
|
|
65
|
+
Date and time inputs omit transformations and instead expose a `format` property directly under `advanced` for displaying values properly:
|
|
66
|
+
|
|
67
|
+
- **Date format** _(used with `input/date`)_: `"yyyy-MM-dd"`, `"MM/dd/yyyy"`, `"dd/MM/yyyy"`, or `"MMMM d, yyyy"`.
|
|
68
|
+
- **Time format** _(used with `input/time`)_: `"HH:mm"`, `"HH:mm:ss"`, `"hh:mm a"`, or `"hh:mm:ss a"`.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Validation (`validation` object)
|
|
73
|
+
|
|
74
|
+
The `validation` object resolves form errors overriding generic defaults, mapping the constraint rule name directly to the specific error message string.
|
|
75
|
+
|
|
76
|
+
- **`required`** _(string)_: Specifies the error message exposed when the element is marked exactly as `required: true` and the field is empty.
|
|
77
|
+
- **`email`** _(string)_: Error message specifically asserting an invalid email format.
|
|
78
|
+
- **`length`** _({ min?: string, max?: string })_: Specific string messages shown when input lengths are breached.
|
|
79
|
+
- **`custom`** _(CustomValidation | CustomValidation[])_: Powerful conditional-based logic blocks. An array specifying:
|
|
80
|
+
- `field`: Optional target reference string.
|
|
81
|
+
- `operator`: Logical evaluation operations (e.g., `eq`, `neq`, `gt`, `lt`).
|
|
82
|
+
- `message`: The resulting validation string.
|
|
83
|
+
- **`requiredWhen`** _(WhenRule | WhenRule[])_: Makes the field required only when a condition over a sibling field holds.
|
|
84
|
+
- **`pattern`** _(PatternRule)_: Validates the value against a regular expression (with optional interpolation bypass).
|
|
85
|
+
- **`rules`** _(AssertRule[])_: A list of gated assertions (`oneOf`, `min`/`max`, `pattern`, …).
|
|
86
|
+
|
|
87
|
+
_See the full [Validation reference](../validation/overview.md) for `requiredWhen`, `pattern`, `rules`, the operator vocabulary, and headless (server-side) validation._
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Events (`event` object)
|
|
92
|
+
|
|
93
|
+
You can attach logic-rich cascading events seamlessly directly inside the field schema.
|
|
94
|
+
|
|
95
|
+
- **`event.change`** _(ChangeEvent[])_: Triggers actions when logic runs. Common actions include emitting `value`, interacting with dependencies (`state`), or triggering data refetches (`source`). _Refer to the [Change Event Documentation](../events/change.md) for robust logic breakdowns._
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Complete Example Reference
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "username",
|
|
104
|
+
"type": "input/text",
|
|
105
|
+
"label": "User Nickname",
|
|
106
|
+
"placeholder": "e.g. john_doe",
|
|
107
|
+
"required": true,
|
|
108
|
+
"description": {
|
|
109
|
+
"title": "Rule",
|
|
110
|
+
"message": "No spaces. No special characters.",
|
|
111
|
+
"collapsed": true
|
|
112
|
+
},
|
|
113
|
+
"advanced": {
|
|
114
|
+
"transform": ["lowercase", "remove-space"],
|
|
115
|
+
"length": {
|
|
116
|
+
"min": 4,
|
|
117
|
+
"max": 20
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"validation": {
|
|
121
|
+
"required": "You must provide a nickname to continue.",
|
|
122
|
+
"length": {
|
|
123
|
+
"min": "Your nickname needs at least 4 letters."
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## `textarea`
|
|
132
|
+
|
|
133
|
+
Multi-line text. It takes the same core properties as an `input/*` field, and
|
|
134
|
+
two of the `advanced` options above apply to it: `length` and `autocomplete`.
|
|
135
|
+
Everything else in `advanced` is ignored for this type.
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"name": "notes",
|
|
140
|
+
"type": "textarea",
|
|
141
|
+
"label": "Notes",
|
|
142
|
+
"advanced": {
|
|
143
|
+
"length": { "max": 500 }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`length.min` and `length.max` are emitted as the `minLength` and `maxLength`
|
|
149
|
+
attributes on the rendered element.
|
|
150
|
+
|
|
151
|
+
Like the `input/*` family, a declared `change` event on a `textarea` is
|
|
152
|
+
debounced: it fires 300 ms after the last keystroke, not on every one.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## `checkbox`
|
|
157
|
+
|
|
158
|
+
A single boolean. It is the one field family whose value is not carried by
|
|
159
|
+
`value`: the rendered element receives `checked` instead.
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"name": "accepted",
|
|
164
|
+
"type": "checkbox",
|
|
165
|
+
"label": "I accept the terms",
|
|
166
|
+
"required": true
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
A checkbox reads its state by truthiness, not by presence. `defaultValue: true`
|
|
171
|
+
starts it checked; `false`, an omitted value, `null` and `0` all start it clear.
|
|
172
|
+
A value passed to the form is read the same way, and both render paths agree.
|
|
173
|
+
|
|
174
|
+
For a group of options rather than a single flag, use `chips` or `radio` — see
|
|
175
|
+
[select.md](select.md).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# List Field (`list`)
|
|
2
|
+
|
|
3
|
+
The `list` field type in Luna Form acts as an iterative array structure, enabling users to add, edit, and remove a repeating set of fields dynamically. It's particularly useful when capturing a collection of identical data objects—like a list of addresses, multiple users, or repeated configurations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Properties
|
|
8
|
+
|
|
9
|
+
- **`name`** _(string, required)_: The unique identifier. The resulting form state binds to an array containing objects referencing the child elements.
|
|
10
|
+
- **`type`** _(string, required)_: Evaluates the type definition exactly to `"list"`.
|
|
11
|
+
- **`label`** _(string, optional)_: The human-readable label (typically rendered as a fieldset legend).
|
|
12
|
+
- **`description`** _(string | object, optional)_: Help text or extra context shown globally just under the legend. Supports **Markdown** (specifically links).
|
|
13
|
+
- **`fields`** _(Array<Field | Column | List>, required)_: The nested structure defining what underlying standard HTML inputs are repeated for each iteration in the array. A `list` among them is a list nested inside this one: each row gets its own, named by its path (`groups.0.checks`).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Advanced Configuration (`advanced` object)
|
|
18
|
+
|
|
19
|
+
The `advanced` property customizes structural interaction rules and layout rendering for the collection container.
|
|
20
|
+
|
|
21
|
+
- **`action`** _(string)_: A custom label applied strictly to the generic "Add item" button rendering at the end of the collection (e.g. `"Add email address"`).
|
|
22
|
+
A custom label has to be translated by the form; the built-in `Add item` default is already translated by the library — see [Localization](#localization).
|
|
23
|
+
- **`length`** _({ min?: number, max?: number })_: Configures restrictions on how many items can be generated. Enforces boundary conditions where minimum instances ensure permanent default items and maximum instances automatically disable insertion toggles. The matching error messages are declared under the list's `validation.length` — see the [Validation reference](../validation/overview.md#list-length).
|
|
24
|
+
Both bounds apply to the rows a list opens with, not only to the ones added by hand: an initial `value` shorter than `min` leaves the remainder standing and empty, and one longer than `max` stops at `max`. A [`value` event](../events/change.md#targeting-a-list) assigning rows is clamped the same way.
|
|
25
|
+
- **`collapsed`** _(boolean)_: When enabled, list items default to a visually collapsed representation upon mounting or adding.
|
|
26
|
+
|
|
27
|
+
### Collapsed Previews (`preview` object)
|
|
28
|
+
|
|
29
|
+
If list items render into structural wrappers (e.g., when they contain multiple fields and become automatically collapsible), the **`preview`** object handles mapping internal field values outward toward the unexpanded accordion headers directly.
|
|
30
|
+
|
|
31
|
+
- **`label`** _(string | PreviewItem)_: Targets a primary field to replace the list element's fallback title string.
|
|
32
|
+
- **`badge`** _(string | PreviewItem)_: Extracts a selected field mapping dynamically as an isolated badge indicator beside the label.
|
|
33
|
+
- **`tags`** _(PreviewItem[])_: Defines an array pointing to additional fields inside the iteration mapping, injected strictly below the label row simulating tag clouds.
|
|
34
|
+
|
|
35
|
+
**`PreviewItem` Definition:**
|
|
36
|
+
Previews can map seamlessly pointing out the desired child `name` simply as strings format, or strictly defined mapping condition rules optionally:
|
|
37
|
+
|
|
38
|
+
- **`field`** _(string)_: The child layout field name.
|
|
39
|
+
- **`label`** _(string)_: Provides contextual prefix naming before mapping the preview value statically.
|
|
40
|
+
- **`when`** _(string | string[] | Condition)_: Standard complex logical operators dictating runtime evaluation rules. When an **array** is provided, it performs an **OR match** against the field's current value.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Filling a list from another field
|
|
45
|
+
|
|
46
|
+
A `list` can be the target of a `value` change event. Aim one at the list's own name and give it an array of objects, and the list becomes exactly those rows — one object per row, each key matching a field name inside it.
|
|
47
|
+
|
|
48
|
+
It assigns rather than adds: the rows you send are the rows that remain, `advanced.length` still bounds the result, and rows that survive are matched by position so they are not remounted.
|
|
49
|
+
|
|
50
|
+
A row's key can name a nested `list` instead of a field, in which case its value is that list's rows and the whole tree is assigned in one event. The full rules and worked examples are in [Targeting a list](../events/change.md#targeting-a-list) and [Rows that hold rows](../events/change.md#rows-that-hold-rows).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Interaction Targeting in Events
|
|
55
|
+
|
|
56
|
+
When interacting internally inside specific items belonging directly to iterators (`list`), Target Actions (like configuring cross dependencies over Change events) inherit relative pathing resolution logically overriding direct top-level values leveraging `"list_name/field_name"` resolution strings.
|
|
57
|
+
|
|
58
|
+
For example, if you want a field inside the list to update another field **in the same iteration/row**, you must prefix the target field's name with the list's name:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"name": "users",
|
|
63
|
+
"type": "list",
|
|
64
|
+
"fields": [
|
|
65
|
+
{
|
|
66
|
+
"name": "role",
|
|
67
|
+
"type": "select",
|
|
68
|
+
"options": [
|
|
69
|
+
{ "value": "admin", "label": "Admin" },
|
|
70
|
+
{ "value": "user", "label": "User" }
|
|
71
|
+
],
|
|
72
|
+
"event": {
|
|
73
|
+
"change": [
|
|
74
|
+
{
|
|
75
|
+
"action": "value",
|
|
76
|
+
"value": {
|
|
77
|
+
"users/access_level": "full_access"
|
|
78
|
+
},
|
|
79
|
+
"when": "admin"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "access_level",
|
|
86
|
+
"type": "input/text"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
See [Change Events](../events/change.md#target-resolution-in-lists) structure definitions closely outlining array value mapping techniques cleanly.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Localization
|
|
97
|
+
|
|
98
|
+
Every piece of copy a list renders goes through the `translations` dictionary, keyed by its English source text. The keys split into two groups by who owns the text.
|
|
99
|
+
|
|
100
|
+
**Declared by the form**, so only the form can translate them:
|
|
101
|
+
|
|
102
|
+
- `label` and `description`, rendered as the fieldset legend and its help text.
|
|
103
|
+
- `advanced.action`, the custom add-button label.
|
|
104
|
+
- `advanced.title`, which supplies the caption of each item (`Contact 1`, `Contact 2`, …) and feeds the accessible names of its controls.
|
|
105
|
+
|
|
106
|
+
**Owned by the library**, so `lang` alone is enough — see [built-in translations](#built-in-translations):
|
|
107
|
+
|
|
108
|
+
- `Add item`, the add-button label when no `action` is declared.
|
|
109
|
+
- `Expand {label} {index}`, `Collapse {label} {index}` and `Remove {label} item {index}`, the accessible names of the item controls.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"lang": "es",
|
|
114
|
+
"translations": {
|
|
115
|
+
"es": {
|
|
116
|
+
"contact_label": "Contacto"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"sections": [
|
|
120
|
+
{
|
|
121
|
+
"fields": [
|
|
122
|
+
{
|
|
123
|
+
"label": "contact_label",
|
|
124
|
+
"name": "contacts",
|
|
125
|
+
"type": "list",
|
|
126
|
+
"fields": [
|
|
127
|
+
{ "name": "name", "label": "Nombre", "type": "input/text" },
|
|
128
|
+
{ "name": "email", "label": "Correo", "type": "input/email" }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The list renders as `Contacto`, its add button as `Añadir elemento`, and its controls announce `Contraer Contacto 1` and `Eliminar Contacto 1` — the form only had to name its own label.
|
|
138
|
+
|
|
139
|
+
### Built-in translations
|
|
140
|
+
|
|
141
|
+
The library ships translations for the copy it renders on its own behalf, because a form cannot name those strings in its schema. Today that covers **Spanish**; any other `lang` falls back to the English source text.
|
|
142
|
+
|
|
143
|
+
Beyond the list keys above, the built-in set also covers the `(Optional)` label suffix, the `Yes`/`No` options of [`select/active`](specialized-selectors.md#5-activebinary-selector-selectactive), and the summary the form shows above itself when a submit fails: `There were validation errors submitting the form.`, `Please correct the errors and try again.`, and `An unexpected error occurred submitting the form.` (plus `Unknown error` for a thrown action carrying no message).
|
|
144
|
+
|
|
145
|
+
The built-ins are layered _under_ the form's own block, so declaring a key still overrides them:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"lang": "es",
|
|
150
|
+
"translations": { "es": { "Add item": "Agregar contacto" } }
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Regional tags resolve to their base language, so `es-MX` and `es-419` both get the Spanish defaults. A form's own entries keep matching on the exact tag, unchanged.
|
|
155
|
+
|
|
156
|
+
### Accessible name templates
|
|
157
|
+
|
|
158
|
+
The accessible names are single keys carrying `{label}` and `{index}` placeholders rather than separate words, so a translation can reorder the parts — or drop one. The Spanish default for `Remove {label} item {index}` is `Eliminar {label} {index}`, without the English `item` filler:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"translations": {
|
|
163
|
+
"fr": { "Remove {label} item {index}": "Supprimer {label} {index}" }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Left untranslated, the template fills in to the same English text it replaced.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Complete Example Reference
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"label": "Working Experiences",
|
|
177
|
+
"name": "experiences",
|
|
178
|
+
"type": "list",
|
|
179
|
+
"description": "Please detail your previous relevant jobs.",
|
|
180
|
+
"advanced": {
|
|
181
|
+
"action": "Add Experience Record",
|
|
182
|
+
"collapsed": true,
|
|
183
|
+
"length": {
|
|
184
|
+
"min": 1,
|
|
185
|
+
"max": 3
|
|
186
|
+
},
|
|
187
|
+
"preview": {
|
|
188
|
+
"label": "company",
|
|
189
|
+
"badge": "status",
|
|
190
|
+
"tags": [
|
|
191
|
+
"title",
|
|
192
|
+
{
|
|
193
|
+
"field": "internal_review",
|
|
194
|
+
"label": "Checked",
|
|
195
|
+
"when": {
|
|
196
|
+
"operator": "eq",
|
|
197
|
+
"value": true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"fields": [
|
|
204
|
+
{
|
|
205
|
+
"name": "company",
|
|
206
|
+
"label": "Company Name",
|
|
207
|
+
"type": "input/text",
|
|
208
|
+
"required": true
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "title",
|
|
212
|
+
"label": "Job Title",
|
|
213
|
+
"type": "input/text"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "status",
|
|
217
|
+
"type": "select",
|
|
218
|
+
"options": [
|
|
219
|
+
{ "value": "active", "label": "Currently Employed" },
|
|
220
|
+
{ "value": "past", "label": "Past Job" }
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "internal_review",
|
|
225
|
+
"type": "checkbox",
|
|
226
|
+
"hidden": true
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Select Fields (`select`, `radio`, `chips`)
|
|
2
|
+
|
|
3
|
+
Luna Form provides several field types for selecting one or more values from a set of options. These fields share a common way of managing their data source.
|
|
4
|
+
|
|
5
|
+
## Supported Types
|
|
6
|
+
|
|
7
|
+
- `select`: A standard dropdown menu (`<select>`).
|
|
8
|
+
- `radio`: A group of radio buttons where only one can be selected.
|
|
9
|
+
- `chips`: Interactive buttons (tags) for selecting values. Supports single or multiple selection.
|
|
10
|
+
|
|
11
|
+
> **Tip:** Luna Form also includes **[Specialized Selectors](specialized-selectors.md)** with built-in sources for months, years, days, timezones, and more.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The `source` Property
|
|
16
|
+
|
|
17
|
+
Unlike input fields, selectors require a list of options. This is managed via the `source` property.
|
|
18
|
+
|
|
19
|
+
### 1. Static Options
|
|
20
|
+
|
|
21
|
+
You can provide an array of objects directly in the schema.
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
"source": [
|
|
25
|
+
{ "label": "Apple", "value": "apple" },
|
|
26
|
+
{ "label": "Banana", "value": "banana" },
|
|
27
|
+
{ "label": "Orange", "value": "orange", "description": "High in Vitamin C" }
|
|
28
|
+
]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Each option consists of:
|
|
32
|
+
|
|
33
|
+
- **`label`** _(string)_: The text displayed to the user.
|
|
34
|
+
- **`value`** _(string | number | boolean)_: The technical value submitted in the form.
|
|
35
|
+
- **`description`** _(string, optional)_: Additional help text for the specific option.
|
|
36
|
+
|
|
37
|
+
### 2. Remote Data Source
|
|
38
|
+
|
|
39
|
+
You can fetch options from a remote API by providing a `DataSource` configuration.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
"source": {
|
|
43
|
+
"url": "/api/v1/categories",
|
|
44
|
+
"method": "GET"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If your API returns objects with different keys, you can map them using the `advanced.options` property. This is useful when your data doesn't match the default `{ label, value }` structure.
|
|
49
|
+
|
|
50
|
+
### Example: Mapping a User Object
|
|
51
|
+
|
|
52
|
+
Suppose your source returns an array of user objects with properties like `id`, `name`, `lastname`, and `age`:
|
|
53
|
+
`[{ "id": 101, "name": "John", "lastname": "Doe", "age": 25 }]`
|
|
54
|
+
|
|
55
|
+
You can use the `options` mapping to decide which properties are used for the selector's value, label, and (optional) description:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"name": "user_selection",
|
|
60
|
+
"type": "select",
|
|
61
|
+
"source": { "url": "/api/users" },
|
|
62
|
+
"advanced": {
|
|
63
|
+
"options": {
|
|
64
|
+
"value": "id",
|
|
65
|
+
"label": "name",
|
|
66
|
+
"description": "age"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Property Mapping Reference
|
|
73
|
+
|
|
74
|
+
Inside `advanced.options`, you can map:
|
|
75
|
+
|
|
76
|
+
- **`label`**: The key to use for the display text (e.g., `"name"`).
|
|
77
|
+
- **`value`**: The key to use for the technical value (e.g., `"id"`).
|
|
78
|
+
- **`description`** _(optional)_: The key to use for additional help text (e.g., `"age"`).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Translating Options
|
|
83
|
+
|
|
84
|
+
When the options are declared inline as an array, `label` and `description` are resolved against the form dictionary, exactly like a field's own `label`. Missing keys fall back to the key itself.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"lang": "es",
|
|
89
|
+
"translations": {
|
|
90
|
+
"es": {
|
|
91
|
+
"fruit_apple": "Manzana",
|
|
92
|
+
"fruit_banana": "Plátano"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"sections": [
|
|
96
|
+
{
|
|
97
|
+
"fields": [
|
|
98
|
+
{
|
|
99
|
+
"name": "fruit",
|
|
100
|
+
"type": "select",
|
|
101
|
+
"source": [
|
|
102
|
+
{ "value": "apple", "label": "fruit_apple" },
|
|
103
|
+
{ "value": "banana", "label": "fruit_banana" }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This applies to `select`, `radio` and `chips`, works with `advanced.options` mapping, and reaches the collapsed preview of a list.
|
|
113
|
+
|
|
114
|
+
Three rules bound the behavior:
|
|
115
|
+
|
|
116
|
+
- **`value` is never translated.** It is the technical value submitted with the form, so the payload stays identical in every language.
|
|
117
|
+
- **Only array sources are translated.** A remote `DataSource` returns data, not authored copy, so its labels are rendered as fetched. The same applies when a `source` change event replaces the array with a remote source at runtime.
|
|
118
|
+
- **Only object options are translated.** In a bare string array (`["apple", "banana"]`) the string is both the label and the value, so translating it would change what the form submits.
|
|
119
|
+
|
|
120
|
+
Specialized selectors carry their own built-in labels and are not resolved against the dictionary. See [Specialized Selectors](specialized-selectors.md#localization).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Core Properties
|
|
127
|
+
|
|
128
|
+
- **`name`** _(string, required)_: The unique identifier.
|
|
129
|
+
- **`type`** _(string, required)_: One of the supported select types.
|
|
130
|
+
- **`label`** _(string, optional)_: Human-readable label.
|
|
131
|
+
- **`defaultValue`** _(any, optional)_: The initial selected value. Matches the `value` of one of the options.
|
|
132
|
+
For `chips`, it must be an **array** — see below.
|
|
133
|
+
- **`required`** _(boolean, optional)_: If `true`, a value must be selected.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Advanced Configuration (`advanced` object)
|
|
138
|
+
|
|
139
|
+
- **`multiple`** _(boolean, optional)_: (Available for `chips`) Allows selecting more than one option. Defaults to `true`.
|
|
140
|
+
- **`preselected`** _(boolean, optional)_: For specialized selectors like `select/month`, determines if the current value (e.g., current month) should be selected by default.
|
|
141
|
+
- **`horizontal`** _(boolean, optional)_: Renders the options or selector horizontally.
|
|
142
|
+
|
|
143
|
+
### A chips value is always an array
|
|
144
|
+
|
|
145
|
+
Whatever `multiple` says, a `chips` field holds an array. The value is coerced
|
|
146
|
+
on the way in: anything that is not an array becomes `[]`.
|
|
147
|
+
|
|
148
|
+
The consequence is a scalar `defaultValue`. This looks reasonable and does
|
|
149
|
+
nothing:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{ "name": "method", "type": "chips", "defaultValue": "GET" }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`"GET"` is not an array, so the field mounts with nothing selected. Nothing
|
|
156
|
+
throws and nothing warns. Write it as an array, single selection or not:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{ "name": "method", "type": "chips", "defaultValue": ["GET"] }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Setting `advanced.multiple` to `false` limits what a user can pick. It does not
|
|
163
|
+
change the shape of the value, so it does not make a scalar default work.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Validation
|
|
168
|
+
|
|
169
|
+
- **`required`** _(string)_: Custom error message when no value is selected.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Complete Example
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"name": "notification_preference",
|
|
178
|
+
"type": "radio",
|
|
179
|
+
"label": "How should we contact you?",
|
|
180
|
+
"defaultValue": "email",
|
|
181
|
+
"source": [
|
|
182
|
+
{ "label": "Email", "value": "email" },
|
|
183
|
+
{ "label": "SMS", "value": "sms" },
|
|
184
|
+
{
|
|
185
|
+
"label": "Push Notification",
|
|
186
|
+
"value": "push",
|
|
187
|
+
"description": "Requires mobile app"
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"validation": {
|
|
191
|
+
"required": "Please select a notification method."
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|