react-luna-form 0.0.95 → 0.0.97

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.
@@ -1,5 +1,7 @@
1
1
  import { type PrimitiveAtom } from 'jotai';
2
2
  export declare function omitKey<T extends Record<string, unknown>>(obj: T, key: string): T;
3
+ export declare function isUnder(key: string, targets: readonly string[]): boolean;
4
+ export declare function omitUnder<T>(record: Record<string, T>, targets: readonly string[]): Record<string, T>;
3
5
  export declare function createEntryAtom<T>(base: PrimitiveAtom<Record<string, T>>, name: string): import("jotai").WritableAtom<NonNullable<T> | undefined, [next: T | undefined], void>;
4
6
  export declare function createContributionAtom<T>(base: PrimitiveAtom<Record<string, Record<string, T>>>, name: string, options: Readonly<{
5
7
  merge: (values: T[]) => T | undefined;
@@ -31,7 +31,7 @@ The `advanced` property customizes structural interaction rules and layout rende
31
31
  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.
32
32
  - **`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.
33
33
  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.
34
- - **`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).
34
+ - **`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, and showing the list again gives the rows back as they were -- a list inside another list's row included. See [Keeping a value through a hide](../events/change.md#keeping-a-value-through-a-hide).
35
35
 
36
36
  ### Collapsed Previews (`preview` object)
37
37
 
@@ -215,6 +215,47 @@ change the shape of the value, so it does not make a scalar default work.
215
215
  ## Validation
216
216
 
217
217
  - **`required`** _(string)_: Custom error message when no value is selected.
218
+ - **`option`** _(string)_: Custom error message when the value is not one of the options the definition writes. See below.
219
+
220
+ ### Options written in the definition
221
+
222
+ When `source` is an array, written inline or through a `$ref` that resolves to one, those options are the only answers the field takes, whether the field is required or not. Any other value is held back with `validation.option` as the message, or `This option is not available` when there is none. The rule is part of the field's schema, so [`buildFormSchema`](../validation/overview.md#headless-validation) and the rendered `<Form>` check it alike. This covers a stored configuration and a request made by hand, such as a value that was removed from the options after it was saved.
223
+
224
+ ```json
225
+ {
226
+ "name": "fruit",
227
+ "type": "select",
228
+ "source": [
229
+ { "label": "Apple", "value": "apple" },
230
+ { "label": "Banana", "value": "banana" }
231
+ ],
232
+ "validation": { "option": "Pick a fruit from the list" }
233
+ }
234
+ ```
235
+
236
+ How the value is read:
237
+
238
+ - **Through the mapping.** With `advanced.options.value`, the option's value is that key, so `{ "id": 101 }` offers `101`.
239
+ - **As text.** `101` and `"101"` are the same answer, as they are when the field matches a selection.
240
+ - **One by one for `chips`.** Every value in the array has to be offered.
241
+ - **Empty is not this rule's question.** An empty value or an empty `chips` array passes here. `required` decides whether the field may be empty.
242
+
243
+ Two kinds of field are not checked, because the definition does not know their options:
244
+
245
+ - A field whose `source` is a remote `DataSource`, or a `$ref` that did not resolve.
246
+ - A field that a [`source` change event](../events/change.md#3-source-action-sourceevent) aims at, even when it also writes an array. The target names one field, as it does for the event: `city` is the `city` at the top level, and `shipping/city` is the `city` of each `shipping` row. Any other field called `city` is still checked.
247
+
248
+ A list declares its options once for every row, so each row is checked against them, however deep the list is.
249
+
250
+ ### A value the control cannot show
251
+
252
+ A select, radio or chips draws only the options it offers, so a value it holds outside them, from the host or from a `value` event, is not on screen. The browser does not submit that value either: a native select behind the control submits its first option instead, an option nobody picked, and a radio submits nothing.
253
+
254
+ The submit of the rendered `<Form>` therefore checks, for these fields, the value the field holds rather than what the browser sends. The field shows `validation.option`, or `This option is not available`, whether it is required or not, and nothing is sent until an option is picked. A field that holds one of its options, or nothing, is sent as the control sends it: a `select` with nothing picked sends `""`, and a `radio` sends nothing.
255
+
256
+ A field a `source` event aims at is sent as it holds its value, since its options are only known at run time.
257
+
258
+ Writing the same values again in a [`rules`](../validation/overview.md#rules) `oneOf` assertion is no longer needed. If both are present, a value that is not offered gets both messages.
218
259
 
219
260
  ---
220
261
 
@@ -123,11 +123,13 @@ A simple binary toggle, typically rendered as "Yes" and "No".
123
123
  }
124
124
  ```
125
125
 
126
- Left empty, it holds no answer: an optional one is left out of what the form
126
+ Left empty, it takes its `defaultValue`, the answer the select shows, so a
127
+ value saved before the field existed reads as that default. With no
128
+ `defaultValue` it holds no answer: an optional one is left out of what the form
127
129
  submits, never sent as `false`, and a required one fails with its
128
130
  `validation.required` message, as a required text does. Only `true`/`false`
129
131
  (or `'true'`/`'false'`) are answers. A `checkbox` differs on purpose: left
130
- unticked, it answers `false`.
132
+ unticked, it answers `false`, whatever its `defaultValue`.
131
133
 
132
134
  ---
133
135
 
@@ -19,6 +19,7 @@ This page documents every key of the `validation` object plus the shared operato
19
19
  - **`email`** _(string)_: Message for an invalid email format (`input/email`).
20
20
  - **`date`** _(string)_: Message when an `input/date` holds text that is no day. See [Input](../fields/input.md#what-a-date-holds).
21
21
  - **`length`** _({ min?: string, max?: string })_: Messages when the value breaches `advanced.length`: its length, a number's range, or a date's first and last day. See [Input](../fields/input.md#the-first-and-the-last-day).
22
+ - **`option`** _(string)_: Message when a `select`, `radio` or `chips` holds a value its written options do not offer, required or not. See [Select](../fields/select.md#options-written-in-the-definition).
22
23
  - **`reserved`** _(string)_: Message when an `input/date` holds a day its `advanced.reserved` lists. See [Input](../fields/input.md#days-nobody-can-pick).
23
24
  - **`range`** _(string)_: Message when an `input/date` range has an end missing, or its last day before its first. See [Input](../fields/input.md#a-range-of-days).
24
25
  - **`step`** _(string)_: Message when an `input/number` is off its step: a decimal on a whole number, or a value off `advanced.step`. See [Input](../fields/input.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-luna-form",
3
- "version": "0.0.95",
3
+ "version": "0.0.97",
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",
@@ -26,10 +26,10 @@
26
26
  "react-dom": "^19.0.0",
27
27
  "swr": "^2.0.0",
28
28
  "tailwind-merge": "^3.0.0",
29
- "zod": "^4.0.0"
29
+ "zod": "^4.0.6"
30
30
  },
31
31
  "devDependencies": {
32
- "@luna-form/core": "0.0.95",
32
+ "@luna-form/core": "0.0.97",
33
33
  "@types/react": "19.3.0",
34
34
  "@types/react-dom": "19.3.0",
35
35
  "eslint-plugin-react": "7.37.5",