@whatsaaaa/form-builder-react 0.2.0 → 0.3.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 CHANGED
@@ -1,20 +1,26 @@
1
1
  # @whatsaaaa/form-builder-react
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@whatsaaaa/form-builder-react)](https://www.npmjs.com/package/@whatsaaaa/form-builder-react)
4
-
5
3
  Render JSON form definitions with [Formisch](https://formisch.dev) and your own React components.
6
4
 
7
- It's headless: it reads the definition, connects every field to Formisch and turns input into the values the schema expects, but it renders nothing of its own. You give it a component per field type, so it works with plain HTML, shadcn/ui or any other UI library.
5
+ [![npm version](https://img.shields.io/npm/v/@whatsaaaa/form-builder-react)](https://www.npmjs.com/package/@whatsaaaa/form-builder-react)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@whatsaaaa/form-builder-react)](https://www.npmjs.com/package/@whatsaaaa/form-builder-react)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/whatsaaaa/form-builder/ci.yml?branch=main&label=CI)](https://github.com/whatsaaaa/form-builder/actions/workflows/ci.yml)
8
+ [![Coverage](https://codecov.io/gh/whatsaaaa/form-builder/graph/badge.svg?flag=react)](https://codecov.io/gh/whatsaaaa/form-builder)
9
+ [![React](https://img.shields.io/npm/dependency-version/@whatsaaaa/form-builder-react/peer/react?logo=react)](https://react.dev)
10
+ [![Types](https://img.shields.io/npm/types/@whatsaaaa/form-builder-react)](https://www.npmjs.com/package/@whatsaaaa/form-builder-react)
11
+ [![License](https://img.shields.io/npm/l/@whatsaaaa/form-builder-react)](https://github.com/whatsaaaa/form-builder/blob/main/LICENSE)
12
+
13
+ This is the React adapter of [form-builder](https://github.com/whatsaaaa/form-builder). It is headless: it reads the definition, connects every field to Formisch and turns input into the values the schema expects, but renders nothing of its own. You give it a component per field type, so it works with plain HTML, shadcn/ui or any other UI library.
8
14
 
9
15
  ## Install
10
16
 
11
17
  ```sh
12
- pnpm add @whatsaaaa/form-builder-react @formisch/react valibot
18
+ pnpm add @whatsaaaa/form-builder-react @whatsaaaa/form-builder-schema @formisch/react valibot
13
19
  ```
14
20
 
15
- React 18 or 19, `@formisch/react` 1.2 and Valibot 1.4.1 or later are required. `@whatsaaaa/form-builder-schema` comes with it.
21
+ React 18 or 19, `@formisch/react` 1.2 and Valibot 1.4.1 or later are required. Install `@whatsaaaa/form-builder-schema` too: the examples import its types, such as `JsonForm`.
16
22
 
17
- ## Usage
23
+ ## Example
18
24
 
19
25
  ```tsx
20
26
  import { Form } from '@formisch/react'
@@ -38,60 +44,7 @@ export function DefinitionForm({ definition }: { definition: JsonForm }) {
38
44
  }
39
45
  ```
40
46
 
41
- - `useJsonForm(definition, options?)` builds the schema and the initial input, and creates the Formisch form. `jsonForm.form` is a normal Formisch form, so every Formisch method works on it.
42
- - `<JsonFields>` renders the fields inside your `<Form>`. The form element, the submit button and anything around them are yours.
43
- - A definition with issues throws the builder's `DefinitionError` while rendering, so an error boundary can catch it. Run `validate()` first when the definition comes from outside your code.
44
- - Like Formisch's `useForm`, `useJsonForm` reads the definition on the first render. To show another one, give the component that calls `useJsonForm` a new `key`: `<DefinitionForm key={id} definition={definition} />`.
45
-
46
- ### Options
47
-
48
- ```ts
49
- useJsonForm(definition, {
50
- builder: createSchemaBuilder({ fieldTypes: { money } }), // your own types, rules and messages
51
- components, // your component per field type, see below
52
- layoutComponents, // your component per layout type, see Layout
53
- validate: 'blur', // when a field is validated the first time, as in Formisch
54
- revalidate: 'input', // when it is validated again
55
- })
56
- ```
57
-
58
- `builder`, `validate` and `revalidate` are read on the first render, with the definition. `components` and `layoutComponents` are read on every render.
59
-
60
- ## Components
61
-
62
- `components` has one component per field type, by type name. Pass it to `useJsonForm`, or to `<JsonFields>` and `<JsonField>` as a prop, which wins. Several types can share one:
63
-
64
- ```ts
65
- import type { FieldComponents } from '@whatsaaaa/form-builder-react'
66
-
67
- export const components: FieldComponents = {
68
- text: TextInput,
69
- email: TextInput,
70
- number: TextInput,
71
- select: SelectInput,
72
- group: Fieldset,
73
- array: Repeater,
74
- heading: Heading,
75
- // …one for every type your definitions use
76
- }
77
- ```
78
-
79
- A type that a definition uses but `components` doesn't have throws a `TypeError` that names every missing type, when `<JsonFields>` or `<JsonField>` renders. Only the fields it renders count.
80
-
81
- What a component gets depends on the field's role:
82
-
83
- | Role | Which fields | Props |
84
- | ------- | ------------------------------ | -------------------------------------------------------------------------------- |
85
- | value | every field with a value | `field`, `path`, `kind`, `required`, `input`, `errors`, `inputProps`, `setInput` |
86
- | group | a container of kind `object` | `field`, `path`, `children`: the group's fields |
87
- | array | a container of kind `array` | `field`, `path`, `items`, `errors`, `add`, `remove`, `move`, `children(index)` |
88
- | display | a display element, kind `none` | `field` |
89
-
90
- The props are typed as `ValueFieldProps`, `GroupFieldProps`, `ArrayFieldProps` and `DisplayFieldProps`.
91
-
92
- Your own container types render through the group or array role too, by their kind.
93
-
94
- ### A value field
47
+ `components` maps each field type to a component of yours. A text input looks like this:
95
48
 
96
49
  ```tsx
97
50
  import type { ValueFieldProps } from '@whatsaaaa/form-builder-react'
@@ -113,262 +66,28 @@ export function TextInput({ field, kind, input, errors, inputProps, ...props }:
113
66
  }
114
67
  ```
115
68
 
116
- - `field` is the field from the definition, with its `label`, `placeholder`, `options` and `ui`.
117
- - `path` is its Formisch path with real indexes, like `['lines', 0, 'quantity']`.
118
- - `inputProps` are Formisch's element props: the field's name, a ref, and focus, change and blur handlers. Spread them on the input element, so Formisch knows when the field is touched and can focus it after a failed submit. Your own `onChange` after the spread replaces Formisch's, and calls `setInput` instead.
119
- - `required` is true when the definition makes the field required.
120
-
121
- ### setInput
122
-
123
- `setInput` sets the field's input after turning what an input element gives into what the schema expects, by the field's kind:
124
-
125
- | Kind | Input | Value set |
126
- | ------------------- | ----------------------- | ------------------------------ |
127
- | `number` | `''` or only spaces | `undefined` |
128
- | `number` | a numeric string, `'5'` | `5` |
129
- | `file` | a `FileList` | its first file, or `undefined` |
130
- | `array` of `file` | a `FileList` | an array of its files |
131
- | `array` of `number` | numeric strings | numbers |
132
-
133
- Anything else is set as it is, and the schema reports it. For a choice, like a radio or a select, set the option's value from `field.options` rather than the element's value: that keeps `2` a number and `true` a boolean.
134
-
135
- ```tsx
136
- const options = field.options as { value: string | number | boolean; label: string }[]
137
-
138
- <select
139
- {...inputProps}
140
- value={options.findIndex((option) => option.value === input)}
141
- onChange={(e) => setInput(options[Number(e.target.value)]?.value)}
142
- >
143
- <option value={-1}>Choose…</option>
144
- {options.map((option, i) => (
145
- <option key={i} value={i}>
146
- {option.label}
147
- </option>
148
- ))}
149
- </select>
150
- ```
151
-
152
- For a file input, pass the element's `files` with `onChange={(e) => setInput(e.target.files)}`, and never set its `value`.
153
-
154
- `setInput` stays the same function while the field is the same, so it is safe in an effect's or a memo's dependencies.
155
-
156
- ### A group
157
-
158
- ```tsx
159
- import type { GroupFieldProps } from '@whatsaaaa/form-builder-react'
160
-
161
- export function Fieldset({ field, children }: GroupFieldProps) {
162
- return (
163
- <fieldset>
164
- <legend>{field.label}</legend>
165
- {children}
166
- </fieldset>
167
- )
168
- }
169
- ```
170
-
171
- ### An array
172
-
173
- ```tsx
174
- import type { ArrayFieldProps } from '@whatsaaaa/form-builder-react'
175
-
176
- export function Repeater({ field, items, errors, add, remove, children }: ArrayFieldProps) {
177
- return (
178
- <fieldset>
179
- <legend>{field.label}</legend>
180
- {items.map((item, index) => (
181
- <div key={item}>
182
- {children(index)}
183
- <button type="button" onClick={() => remove(index)}>
184
- Remove
185
- </button>
186
- </div>
187
- ))}
188
- <button type="button" onClick={add}>
189
- Add
190
- </button>
191
- {errors && <small>{errors[0]}</small>}
192
- </fieldset>
193
- )
194
- }
195
- ```
196
-
197
- - `children(index)` renders the fields of the item at `index`.
198
- - `items` has one id per item. Use it as the `key`, so each item keeps its inputs when one is removed or moved.
199
- - `add()` adds an item at the end, with the defaults its fields have in the definition.
200
- - `errors` are the array's own messages, like a required array's or `maxLength`'s. Its items' fields show their own.
201
-
202
- ### A display element
203
-
204
- ```tsx
205
- import type { DisplayFieldProps } from '@whatsaaaa/form-builder-react'
206
-
207
- export function Heading({ field }: DisplayFieldProps) {
208
- return <h2>{String(field.text ?? '')}</h2>
209
- }
210
- ```
211
-
212
- ## Layout
213
-
214
- A definition's [`layout`](https://github.com/whatsaaaa/form-builder/tree/main/packages/schema#layout) puts its fields in sections, rows of a 12-column grid and steps, and leaves slots for content of yours. `<JsonFields>` renders the current step of it. Give `useJsonForm` a component for each layout type the definition uses, and `<JsonFields>` the content of its slots:
215
-
216
- ```tsx
217
- import type { LayoutComponents, RowProps, SectionProps } from '@whatsaaaa/form-builder-react'
218
-
219
- function Section({ section, children }: SectionProps) {
220
- return (
221
- <fieldset>
222
- <legend>{section.label}</legend>
223
- {children}
224
- </fieldset>
225
- )
226
- }
227
-
228
- // One line of a 12-column grid. On a narrow screen, give each cell the whole row.
229
- function Row({ spans, children }: RowProps) {
230
- return (
231
- <div className="row">
232
- {spans.map((span, index) => (
233
- <div key={index} style={{ gridColumn: `span ${span}` }}>
234
- {children(index)}
235
- </div>
236
- ))}
237
- </div>
238
- )
239
- }
240
-
241
- const layoutComponents: LayoutComponents = { section: Section, row: Row }
242
-
243
- function Checkout({ definition }: { definition: JsonForm }) {
244
- const jsonForm = useJsonForm(definition, { components, layoutComponents })
245
- return (
246
- <Form of={jsonForm.form} onSubmit={save}>
247
- <JsonFields of={jsonForm} slots={{ submit: <button type="submit">Place order</button> }} />
248
- </Form>
249
- )
250
- }
251
- ```
252
-
253
- ```css
254
- .row {
255
- display: grid;
256
- grid-template-columns: repeat(12, 1fr);
257
- gap: 1rem;
258
- }
259
- ```
260
-
261
- | Layout type | Props |
262
- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
263
- | `section` | `section`, as written, with its `id`, `label`, `description` and `ui`; `children` |
264
- | `row` | `row`, as written, with its `id` and `ui`; `spans`, the span of each cell; `children(index)` renders the cell at `index` |
265
-
266
- - `layoutComponents` goes to `useJsonForm`, or to `<JsonFields>` as a prop, which wins. A definition without sections or rows needs none, and one without a `layout` renders as it always did.
267
- - A layout type the layout uses without a component, or a slot without content in `slots`, throws a `TypeError` naming each one, on every step.
268
- - Display elements written in the layout render with your field components, like the ones in `fields`.
269
- - `except` leaves fields out of the layout too, and a section or a row left without fields drops out. The other cells of a row keep their spans. `only` renders the fields it names without the layout.
270
- - The spans are for a wide screen: the layout says nothing about narrow ones, so your row component decides, e.g. with a media query.
271
-
272
- ### Steps
273
-
274
- `jsonForm.steps` is where the form is, and how to move:
275
-
276
- ```tsx
277
- function Signup({ definition }: { definition: JsonForm }) {
278
- const jsonForm = useJsonForm(definition, { components, layoutComponents })
279
- const { steps } = jsonForm
280
- return (
281
- <Form of={jsonForm.form} onSubmit={save}>
282
- <ol>
283
- {steps.list.map((step, i) => (
284
- <li key={step.step.id ?? i} aria-current={i === steps.index ? 'step' : undefined}>
285
- {step.step.label}
286
- </li>
287
- ))}
288
- </ol>
289
- <JsonFields of={jsonForm} />
290
- {!steps.isFirst && (
291
- <button type="button" onClick={steps.back}>
292
- Back
293
- </button>
294
- )}
295
- {steps.isLast ? (
296
- <button type="submit">Sign up</button>
297
- ) : (
298
- <button type="button" onClick={() => void steps.next()}>
299
- Next
300
- </button>
301
- )}
302
- </Form>
303
- )
304
- }
305
- ```
306
-
307
- | Member | |
308
- | ------------------- | ---------------------------------------------------------------------------------------------------- |
309
- | `list` | every step, with its `step` as written (`id`, `label`, `description`, `ui`), `children` and `fields` |
310
- | `index`, `current` | the step `<JsonFields>` renders |
311
- | `isFirst`, `isLast` | |
312
- | `next()` | validates, and moves on when the step's fields have no issues. Resolves to whether it moved |
313
- | `back()` | moves back, without validating |
314
- | `goTo(index)` | back freely; forward through each step on the way, stopping at the first with issues |
315
-
316
- The form is one Formisch form, so Formisch validates all of it at once. The steps make that work:
317
-
318
- - **Next** validates the whole form, and only the issues on the current step's fields keep the user there. It focuses the first one, in layout order.
319
- - **A step shows what the user left**: the errors it had when they left it, and none the first time they get to it, so the issues of steps nobody saw yet stay out of sight.
320
- - **A step that failed Next revalidates** as the user fixes it, on the `revalidate` event, like a form after a failed submit.
321
- - **A submit before the last step is a Next**, so Enter in a step with a single text input doesn't submit the form early. On the last step, Formisch submits as usual.
322
- - **A submit that fails because of an earlier step**, like a form rule that reads fields of two steps, goes back to that step.
323
- - `<JsonFields step={2} />` renders a given step instead of the current one, e.g. on a review page. A form without steps has one, and nothing changes for it.
324
-
325
- ## Placing fields yourself
326
-
327
- Without a `layout`, `<JsonFields>` renders every field in the order of the definition. To put some of them somewhere else, in code rather than in the definition, place them with `<JsonField>` and leave them out of `<JsonFields>` with `except`:
328
-
329
- ```tsx
330
- <Form of={jsonForm.form} onSubmit={save}>
331
- <main>
332
- <JsonFields of={jsonForm} except={['coupon', 'acceptTerms']} />
333
- </main>
334
- <aside>
335
- <JsonField of={jsonForm} path="coupon" />
336
- <JsonField of={jsonForm} path="acceptTerms" />
337
- <button type="submit">Place order</button>
338
- </aside>
339
- </Form>
340
- ```
341
-
342
- - A path is the field's names joined with dots: `'email'`, or `'address.city'` for a field of a group. A placed field validates, focuses and submits like any other, because it is still part of the one Formisch form.
343
- - A group or an array comes with all its fields. A field of an array's items can't be placed on its own, because it has no index outside its item, so place the array. To leave such a field out of every item, use `'*'` for the index: `except={['lines.*.note']}`.
344
- - `only` renders just the fields it names, in the order of the definition: `<JsonFields of={jsonForm} only={['name', 'email']} />`. Display elements have no dot path, even with a name, so `only` leaves them out.
345
- - A path that names no field throws a `TypeError`, so a typo shows up at once. `only` and `except` are read on every render, so they can change, and a field keeps what was typed while it is hidden.
69
+ `jsonForm.form` is a normal Formisch form, so every Formisch method works on it.
346
70
 
347
- ## Typing the form
71
+ ## Highlights
348
72
 
349
- The definition is data, so TypeScript doesn't know its fields, and `jsonForm.form` is typed loosely: Formisch's own functions accept only top-level paths on it. Give `useJsonForm` your input type, and your output type when it differs, and they check paths and values as in any Formisch form:
73
+ - Headless: one component of yours per field type, and per layout type
74
+ - `setInput` turns what an element gives into the value the schema expects
75
+ - Groups, arrays and display elements, with stable keys for array items
76
+ - Sections, 12-column rows, slots and multi-step forms from the definition's layout
77
+ - Steps that validate on Next and only show errors the user has seen
78
+ - Conditions from the affects package, re-rendering only when what is hidden changes
79
+ - Fields placed in your own markup with `<JsonField>`, `only` and `except`
350
80
 
351
- ```ts
352
- import { getInput, useFieldArray } from '@formisch/react'
81
+ ## Documentation
353
82
 
354
- interface CheckoutInput {
355
- name: string
356
- address: { city: string }
357
- lines: { product: string; quantity: number }[]
358
- }
359
-
360
- const jsonForm = useJsonForm<CheckoutInput>(definition, { components })
83
+ The [guide](https://github.com/whatsaaaa/form-builder/blob/main/frameworks/react/docs/guide.md) covers options, components for every role, layout, steps, conditions, placing fields yourself and typing the form.
361
84
 
362
- getInput(jsonForm.form, { path: ['address', 'city'] }) // string | undefined
363
- useFieldArray(jsonForm.form, { path: ['lines'] })
364
- ```
85
+ ## Related packages
365
86
 
366
- - Formisch's `<Field>`, `<FieldArray>`, `useField`, `getInput` and `setInput` work on `jsonForm.form` either way. With a type, nested paths type-check, their values are typed, and so is the output your submit handler gets.
367
- - The type is a promise you make: nothing checks it against the definition. Keep the two in step.
368
- - A field you render with Formisch's `<Field>` gets none of the adapter's help, like `setInput` turning `'5'` into `5`, or the defaults of a new array item. Leave it out of `<JsonFields>` with `except`.
87
+ - [`@whatsaaaa/form-builder-schema`](https://github.com/whatsaaaa/form-builder/tree/main/packages/schema): build Formisch-ready Valibot schemas from JSON definitions
88
+ - [`@whatsaaaa/form-builder-affects`](https://github.com/whatsaaaa/form-builder/tree/main/packages/affects): show, hide and clear fields by conditions
89
+ - [`@whatsaaaa/form-builder-vue`](https://github.com/whatsaaaa/form-builder/tree/main/frameworks/vue): the same adapter for Vue
369
90
 
370
- ## Not included
91
+ ## License
371
92
 
372
- - Labels and display text reach your components as written in the definition. The builder's `resolveMessage` only changes what goes into the schema, so translate `field.label` in your components.
373
- - `ui.widget` doesn't pick a component. A component can read `field.ui` itself, e.g. to show a multiselect as checkboxes.
374
- - Showing and hiding fields, or making one required because of another, belongs to a future affects package. The layout already hides a section, a row or a step whose fields are all hidden, through `shownNodes` in the schema package.
93
+ [MIT](https://github.com/whatsaaaa/form-builder/blob/main/LICENSE)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FieldNode, FieldNode as FieldNode$1, FieldPath, FieldPath as FieldPath$1, FieldRole, FormLayout, FormLayout as FormLayout$1, JsonField as JsonField$1, JsonForm, JsonFormSchema, JsonFormSchema as JsonFormSchema$1, JsonLayoutRow, JsonLayoutSection, LayoutNode, LayoutStep, LayoutStep as LayoutStep$1, SchemaBuilder, TypedJsonFormSchema, TypedJsonFormSchema as TypedJsonFormSchema$1, ValueKind } from "@whatsaaaa/form-builder-schema";
1
+ import { FieldNode, FieldNode as FieldNode$1, FieldPath, FieldPath as FieldPath$1, FieldRole, FormLayout, FormLayout as FormLayout$1, JsonField as JsonField$1, JsonForm, JsonFormSchema, JsonFormSchema as JsonFormSchema$1, JsonLayoutRow, JsonLayoutSection, LayoutNode, LayoutStep, LayoutStep as LayoutStep$1, SchemaBuilder, TypedJsonFormSchema, TypedJsonFormSchema as TypedJsonFormSchema$1, ValueKind, VisibilityState, VisibilityState as VisibilityState$1 } from "@whatsaaaa/form-builder-schema";
2
2
  import { FieldElementProps, FormSchema, FormStore, ValidationMode } from "@formisch/react";
3
3
  import { ComponentType, ReactElement, ReactNode } from "react";
4
4
  /**
@@ -75,7 +75,10 @@ interface RowProps {
75
75
  }
76
76
  /** The steps of a form, from its layout. A form without `steps` in its layout has one. */
77
77
  interface JsonFormSteps {
78
- /** Every step, in order. */
78
+ /**
79
+ * The steps on show, in order. A step whose fields are all hidden by conditions, or that is
80
+ * hidden itself, is left out, unless it is the current one: Next and Back skip it.
81
+ */
79
82
  readonly list: readonly LayoutStep$1[];
80
83
  /** The index of the current step in `list`. */
81
84
  readonly index: number;
@@ -125,6 +128,13 @@ interface JsonFormInstance<TSchema extends FormSchema = JsonFormSchema$1> {
125
128
  readonly layout: FormLayout$1;
126
129
  /** The layout's steps: the current one, and Next, Back and Go to. A form without steps has one. */
127
130
  readonly steps: JsonFormSteps;
131
+ /**
132
+ * What the conditions of the builder's extensions hide for the form's input now, like the
133
+ * `affects` of `@whatsaaaa/form-builder-affects`: `<JsonFields>` and `<JsonField>` leave it out.
134
+ * Read it in your own renderers. A new object only when what is hidden changes, so the form
135
+ * re-renders for that, not for every keystroke. Without conditions, it hides nothing.
136
+ */
137
+ readonly visibility: VisibilityState$1;
128
138
  }
129
139
  /** Any form `useJsonForm` returns, typed or not: what the components take. */
130
140
  type AnyJsonFormInstance = JsonFormInstance<FormSchema>;
@@ -133,7 +143,9 @@ type AnyJsonFormInstance = JsonFormInstance<FormSchema>;
133
143
  * definition with issues throws the builder's `DefinitionError`, so run `validate()` first when it
134
144
  * comes from outside your code. Like Formisch's `useForm`, it reads the definition, the builder and
135
145
  * the validation modes on the first render: give the component a new `key` to use another
136
- * definition. The components and layout components are read on every render.
146
+ * definition. The components and layout components are read on every render. With conditions, it
147
+ * clears the fields they clear as soon as they do, with Formisch's `setInput`, wherever the fields
148
+ * are rendered.
137
149
  */
138
150
  export declare function useJsonForm(definition: JsonForm, options?: UseJsonFormOptions): JsonFormInstance;
139
151
  /**
@@ -152,8 +164,9 @@ interface JsonFieldProps {
152
164
  /**
153
165
  * Renders one field of a `useJsonForm` form, wherever you put it inside Formisch's `<Form>`. A
154
166
  * group or an array comes with its fields. Leave the field out of `<JsonFields>` with `except`.
167
+ * It renders nothing while conditions hide the field.
155
168
  */
156
- export declare function JsonField({ of, path, components }: JsonFieldProps): ReactElement;
169
+ export declare function JsonField({ of, path, components }: JsonFieldProps): ReactElement | null;
157
170
  interface JsonFieldsProps {
158
171
  /** What `useJsonForm` returned. */
159
172
  of: AnyJsonFormInstance;
@@ -179,5 +192,5 @@ interface JsonFieldsProps {
179
192
  * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button.
180
193
  */
181
194
  export declare function JsonFields(props: JsonFieldsProps): ReactElement;
182
- export type { AnyJsonFormInstance, ArrayFieldProps, DisplayFieldProps, FieldComponents, FieldNode, FieldPath, FieldRole, FormLayout, GroupFieldProps, JsonFieldProps, JsonFieldsProps, JsonFormInstance, JsonFormSchema, JsonFormSteps, LayoutComponents, LayoutNode, LayoutStep, RowProps, SectionProps, TypedJsonFormSchema, UseJsonFormOptions, ValueFieldProps };
195
+ export type { AnyJsonFormInstance, ArrayFieldProps, DisplayFieldProps, FieldComponents, FieldNode, FieldPath, FieldRole, FormLayout, GroupFieldProps, JsonFieldProps, JsonFieldsProps, JsonFormInstance, JsonFormSchema, JsonFormSteps, LayoutComponents, LayoutNode, LayoutStep, RowProps, SectionProps, TypedJsonFormSchema, UseJsonFormOptions, ValueFieldProps, VisibilityState };
183
196
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,19 +1,21 @@
1
- import { checkComponents, checkLayoutComponents, coerceInput, createSchemaBuilder, dotPathOf, fieldPathsOf, findField, isRequired, issuesOfStep, selectFields, shownNodes, toFieldPath } from "@whatsaaaa/form-builder-schema";
2
- import { focus, getErrors, getInput, insert, move, remove, setErrors, useField, useFieldArray, useForm, validate } from "@formisch/react";
3
- import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import { checkComponents, checkLayoutComponents, coerceInput, createSchemaBuilder, dotPathOf, fieldPathsOf, findField, isRequired, issuesOfStep, selectFields, toFieldPath } from "@whatsaaaa/form-builder-schema";
2
+ import { focus, getErrors, getInput, insert, move, remove, setErrors, setInput, useField, useFieldArray, useForm, validate } from "@formisch/react";
3
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
5
+ import { INTERNAL, getFieldInput, setListener, untrack } from "@formisch/react/internals";
5
6
  // src/json-form-steps.ts
6
7
  const fieldEvents = /* @__PURE__ */ new WeakMap();
7
8
  /** What a field of `form` calls on each event, for a form with steps. Undefined with one step. */
8
9
  const fieldEventsOf = (form) => fieldEvents.get(form);
9
10
  /**
10
11
  * The steps of `layout`, for `useJsonForm`. With more than one, a step shows the errors it had
11
- * when the user left it, and none the first time they get to it, a step that failed Next revalidates as the user fixes it, a submit
12
- * before the last step moves on like Next, and a submit that fails because of an earlier step
13
- * goes back to it. Like Formisch's `useForm`, it reads the validation modes on the first render.
12
+ * when the user left it, and none the first time they get to it, a step that failed Next
13
+ * revalidates as the user fixes it, a submit before the last step moves on like Next, and a submit
14
+ * that fails because of an earlier step goes back to it. The steps `visibility` hides are skipped.
15
+ * Like Formisch's `useForm`, it reads the validation modes on the first render.
14
16
  */
15
- function useJsonFormSteps(form, layout, modes) {
16
- const list = layout.steps;
17
+ function useJsonFormSteps(form, layout, modes, visibility) {
18
+ const all = layout.steps;
17
19
  const [index, setIndex] = useState(0);
18
20
  const state = useRef({
19
21
  index: 0,
@@ -25,22 +27,27 @@ function useJsonFormSteps(form, layout, modes) {
25
27
  });
26
28
  const methods = useMemo(() => {
27
29
  const at = state.current;
30
+ const shown = () => all.filter((step, i) => i === at.index || !visibility.isStepHidden(step));
31
+ const position = (list) => list.indexOf(all[at.index]);
28
32
  async function goTo(target) {
33
+ const list = shown();
29
34
  if (!Number.isInteger(target) || target < 0 || target >= list.length) throw new RangeError(`No step ${target}. The form has ${list.length}.`);
30
- if (target <= at.index) {
31
- move(target);
35
+ const to = all.indexOf(list[target]);
36
+ if (target <= position(list)) {
37
+ move(to);
32
38
  return true;
33
39
  }
40
+ await afterTask();
34
41
  const { issues } = await validate(form);
35
- for (let step = at.index; step < target; step++) {
36
- const found = issuesOfStep(list[step], issues);
42
+ for (const step of list.slice(position(list), target)) {
43
+ const found = issuesOfStep(step, issues);
37
44
  if (found.length) {
38
- stop(step, found);
45
+ stop(all.indexOf(step), found);
39
46
  return false;
40
47
  }
41
- at.failed.delete(step);
48
+ at.failed.delete(all.indexOf(step));
42
49
  }
43
- move(target);
50
+ move(to);
44
51
  return true;
45
52
  }
46
53
  /**
@@ -51,7 +58,7 @@ function useJsonFormSteps(form, layout, modes) {
51
58
  function move(target) {
52
59
  leave();
53
60
  const errors = at.left.get(target);
54
- for (const path of fieldPathsOf(list[target], getInput(form))) setErrors(form, {
61
+ for (const path of fieldPathsOf(all[target], getInput(form))) setErrors(form, {
55
62
  path,
56
63
  errors: errors?.get(JSON.stringify(path)) ?? null
57
64
  });
@@ -60,7 +67,7 @@ function useJsonFormSteps(form, layout, modes) {
60
67
  }
61
68
  /** Keeps the errors of the current step, for when the user comes back to it. */
62
69
  function leave() {
63
- const errors = fieldPathsOf(list[at.index], getInput(form)).map((path) => [JSON.stringify(path), getErrors(form, { path })]);
70
+ const errors = fieldPathsOf(all[at.index], getInput(form)).map((path) => [JSON.stringify(path), getErrors(form, { path })]);
64
71
  at.left.set(at.index, new Map(errors));
65
72
  }
66
73
  /** Stays on, or goes to, the step at `target`, with its issues shown. */
@@ -84,33 +91,45 @@ function useJsonFormSteps(form, layout, modes) {
84
91
  /** After a failed submit: back to the first step before this one with issues, if any. */
85
92
  async function afterSubmit() {
86
93
  const { issues } = await validate(form);
87
- const step = list.findIndex((each) => issuesOfStep(each, issues).length);
88
- if (step !== -1 && step < at.index) stop(step, issuesOfStep(list[step], issues));
94
+ const list = shown();
95
+ const step = list.find((each) => issuesOfStep(each, issues).length);
96
+ if (step && list.indexOf(step) < position(list)) stop(all.indexOf(step), issuesOfStep(step, issues));
89
97
  }
90
- const next = () => at.index === list.length - 1 ? Promise.resolve(false) : goTo(at.index + 1);
98
+ const isLast = () => {
99
+ const list = shown();
100
+ return position(list) === list.length - 1;
101
+ };
102
+ const next = () => isLast() ? Promise.resolve(false) : goTo(position(shown()) + 1);
91
103
  const back = () => {
92
- if (at.index > 0) move(at.index - 1);
104
+ const list = shown();
105
+ const before = list[position(list) - 1];
106
+ if (before) move(all.indexOf(before));
93
107
  };
94
108
  return {
95
109
  goTo,
96
110
  next,
97
111
  back,
112
+ isLast,
98
113
  events,
99
114
  afterSubmit
100
115
  };
101
- }, [form, list]);
116
+ }, [
117
+ form,
118
+ all,
119
+ visibility
120
+ ]);
102
121
  useEffect(() => {
103
122
  const path = state.current.focus;
104
123
  state.current.focus = void 0;
105
124
  if (path) focus(form, { path });
106
125
  }, [form, index]);
107
- const stepped = list.length > 1;
126
+ const stepped = all.length > 1;
108
127
  useEffect(() => {
109
128
  if (!stepped) return;
110
129
  fieldEvents.set(form, methods.events);
111
130
  const onSubmit = (event) => {
112
131
  if (event.target !== formElementOf(form)) return;
113
- if (state.current.index === list.length - 1) {
132
+ if (methods.isLast()) {
114
133
  state.current.submitting = true;
115
134
  return;
116
135
  }
@@ -125,7 +144,6 @@ function useJsonFormSteps(form, layout, modes) {
125
144
  };
126
145
  }, [
127
146
  form,
128
- list,
129
147
  methods,
130
148
  stepped
131
149
  ]);
@@ -135,21 +153,29 @@ function useJsonFormSteps(form, layout, modes) {
135
153
  state.current.submitting = false;
136
154
  if (!form.isValid) methods.afterSubmit();
137
155
  });
138
- return useMemo(() => ({
139
- list,
140
- index,
141
- current: list[index],
142
- isFirst: index === 0,
143
- isLast: index === list.length - 1,
144
- next: methods.next,
145
- back: methods.back,
146
- goTo: methods.goTo
147
- }), [
148
- list,
156
+ return useMemo(() => {
157
+ const list = all.filter((step, i) => i === index || !visibility.isStepHidden(step));
158
+ const current = all[index];
159
+ const at = list.indexOf(current);
160
+ return {
161
+ list,
162
+ index: at,
163
+ current,
164
+ isFirst: at === 0,
165
+ isLast: at === list.length - 1,
166
+ next: methods.next,
167
+ back: methods.back,
168
+ goTo: methods.goTo
169
+ };
170
+ }, [
171
+ all,
149
172
  index,
173
+ visibility,
150
174
  methods
151
175
  ]);
152
176
  }
177
+ /** Resolves once the task that called it is over, and the browser is done with its event. */
178
+ const afterTask = () => new Promise((resolve) => setTimeout(resolve));
153
179
  /** The `<form>` Formisch's `<Form>` renders, which it keeps on the store for its `submit()`. */
154
180
  function formElementOf(form) {
155
181
  return form["~internal"]?.element;
@@ -246,9 +272,13 @@ function resolveComponents(components, of) {
246
272
  if (!resolved) throw new TypeError("No field components. Pass them to useJsonForm, or to the component.");
247
273
  return resolved;
248
274
  }
249
- /** `indexes` are the indexes of the array items the fields are in, outermost first. */
275
+ /**
276
+ * Renders the fields that conditions don't hide. `indexes` are the indexes of the array items the
277
+ * fields are in, outermost first.
278
+ */
250
279
  function renderFields(ctx, nodes, indexes) {
251
- return nodes.map((node, position) => renderField(ctx, node, indexes, dotPathOf(node) ?? `#${position}`, (inner) => renderFields(ctx, node.children, inner)));
280
+ const { visibility } = ctx.of;
281
+ return nodes.flatMap((node, position) => visibility.isHidden(node, indexes) ? [] : [renderField(ctx, node, indexes, dotPathOf(node) ?? `#${position}`, (inner) => renderFields(ctx, node.children, inner))]);
252
282
  }
253
283
  /**
254
284
  * Renders one field with its component. `children` renders the fields inside a group, or inside
@@ -283,6 +313,7 @@ function renderField(ctx, node, indexes, key, children) {
283
313
  /**
284
314
  * Renders one field of a `useJsonForm` form, wherever you put it inside Formisch's `<Form>`. A
285
315
  * group or an array comes with its fields. Leave the field out of `<JsonFields>` with `except`.
316
+ * It renders nothing while conditions hide the field.
286
317
  */
287
318
  function JsonField({ of, path, components }) {
288
319
  const node = findField(of.fields, path, { placed: true });
@@ -291,15 +322,17 @@ function JsonField({ of, path, components }) {
291
322
  components: resolveComponents(components, of)
292
323
  };
293
324
  checkComponents([node], ctx.components);
325
+ if (of.visibility.isHidden(node, [])) return null;
294
326
  return renderField(ctx, node, [], dotPathOf(node) ?? "#0", (indexes) => renderFields(ctx, node.children, indexes));
295
327
  }
296
328
  // src/render-layout.tsx
297
329
  /**
298
- * Renders layout nodes. A field left out drops out, and so does a section or a row left without
299
- * fields. `indexes` are the indexes of the array items the nodes are in, outermost first.
330
+ * Renders layout nodes. A field left out or hidden by conditions drops out, and so does a section
331
+ * hidden by them, and a section or a row left without fields. `indexes` are the indexes of the
332
+ * array items the nodes are in, outermost first.
300
333
  */
301
334
  function renderLayout(ctx, nodes, indexes) {
302
- return shownNodes(nodes, indexes, ctx.isShown).map((node) => renderNode(ctx, node, indexes));
335
+ return ctx.of.visibility.shownNodes(nodes, indexes, ctx.isShown).map((node) => renderNode(ctx, node, indexes));
303
336
  }
304
337
  function renderNode(ctx, node, indexes) {
305
338
  const { key } = node;
@@ -366,6 +399,47 @@ function fieldsOf(nodes) {
366
399
  return node.type === "field" ? [node.node, ...inner] : inner;
367
400
  });
368
401
  }
402
+ // src/use-visibility.ts
403
+ /**
404
+ * What the conditions of the definition hide for the form's input, as it changes. Formisch
405
+ * re-renders a component for each signal it reads, so reading the whole input in `useJsonForm`
406
+ * would re-render the form on every keystroke. This watches the input outside React instead, and
407
+ * re-renders only when the state changes: `of()` gives the same object until then. Without
408
+ * conditions, it watches nothing.
409
+ */
410
+ function useVisibility(form, visibility) {
411
+ const subscribe = useCallback((onChange) => visibility.conditional ? watchInput(form, onChange) : () => {}, [form, visibility]);
412
+ const getSnapshot = () => visibility.of(visibility.conditional ? untrack(() => getFieldInput(form[INTERNAL])) : void 0);
413
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
414
+ }
415
+ /**
416
+ * Calls `onChange` after each change of the form's input, until the function it returns is called.
417
+ * It reads the input as Formisch's own components do, with a listener: a signal that changes calls
418
+ * it once, so it reads the input again each time, the items of new array items too.
419
+ */
420
+ function watchInput(form, onChange) {
421
+ const listener = [() => {
422
+ read();
423
+ onChange();
424
+ }, /* @__PURE__ */ new Set()];
425
+ const stop = () => {
426
+ for (const subscribers of listener[1]) subscribers.delete(listener);
427
+ listener[1].clear();
428
+ };
429
+ function read() {
430
+ stop();
431
+ untrack(() => {
432
+ setListener(listener);
433
+ try {
434
+ getFieldInput(form[INTERNAL]);
435
+ } finally {
436
+ setListener(void 0);
437
+ }
438
+ });
439
+ }
440
+ read();
441
+ return stop;
442
+ }
369
443
  // src/use-json-form.ts
370
444
  function useJsonForm(definition, options = {}) {
371
445
  const [built] = useState(() => {
@@ -376,7 +450,8 @@ function useJsonForm(definition, options = {}) {
376
450
  schema: builder.build(definition),
377
451
  initialInput: builder.getInitialInput(definition),
378
452
  fields: builder.getFieldTree(definition),
379
- layout: builder.getLayout(definition)
453
+ layout: builder.getLayout(definition),
454
+ visibility: builder.getVisibility(definition)
380
455
  };
381
456
  });
382
457
  const form = useForm({
@@ -385,7 +460,15 @@ function useJsonForm(definition, options = {}) {
385
460
  validate: options.validate,
386
461
  revalidate: options.revalidate
387
462
  });
388
- const steps = useJsonFormSteps(form, built.layout, options);
463
+ const visibility = useVisibility(form, built.visibility);
464
+ const { toClear } = visibility;
465
+ useEffect(() => {
466
+ for (const { path, input } of toClear) setInput(form, {
467
+ path,
468
+ input
469
+ });
470
+ }, [form, toClear]);
471
+ const steps = useJsonFormSteps(form, built.layout, options, visibility);
389
472
  const { components, layoutComponents } = options;
390
473
  return useMemo(() => ({
391
474
  form,
@@ -395,13 +478,15 @@ function useJsonForm(definition, options = {}) {
395
478
  layoutComponents,
396
479
  fields: built.fields,
397
480
  layout: built.layout,
398
- steps
481
+ steps,
482
+ visibility
399
483
  }), [
400
484
  form,
401
485
  built,
402
486
  components,
403
487
  layoutComponents,
404
- steps
488
+ steps,
489
+ visibility
405
490
  ]);
406
491
  }
407
492
  export { JsonField, JsonFields, useJsonForm };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Display","Group"],"sources":["../src/json-form-steps.ts","../src/array-field.tsx","../src/value-field.tsx","../src/render-fields.tsx","../src/json-field.tsx","../src/render-layout.tsx","../src/json-fields.tsx","../src/use-json-form.ts"],"sourcesContent":["import {\n focus,\n type FormStore,\n getErrors,\n getInput,\n setErrors,\n validate,\n type ValidationMode,\n} from '@formisch/react'\nimport {\n type FieldPath,\n fieldPathsOf,\n type FormLayout,\n issuesOfStep,\n type LayoutStep,\n type StepIssue,\n} from '@whatsaaaa/form-builder-schema'\nimport { useEffect, useMemo, useRef, useState } from 'react'\n\n/** The steps of a form, from its layout. A form without `steps` in its layout has one. */\nexport interface JsonFormSteps {\n /** Every step, in order. */\n readonly list: readonly LayoutStep[]\n /** The index of the current step in `list`. */\n readonly index: number\n /** The step `<JsonFields>` renders. */\n readonly current: LayoutStep\n readonly isFirst: boolean\n readonly isLast: boolean\n /**\n * Validates the whole form, and moves to the next step when the fields of this one have no\n * issues. Otherwise it stays, and focuses the first field with an issue, in layout order.\n * Resolves to whether it moved.\n */\n next(): Promise<boolean>\n /** Moves to the step before, without validating. */\n back(): void\n /**\n * Moves to the step at `index`. Back is free, and forward validates every step on the way, like\n * `next()`, and stops at the first with issues. Resolves to whether it got there.\n */\n goTo(index: number): Promise<boolean>\n}\n\n/** A field's error messages in Formisch, or null. */\ntype Errors = [string, ...string[]] | null\n\n/** What a field tells the steps it was through, by Formisch's name for the validation mode. */\nexport type FieldEvent = Exclude<ValidationMode, 'initial' | 'submit'>\n\n/** Revalidates a step that failed Next on each field event, like a form after a failed submit. */\ntype FieldEvents = (event: FieldEvent) => void\n\nconst fieldEvents = new WeakMap<FormStore, FieldEvents>()\n\n/** What a field of `form` calls on each event, for a form with steps. Undefined with one step. */\nexport const fieldEventsOf = (form: FormStore): FieldEvents | undefined => fieldEvents.get(form)\n\ninterface Modes {\n validate?: ValidationMode | undefined\n revalidate?: Exclude<ValidationMode, 'initial'> | undefined\n}\n\n/**\n * The steps of `layout`, for `useJsonForm`. With more than one, a step shows the errors it had\n * when the user left it, and none the first time they get to it, a step that failed Next revalidates as the user fixes it, a submit\n * before the last step moves on like Next, and a submit that fails because of an earlier step\n * goes back to it. Like Formisch's `useForm`, it reads the validation modes on the first render.\n */\nexport function useJsonFormSteps(form: FormStore, layout: FormLayout, modes: Modes): JsonFormSteps {\n const list = layout.steps\n const [index, setIndex] = useState(0)\n // What the async methods and the event handlers read: always the latest.\n const state = useRef({\n index: 0,\n // The errors each step had when the user left it, by the JSON of each field's path.\n left: new Map<number, Map<string, Errors>>(),\n // The steps that failed Next, and haven't passed since.\n failed: new Set<number>(),\n // The field to focus once the step it is on has rendered.\n focus: undefined as FieldPath | undefined,\n // Whether a submit on the last step is on its way through Formisch.\n submitting: false,\n modes,\n })\n\n const methods = useMemo(() => {\n const at = state.current\n\n async function goTo(target: number): Promise<boolean> {\n if (!Number.isInteger(target) || target < 0 || target >= list.length) {\n throw new RangeError(`No step ${target}. The form has ${list.length}.`)\n }\n if (target <= at.index) {\n move(target)\n return true\n }\n const { issues } = await validate(form)\n for (let step = at.index; step < target; step++) {\n const found = issuesOfStep(list[step] as LayoutStep, issues)\n if (found.length) {\n stop(step, found)\n return false\n }\n at.failed.delete(step)\n }\n move(target)\n return true\n }\n\n /**\n * Moves to a step as the user left it: with the errors it had then, and none the first time, and\n * revalidating if it failed Next. Validating the steps before set errors on its fields, which\n * the user never saw.\n */\n function move(target: number) {\n leave()\n const errors = at.left.get(target)\n for (const path of fieldPathsOf(list[target] as LayoutStep, getInput(form))) {\n setErrors(form, { path, errors: errors?.get(JSON.stringify(path)) ?? null } as never)\n }\n at.index = target\n setIndex(target)\n }\n\n /** Keeps the errors of the current step, for when the user comes back to it. */\n function leave() {\n const paths = fieldPathsOf(list[at.index] as LayoutStep, getInput(form))\n const errors = paths.map((path): [string, Errors] => [\n JSON.stringify(path),\n getErrors(form, { path } as never),\n ])\n at.left.set(at.index, new Map(errors))\n }\n\n /** Stays on, or goes to, the step at `target`, with its issues shown. */\n function stop(target: number, found: readonly StepIssue[]) {\n at.failed.add(target)\n // A group or an array has no element: the first field that has one gets the focus.\n const path = found.find((issue) => issue.field.role === 'value')?.path\n if (target === at.index) {\n if (path) focus(form, { path: path as never })\n return\n }\n leave()\n at.focus = path\n at.index = target\n setIndex(target)\n }\n\n const events: FieldEvents = (event) => {\n // In the other modes, Formisch revalidates a field with errors itself.\n const validateMode = at.modes.validate ?? 'submit'\n if (!at.failed.has(at.index) || validateMode !== 'submit' || form.isSubmitted) return\n if (event === (at.modes.revalidate ?? 'input')) void validate(form)\n }\n\n /** After a failed submit: back to the first step before this one with issues, if any. */\n async function afterSubmit() {\n const { issues } = await validate(form)\n const step = list.findIndex((each) => issuesOfStep(each, issues).length)\n if (step !== -1 && step < at.index) stop(step, issuesOfStep(list[step] as LayoutStep, issues))\n }\n\n const next = () => (at.index === list.length - 1 ? Promise.resolve(false) : goTo(at.index + 1))\n const back = () => {\n if (at.index > 0) move(at.index - 1)\n }\n return { goTo, next, back, events, afterSubmit }\n }, [form, list])\n\n // The field a step was stopped on, once the step has rendered.\n useEffect(() => {\n const path = state.current.focus\n state.current.focus = undefined\n if (path) focus(form, { path: path as never })\n }, [form, index])\n\n const stepped = list.length > 1\n useEffect(() => {\n if (!stepped) return\n fieldEvents.set(form, methods.events)\n // Before the last step, a submit, like Enter in a step with a single text input, is a Next.\n // It is caught on its way down to the form, before Formisch's own handler sees it.\n const onSubmit = (event: Event) => {\n if (event.target !== formElementOf(form)) return\n if (state.current.index === list.length - 1) {\n state.current.submitting = true\n return\n }\n event.preventDefault()\n event.stopImmediatePropagation()\n void methods.next()\n }\n document.addEventListener('submit', onSubmit, { capture: true })\n return () => {\n fieldEvents.delete(form)\n document.removeEventListener('submit', onSubmit, { capture: true })\n }\n }, [form, list, methods, stepped])\n\n // A submit that fails because of a step before the last, e.g. on a form rule, goes back to it.\n // React can batch the start and the end of a submit into one render, so the submit is marked\n // as it starts, and each render checks whether Formisch has finished it.\n const submitting = form.isSubmitting\n useEffect(() => {\n if (!state.current.submitting || submitting) return\n state.current.submitting = false\n if (!form.isValid) void methods.afterSubmit()\n })\n\n return useMemo(\n () => ({\n list,\n index,\n current: list[index] as LayoutStep,\n isFirst: index === 0,\n isLast: index === list.length - 1,\n next: methods.next,\n back: methods.back,\n goTo: methods.goTo,\n }),\n [list, index, methods],\n )\n}\n\n/** The `<form>` Formisch's `<Form>` renders, which it keeps on the store for its `submit()`. */\nfunction formElementOf(form: FormStore): HTMLFormElement | undefined {\n return (form as unknown as { '~internal'?: { element?: HTMLFormElement } })['~internal']?.element\n}\n","import { insert, move, remove, useFieldArray } from '@formisch/react'\nimport type { FieldNode, FieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, type ReactElement, type ReactNode, useMemo } from 'react'\nimport type { ArrayFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\ninterface ArrayFieldConnectionProps {\n of: AnyJsonFormInstance\n node: FieldNode\n path: FieldPath\n component: ComponentType<ArrayFieldProps>\n /** Renders the fields of the item at `index`. */\n renderItem: (index: number) => ReactNode\n}\n\n/** Connects a container of kind `array` to Formisch, and renders your component for it. */\nexport function ArrayField({\n of,\n node,\n path,\n component: Component,\n renderItem,\n}: ArrayFieldConnectionProps): ReactElement {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const array = useFieldArray(of.form, { path: path as never })\n\n // Formisch keeps the path it got while the array is the same, so the methods stay the same too.\n const { form, builder } = of\n const { field } = node\n const at = array.path\n // In a form with steps, a step that failed Next revalidates on a change of its items too.\n const methods = useMemo(\n () => ({\n // A new item starts with the defaults of its fields, like the items of a new form.\n add: () => {\n insert(form, { path: at, initialInput: builder.getItemInput(field) as never })\n fieldEventsOf(form)?.('input')\n },\n remove: (index: number) => {\n remove(form, { path: at, at: index })\n fieldEventsOf(form)?.('input')\n },\n move: (from: number, to: number) => {\n move(form, { path: at, from, to })\n fieldEventsOf(form)?.('input')\n },\n }),\n [form, builder, field, at],\n )\n\n return (\n <Component field={field} path={path} items={array.items} errors={array.errors} {...methods}>\n {renderItem}\n </Component>\n )\n}\n","import { useField } from '@formisch/react'\nimport {\n coerceInput,\n type FieldNode,\n type FieldPath,\n isRequired,\n} from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, type ReactElement, useCallback } from 'react'\nimport type { ValueFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\ninterface ValueFieldConnectionProps {\n of: AnyJsonFormInstance\n node: FieldNode\n path: FieldPath\n component: ComponentType<ValueFieldProps>\n}\n\n/** Connects a field with a value to Formisch, and renders your component for it. */\nexport function ValueField({\n of,\n node,\n path,\n component: Component,\n}: ValueFieldConnectionProps): ReactElement {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const field = useField(of.form, { path: path as never })\n const { form } = of\n const setInput = useCallback(\n (input: unknown) => {\n field.onChange(coerceInput(input, node) as never)\n fieldEventsOf(form)?.('input')\n },\n [field, node, form],\n )\n // In a form with steps, a step that failed Next revalidates on these events too.\n const inputProps =\n of.steps.list.length > 1\n ? {\n ...field.props,\n onFocus: () => {\n field.props.onFocus()\n fieldEventsOf(form)?.('touch')\n },\n onChange: (event: Parameters<typeof field.props.onChange>[0]) => {\n field.props.onChange(event)\n fieldEventsOf(form)?.('input')\n fieldEventsOf(form)?.('change')\n },\n onBlur: () => {\n field.props.onBlur()\n fieldEventsOf(form)?.('blur')\n },\n }\n : field.props\n\n return (\n <Component\n field={node.field}\n path={path}\n kind={node.kind}\n required={isRequired(node.field)}\n input={field.input}\n errors={field.errors}\n inputProps={inputProps}\n setInput={setInput}\n />\n )\n}\n","import { dotPathOf, type FieldNode, toFieldPath } from '@whatsaaaa/form-builder-schema'\nimport type { ComponentType, ReactElement, ReactNode } from 'react'\nimport { ArrayField } from './array-field'\nimport type {\n ArrayFieldProps,\n DisplayFieldProps,\n FieldComponents,\n GroupFieldProps,\n ValueFieldProps,\n} from './field-components'\nimport type { AnyJsonFormInstance } from './use-json-form'\nimport { ValueField } from './value-field'\n\n/** What `<JsonFields>` and `<JsonField>` render with. */\nexport interface RenderContext {\n of: AnyJsonFormInstance\n components: FieldComponents\n}\n\n/** The components from the props, or else the ones `useJsonForm` got. */\nexport function resolveComponents(\n components: FieldComponents | undefined,\n of: AnyJsonFormInstance,\n): FieldComponents {\n const resolved = components ?? of.components\n if (!resolved) {\n throw new TypeError('No field components. Pass them to useJsonForm, or to the component.')\n }\n return resolved\n}\n\n/** `indexes` are the indexes of the array items the fields are in, outermost first. */\nexport function renderFields(\n ctx: RenderContext,\n nodes: readonly FieldNode[],\n indexes: readonly number[],\n): ReactElement[] {\n return nodes.map((node, position) =>\n renderField(ctx, node, indexes, dotPathOf(node) ?? `#${position}`, (inner) =>\n renderFields(ctx, node.children, inner),\n ),\n )\n}\n\n/**\n * Renders one field with its component. `children` renders the fields inside a group, or inside\n * the item of an array, with the indexes of the items they are in.\n */\nexport function renderField(\n ctx: RenderContext,\n node: FieldNode,\n indexes: readonly number[],\n key: string,\n children: (indexes: readonly number[]) => ReactNode,\n): ReactElement {\n // The fields were checked for components, and the role tells which props the component takes.\n const component = ctx.components[node.type]\n const path = toFieldPath(node.dataPath, indexes)\n switch (node.role) {\n case 'display': {\n const Display = component as ComponentType<DisplayFieldProps>\n return <Display key={key} field={node.field} />\n }\n case 'group': {\n const Group = component as ComponentType<GroupFieldProps>\n return (\n <Group key={key} field={node.field} path={path}>\n {children(indexes)}\n </Group>\n )\n }\n case 'array':\n return (\n <ArrayField\n key={key}\n of={ctx.of}\n node={node}\n path={path}\n component={component as ComponentType<ArrayFieldProps>}\n renderItem={(index) => children([...indexes, index])}\n />\n )\n case 'value':\n return (\n <ValueField\n key={key}\n of={ctx.of}\n node={node}\n path={path}\n component={component as ComponentType<ValueFieldProps>}\n />\n )\n }\n}\n","import { checkComponents, dotPathOf, findField } from '@whatsaaaa/form-builder-schema'\nimport type { ReactElement } from 'react'\nimport type { FieldComponents } from './field-components'\nimport { type RenderContext, renderField, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\nexport interface JsonFieldProps {\n /** What `useJsonForm` returned. */\n of: AnyJsonFormInstance\n /** The field's path: `'email'`, or `'address.city'` for a field of a group. */\n path: string\n /** Defaults to the components `useJsonForm` got. */\n components?: FieldComponents | undefined\n}\n\n/**\n * Renders one field of a `useJsonForm` form, wherever you put it inside Formisch's `<Form>`. A\n * group or an array comes with its fields. Leave the field out of `<JsonFields>` with `except`.\n */\nexport function JsonField({ of, path, components }: JsonFieldProps): ReactElement {\n const node = findField(of.fields, path, { placed: true })\n const ctx: RenderContext = { of, components: resolveComponents(components, of) }\n checkComponents([node], ctx.components)\n\n return renderField(ctx, node, [], dotPathOf(node) ?? '#0', (indexes) =>\n renderFields(ctx, node.children, indexes),\n )\n}\n","import { type IsFieldShown, type LayoutNode, shownNodes } from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, Fragment, type ReactElement, type ReactNode } from 'react'\nimport type { LayoutComponents, RowProps, SectionProps } from './field-components'\nimport { type RenderContext, renderField } from './render-fields'\n\n/** What `<JsonFields>` renders a layout with. */\nexport interface LayoutContext extends RenderContext {\n layoutComponents: LayoutComponents\n /** The content of the layout's slots, by name. */\n slots: Readonly<Record<string, ReactNode>>\n /** False for a field left out with `except`. */\n isShown: IsFieldShown\n}\n\n/**\n * Renders layout nodes. A field left out drops out, and so does a section or a row left without\n * fields. `indexes` are the indexes of the array items the nodes are in, outermost first.\n */\nexport function renderLayout(\n ctx: LayoutContext,\n nodes: readonly LayoutNode[],\n indexes: readonly number[],\n): ReactElement[] {\n return shownNodes(nodes, indexes, ctx.isShown).map((node) => renderNode(ctx, node, indexes))\n}\n\nfunction renderNode(\n ctx: LayoutContext,\n node: LayoutNode,\n indexes: readonly number[],\n): ReactElement {\n const { key } = node\n switch (node.type) {\n case 'field':\n return renderField(ctx, node.node, indexes, key, (inner) =>\n renderLayout(ctx, node.children, inner),\n )\n case 'section': {\n // The layout was checked for components.\n const Section = ctx.layoutComponents.section as ComponentType<SectionProps>\n return (\n <Section key={key} section={node.section}>\n {renderLayout(ctx, node.children, indexes)}\n </Section>\n )\n }\n case 'row': {\n const Row = ctx.layoutComponents.row as ComponentType<RowProps>\n return (\n <Row key={key} row={node.row} spans={node.children.map((cell) => cell.span)}>\n {(index) => renderNode(ctx, node.children[index] as LayoutNode, indexes)}\n </Row>\n )\n }\n case 'slot':\n return <Fragment key={key}>{ctx.slots[node.name]}</Fragment>\n }\n}\n","import {\n checkComponents,\n checkLayoutComponents,\n dotPathOf,\n type FieldNode,\n type LayoutNode,\n selectFields,\n} from '@whatsaaaa/form-builder-schema'\nimport { Fragment, type ReactElement, type ReactNode } from 'react'\nimport type { FieldComponents, LayoutComponents } from './field-components'\nimport { type LayoutContext, renderLayout } from './render-layout'\nimport { type RenderContext, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\nexport interface JsonFieldsProps {\n /** What `useJsonForm` returned. */\n of: AnyJsonFormInstance\n /** Defaults to the components `useJsonForm` got. */\n components?: FieldComponents | undefined\n /** Defaults to the layout components `useJsonForm` got. */\n layoutComponents?: LayoutComponents | undefined\n /** The content of the layout's slots, by name: `{ submit: <button type=\"submit\">Save</button> }`. */\n slots?: Readonly<Record<string, ReactNode>> | undefined\n /** Renders only these fields, by path, in the order of the definition, without the layout. */\n only?: readonly string[] | undefined\n /**\n * Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. A section or a\n * row left without fields drops out too.\n */\n except?: readonly string[] | undefined\n /** Renders the step at this index instead of the current one, e.g. on a review page. */\n step?: number | undefined\n}\n\n/**\n * Renders the fields of a `useJsonForm` form with your components, one per field type, where the\n * definition's `layout` puts them: the current step, with its sections, rows and slots. It renders\n * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button.\n */\nexport function JsonFields(props: JsonFieldsProps): ReactElement {\n const { of, only, except } = props\n const ctx: RenderContext = { of, components: resolveComponents(props.components, of) }\n const nodes = selectFields(of.fields, { only, except })\n // Without a layout, it renders the fields the way it always did, and so with `only`.\n if (only || !of.definition.layout) {\n checkComponents(nodes, ctx.components)\n return <>{renderFields(ctx, nodes, [])}</>\n }\n\n const { layout, steps } = of\n const left = new Set(except)\n const layoutCtx: LayoutContext = {\n ...ctx,\n layoutComponents: props.layoutComponents ?? of.layoutComponents ?? {},\n slots: props.slots ?? {},\n isShown: (node) => !left.has(dotPathOf(node) ?? ''),\n }\n checkComponents(\n layout.steps.flatMap((step) => fieldsOf(step.children)),\n ctx.components,\n )\n checkLayoutComponents(layout, layoutCtx.layoutComponents, layoutCtx.slots)\n\n const index = props.step ?? steps.index\n const step = steps.list[index]\n if (!step) throw new RangeError(`No step ${props.step}. The form has ${steps.list.length}.`)\n // Keyed by the step, so another step's fields don't take over this one's components.\n return <Fragment key={index}>{renderLayout(layoutCtx, step.children, [])}</Fragment>\n}\n\n/** Every field of the nodes, display elements written in the layout included. */\nfunction fieldsOf(nodes: readonly LayoutNode[]): FieldNode[] {\n return nodes.flatMap((node) => {\n if (node.type === 'slot') return []\n const inner = fieldsOf(node.children)\n return node.type === 'field' ? [node.node, ...inner] : inner\n })\n}\n","import { type FormSchema, type FormStore, useForm, type ValidationMode } from '@formisch/react'\nimport {\n createSchemaBuilder,\n type FieldNode,\n type FormLayout,\n type JsonForm,\n type JsonFormSchema,\n type SchemaBuilder,\n type TypedJsonFormSchema,\n} from '@whatsaaaa/form-builder-schema'\nimport { useMemo, useState } from 'react'\nimport type { FieldComponents, LayoutComponents } from './field-components'\nimport { type JsonFormSteps, useJsonFormSteps } from './json-form-steps'\n\nexport interface UseJsonFormOptions {\n /** Builds the schema and knows your own types and rules. Defaults to `createSchemaBuilder()`. */\n builder?: SchemaBuilder\n /** Your component for each field type, so `<JsonFields>` and `<JsonField>` don't need them. */\n components?: FieldComponents\n /** Your component for each layout type the definition's `layout` uses, like `section`. */\n layoutComponents?: LayoutComponents\n /** When a field is validated the first time, as in Formisch. */\n validate?: ValidationMode\n /** When a field is validated again after that, as in Formisch. */\n revalidate?: Exclude<ValidationMode, 'initial'>\n}\n\n/** What `useJsonForm` returns. Pass it to `<JsonFields>`. */\nexport interface JsonFormInstance<TSchema extends FormSchema = JsonFormSchema> {\n /** The Formisch form: pass it to Formisch's `<Form of>`, or to any of its methods. */\n readonly form: FormStore<TSchema>\n readonly definition: JsonForm\n readonly builder: SchemaBuilder\n /** The components from the options, if any. */\n readonly components: FieldComponents | undefined\n /** The layout components from the options, if any. */\n readonly layoutComponents: LayoutComponents | undefined\n /** The definition's fields as a tree, for `<JsonFields only>` and `<JsonField>`. */\n readonly fields: readonly FieldNode[]\n /** The definition's layout, the way `<JsonFields>` renders it. */\n readonly layout: FormLayout\n /** The layout's steps: the current one, and Next, Back and Go to. A form without steps has one. */\n readonly steps: JsonFormSteps\n}\n\n/** Any form `useJsonForm` returns, typed or not: what the components take. */\nexport type AnyJsonFormInstance = JsonFormInstance<FormSchema>\n\n/**\n * A Formisch form for a definition, with its schema and initial input from the builder. A\n * definition with issues throws the builder's `DefinitionError`, so run `validate()` first when it\n * comes from outside your code. Like Formisch's `useForm`, it reads the definition, the builder and\n * the validation modes on the first render: give the component a new `key` to use another\n * definition. The components and layout components are read on every render.\n */\nexport function useJsonForm(definition: JsonForm, options?: UseJsonFormOptions): JsonFormInstance\n/**\n * The same, with the form typed by your input type, and output type when it differs, so Formisch's\n * paths and values are checked. The types are yours to keep in step with the definition.\n */\nexport function useJsonForm<TInput extends object, TOutput extends object = TInput>(\n definition: JsonForm,\n options?: UseJsonFormOptions,\n): JsonFormInstance<TypedJsonFormSchema<TInput, TOutput>>\nexport function useJsonForm(\n definition: JsonForm,\n options: UseJsonFormOptions = {},\n): JsonFormInstance {\n // Formisch reads its config on the first render only, so the schema is built once too.\n const [built] = useState(() => {\n const builder = options.builder ?? createSchemaBuilder()\n return {\n definition,\n builder,\n schema: builder.build(definition),\n initialInput: builder.getInitialInput(definition),\n fields: builder.getFieldTree(definition),\n layout: builder.getLayout(definition),\n }\n })\n const form = useForm({\n schema: built.schema,\n initialInput: built.initialInput,\n validate: options.validate,\n revalidate: options.revalidate,\n })\n const steps = useJsonFormSteps(form, built.layout, options)\n const { components, layoutComponents } = options\n return useMemo(\n () => ({\n form,\n definition: built.definition,\n builder: built.builder,\n components,\n layoutComponents,\n fields: built.fields,\n layout: built.layout,\n steps,\n }),\n [form, built, components, layoutComponents, steps],\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Display","Group"],"sources":["../src/json-form-steps.ts","../src/array-field.tsx","../src/value-field.tsx","../src/render-fields.tsx","../src/json-field.tsx","../src/render-layout.tsx","../src/json-fields.tsx","../src/use-visibility.ts","../src/use-json-form.ts"],"sourcesContent":["import {\n focus,\n type FormStore,\n getErrors,\n getInput,\n setErrors,\n validate,\n type ValidationMode,\n} from '@formisch/react'\nimport {\n type FieldPath,\n fieldPathsOf,\n type FormLayout,\n issuesOfStep,\n type LayoutStep,\n type StepIssue,\n type VisibilityState,\n} from '@whatsaaaa/form-builder-schema'\nimport { useEffect, useMemo, useRef, useState } from 'react'\n\n/** The steps of a form, from its layout. A form without `steps` in its layout has one. */\nexport interface JsonFormSteps {\n /**\n * The steps on show, in order. A step whose fields are all hidden by conditions, or that is\n * hidden itself, is left out, unless it is the current one: Next and Back skip it.\n */\n readonly list: readonly LayoutStep[]\n /** The index of the current step in `list`. */\n readonly index: number\n /** The step `<JsonFields>` renders. */\n readonly current: LayoutStep\n readonly isFirst: boolean\n readonly isLast: boolean\n /**\n * Validates the whole form, and moves to the next step when the fields of this one have no\n * issues. Otherwise it stays, and focuses the first field with an issue, in layout order.\n * Resolves to whether it moved.\n */\n next(): Promise<boolean>\n /** Moves to the step before, without validating. */\n back(): void\n /**\n * Moves to the step at `index`. Back is free, and forward validates every step on the way, like\n * `next()`, and stops at the first with issues. Resolves to whether it got there.\n */\n goTo(index: number): Promise<boolean>\n}\n\n/** A field's error messages in Formisch, or null. */\ntype Errors = [string, ...string[]] | null\n\n/** What a field tells the steps it was through, by Formisch's name for the validation mode. */\nexport type FieldEvent = Exclude<ValidationMode, 'initial' | 'submit'>\n\n/** Revalidates a step that failed Next on each field event, like a form after a failed submit. */\ntype FieldEvents = (event: FieldEvent) => void\n\nconst fieldEvents = new WeakMap<FormStore, FieldEvents>()\n\n/** What a field of `form` calls on each event, for a form with steps. Undefined with one step. */\nexport const fieldEventsOf = (form: FormStore): FieldEvents | undefined => fieldEvents.get(form)\n\ninterface Modes {\n validate?: ValidationMode | undefined\n revalidate?: Exclude<ValidationMode, 'initial'> | undefined\n}\n\n/**\n * The steps of `layout`, for `useJsonForm`. With more than one, a step shows the errors it had\n * when the user left it, and none the first time they get to it, a step that failed Next\n * revalidates as the user fixes it, a submit before the last step moves on like Next, and a submit\n * that fails because of an earlier step goes back to it. The steps `visibility` hides are skipped.\n * Like Formisch's `useForm`, it reads the validation modes on the first render.\n */\nexport function useJsonFormSteps(\n form: FormStore,\n layout: FormLayout,\n modes: Modes,\n visibility: VisibilityState,\n): JsonFormSteps {\n const all = layout.steps\n // The current step, by its index in `all`, as are the steps in `state` below.\n const [index, setIndex] = useState(0)\n // What the async methods and the event handlers read: always the latest.\n const state = useRef({\n index: 0,\n // The errors each step had when the user left it, by the JSON of each field's path.\n left: new Map<number, Map<string, Errors>>(),\n // The steps that failed Next, and haven't passed since.\n failed: new Set<number>(),\n // The field to focus once the step it is on has rendered.\n focus: undefined as FieldPath | undefined,\n // Whether a submit on the last step is on its way through Formisch.\n submitting: false,\n modes,\n })\n\n const methods = useMemo(() => {\n const at = state.current\n // The current step stays on show, so it never goes from under the user.\n const shown = () => all.filter((step, i) => i === at.index || !visibility.isStepHidden(step))\n const position = (list: readonly LayoutStep[]) => list.indexOf(all[at.index] as LayoutStep)\n\n async function goTo(target: number): Promise<boolean> {\n const list = shown()\n if (!Number.isInteger(target) || target < 0 || target >= list.length) {\n throw new RangeError(`No step ${target}. The form has ${list.length}.`)\n }\n const to = all.indexOf(list[target] as LayoutStep)\n if (target <= position(list)) {\n move(to)\n return true\n }\n // Next goes on once the task that called it is over. A click runs its default action after its\n // handlers, and React can render in between: a Next button that turns into the submit button on\n // the last step would then submit the form as the user gets there.\n await afterTask()\n const { issues } = await validate(form)\n for (const step of list.slice(position(list), target)) {\n const found = issuesOfStep(step, issues)\n if (found.length) {\n stop(all.indexOf(step), found)\n return false\n }\n at.failed.delete(all.indexOf(step))\n }\n move(to)\n return true\n }\n\n /**\n * Moves to a step as the user left it: with the errors it had then, and none the first time, and\n * revalidating if it failed Next. Validating the steps before set errors on its fields, which\n * the user never saw.\n */\n function move(target: number) {\n leave()\n const errors = at.left.get(target)\n for (const path of fieldPathsOf(all[target] as LayoutStep, getInput(form))) {\n setErrors(form, { path, errors: errors?.get(JSON.stringify(path)) ?? null } as never)\n }\n at.index = target\n setIndex(target)\n }\n\n /** Keeps the errors of the current step, for when the user comes back to it. */\n function leave() {\n const paths = fieldPathsOf(all[at.index] as LayoutStep, getInput(form))\n const errors = paths.map((path): [string, Errors] => [\n JSON.stringify(path),\n getErrors(form, { path } as never),\n ])\n at.left.set(at.index, new Map(errors))\n }\n\n /** Stays on, or goes to, the step at `target`, with its issues shown. */\n function stop(target: number, found: readonly StepIssue[]) {\n at.failed.add(target)\n // A group or an array has no element: the first field that has one gets the focus.\n const path = found.find((issue) => issue.field.role === 'value')?.path\n if (target === at.index) {\n if (path) focus(form, { path: path as never })\n return\n }\n leave()\n at.focus = path\n at.index = target\n setIndex(target)\n }\n\n const events: FieldEvents = (event) => {\n // In the other modes, Formisch revalidates a field with errors itself.\n const validateMode = at.modes.validate ?? 'submit'\n if (!at.failed.has(at.index) || validateMode !== 'submit' || form.isSubmitted) return\n if (event === (at.modes.revalidate ?? 'input')) void validate(form)\n }\n\n /** After a failed submit: back to the first step before this one with issues, if any. */\n async function afterSubmit() {\n const { issues } = await validate(form)\n const list = shown()\n const step = list.find((each) => issuesOfStep(each, issues).length)\n if (step && list.indexOf(step) < position(list)) {\n stop(all.indexOf(step), issuesOfStep(step, issues))\n }\n }\n\n const isLast = () => {\n const list = shown()\n return position(list) === list.length - 1\n }\n const next = () => (isLast() ? Promise.resolve(false) : goTo(position(shown()) + 1))\n const back = () => {\n const list = shown()\n const before = list[position(list) - 1]\n if (before) move(all.indexOf(before))\n }\n return { goTo, next, back, isLast, events, afterSubmit }\n }, [form, all, visibility])\n\n // The field a step was stopped on, once the step has rendered.\n useEffect(() => {\n const path = state.current.focus\n state.current.focus = undefined\n if (path) focus(form, { path: path as never })\n }, [form, index])\n\n const stepped = all.length > 1\n useEffect(() => {\n if (!stepped) return\n fieldEvents.set(form, methods.events)\n // Before the last step, a submit, like Enter in a step with a single text input, is a Next.\n // It is caught on its way down to the form, before Formisch's own handler sees it.\n const onSubmit = (event: Event) => {\n if (event.target !== formElementOf(form)) return\n if (methods.isLast()) {\n state.current.submitting = true\n return\n }\n event.preventDefault()\n event.stopImmediatePropagation()\n void methods.next()\n }\n document.addEventListener('submit', onSubmit, { capture: true })\n return () => {\n fieldEvents.delete(form)\n document.removeEventListener('submit', onSubmit, { capture: true })\n }\n }, [form, methods, stepped])\n\n // A submit that fails because of a step before the last, e.g. on a form rule, goes back to it.\n // React can batch the start and the end of a submit into one render, so the submit is marked\n // as it starts, and each render checks whether Formisch has finished it.\n const submitting = form.isSubmitting\n useEffect(() => {\n if (!state.current.submitting || submitting) return\n state.current.submitting = false\n if (!form.isValid) void methods.afterSubmit()\n })\n\n return useMemo(() => {\n const list = all.filter((step, i) => i === index || !visibility.isStepHidden(step))\n const current = all[index] as LayoutStep\n const at = list.indexOf(current)\n return {\n list,\n index: at,\n current,\n isFirst: at === 0,\n isLast: at === list.length - 1,\n next: methods.next,\n back: methods.back,\n goTo: methods.goTo,\n }\n }, [all, index, visibility, methods])\n}\n\n/** Resolves once the task that called it is over, and the browser is done with its event. */\nconst afterTask = () => new Promise<void>((resolve) => setTimeout(resolve))\n\n/** The `<form>` Formisch's `<Form>` renders, which it keeps on the store for its `submit()`. */\nfunction formElementOf(form: FormStore): HTMLFormElement | undefined {\n return (form as unknown as { '~internal'?: { element?: HTMLFormElement } })['~internal']?.element\n}\n","import { insert, move, remove, useFieldArray } from '@formisch/react'\nimport type { FieldNode, FieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, type ReactElement, type ReactNode, useMemo } from 'react'\nimport type { ArrayFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\ninterface ArrayFieldConnectionProps {\n of: AnyJsonFormInstance\n node: FieldNode\n path: FieldPath\n component: ComponentType<ArrayFieldProps>\n /** Renders the fields of the item at `index`. */\n renderItem: (index: number) => ReactNode\n}\n\n/** Connects a container of kind `array` to Formisch, and renders your component for it. */\nexport function ArrayField({\n of,\n node,\n path,\n component: Component,\n renderItem,\n}: ArrayFieldConnectionProps): ReactElement {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const array = useFieldArray(of.form, { path: path as never })\n\n // Formisch keeps the path it got while the array is the same, so the methods stay the same too.\n const { form, builder } = of\n const { field } = node\n const at = array.path\n // In a form with steps, a step that failed Next revalidates on a change of its items too.\n const methods = useMemo(\n () => ({\n // A new item starts with the defaults of its fields, like the items of a new form.\n add: () => {\n insert(form, { path: at, initialInput: builder.getItemInput(field) as never })\n fieldEventsOf(form)?.('input')\n },\n remove: (index: number) => {\n remove(form, { path: at, at: index })\n fieldEventsOf(form)?.('input')\n },\n move: (from: number, to: number) => {\n move(form, { path: at, from, to })\n fieldEventsOf(form)?.('input')\n },\n }),\n [form, builder, field, at],\n )\n\n return (\n <Component field={field} path={path} items={array.items} errors={array.errors} {...methods}>\n {renderItem}\n </Component>\n )\n}\n","import { useField } from '@formisch/react'\nimport {\n coerceInput,\n type FieldNode,\n type FieldPath,\n isRequired,\n} from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, type ReactElement, useCallback } from 'react'\nimport type { ValueFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\ninterface ValueFieldConnectionProps {\n of: AnyJsonFormInstance\n node: FieldNode\n path: FieldPath\n component: ComponentType<ValueFieldProps>\n}\n\n/** Connects a field with a value to Formisch, and renders your component for it. */\nexport function ValueField({\n of,\n node,\n path,\n component: Component,\n}: ValueFieldConnectionProps): ReactElement {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const field = useField(of.form, { path: path as never })\n const { form } = of\n const setInput = useCallback(\n (input: unknown) => {\n field.onChange(coerceInput(input, node) as never)\n fieldEventsOf(form)?.('input')\n },\n [field, node, form],\n )\n // In a form with steps, a step that failed Next revalidates on these events too.\n const inputProps =\n of.steps.list.length > 1\n ? {\n ...field.props,\n onFocus: () => {\n field.props.onFocus()\n fieldEventsOf(form)?.('touch')\n },\n onChange: (event: Parameters<typeof field.props.onChange>[0]) => {\n field.props.onChange(event)\n fieldEventsOf(form)?.('input')\n fieldEventsOf(form)?.('change')\n },\n onBlur: () => {\n field.props.onBlur()\n fieldEventsOf(form)?.('blur')\n },\n }\n : field.props\n\n return (\n <Component\n field={node.field}\n path={path}\n kind={node.kind}\n required={isRequired(node.field)}\n input={field.input}\n errors={field.errors}\n inputProps={inputProps}\n setInput={setInput}\n />\n )\n}\n","import { dotPathOf, type FieldNode, toFieldPath } from '@whatsaaaa/form-builder-schema'\nimport type { ComponentType, ReactElement, ReactNode } from 'react'\nimport { ArrayField } from './array-field'\nimport type {\n ArrayFieldProps,\n DisplayFieldProps,\n FieldComponents,\n GroupFieldProps,\n ValueFieldProps,\n} from './field-components'\nimport type { AnyJsonFormInstance } from './use-json-form'\nimport { ValueField } from './value-field'\n\n/** What `<JsonFields>` and `<JsonField>` render with. */\nexport interface RenderContext {\n of: AnyJsonFormInstance\n components: FieldComponents\n}\n\n/** The components from the props, or else the ones `useJsonForm` got. */\nexport function resolveComponents(\n components: FieldComponents | undefined,\n of: AnyJsonFormInstance,\n): FieldComponents {\n const resolved = components ?? of.components\n if (!resolved) {\n throw new TypeError('No field components. Pass them to useJsonForm, or to the component.')\n }\n return resolved\n}\n\n/**\n * Renders the fields that conditions don't hide. `indexes` are the indexes of the array items the\n * fields are in, outermost first.\n */\nexport function renderFields(\n ctx: RenderContext,\n nodes: readonly FieldNode[],\n indexes: readonly number[],\n): ReactElement[] {\n const { visibility } = ctx.of\n // A display element is keyed by its position among all the nodes, which stays the same.\n return nodes.flatMap((node, position) =>\n visibility.isHidden(node, indexes)\n ? []\n : [\n renderField(ctx, node, indexes, dotPathOf(node) ?? `#${position}`, (inner) =>\n renderFields(ctx, node.children, inner),\n ),\n ],\n )\n}\n\n/**\n * Renders one field with its component. `children` renders the fields inside a group, or inside\n * the item of an array, with the indexes of the items they are in.\n */\nexport function renderField(\n ctx: RenderContext,\n node: FieldNode,\n indexes: readonly number[],\n key: string,\n children: (indexes: readonly number[]) => ReactNode,\n): ReactElement {\n // The fields were checked for components, and the role tells which props the component takes.\n const component = ctx.components[node.type]\n const path = toFieldPath(node.dataPath, indexes)\n switch (node.role) {\n case 'display': {\n const Display = component as ComponentType<DisplayFieldProps>\n return <Display key={key} field={node.field} />\n }\n case 'group': {\n const Group = component as ComponentType<GroupFieldProps>\n return (\n <Group key={key} field={node.field} path={path}>\n {children(indexes)}\n </Group>\n )\n }\n case 'array':\n return (\n <ArrayField\n key={key}\n of={ctx.of}\n node={node}\n path={path}\n component={component as ComponentType<ArrayFieldProps>}\n renderItem={(index) => children([...indexes, index])}\n />\n )\n case 'value':\n return (\n <ValueField\n key={key}\n of={ctx.of}\n node={node}\n path={path}\n component={component as ComponentType<ValueFieldProps>}\n />\n )\n }\n}\n","import { checkComponents, dotPathOf, findField } from '@whatsaaaa/form-builder-schema'\nimport type { ReactElement } from 'react'\nimport type { FieldComponents } from './field-components'\nimport { type RenderContext, renderField, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\nexport interface JsonFieldProps {\n /** What `useJsonForm` returned. */\n of: AnyJsonFormInstance\n /** The field's path: `'email'`, or `'address.city'` for a field of a group. */\n path: string\n /** Defaults to the components `useJsonForm` got. */\n components?: FieldComponents | undefined\n}\n\n/**\n * Renders one field of a `useJsonForm` form, wherever you put it inside Formisch's `<Form>`. A\n * group or an array comes with its fields. Leave the field out of `<JsonFields>` with `except`.\n * It renders nothing while conditions hide the field.\n */\nexport function JsonField({ of, path, components }: JsonFieldProps): ReactElement | null {\n const node = findField(of.fields, path, { placed: true })\n const ctx: RenderContext = { of, components: resolveComponents(components, of) }\n checkComponents([node], ctx.components)\n\n if (of.visibility.isHidden(node, [])) return null\n return renderField(ctx, node, [], dotPathOf(node) ?? '#0', (indexes) =>\n renderFields(ctx, node.children, indexes),\n )\n}\n","import type { IsFieldShown, LayoutNode } from '@whatsaaaa/form-builder-schema'\nimport { type ComponentType, Fragment, type ReactElement, type ReactNode } from 'react'\nimport type { LayoutComponents, RowProps, SectionProps } from './field-components'\nimport { type RenderContext, renderField } from './render-fields'\n\n/** What `<JsonFields>` renders a layout with. */\nexport interface LayoutContext extends RenderContext {\n layoutComponents: LayoutComponents\n /** The content of the layout's slots, by name. */\n slots: Readonly<Record<string, ReactNode>>\n /** False for a field left out with `except`. */\n isShown: IsFieldShown\n}\n\n/**\n * Renders layout nodes. A field left out or hidden by conditions drops out, and so does a section\n * hidden by them, and a section or a row left without fields. `indexes` are the indexes of the\n * array items the nodes are in, outermost first.\n */\nexport function renderLayout(\n ctx: LayoutContext,\n nodes: readonly LayoutNode[],\n indexes: readonly number[],\n): ReactElement[] {\n return ctx.of.visibility\n .shownNodes(nodes, indexes, ctx.isShown)\n .map((node) => renderNode(ctx, node, indexes))\n}\n\nfunction renderNode(\n ctx: LayoutContext,\n node: LayoutNode,\n indexes: readonly number[],\n): ReactElement {\n const { key } = node\n switch (node.type) {\n case 'field':\n return renderField(ctx, node.node, indexes, key, (inner) =>\n renderLayout(ctx, node.children, inner),\n )\n case 'section': {\n // The layout was checked for components.\n const Section = ctx.layoutComponents.section as ComponentType<SectionProps>\n return (\n <Section key={key} section={node.section}>\n {renderLayout(ctx, node.children, indexes)}\n </Section>\n )\n }\n case 'row': {\n const Row = ctx.layoutComponents.row as ComponentType<RowProps>\n return (\n <Row key={key} row={node.row} spans={node.children.map((cell) => cell.span)}>\n {(index) => renderNode(ctx, node.children[index] as LayoutNode, indexes)}\n </Row>\n )\n }\n case 'slot':\n return <Fragment key={key}>{ctx.slots[node.name]}</Fragment>\n }\n}\n","import {\n checkComponents,\n checkLayoutComponents,\n dotPathOf,\n type FieldNode,\n type LayoutNode,\n selectFields,\n} from '@whatsaaaa/form-builder-schema'\nimport { Fragment, type ReactElement, type ReactNode } from 'react'\nimport type { FieldComponents, LayoutComponents } from './field-components'\nimport { type LayoutContext, renderLayout } from './render-layout'\nimport { type RenderContext, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\nexport interface JsonFieldsProps {\n /** What `useJsonForm` returned. */\n of: AnyJsonFormInstance\n /** Defaults to the components `useJsonForm` got. */\n components?: FieldComponents | undefined\n /** Defaults to the layout components `useJsonForm` got. */\n layoutComponents?: LayoutComponents | undefined\n /** The content of the layout's slots, by name: `{ submit: <button type=\"submit\">Save</button> }`. */\n slots?: Readonly<Record<string, ReactNode>> | undefined\n /** Renders only these fields, by path, in the order of the definition, without the layout. */\n only?: readonly string[] | undefined\n /**\n * Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. A section or a\n * row left without fields drops out too.\n */\n except?: readonly string[] | undefined\n /** Renders the step at this index instead of the current one, e.g. on a review page. */\n step?: number | undefined\n}\n\n/**\n * Renders the fields of a `useJsonForm` form with your components, one per field type, where the\n * definition's `layout` puts them: the current step, with its sections, rows and slots. It renders\n * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button.\n */\nexport function JsonFields(props: JsonFieldsProps): ReactElement {\n const { of, only, except } = props\n const ctx: RenderContext = { of, components: resolveComponents(props.components, of) }\n const nodes = selectFields(of.fields, { only, except })\n // Without a layout, it renders the fields the way it always did, and so with `only`.\n if (only || !of.definition.layout) {\n checkComponents(nodes, ctx.components)\n return <>{renderFields(ctx, nodes, [])}</>\n }\n\n const { layout, steps } = of\n const left = new Set(except)\n const layoutCtx: LayoutContext = {\n ...ctx,\n layoutComponents: props.layoutComponents ?? of.layoutComponents ?? {},\n slots: props.slots ?? {},\n isShown: (node) => !left.has(dotPathOf(node) ?? ''),\n }\n checkComponents(\n layout.steps.flatMap((step) => fieldsOf(step.children)),\n ctx.components,\n )\n checkLayoutComponents(layout, layoutCtx.layoutComponents, layoutCtx.slots)\n\n const index = props.step ?? steps.index\n const step = steps.list[index]\n if (!step) throw new RangeError(`No step ${props.step}. The form has ${steps.list.length}.`)\n // Keyed by the step, so another step's fields don't take over this one's components.\n return <Fragment key={index}>{renderLayout(layoutCtx, step.children, [])}</Fragment>\n}\n\n/** Every field of the nodes, display elements written in the layout included. */\nfunction fieldsOf(nodes: readonly LayoutNode[]): FieldNode[] {\n return nodes.flatMap((node) => {\n if (node.type === 'slot') return []\n const inner = fieldsOf(node.children)\n return node.type === 'field' ? [node.node, ...inner] : inner\n })\n}\n","import type { FormStore } from '@formisch/react'\nimport {\n getFieldInput,\n INTERNAL,\n type Listener,\n setListener,\n untrack,\n} from '@formisch/react/internals'\nimport type { Visibility, VisibilityState } from '@whatsaaaa/form-builder-schema'\nimport { useCallback, useSyncExternalStore } from 'react'\n\n/**\n * What the conditions of the definition hide for the form's input, as it changes. Formisch\n * re-renders a component for each signal it reads, so reading the whole input in `useJsonForm`\n * would re-render the form on every keystroke. This watches the input outside React instead, and\n * re-renders only when the state changes: `of()` gives the same object until then. Without\n * conditions, it watches nothing.\n */\nexport function useVisibility(form: FormStore, visibility: Visibility): VisibilityState {\n const subscribe = useCallback(\n (onChange: () => void) => (visibility.conditional ? watchInput(form, onChange) : () => {}),\n [form, visibility],\n )\n const getSnapshot = () =>\n visibility.of(visibility.conditional ? untrack(() => getFieldInput(form[INTERNAL])) : undefined)\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/**\n * Calls `onChange` after each change of the form's input, until the function it returns is called.\n * It reads the input as Formisch's own components do, with a listener: a signal that changes calls\n * it once, so it reads the input again each time, the items of new array items too.\n */\nfunction watchInput(form: FormStore, onChange: () => void): () => void {\n const listener: Listener = [\n () => {\n read()\n onChange()\n },\n new Set(),\n ]\n const stop = () => {\n for (const subscribers of listener[1]) subscribers.delete(listener)\n listener[1].clear()\n }\n function read() {\n stop()\n // untrack() sets back the listener of a component that may be rendering now.\n untrack(() => {\n setListener(listener)\n try {\n getFieldInput(form[INTERNAL])\n } finally {\n setListener(undefined)\n }\n })\n }\n read()\n return stop\n}\n","import {\n type FormSchema,\n type FormStore,\n setInput,\n useForm,\n type ValidationMode,\n} from '@formisch/react'\nimport {\n createSchemaBuilder,\n type FieldNode,\n type FormLayout,\n type JsonForm,\n type JsonFormSchema,\n type SchemaBuilder,\n type TypedJsonFormSchema,\n type VisibilityState,\n} from '@whatsaaaa/form-builder-schema'\nimport { useEffect, useMemo, useState } from 'react'\nimport type { FieldComponents, LayoutComponents } from './field-components'\nimport { type JsonFormSteps, useJsonFormSteps } from './json-form-steps'\nimport { useVisibility } from './use-visibility'\n\nexport interface UseJsonFormOptions {\n /** Builds the schema and knows your own types and rules. Defaults to `createSchemaBuilder()`. */\n builder?: SchemaBuilder\n /** Your component for each field type, so `<JsonFields>` and `<JsonField>` don't need them. */\n components?: FieldComponents\n /** Your component for each layout type the definition's `layout` uses, like `section`. */\n layoutComponents?: LayoutComponents\n /** When a field is validated the first time, as in Formisch. */\n validate?: ValidationMode\n /** When a field is validated again after that, as in Formisch. */\n revalidate?: Exclude<ValidationMode, 'initial'>\n}\n\n/** What `useJsonForm` returns. Pass it to `<JsonFields>`. */\nexport interface JsonFormInstance<TSchema extends FormSchema = JsonFormSchema> {\n /** The Formisch form: pass it to Formisch's `<Form of>`, or to any of its methods. */\n readonly form: FormStore<TSchema>\n readonly definition: JsonForm\n readonly builder: SchemaBuilder\n /** The components from the options, if any. */\n readonly components: FieldComponents | undefined\n /** The layout components from the options, if any. */\n readonly layoutComponents: LayoutComponents | undefined\n /** The definition's fields as a tree, for `<JsonFields only>` and `<JsonField>`. */\n readonly fields: readonly FieldNode[]\n /** The definition's layout, the way `<JsonFields>` renders it. */\n readonly layout: FormLayout\n /** The layout's steps: the current one, and Next, Back and Go to. A form without steps has one. */\n readonly steps: JsonFormSteps\n /**\n * What the conditions of the builder's extensions hide for the form's input now, like the\n * `affects` of `@whatsaaaa/form-builder-affects`: `<JsonFields>` and `<JsonField>` leave it out.\n * Read it in your own renderers. A new object only when what is hidden changes, so the form\n * re-renders for that, not for every keystroke. Without conditions, it hides nothing.\n */\n readonly visibility: VisibilityState\n}\n\n/** Any form `useJsonForm` returns, typed or not: what the components take. */\nexport type AnyJsonFormInstance = JsonFormInstance<FormSchema>\n\n/**\n * A Formisch form for a definition, with its schema and initial input from the builder. A\n * definition with issues throws the builder's `DefinitionError`, so run `validate()` first when it\n * comes from outside your code. Like Formisch's `useForm`, it reads the definition, the builder and\n * the validation modes on the first render: give the component a new `key` to use another\n * definition. The components and layout components are read on every render. With conditions, it\n * clears the fields they clear as soon as they do, with Formisch's `setInput`, wherever the fields\n * are rendered.\n */\nexport function useJsonForm(definition: JsonForm, options?: UseJsonFormOptions): JsonFormInstance\n/**\n * The same, with the form typed by your input type, and output type when it differs, so Formisch's\n * paths and values are checked. The types are yours to keep in step with the definition.\n */\nexport function useJsonForm<TInput extends object, TOutput extends object = TInput>(\n definition: JsonForm,\n options?: UseJsonFormOptions,\n): JsonFormInstance<TypedJsonFormSchema<TInput, TOutput>>\nexport function useJsonForm(\n definition: JsonForm,\n options: UseJsonFormOptions = {},\n): JsonFormInstance {\n // Formisch reads its config on the first render only, so the schema is built once too.\n const [built] = useState(() => {\n const builder = options.builder ?? createSchemaBuilder()\n return {\n definition,\n builder,\n schema: builder.build(definition),\n initialInput: builder.getInitialInput(definition),\n fields: builder.getFieldTree(definition),\n layout: builder.getLayout(definition),\n visibility: builder.getVisibility(definition),\n }\n })\n const form = useForm({\n schema: built.schema,\n initialInput: built.initialInput,\n validate: options.validate,\n revalidate: options.revalidate,\n })\n const visibility = useVisibility(form, built.visibility)\n // A new state means something to clear may have come. It is empty once the fields are blank.\n const { toClear } = visibility\n useEffect(() => {\n // The paths come from the definition at runtime, so the schema's types can't check them.\n for (const { path, input } of toClear) setInput(form, { path, input } as never)\n }, [form, toClear])\n const steps = useJsonFormSteps(form, built.layout, options, visibility)\n const { components, layoutComponents } = options\n return useMemo(\n () => ({\n form,\n definition: built.definition,\n builder: built.builder,\n components,\n layoutComponents,\n fields: built.fields,\n layout: built.layout,\n steps,\n visibility,\n }),\n [form, built, components, layoutComponents, steps, visibility],\n )\n}\n"],"mappings":";;;;;;AAyDA,MAAM,8BAAc,IAAI,QAAgC;;AAGxD,MAAa,iBAAiB,SAA6C,YAAY,IAAI,IAAI;;;;;;;;AAc/F,SAAgB,iBACd,MACA,QACA,OACA,YACe;CACf,MAAM,MAAM,OAAO;CAEnB,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CAEpC,MAAM,QAAQ,OAAO;EACnB,OAAO;EAEP,sBAAM,IAAI,IAAiC;EAE3C,wBAAQ,IAAI,IAAY;EAExB,OAAO,KAAA;EAEP,YAAY;EACZ;CACF,CAAC;CAED,MAAM,UAAU,cAAc;EAC5B,MAAM,KAAK,MAAM;EAEjB,MAAM,cAAc,IAAI,QAAQ,MAAM,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,aAAa,IAAI,CAAC;EAC5F,MAAM,YAAY,SAAgC,KAAK,QAAQ,IAAI,GAAG,MAAoB;EAE1F,eAAe,KAAK,QAAkC;GACpD,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,KAAK,UAAU,KAAK,QAC5D,MAAM,IAAI,WAAW,WAAW,OAAO,iBAAiB,KAAK,OAAO,EAAE;GAExE,MAAM,KAAK,IAAI,QAAQ,KAAK,OAAqB;GACjD,IAAI,UAAU,SAAS,IAAI,GAAG;IAC5B,KAAK,EAAE;IACP,OAAO;GACT;GAIA,MAAM,UAAU;GAChB,MAAM,EAAE,WAAW,MAAM,SAAS,IAAI;GACtC,KAAK,MAAM,QAAQ,KAAK,MAAM,SAAS,IAAI,GAAG,MAAM,GAAG;IACrD,MAAM,QAAQ,aAAa,MAAM,MAAM;IACvC,IAAI,MAAM,QAAQ;KAChB,KAAK,IAAI,QAAQ,IAAI,GAAG,KAAK;KAC7B,OAAO;IACT;IACA,GAAG,OAAO,OAAO,IAAI,QAAQ,IAAI,CAAC;GACpC;GACA,KAAK,EAAE;GACP,OAAO;EACT;;;;;;EAOA,SAAS,KAAK,QAAgB;GAC5B,MAAM;GACN,MAAM,SAAS,GAAG,KAAK,IAAI,MAAM;GACjC,KAAK,MAAM,QAAQ,aAAa,IAAI,SAAuB,SAAS,IAAI,CAAC,GACvE,UAAU,MAAM;IAAE;IAAM,QAAQ,QAAQ,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;GAAK,CAAU;GAEtF,GAAG,QAAQ;GACX,SAAS,MAAM;EACjB;;EAGA,SAAS,QAAQ;GAEf,MAAM,SADQ,aAAa,IAAI,GAAG,QAAsB,SAAS,IAAI,CAClD,CAAC,CAAC,KAAK,SAA2B,CACnD,KAAK,UAAU,IAAI,GACnB,UAAU,MAAM,EAAE,KAAK,CAAU,CACnC,CAAC;GACD,GAAG,KAAK,IAAI,GAAG,OAAO,IAAI,IAAI,MAAM,CAAC;EACvC;;EAGA,SAAS,KAAK,QAAgB,OAA6B;GACzD,GAAG,OAAO,IAAI,MAAM;GAEpB,MAAM,OAAO,MAAM,MAAM,UAAU,MAAM,MAAM,SAAS,OAAO,CAAC,EAAE;GAClE,IAAI,WAAW,GAAG,OAAO;IACvB,IAAI,MAAM,MAAM,MAAM,EAAQ,KAAc,CAAC;IAC7C;GACF;GACA,MAAM;GACN,GAAG,QAAQ;GACX,GAAG,QAAQ;GACX,SAAS,MAAM;EACjB;EAEA,MAAM,UAAuB,UAAU;GAErC,MAAM,eAAe,GAAG,MAAM,YAAY;GAC1C,IAAI,CAAC,GAAG,OAAO,IAAI,GAAG,KAAK,KAAK,iBAAiB,YAAY,KAAK,aAAa;GAC/E,IAAI,WAAW,GAAG,MAAM,cAAc,UAAU,SAAc,IAAI;EACpE;;EAGA,eAAe,cAAc;GAC3B,MAAM,EAAE,WAAW,MAAM,SAAS,IAAI;GACtC,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,KAAK,MAAM,SAAS,aAAa,MAAM,MAAM,CAAC,CAAC,MAAM;GAClE,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI,SAAS,IAAI,GAC5C,KAAK,IAAI,QAAQ,IAAI,GAAG,aAAa,MAAM,MAAM,CAAC;EAEtD;EAEA,MAAM,eAAe;GACnB,MAAM,OAAO,MAAM;GACnB,OAAO,SAAS,IAAI,MAAM,KAAK,SAAS;EAC1C;EACA,MAAM,aAAc,OAAO,IAAI,QAAQ,QAAQ,KAAK,IAAI,KAAK,SAAS,MAAM,CAAC,IAAI,CAAC;EAClF,MAAM,aAAa;GACjB,MAAM,OAAO,MAAM;GACnB,MAAM,SAAS,KAAK,SAAS,IAAI,IAAI;GACrC,IAAI,QAAQ,KAAK,IAAI,QAAQ,MAAM,CAAC;EACtC;EACA,OAAO;GAAE;GAAM;GAAM;GAAM;GAAQ;GAAQ;EAAY;CACzD,GAAG;EAAC;EAAM;EAAK;CAAU,CAAC;CAG1B,gBAAgB;EACd,MAAM,OAAO,MAAM,QAAQ;EAC3B,MAAM,QAAQ,QAAQ,KAAA;EACtB,IAAI,MAAM,MAAM,MAAM,EAAQ,KAAc,CAAC;CAC/C,GAAG,CAAC,MAAM,KAAK,CAAC;CAEhB,MAAM,UAAU,IAAI,SAAS;CAC7B,gBAAgB;EACd,IAAI,CAAC,SAAS;EACd,YAAY,IAAI,MAAM,QAAQ,MAAM;EAGpC,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,cAAc,IAAI,GAAG;GAC1C,IAAI,QAAQ,OAAO,GAAG;IACpB,MAAM,QAAQ,aAAa;IAC3B;GACF;GACA,MAAM,eAAe;GACrB,MAAM,yBAAyB;GAC/B,QAAa,KAAK;EACpB;EACA,SAAS,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;EAC/D,aAAa;GACX,YAAY,OAAO,IAAI;GACvB,SAAS,oBAAoB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;EACpE;CACF,GAAG;EAAC;EAAM;EAAS;CAAO,CAAC;CAK3B,MAAM,aAAa,KAAK;CACxB,gBAAgB;EACd,IAAI,CAAC,MAAM,QAAQ,cAAc,YAAY;EAC7C,MAAM,QAAQ,aAAa;EAC3B,IAAI,CAAC,KAAK,SAAS,QAAa,YAAY;CAC9C,CAAC;CAED,OAAO,cAAc;EACnB,MAAM,OAAO,IAAI,QAAQ,MAAM,MAAM,MAAM,SAAS,CAAC,WAAW,aAAa,IAAI,CAAC;EAClF,MAAM,UAAU,IAAI;EACpB,MAAM,KAAK,KAAK,QAAQ,OAAO;EAC/B,OAAO;GACL;GACA,OAAO;GACP;GACA,SAAS,OAAO;GAChB,QAAQ,OAAO,KAAK,SAAS;GAC7B,MAAM,QAAQ;GACd,MAAM,QAAQ;GACd,MAAM,QAAQ;EAChB;CACF,GAAG;EAAC;EAAK;EAAO;EAAY;CAAO,CAAC;AACtC;;AAGA,MAAM,kBAAkB,IAAI,SAAe,YAAY,WAAW,OAAO,CAAC;;AAG1E,SAAS,cAAc,MAA8C;CACnE,OAAQ,KAAoE,YAAY,EAAE;AAC5F;;;ACtPA,SAAgB,WAAW,EACzB,IACA,MACA,MACA,WAAW,WACX,cAC0C;CAE1C,MAAM,QAAQ,cAAc,GAAG,MAAM,EAAQ,KAAc,CAAC;CAG5D,MAAM,EAAE,MAAM,YAAY;CAC1B,MAAM,EAAE,UAAU;CAClB,MAAM,KAAK,MAAM;CAEjB,MAAM,UAAU,eACP;EAEL,WAAW;GACT,OAAO,MAAM;IAAE,MAAM;IAAI,cAAc,QAAQ,aAAa,KAAK;GAAW,CAAC;GAC7E,cAAc,IAAI,CAAC,GAAG,OAAO;EAC/B;EACA,SAAS,UAAkB;GACzB,OAAO,MAAM;IAAE,MAAM;IAAI,IAAI;GAAM,CAAC;GACpC,cAAc,IAAI,CAAC,GAAG,OAAO;EAC/B;EACA,OAAO,MAAc,OAAe;GAClC,KAAK,MAAM;IAAE,MAAM;IAAI;IAAM;GAAG,CAAC;GACjC,cAAc,IAAI,CAAC,GAAG,OAAO;EAC/B;CACF,IACA;EAAC;EAAM;EAAS;EAAO;CAAE,CAC3B;CAEA,OACE,oBAAC,WAAD;EAAkB;EAAa;EAAM,OAAO,MAAM;EAAO,QAAQ,MAAM;EAAQ,GAAI;EAChF,UAAA;CACQ,CAAA;AAEf;;;ACpCA,SAAgB,WAAW,EACzB,IACA,MACA,MACA,WAAW,aAC+B;CAE1C,MAAM,QAAQ,SAAS,GAAG,MAAM,EAAQ,KAAc,CAAC;CACvD,MAAM,EAAE,SAAS;CACjB,MAAM,WAAW,aACd,UAAmB;EAClB,MAAM,SAAS,YAAY,OAAO,IAAI,CAAU;EAChD,cAAc,IAAI,CAAC,GAAG,OAAO;CAC/B,GACA;EAAC;EAAO;EAAM;CAAI,CACpB;CAEA,MAAM,aACJ,GAAG,MAAM,KAAK,SAAS,IACnB;EACE,GAAG,MAAM;EACT,eAAe;GACb,MAAM,MAAM,QAAQ;GACpB,cAAc,IAAI,CAAC,GAAG,OAAO;EAC/B;EACA,WAAW,UAAsD;GAC/D,MAAM,MAAM,SAAS,KAAK;GAC1B,cAAc,IAAI,CAAC,GAAG,OAAO;GAC7B,cAAc,IAAI,CAAC,GAAG,QAAQ;EAChC;EACA,cAAc;GACZ,MAAM,MAAM,OAAO;GACnB,cAAc,IAAI,CAAC,GAAG,MAAM;EAC9B;CACF,IACA,MAAM;CAEZ,OACE,oBAAC,WAAD;EACE,OAAO,KAAK;EACN;EACN,MAAM,KAAK;EACX,UAAU,WAAW,KAAK,KAAK;EAC/B,OAAO,MAAM;EACb,QAAQ,MAAM;EACF;EACF;CACX,CAAA;AAEL;;;ACjDA,SAAgB,kBACd,YACA,IACiB;CACjB,MAAM,WAAW,cAAc,GAAG;CAClC,IAAI,CAAC,UACH,MAAM,IAAI,UAAU,qEAAqE;CAE3F,OAAO;AACT;;;;;AAMA,SAAgB,aACd,KACA,OACA,SACgB;CAChB,MAAM,EAAE,eAAe,IAAI;CAE3B,OAAO,MAAM,SAAS,MAAM,aAC1B,WAAW,SAAS,MAAM,OAAO,IAC7B,CAAC,IACD,CACE,YAAY,KAAK,MAAM,SAAS,UAAU,IAAI,KAAK,IAAI,aAAa,UAClE,aAAa,KAAK,KAAK,UAAU,KAAK,CACxC,CACF,CACN;AACF;;;;;AAMA,SAAgB,YACd,KACA,MACA,SACA,KACA,UACc;CAEd,MAAM,YAAY,IAAI,WAAW,KAAK;CACtC,MAAM,OAAO,YAAY,KAAK,UAAU,OAAO;CAC/C,QAAQ,KAAK,MAAb;EACE,KAAK,WAEH,OAAO,oBAACA,WAAD,EAAmB,OAAO,KAAK,MAAQ,GAAzB,GAAyB;EAEhD,KAAK,SAEH,OACE,oBAACC,WAAD;GAAiB,OAAO,KAAK;GAAa;GACvC,UAAA,SAAS,OAAO;EACZ,GAFK,GAEL;EAGX,KAAK,SACH,OACE,oBAAC,YAAD;GAEE,IAAI,IAAI;GACF;GACA;GACK;GACX,aAAa,UAAU,SAAS,CAAC,GAAG,SAAS,KAAK,CAAC;EACpD,GANM,GAMN;EAEL,KAAK,SACH,OACE,oBAAC,YAAD;GAEE,IAAI,IAAI;GACF;GACA;GACK;EACZ,GALM,GAKN;CAEP;AACF;;;;;;;AClFA,SAAgB,UAAU,EAAE,IAAI,MAAM,cAAmD;CACvF,MAAM,OAAO,UAAU,GAAG,QAAQ,MAAM,EAAE,QAAQ,KAAK,CAAC;CACxD,MAAM,MAAqB;EAAE;EAAI,YAAY,kBAAkB,YAAY,EAAE;CAAE;CAC/E,gBAAgB,CAAC,IAAI,GAAG,IAAI,UAAU;CAEtC,IAAI,GAAG,WAAW,SAAS,MAAM,CAAC,CAAC,GAAG,OAAO;CAC7C,OAAO,YAAY,KAAK,MAAM,CAAC,GAAG,UAAU,IAAI,KAAK,OAAO,YAC1D,aAAa,KAAK,KAAK,UAAU,OAAO,CAC1C;AACF;;;;;;;ACVA,SAAgB,aACd,KACA,OACA,SACgB;CAChB,OAAO,IAAI,GAAG,WACX,WAAW,OAAO,SAAS,IAAI,OAAO,CAAC,CACvC,KAAK,SAAS,WAAW,KAAK,MAAM,OAAO,CAAC;AACjD;AAEA,SAAS,WACP,KACA,MACA,SACc;CACd,MAAM,EAAE,QAAQ;CAChB,QAAQ,KAAK,MAAb;EACE,KAAK,SACH,OAAO,YAAY,KAAK,KAAK,MAAM,SAAS,MAAM,UAChD,aAAa,KAAK,KAAK,UAAU,KAAK,CACxC;EACF,KAAK,WAAW;GAEd,MAAM,UAAU,IAAI,iBAAiB;GACrC,OACE,oBAAC,SAAD;IAAmB,SAAS,KAAK;IAC9B,UAAA,aAAa,KAAK,KAAK,UAAU,OAAO;GAClC,GAFK,GAEL;EAEb;EACA,KAAK,OAAO;GACV,MAAM,MAAM,IAAI,iBAAiB;GACjC,OACE,oBAAC,KAAD;IAAe,KAAK,KAAK;IAAK,OAAO,KAAK,SAAS,KAAK,SAAS,KAAK,IAAI;IACtE,WAAA,UAAU,WAAW,KAAK,KAAK,SAAS,QAAsB,OAAO;GACpE,GAFK,GAEL;EAET;EACA,KAAK,QACH,OAAO,oBAAC,UAAD,EAAA,UAAqB,IAAI,MAAM,KAAK,MAAgB,GAArC,GAAqC;CAC/D;AACF;;;;;;;ACrBA,SAAgB,WAAW,OAAsC;CAC/D,MAAM,EAAE,IAAI,MAAM,WAAW;CAC7B,MAAM,MAAqB;EAAE;EAAI,YAAY,kBAAkB,MAAM,YAAY,EAAE;CAAE;CACrF,MAAM,QAAQ,aAAa,GAAG,QAAQ;EAAE;EAAM;CAAO,CAAC;CAEtD,IAAI,QAAQ,CAAC,GAAG,WAAW,QAAQ;EACjC,gBAAgB,OAAO,IAAI,UAAU;EACrC,OAAO,oBAAA,YAAA,EAAA,UAAG,aAAa,KAAK,OAAO,CAAC,CAAC,EAAI,CAAA;CAC3C;CAEA,MAAM,EAAE,QAAQ,UAAU;CAC1B,MAAM,OAAO,IAAI,IAAI,MAAM;CAC3B,MAAM,YAA2B;EAC/B,GAAG;EACH,kBAAkB,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;EACpE,OAAO,MAAM,SAAS,CAAC;EACvB,UAAU,SAAS,CAAC,KAAK,IAAI,UAAU,IAAI,KAAK,EAAE;CACpD;CACA,gBACE,OAAO,MAAM,SAAS,SAAS,SAAS,KAAK,QAAQ,CAAC,GACtD,IAAI,UACN;CACA,sBAAsB,QAAQ,UAAU,kBAAkB,UAAU,KAAK;CAEzE,MAAM,QAAQ,MAAM,QAAQ,MAAM;CAClC,MAAM,OAAO,MAAM,KAAK;CACxB,IAAI,CAAC,MAAM,MAAM,IAAI,WAAW,WAAW,MAAM,KAAK,iBAAiB,MAAM,KAAK,OAAO,EAAE;CAE3F,OAAO,oBAAC,UAAD,EAAA,UAAuB,aAAa,WAAW,KAAK,UAAU,CAAC,CAAC,EAAY,GAA7D,KAA6D;AACrF;;AAGA,SAAS,SAAS,OAA2C;CAC3D,OAAO,MAAM,SAAS,SAAS;EAC7B,IAAI,KAAK,SAAS,QAAQ,OAAO,CAAC;EAClC,MAAM,QAAQ,SAAS,KAAK,QAAQ;EACpC,OAAO,KAAK,SAAS,UAAU,CAAC,KAAK,MAAM,GAAG,KAAK,IAAI;CACzD,CAAC;AACH;;;;;;;;;AC3DA,SAAgB,cAAc,MAAiB,YAAyC;CACtF,MAAM,YAAY,aACf,aAA0B,WAAW,cAAc,WAAW,MAAM,QAAQ,UAAU,CAAC,GACxF,CAAC,MAAM,UAAU,CACnB;CACA,MAAM,oBACJ,WAAW,GAAG,WAAW,cAAc,cAAc,cAAc,KAAK,SAAS,CAAC,IAAI,KAAA,CAAS;CACjG,OAAO,qBAAqB,WAAW,aAAa,WAAW;AACjE;;;;;;AAOA,SAAS,WAAW,MAAiB,UAAkC;CACrE,MAAM,WAAqB,OACnB;EACJ,KAAK;EACL,SAAS;CACX,mBACA,IAAI,IAAI,CACV;CACA,MAAM,aAAa;EACjB,KAAK,MAAM,eAAe,SAAS,IAAI,YAAY,OAAO,QAAQ;EAClE,SAAS,EAAE,CAAC,MAAM;CACpB;CACA,SAAS,OAAO;EACd,KAAK;EAEL,cAAc;GACZ,YAAY,QAAQ;GACpB,IAAI;IACF,cAAc,KAAK,SAAS;GAC9B,UAAU;IACR,YAAY,KAAA,CAAS;GACvB;EACF,CAAC;CACH;CACA,KAAK;CACL,OAAO;AACT;;ACsBA,SAAgB,YACd,YACA,UAA8B,CAAC,GACb;CAElB,MAAM,CAAC,SAAS,eAAe;EAC7B,MAAM,UAAU,QAAQ,WAAW,oBAAoB;EACvD,OAAO;GACL;GACA;GACA,QAAQ,QAAQ,MAAM,UAAU;GAChC,cAAc,QAAQ,gBAAgB,UAAU;GAChD,QAAQ,QAAQ,aAAa,UAAU;GACvC,QAAQ,QAAQ,UAAU,UAAU;GACpC,YAAY,QAAQ,cAAc,UAAU;EAC9C;CACF,CAAC;CACD,MAAM,OAAO,QAAQ;EACnB,QAAQ,MAAM;EACd,cAAc,MAAM;EACpB,UAAU,QAAQ;EAClB,YAAY,QAAQ;CACtB,CAAC;CACD,MAAM,aAAa,cAAc,MAAM,MAAM,UAAU;CAEvD,MAAM,EAAE,YAAY;CACpB,gBAAgB;EAEd,KAAK,MAAM,EAAE,MAAM,WAAW,SAAS,SAAS,MAAM;GAAE;GAAM;EAAM,CAAU;CAChF,GAAG,CAAC,MAAM,OAAO,CAAC;CAClB,MAAM,QAAQ,iBAAiB,MAAM,MAAM,QAAQ,SAAS,UAAU;CACtE,MAAM,EAAE,YAAY,qBAAqB;CACzC,OAAO,eACE;EACL;EACA,YAAY,MAAM;EAClB,SAAS,MAAM;EACf;EACA;EACA,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd;EACA;CACF,IACA;EAAC;EAAM;EAAO;EAAY;EAAkB;EAAO;CAAU,CAC/D;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatsaaaa/form-builder-react",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Render JSON form definitions with Formisch and your own React components.",
5
5
  "keywords": [
6
6
  "form",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@whatsaaaa/form-builder-schema": "^0.14.0"
36
+ "@whatsaaaa/form-builder-schema": "^0.15.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@arethetypeswrong/cli": "^0.18.5",
@@ -42,6 +42,7 @@
42
42
  "@types/react": "^19.3.0",
43
43
  "@types/react-dom": "^19.3.0",
44
44
  "@vitest/coverage-v8": "^5.0.5",
45
+ "@whatsaaaa/form-builder-affects": "^0.1.1",
45
46
  "happy-dom": "^20.0.0",
46
47
  "publint": "^0.3.25",
47
48
  "react": "^19.3.1",