react-luna-form 0.0.88 → 0.0.90

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.
@@ -54,7 +54,7 @@ The `advanced` property dictates finer HTML details, interactive structures, and
54
54
 
55
55
  These basic field types support extra manipulation properties inside the `advanced` block:
56
56
 
57
- - **`length`** _({ min?: number, max?: number })_: Applies HTML structural limits (`minlength` / `maxlength`, or `min` / `max` depending on the input type). On an `input/date` the bounds are days instead; see [The first and the last day](#the-first-and-the-last-day).
57
+ - **`length`** _({ min?: number, max?: number })_: Applies HTML structural limits (`minlength` / `maxlength`, or `min` / `max` depending on the input type). Each bound is a number, or a `$ref` the host resolves to one; see [A bound that is no number](#a-bound-that-is-no-number). On an `input/date` the bounds are days instead; see [The first and the last day](#the-first-and-the-last-day).
58
58
  - **`step`** _(number)_ (`input/number` only): A number is whole unless it declares a step, which is the browser's own default of 1. The step is rendered on the input, so its arrows move by it, and validation accepts only values on it, counted from `length.min` when there is one, as the browser does: `0.01` for a price, `0.5` for halves, `0.001` for three decimals. A step has to be a number above 0. The browser ignores 0 and below, and so does the form; text such as `"any"` is no step either, and it is never rendered. Either way the number stays whole.
59
59
  - **`transform`** _(string | string[])_: Safely intercepts user inputs and manipulates content dynamically. Options include:
60
60
  - `"lowercase"`
@@ -66,6 +66,22 @@ These basic field types support extra manipulation properties inside the `advanc
66
66
 
67
67
  A required `input/number` accepts `0` and negative numbers: required means a value is present, not that it is at least 1. An optional one left empty is not submitted at all, rather than submitted as `0`, and its `length` bounds only apply to a value that is there. `select/year` and `select/month` read an empty selection the same way.
68
68
 
69
+ ### Empty and required text
70
+
71
+ A text left empty is no value for its `length` bounds to check, as the browser's `minlength` does not apply to an empty value either. An optional field left empty, or holding only spaces, passes as `""`, whatever its `length.min`. A required one left empty asks for a value with `validation.required`, not with `validation.length.min`, and its bounds only apply once there is text. This holds for `input/text`, `input/tel`, `input/password`, a [`textarea`](#textarea), and a type the form does not know, which it checks as text.
72
+
73
+ ### A bound that is no number
74
+
75
+ On the types above, on a [`textarea`](#textarea), and on a type the form does not know, which it checks as text, a `length` bound is a number. A day such as `"2026-10-05"` is not one, nor is text that reads as one, such as `"3"`, nor a `$ref` the context does not hold or holds as `undefined`. Such a bound is a rule the form cannot read, so the field takes no value: anything in it is held back with `This value cannot be checked`, whatever else the field would have checked, such as the address of an `input/email` or the `step` of a number. An optional field left empty still passes, and a required one still asks for a value.
76
+
77
+ That is deliberate, as it is for [a date](#the-first-and-the-last-day): a server that validates with `buildFormSchema` and forgets its `context` would otherwise let through what the bound was there to keep out, with nothing in production to say so. The input is not handed such a bound, and a development build names it in the console, in the browser and on the server alike, once for each field:
78
+
79
+ ```text
80
+ [Luna Form] when: advanced.length.min is "2026-10-05", which is no number, so the field takes no value
81
+ ```
82
+
83
+ A minimum above the maximum, which no value passes, is named the same way.
84
+
69
85
  ### Temporal Options (`input/date`, `input/time`)
70
86
 
71
87
  Date and time inputs omit transformations and instead expose a `format` property directly under `advanced` for displaying values properly:
@@ -251,7 +267,7 @@ The `validation` object resolves form errors overriding generic defaults, mappin
251
267
  - **`required`** _(string)_: Specifies the error message exposed when the element is marked exactly as `required: true` and the field is empty.
252
268
  - **`email`** _(string)_: Error message specifically asserting an invalid email format.
253
269
  - **`date`** _(string)_: `input/date` only. The message shown when the field holds text that is no day. Without it, the message is `Invalid date`.
254
- - **`length`** _({ min?: string, max?: string })_: Specific string messages shown when a value breaches `advanced.length`: a text too short or too long, a number out of range, a day before the first or after the last.
270
+ - **`length`** _({ min?: string, max?: string })_: Specific string messages shown when a value breaches `advanced.length`: a text too short or too long, the address of an `input/email` included, a number out of range, a day before the first or after the last. An address that is no address and is out of its bounds shows both messages, the `email` one first. An optional `input/email` left empty still passes.
255
271
  - **`reserved`** _(string)_: `input/date` only. The message shown for a day its `advanced.reserved` lists. Without it, the message is `This date is not available`.
256
272
  - **`range`** _(string)_: `input/date` ranges only. The message shown for a range with an end missing, or whose last day comes before its first. Without it, the message is `Invalid date range`.
257
273
  - **`step`** _(string)_: The message shown when an `input/number` is off its step: a decimal on a number that declares no step, or a value off the `advanced.step` it declares. The form's dictionary translates it, as it does every other message.
@@ -325,7 +341,10 @@ Everything else in `advanced` is ignored for this type.
325
341
  ```
326
342
 
327
343
  `length.min` and `length.max` are emitted as the `minLength` and `maxLength`
328
- attributes on the rendered element.
344
+ attributes on the rendered element, and validation checks the same bounds on
345
+ text that is there: see [Empty and required text](#empty-and-required-text). A
346
+ bound that is no number is neither: see
347
+ [A bound that is no number](#a-bound-that-is-no-number).
329
348
 
330
349
  Like the `input/*` family, a declared `change` event on a `textarea` is
331
350
  debounced: it fires 300 ms after the last keystroke, not on every one.
@@ -25,8 +25,11 @@ The `advanced` property customizes structural interaction rules and layout rende
25
25
  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).
26
26
  - **`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).
27
27
  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.
28
+ Each bound is a number, or a `$ref` the host resolves to one. A bound that is not, such as a `$ref` the context does not hold, is a rule the form cannot read: the list is held back with `This list cannot be checked`, an empty one included, since a list has no `required` of its own and `length.min` is how it asks for rows. Meanwhile it shows its rows as if that bound were not declared, a `min` of one or no `max` at all, and a development build names the bound in the console, as it does [for a field](input.md#a-bound-that-is-no-number).
28
29
  - **`collapsed`** _(boolean)_: When enabled, list items default to a visually collapsed representation upon mounting or adding.
29
30
  Collapsing only hides a row: its fields keep their values, still validate and are still submitted, and a submit that fails on one of them opens the row so the error can be seen.
31
+ - **`fixed`** _(boolean)_: The list keeps the rows it has. It renders no add button and no remove control, and every row stays editable. Reach for it when the rows are created somewhere else and this form only corrects them.
32
+ It says nothing about how many rows are valid: that is still `length`, read the same way by the schema, and the rows a list opens with are still clamped to `min`. A fixed list that opens with no rows needs `"length": { "min": 0 }`, or it opens with an empty one it cannot remove.
30
33
  - **`keepValue`** _(boolean)_: If `true`, hiding the list leaves the values behind instead of taking them. A list holds nothing under its own name, so declaring it here is a statement about the rows: it covers every value they hold. See [Keeping a value through a hide](../events/change.md#keeping-a-value-through-a-hide).
31
34
 
32
35
  ### Collapsed Previews (`preview` object)
@@ -42,6 +45,7 @@ Previews can map seamlessly pointing out the desired child `name` simply as stri
42
45
 
43
46
  - **`field`** _(string)_: The child layout field name.
44
47
  - **`label`** _(string)_: Provides contextual prefix naming before mapping the preview value statically.
48
+ - **`scope`** _(`"parent"`)_: Reads `field` from the row that holds this list rather than from the row being previewed, one level up. A nested list's rows can then name the outer row they belong to, such as the section a question sits in. In a top-level list there is no row above, and the entry is left out of the preview.
45
49
  - **`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.
46
50
 
47
51
  ---
package/docs/setup.md CHANGED
@@ -16,8 +16,8 @@ dependency, so your application resolves one copy of React, Zod and the rest.
16
16
  | Peer | Version | Used for |
17
17
  | -------------------- | --------- | ------------------------------------------- |
18
18
  | `react`, `react-dom` | `^19.0.0` | rendering, and `useActionState` on submit |
19
- | `zod` | `^4.0.0` | the schema derived from `sections` |
20
- | `jotai` | `^2.0.0` | the value, error and source stores |
19
+ | `zod` | `^4.0.6` | the schema derived from `sections` |
20
+ | `jotai` | `^3.0.0` | the value, error and source stores |
21
21
  | `swr` | `^2.0.0` | fetching a field's remote `source` |
22
22
  | `date-fns` | `^4.0.0` | date and time formatting, `duration` filter |
23
23
  | `fast-equals` | `^6.0.0` | change detection in the stores |
@@ -29,6 +29,10 @@ them in for you; anywhere else, install them alongside the library. A missing
29
29
  peer surfaces as a module resolution error at import time, not as a form that
30
30
  renders badly.
31
31
 
32
+ Zod has to be 4.0.6 or later. An earlier 4.0 reports a value that an optional
33
+ text or email field holds back as `Invalid input`, instead of with the field's
34
+ own message.
35
+
32
36
  ## Styling
33
37
 
34
38
  The components render Tailwind utility classes and **the build ships no CSS**.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-luna-form",
3
- "version": "0.0.88",
3
+ "version": "0.0.90",
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",
@@ -21,15 +21,15 @@
21
21
  "peerDependencies": {
22
22
  "date-fns": "^4.0.0",
23
23
  "fast-equals": "^6.0.0",
24
- "jotai": "^2.0.0",
24
+ "jotai": "^3.0.0",
25
25
  "react": "^19.0.0",
26
26
  "react-dom": "^19.0.0",
27
27
  "swr": "^2.0.0",
28
28
  "tailwind-merge": "^3.7.0",
29
- "zod": "^4.0.0"
29
+ "zod": "^4.0.6"
30
30
  },
31
31
  "devDependencies": {
32
- "@luna-form/core": "0.0.88",
32
+ "@luna-form/core": "0.0.90",
33
33
  "@types/react": "19.3.0",
34
34
  "@types/react-dom": "19.3.0",
35
35
  "eslint-plugin-react": "7.37.5",