@whatsaaaa/form-builder-vue 0.1.5 → 0.2.0

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
@@ -51,6 +51,7 @@ function save(output: unknown) {
51
51
  useJsonForm(definition, {
52
52
  builder: createSchemaBuilder({ fieldTypes: { money } }), // your own types, rules and messages
53
53
  components, // your component per field type, see below
54
+ layoutComponents, // your component per layout type, see Layout
54
55
  validate: 'blur', // when a field is validated the first time, as in Formisch
55
56
  revalidate: 'input', // when it is validated again
56
57
  })
@@ -194,9 +195,130 @@ defineProps<DisplayFieldProps>()
194
195
 
195
196
  Declare the props even when a component doesn't use them, like a divider, or Vue adds `field` to its element as an attribute.
196
197
 
198
+ ## Layout
199
+
200
+ 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>` a named slot for each of its slots:
201
+
202
+ ```vue
203
+ <!-- Row.vue: one line of a 12-column grid. On a narrow screen, give each cell the whole row. -->
204
+ <script setup lang="ts">
205
+ import type { RowProps, RowSlots } from '@whatsaaaa/form-builder-vue'
206
+
207
+ defineProps<RowProps>()
208
+ defineSlots<RowSlots>()
209
+ </script>
210
+
211
+ <template>
212
+ <div class="row">
213
+ <div v-for="(span, index) in spans" :key="index" :style="{ gridColumn: `span ${span}` }">
214
+ <slot :index="index" />
215
+ </div>
216
+ </div>
217
+ </template>
218
+
219
+ <style scoped>
220
+ .row {
221
+ display: grid;
222
+ grid-template-columns: repeat(12, 1fr);
223
+ gap: 1rem;
224
+ }
225
+ </style>
226
+ ```
227
+
228
+ ```vue
229
+ <!-- Section.vue -->
230
+ <script setup lang="ts">
231
+ import type { SectionProps, SectionSlots } from '@whatsaaaa/form-builder-vue'
232
+
233
+ defineProps<SectionProps>()
234
+ defineSlots<SectionSlots>()
235
+ </script>
236
+
237
+ <template>
238
+ <fieldset>
239
+ <legend>{{ section.label }}</legend>
240
+ <slot />
241
+ </fieldset>
242
+ </template>
243
+ ```
244
+
245
+ ```vue
246
+ <script setup lang="ts">
247
+ const jsonForm = useJsonForm(definition, {
248
+ components,
249
+ layoutComponents: { section: Section, row: Row },
250
+ })
251
+ </script>
252
+
253
+ <template>
254
+ <Form :of="jsonForm.form" @submit="save">
255
+ <JsonFields :of="jsonForm">
256
+ <template #submit><button type="submit">Place order</button></template>
257
+ </JsonFields>
258
+ </Form>
259
+ </template>
260
+ ```
261
+
262
+ | Layout type | Props | Slot |
263
+ | ----------- | ------------------------------------------------------------------------- | ---------------------------------------------- |
264
+ | `section` | `section`, as written, with its `id`, `label`, `description` and `ui` | default: the section's items |
265
+ | `row` | `row`, as written, with its `id` and `ui`; `spans`, the span of each cell | default, with `{ index }`: the cell at `index` |
266
+
267
+ The props are typed as `SectionProps` and `RowProps`, and the slots as `SectionSlots` and `RowSlots`.
268
+
269
+ - `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.
270
+ - A layout type the layout uses without a component, or a slot without content, throws a `TypeError` naming each one when `<JsonFields>` sets up, on every step.
271
+ - Display elements written in the layout render with your field components, like the ones in `fields`.
272
+ - `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.
273
+ - 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.
274
+
275
+ ### Steps
276
+
277
+ `jsonForm.steps` is where the form is, and how to move. It is reactive, so templates follow it:
278
+
279
+ ```vue
280
+ <template>
281
+ <Form :of="jsonForm.form" @submit="save">
282
+ <ol>
283
+ <li
284
+ v-for="(step, i) in jsonForm.steps.list"
285
+ :key="step.step.id ?? i"
286
+ :aria-current="i === jsonForm.steps.index ? 'step' : undefined"
287
+ >
288
+ {{ step.step.label }}
289
+ </li>
290
+ </ol>
291
+ <JsonFields :of="jsonForm" />
292
+ <button v-if="!jsonForm.steps.isFirst" type="button" @click="jsonForm.steps.back()">
293
+ Back
294
+ </button>
295
+ <button v-if="!jsonForm.steps.isLast" type="button" @click="jsonForm.steps.next()">Next</button>
296
+ <button v-else type="submit">Sign up</button>
297
+ </Form>
298
+ </template>
299
+ ```
300
+
301
+ | Member | |
302
+ | ------------------- | ---------------------------------------------------------------------------------------------------- |
303
+ | `list` | every step, with its `step` as written (`id`, `label`, `description`, `ui`), `children` and `fields` |
304
+ | `index`, `current` | the step `<JsonFields>` renders |
305
+ | `isFirst`, `isLast` | |
306
+ | `next()` | validates, and moves on when the step's fields have no issues. Resolves to whether it moved |
307
+ | `back()` | moves back, without validating |
308
+ | `goTo(index)` | back freely; forward through each step on the way, stopping at the first with issues |
309
+
310
+ The form is one Formisch form, so Formisch validates all of it at once. The steps make that work:
311
+
312
+ - **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.
313
+ - **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.
314
+ - **A step that failed Next revalidates** as the user fixes it, on the `revalidate` event, like a form after a failed submit.
315
+ - **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.
316
+ - **A submit that fails because of an earlier step**, like a form rule that reads fields of two steps, goes back to that step.
317
+ - `<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.
318
+
197
319
  ## Placing fields yourself
198
320
 
199
- `<JsonFields>` renders every field in the order of the definition. To put some of them somewhere else, place them with `<JsonField>` and leave them out of `<JsonFields>` with `except`:
321
+ 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`:
200
322
 
201
323
  ```vue
202
324
  <template>
@@ -215,7 +337,7 @@ Declare the props even when a component doesn't use them, like a divider, or Vue
215
337
 
216
338
  - 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.
217
339
  - 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']`.
218
- - `only` renders just the fields it names, in the order of the definition: `<JsonFields :of="jsonForm" :only="['name', 'email']" />`. Display elements have no name, so `only` leaves them out.
340
+ - `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.
219
341
  - A path that names no field throws a `TypeError`, so a typo shows up at once. `only` and `except` are read when the component sets up.
220
342
 
221
343
  ## Typing the form
@@ -245,4 +367,4 @@ useFieldArray(jsonForm.form, { path: ['lines'] })
245
367
 
246
368
  - 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.
247
369
  - `ui.widget` doesn't pick a component. A component can read `field.ui` itself, e.g. to show a multiselect as checkboxes.
248
- - Showing and hiding fields, or making one required because of another, belongs to a future affects package.
370
+ - 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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FieldNode, FieldNode as FieldNode$1, FieldPath, FieldPath as FieldPath$1, FieldRole, JsonField as JsonField$1, JsonForm, JsonFormSchema, JsonFormSchema as JsonFormSchema$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 } from "@whatsaaaa/form-builder-schema";
2
2
  import { Component, PropType, VNode } from "vue";
3
3
  import { FieldElementProps, FormSchema, FormStore, ValidationMode } from "@formisch/vue";
4
4
  /** Your component for each field type, by type name: `{ text: TextInput, group: Fieldset }`. */
@@ -52,11 +52,67 @@ interface ArrayFieldSlots {
52
52
  index: number;
53
53
  }): VNode[];
54
54
  }
55
+ /**
56
+ * Your component for each layout type a definition's `layout` uses: `{ section: Card, row: Grid }`.
57
+ * A definition without sections or rows needs none.
58
+ */
59
+ interface LayoutComponents {
60
+ section?: Component;
61
+ row?: Component;
62
+ }
63
+ /** The props of a component for a section of the layout. */
64
+ interface SectionProps {
65
+ /** The section as written, with its `id`, `label`, `description` and `ui`. */
66
+ section: JsonLayoutSection;
67
+ }
68
+ /** The slots of a section component: its default slot renders the section's items. */
69
+ interface SectionSlots {
70
+ default(): VNode[];
71
+ }
72
+ /** The props of a component for a row of the layout: one line of a 12-column grid. */
73
+ interface RowProps {
74
+ /** The row as written, with its `id` and `ui`. */
75
+ row: JsonLayoutRow;
76
+ /** How many of the 12 columns each cell takes, in order. They add up to 12 at most. */
77
+ spans: number[];
78
+ }
79
+ /** The slots of a row component: its default slot renders the cell at `index`. */
80
+ interface RowSlots {
81
+ default(props: {
82
+ index: number;
83
+ }): VNode[];
84
+ }
85
+ /** The steps of a form, from its layout. A form without `steps` in its layout has one. */
86
+ interface JsonFormSteps {
87
+ /** Every step, in order. */
88
+ readonly list: readonly LayoutStep$1[];
89
+ /** The index of the current step in `list`. */
90
+ readonly index: number;
91
+ /** The step `<JsonFields>` renders. */
92
+ readonly current: LayoutStep$1;
93
+ readonly isFirst: boolean;
94
+ readonly isLast: boolean;
95
+ /**
96
+ * Validates the whole form, and moves to the next step when the fields of this one have no
97
+ * issues. Otherwise it stays, and focuses the first field with an issue, in layout order.
98
+ * Resolves to whether it moved.
99
+ */
100
+ next(): Promise<boolean>;
101
+ /** Moves to the step before, without validating. */
102
+ back(): void;
103
+ /**
104
+ * Moves to the step at `index`. Back is free, and forward validates every step on the way, like
105
+ * `next()`, and stops at the first with issues. Resolves to whether it got there.
106
+ */
107
+ goTo(index: number): Promise<boolean>;
108
+ }
55
109
  interface UseJsonFormOptions {
56
110
  /** Builds the schema and knows your own types and rules. Defaults to `createSchemaBuilder()`. */
57
111
  builder?: SchemaBuilder;
58
112
  /** Your component for each field type, so `<JsonFields>` and `<JsonField>` don't need them. */
59
113
  components?: FieldComponents;
114
+ /** Your component for each layout type the definition's `layout` uses, like `section`. */
115
+ layoutComponents?: LayoutComponents;
60
116
  /** When a field is validated the first time, as in Formisch. */
61
117
  validate?: ValidationMode;
62
118
  /** When a field is validated again after that, as in Formisch. */
@@ -70,8 +126,14 @@ interface JsonFormInstance<TSchema extends FormSchema = JsonFormSchema$1> {
70
126
  readonly builder: SchemaBuilder;
71
127
  /** The components from the options, if any. */
72
128
  readonly components: FieldComponents | undefined;
73
- /** The definition's fields as a tree, the way `<JsonFields>` renders them. */
129
+ /** The layout components from the options, if any. */
130
+ readonly layoutComponents: LayoutComponents | undefined;
131
+ /** The definition's fields as a tree, for `<JsonFields :only>` and `<JsonField>`. */
74
132
  readonly fields: readonly FieldNode$1[];
133
+ /** The definition's layout, the way `<JsonFields>` renders it. */
134
+ readonly layout: FormLayout$1;
135
+ /** The layout's steps: the current one, and Next, Back and Go to. A form without steps has one. */
136
+ readonly steps: JsonFormSteps;
75
137
  }
76
138
  /** Any form `useJsonForm` returns, typed or not: what the components take. */
77
139
  type AnyJsonFormInstance = JsonFormInstance<FormSchema>;
@@ -125,9 +187,10 @@ export declare const JsonField: import("vue").DefineComponent<import("vue").Extr
125
187
  };
126
188
  }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
127
189
  /**
128
- * Renders the fields of a `useJsonForm` form with your components, one per field type. It renders
129
- * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button. `only` and
130
- * `except` are read when it sets up.
190
+ * Renders the fields of a `useJsonForm` form with your components, one per field type, where the
191
+ * definition's `layout` puts them: the current step, with its sections, rows and slots. It renders
192
+ * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button. Its named
193
+ * slots fill the layout's slots. `only` and `except` are read when it sets up.
131
194
  */
132
195
  export declare const JsonFields: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
133
196
  /** What `useJsonForm` returned. */
@@ -139,17 +202,30 @@ export declare const JsonFields: import("vue").DefineComponent<import("vue").Ext
139
202
  components: {
140
203
  type: PropType<FieldComponents>;
141
204
  };
142
- /** Renders only these fields, by path, in the order of the definition. */
205
+ /** Defaults to the layout components `useJsonForm` got. */
206
+ layoutComponents: {
207
+ type: PropType<LayoutComponents>;
208
+ };
209
+ /** Renders only these fields, by path, in the order of the definition, without the layout. */
143
210
  only: {
144
211
  type: PropType<readonly string[]>;
145
212
  };
146
- /** Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. */
213
+ /**
214
+ * Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. A section or a
215
+ * row left without fields drops out too.
216
+ */
147
217
  except: {
148
218
  type: PropType<readonly string[]>;
149
219
  };
150
- }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
220
+ /** Renders the step at this index instead of the current one, e.g. on a review page. */
221
+ step: {
222
+ type: NumberConstructor;
223
+ };
224
+ }>, (() => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
225
+ [key: string]: any;
226
+ }>[]) | (() => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
151
227
  [key: string]: any;
152
- }>[], {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
228
+ }>), {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
153
229
  /** What `useJsonForm` returned. */
154
230
  of: {
155
231
  type: PropType<AnyJsonFormInstance>;
@@ -159,14 +235,25 @@ export declare const JsonFields: import("vue").DefineComponent<import("vue").Ext
159
235
  components: {
160
236
  type: PropType<FieldComponents>;
161
237
  };
162
- /** Renders only these fields, by path, in the order of the definition. */
238
+ /** Defaults to the layout components `useJsonForm` got. */
239
+ layoutComponents: {
240
+ type: PropType<LayoutComponents>;
241
+ };
242
+ /** Renders only these fields, by path, in the order of the definition, without the layout. */
163
243
  only: {
164
244
  type: PropType<readonly string[]>;
165
245
  };
166
- /** Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. */
246
+ /**
247
+ * Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. A section or a
248
+ * row left without fields drops out too.
249
+ */
167
250
  except: {
168
251
  type: PropType<readonly string[]>;
169
252
  };
253
+ /** Renders the step at this index instead of the current one, e.g. on a review page. */
254
+ step: {
255
+ type: NumberConstructor;
256
+ };
170
257
  }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
171
- export type { AnyJsonFormInstance, ArrayFieldProps, ArrayFieldSlots, DisplayFieldProps, FieldComponents, FieldNode, FieldPath, FieldRole, GroupFieldProps, GroupFieldSlots, JsonFormInstance, JsonFormSchema, TypedJsonFormSchema, UseJsonFormOptions, ValueFieldProps };
258
+ export type { AnyJsonFormInstance, ArrayFieldProps, ArrayFieldSlots, DisplayFieldProps, FieldComponents, FieldNode, FieldPath, FieldRole, FormLayout, GroupFieldProps, GroupFieldSlots, JsonFormInstance, JsonFormSchema, JsonFormSteps, LayoutComponents, LayoutNode, LayoutStep, RowProps, RowSlots, SectionProps, SectionSlots, TypedJsonFormSchema, UseJsonFormOptions, ValueFieldProps };
172
259
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,6 +1,118 @@
1
- import { checkComponents, coerceInput, createSchemaBuilder, dotPathOf, findField, isRequired, selectFields, toFieldPath } from "@whatsaaaa/form-builder-schema";
2
- import { defineComponent, h } from "vue";
3
- import { insert, move, remove, useField, useFieldArray, useForm } from "@formisch/vue";
1
+ import { checkComponents, checkLayoutComponents, coerceInput, createSchemaBuilder, dotPathOf, fieldPathsOf, findField, isRequired, issuesOfStep, selectFields, shownNodes, toFieldPath } from "@whatsaaaa/form-builder-schema";
2
+ import { Fragment, computed, defineComponent, getCurrentScope, h, nextTick, onScopeDispose, ref, watch } from "vue";
3
+ import { focus, getErrors, getInput, insert, move, remove, setErrors, useField, useFieldArray, useForm, validate } from "@formisch/vue";
4
+ // src/json-form-steps.ts
5
+ const fieldEvents = /* @__PURE__ */ new WeakMap();
6
+ /** What a field of `form` calls on each event, for a form with steps. Undefined with one step. */
7
+ const fieldEventsOf = (form) => fieldEvents.get(form);
8
+ /**
9
+ * The steps of `layout`, for `useJsonForm`. With more than one, a step shows the errors it had
10
+ * 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
11
+ * before the last step moves on like Next, and a submit that fails because of an earlier step
12
+ * goes back to it.
13
+ */
14
+ function useJsonFormSteps(form, layout, modes) {
15
+ const list = layout.steps;
16
+ const index = ref(0);
17
+ const current = computed(() => list[index.value]);
18
+ const left = /* @__PURE__ */ new Map();
19
+ const failed = /* @__PURE__ */ new Set();
20
+ async function goTo(target) {
21
+ if (!Number.isInteger(target) || target < 0 || target >= list.length) throw new RangeError(`No step ${target}. The form has ${list.length}.`);
22
+ if (target <= index.value) {
23
+ move(target);
24
+ return true;
25
+ }
26
+ const { issues } = await validate(form);
27
+ for (let at = index.value; at < target; at++) {
28
+ const found = issuesOfStep(list[at], issues);
29
+ if (found.length) {
30
+ await stop(at, found);
31
+ return false;
32
+ }
33
+ failed.delete(at);
34
+ }
35
+ move(target);
36
+ return true;
37
+ }
38
+ /**
39
+ * Moves to a step as the user left it: with the errors it had then, and none the first time, and
40
+ * revalidating if it failed Next. Validating the steps before set errors on its fields, which
41
+ * the user never saw.
42
+ */
43
+ function move(target) {
44
+ leave();
45
+ const errors = left.get(target);
46
+ for (const path of fieldPathsOf(list[target], getInput(form))) setErrors(form, {
47
+ path,
48
+ errors: errors?.get(JSON.stringify(path)) ?? null
49
+ });
50
+ index.value = target;
51
+ }
52
+ /** Keeps the errors of the current step, for when the user comes back to it. */
53
+ function leave() {
54
+ const errors = fieldPathsOf(current.value, getInput(form)).map((path) => [JSON.stringify(path), errorsAt(path)]);
55
+ left.set(index.value, new Map(errors));
56
+ }
57
+ const errorsAt = (path) => getErrors(form, { path });
58
+ /** Stays on, or goes to, the step at `at`, with its issues shown. */
59
+ async function stop(at, found) {
60
+ failed.add(at);
61
+ if (at !== index.value) leave();
62
+ index.value = at;
63
+ await nextTick();
64
+ const first = found.find((issue) => issue.field.role === "value");
65
+ if (first) focus(form, { path: first.path });
66
+ }
67
+ const steps = {
68
+ list,
69
+ get index() {
70
+ return index.value;
71
+ },
72
+ get current() {
73
+ return current.value;
74
+ },
75
+ get isFirst() {
76
+ return index.value === 0;
77
+ },
78
+ get isLast() {
79
+ return index.value === list.length - 1;
80
+ },
81
+ next: () => steps.isLast ? Promise.resolve(false) : goTo(index.value + 1),
82
+ back: () => {
83
+ if (index.value > 0) move(index.value - 1);
84
+ },
85
+ goTo
86
+ };
87
+ if (list.length > 1) watchSteps();
88
+ return steps;
89
+ function watchSteps() {
90
+ fieldEvents.set(form, (event) => {
91
+ const validateMode = modes.validate ?? "submit";
92
+ if (!failed.has(index.value) || validateMode !== "submit" || form.isSubmitted) return;
93
+ if (event === (modes.revalidate ?? "input")) validate(form);
94
+ });
95
+ watch(() => form.isSubmitting, async (submitting, was) => {
96
+ if (submitting || !was || form.isValid) return;
97
+ const { issues } = await validate(form);
98
+ const at = list.findIndex((step) => issuesOfStep(step, issues).length);
99
+ if (at !== -1 && at < index.value) await stop(at, issuesOfStep(list[at], issues));
100
+ });
101
+ if (typeof document === "undefined") return;
102
+ const onSubmit = (event) => {
103
+ if (event.target !== formElementOf(form) || steps.isLast) return;
104
+ event.preventDefault();
105
+ event.stopImmediatePropagation();
106
+ steps.next();
107
+ };
108
+ document.addEventListener("submit", onSubmit, { capture: true });
109
+ if (getCurrentScope()) onScopeDispose(() => document.removeEventListener("submit", onSubmit, { capture: true }));
110
+ }
111
+ }
112
+ /** The `<form>` Formisch's `<Form>` renders, which it keeps on the store for its `submit()`. */
113
+ function formElementOf(form) {
114
+ return form["~internal"]?.element;
115
+ }
4
116
  // src/array-field.ts
5
117
  /** Connects a container of kind `array` to Formisch, and renders your component for it. */
6
118
  const ArrayField = defineComponent({
@@ -31,20 +143,30 @@ const ArrayField = defineComponent({
31
143
  setup(props) {
32
144
  const path = () => props.path;
33
145
  const array = useFieldArray(() => props.of.form, () => ({ path: path() }));
146
+ const events = fieldEventsOf(props.of.form);
34
147
  const methods = {
35
- add: () => insert(props.of.form, {
36
- path: path(),
37
- initialInput: props.of.builder.getItemInput(props.node.field)
38
- }),
39
- remove: (index) => remove(props.of.form, {
40
- path: path(),
41
- at: index
42
- }),
43
- move: (from, to) => move(props.of.form, {
44
- path: path(),
45
- from,
46
- to
47
- })
148
+ add: () => {
149
+ insert(props.of.form, {
150
+ path: path(),
151
+ initialInput: props.of.builder.getItemInput(props.node.field)
152
+ });
153
+ events?.("input");
154
+ },
155
+ remove: (index) => {
156
+ remove(props.of.form, {
157
+ path: path(),
158
+ at: index
159
+ });
160
+ events?.("input");
161
+ },
162
+ move: (from, to) => {
163
+ move(props.of.form, {
164
+ path: path(),
165
+ from,
166
+ to
167
+ });
168
+ events?.("input");
169
+ }
48
170
  };
49
171
  return () => {
50
172
  const fieldProps = {
@@ -82,9 +204,32 @@ const ValueField = defineComponent({
82
204
  },
83
205
  setup(props) {
84
206
  const field = useField(() => props.of.form, () => ({ path: props.path }));
207
+ const events = fieldEventsOf(props.of.form);
85
208
  const setInput = (input) => {
86
209
  field.input = coerceInput(input, props.node);
210
+ events?.("input");
87
211
  };
212
+ const inputProps = events ? {
213
+ get name() {
214
+ return field.props.name;
215
+ },
216
+ get autofocus() {
217
+ return field.props.autofocus;
218
+ },
219
+ ref: (element) => field.props.ref(element),
220
+ onFocus: () => {
221
+ field.props.onFocus();
222
+ events("touch");
223
+ },
224
+ onChange: (event) => {
225
+ field.props.onChange(event);
226
+ events("change");
227
+ },
228
+ onBlur: () => {
229
+ field.props.onBlur();
230
+ events("blur");
231
+ }
232
+ } : field.props;
88
233
  return () => {
89
234
  const fieldProps = {
90
235
  field: props.node.field,
@@ -93,7 +238,7 @@ const ValueField = defineComponent({
93
238
  required: isRequired(props.node.field),
94
239
  input: field.input,
95
240
  errors: field.errors,
96
- inputProps: field.props,
241
+ inputProps,
97
242
  setInput
98
243
  };
99
244
  return h(props.component, fieldProps);
@@ -109,12 +254,15 @@ function resolveComponents(components, of) {
109
254
  }
110
255
  /** `indexes` are the indexes of the array items the fields are in, outermost first. */
111
256
  function renderFields(ctx, nodes, indexes) {
112
- return nodes.map((node, position) => renderField(ctx, node, indexes, position));
257
+ return nodes.map((node, position) => renderField(ctx, node, indexes, dotPathOf(node) ?? `#${position}`, (inner) => renderFields(ctx, node.children, inner)));
113
258
  }
114
- function renderField(ctx, node, indexes, position) {
259
+ /**
260
+ * Renders one field with its component. `children` renders the fields inside a group, or inside
261
+ * the item of an array, with the indexes of the items they are in.
262
+ */
263
+ function renderField(ctx, node, indexes, key, children) {
115
264
  const component = ctx.components[node.type];
116
265
  const path = toFieldPath(node.dataPath, indexes);
117
- const key = dotPathOf(node) ?? `#${position}`;
118
266
  switch (node.role) {
119
267
  case "display": return h(component, {
120
268
  key,
@@ -124,14 +272,14 @@ function renderField(ctx, node, indexes, position) {
124
272
  key,
125
273
  field: node.field,
126
274
  path
127
- }, { default: () => renderFields(ctx, node.children, indexes) });
275
+ }, { default: () => children(indexes) });
128
276
  case "array": return h(ArrayField, {
129
277
  key,
130
278
  of: ctx.of,
131
279
  node,
132
280
  path,
133
281
  component,
134
- renderItem: (index) => renderFields(ctx, node.children, [...indexes, index])
282
+ renderItem: (index) => children([...indexes, index])
135
283
  });
136
284
  case "value": return h(ValueField, {
137
285
  key,
@@ -170,14 +318,39 @@ const JsonField = defineComponent({
170
318
  components: resolveComponents(props.components, props.of)
171
319
  };
172
320
  checkComponents([node], ctx.components);
173
- return () => renderField(ctx, node, [], 0);
321
+ return () => renderField(ctx, node, [], dotPathOf(node) ?? "#0", (indexes) => renderFields(ctx, node.children, indexes));
174
322
  }
175
323
  });
324
+ // src/render-layout.ts
325
+ /**
326
+ * Renders layout nodes. A field left out drops out, and so does a section or a row left without
327
+ * fields. `indexes` are the indexes of the array items the nodes are in, outermost first.
328
+ */
329
+ function renderLayout(ctx, nodes, indexes) {
330
+ return shownNodes(nodes, indexes, ctx.isShown).map((node) => renderNode(ctx, node, indexes));
331
+ }
332
+ function renderNode(ctx, node, indexes) {
333
+ const { key } = node;
334
+ switch (node.type) {
335
+ case "field": return renderField(ctx, node.node, indexes, key, (inner) => renderLayout(ctx, node.children, inner));
336
+ case "section": return h(ctx.layoutComponents.section, {
337
+ key,
338
+ section: node.section
339
+ }, { default: () => renderLayout(ctx, node.children, indexes) });
340
+ case "row": return h(ctx.layoutComponents.row, {
341
+ key,
342
+ row: node.row,
343
+ spans: node.children.map((cell) => cell.span)
344
+ }, { default: ({ index }) => [renderNode(ctx, node.children[index], indexes)] });
345
+ case "slot": return h(Fragment, { key }, ctx.slots[node.name]?.());
346
+ }
347
+ }
176
348
  // src/json-fields.ts
177
349
  /**
178
- * Renders the fields of a `useJsonForm` form with your components, one per field type. It renders
179
- * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button. `only` and
180
- * `except` are read when it sets up.
350
+ * Renders the fields of a `useJsonForm` form with your components, one per field type, where the
351
+ * definition's `layout` puts them: the current step, with its sections, rows and slots. It renders
352
+ * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button. Its named
353
+ * slots fill the layout's slots. `only` and `except` are read when it sets up.
181
354
  */
182
355
  const JsonFields = defineComponent({
183
356
  name: "JsonFields",
@@ -189,12 +362,19 @@ const JsonFields = defineComponent({
189
362
  },
190
363
  /** Defaults to the components `useJsonForm` got. */
191
364
  components: { type: Object },
192
- /** Renders only these fields, by path, in the order of the definition. */
365
+ /** Defaults to the layout components `useJsonForm` got. */
366
+ layoutComponents: { type: Object },
367
+ /** Renders only these fields, by path, in the order of the definition, without the layout. */
193
368
  only: { type: Array },
194
- /** Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. */
195
- except: { type: Array }
369
+ /**
370
+ * Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. A section or a
371
+ * row left without fields drops out too.
372
+ */
373
+ except: { type: Array },
374
+ /** Renders the step at this index instead of the current one, e.g. on a review page. */
375
+ step: { type: Number }
196
376
  },
197
- setup(props) {
377
+ setup(props, { slots }) {
198
378
  const ctx = {
199
379
  of: props.of,
200
380
  components: resolveComponents(props.components, props.of)
@@ -203,10 +383,36 @@ const JsonFields = defineComponent({
203
383
  only: props.only,
204
384
  except: props.except
205
385
  });
206
- checkComponents(nodes, ctx.components);
207
- return () => renderFields(ctx, nodes, []);
386
+ if (props.only || !props.of.definition.layout) {
387
+ checkComponents(nodes, ctx.components);
388
+ return () => renderFields(ctx, nodes, []);
389
+ }
390
+ const { layout, steps } = props.of;
391
+ const except = new Set(props.except);
392
+ const layoutCtx = {
393
+ ...ctx,
394
+ layoutComponents: props.layoutComponents ?? props.of.layoutComponents ?? {},
395
+ slots,
396
+ isShown: (node) => !except.has(dotPathOf(node) ?? "")
397
+ };
398
+ checkComponents(layout.steps.flatMap((step) => fieldsOf(step.children)), ctx.components);
399
+ checkLayoutComponents(layout, layoutCtx.layoutComponents, slots);
400
+ return () => {
401
+ const index = props.step ?? steps.index;
402
+ const step = steps.list[index];
403
+ if (!step) throw new RangeError(`No step ${props.step}. The form has ${steps.list.length}.`);
404
+ return h(Fragment, { key: index }, renderLayout(layoutCtx, step.children, []));
405
+ };
208
406
  }
209
407
  });
408
+ /** Every field of the nodes, display elements written in the layout included. */
409
+ function fieldsOf(nodes) {
410
+ return nodes.flatMap((node) => {
411
+ if (node.type === "slot") return [];
412
+ const inner = fieldsOf(node.children);
413
+ return node.type === "field" ? [node.node, ...inner] : inner;
414
+ });
415
+ }
210
416
  // src/use-json-form.ts
211
417
  function useJsonForm(definition, options = {}) {
212
418
  const builder = options.builder ?? createSchemaBuilder();
@@ -216,13 +422,16 @@ function useJsonForm(definition, options = {}) {
216
422
  validate: options.validate,
217
423
  revalidate: options.revalidate
218
424
  });
219
- const { components } = options;
425
+ const layout = builder.getLayout(definition);
220
426
  return {
221
427
  form,
222
428
  definition,
223
429
  builder,
224
- components,
225
- fields: builder.getFieldTree(definition)
430
+ components: options.components,
431
+ layoutComponents: options.layoutComponents,
432
+ fields: builder.getFieldTree(definition),
433
+ layout,
434
+ steps: useJsonFormSteps(form, layout, options)
226
435
  };
227
436
  }
228
437
  export { JsonField, JsonFields, useJsonForm };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/array-field.ts","../src/value-field.ts","../src/render-fields.ts","../src/json-field.ts","../src/json-fields.ts","../src/use-json-form.ts"],"sourcesContent":["import { insert, move, remove, useFieldArray } from '@formisch/vue'\nimport type { FieldNode, FieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type Component, defineComponent, h, type PropType, type VNode } from 'vue'\nimport type { ArrayFieldProps } from './field-components'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\n/** Connects a container of kind `array` to Formisch, and renders your component for it. */\nexport const ArrayField = defineComponent({\n name: 'ArrayField',\n props: {\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n node: { type: Object as PropType<FieldNode>, required: true },\n path: { type: Array as PropType<FieldPath>, required: true },\n component: { type: [Object, Function] as PropType<Component>, required: true },\n /** Renders the fields of the item at `index`. */\n renderItem: { type: Function as PropType<(index: number) => VNode[]>, required: true },\n },\n setup(props) {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const path = () => props.path as never\n const array = useFieldArray(\n () => props.of.form,\n () => ({ path: path() }),\n )\n\n const methods = {\n // A new item starts with the defaults of its fields, like the items of a new form.\n add: () =>\n insert(props.of.form, {\n path: path(),\n initialInput: props.of.builder.getItemInput(props.node.field) as never,\n }),\n remove: (index: number) => remove(props.of.form, { path: path(), at: index }),\n move: (from: number, to: number) => move(props.of.form, { path: path(), from, to }),\n }\n\n return () => {\n const fieldProps: ArrayFieldProps = {\n field: props.node.field,\n path: props.path,\n items: array.items,\n errors: array.errors,\n ...methods,\n }\n return h(props.component, fieldProps, {\n default: ({ index }: { index: number }) => props.renderItem(index),\n })\n }\n },\n})\n","import { useField } from '@formisch/vue'\nimport {\n coerceInput,\n type FieldNode,\n type FieldPath,\n isRequired,\n} from '@whatsaaaa/form-builder-schema'\nimport { type Component, defineComponent, h, type PropType } from 'vue'\nimport type { ValueFieldProps } from './field-components'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\n/** Connects a field with a value to Formisch, and renders your component for it. */\nexport const ValueField = defineComponent({\n name: 'ValueField',\n props: {\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n node: { type: Object as PropType<FieldNode>, required: true },\n path: { type: Array as PropType<FieldPath>, required: true },\n component: { type: [Object, Function] as PropType<Component>, required: true },\n },\n setup(props) {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const field = useField(\n () => props.of.form,\n () => ({ path: props.path as never }),\n )\n\n const setInput = (input: unknown) => {\n field.input = coerceInput(input, props.node) as never\n }\n\n return () => {\n const fieldProps: ValueFieldProps = {\n field: props.node.field,\n path: props.path,\n kind: props.node.kind,\n required: isRequired(props.node.field),\n input: field.input,\n errors: field.errors,\n inputProps: field.props,\n setInput,\n }\n return h(props.component, fieldProps)\n }\n },\n})\n","import { dotPathOf, type FieldNode, toFieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type Component, h, type VNode } from 'vue'\nimport { ArrayField } from './array-field'\nimport type { FieldComponents } 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): VNode[] {\n return nodes.map((node, position) => renderField(ctx, node, indexes, position))\n}\n\nexport function renderField(\n ctx: RenderContext,\n node: FieldNode,\n indexes: readonly number[],\n position: number,\n): VNode {\n const component = ctx.components[node.type] as Component\n const path = toFieldPath(node.dataPath, indexes)\n const key = dotPathOf(node) ?? `#${position}`\n switch (node.role) {\n case 'display':\n return h(component, { key, field: node.field })\n case 'group':\n return h(\n component,\n { key, field: node.field, path },\n { default: () => renderFields(ctx, node.children, indexes) },\n )\n case 'array':\n return h(ArrayField, {\n key,\n of: ctx.of,\n node,\n path,\n component,\n renderItem: (index: number) => renderFields(ctx, node.children, [...indexes, index]),\n })\n case 'value':\n return h(ValueField, { key, of: ctx.of, node, path, component })\n }\n}\n","import { checkComponents, findField } from '@whatsaaaa/form-builder-schema'\nimport { defineComponent, type PropType } from 'vue'\nimport type { FieldComponents } from './field-components'\nimport { type RenderContext, renderField, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\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 const JsonField = defineComponent({\n name: 'JsonField',\n props: {\n /** What `useJsonForm` returned. */\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n /** The field's path: `'email'`, or `'address.city'` for a field of a group. */\n path: { type: String, required: true },\n /** Defaults to the components `useJsonForm` got. */\n components: { type: Object as PropType<FieldComponents> },\n },\n setup(props) {\n const node = findField(props.of.fields, props.path, { placed: true })\n const ctx: RenderContext = {\n of: props.of,\n components: resolveComponents(props.components, props.of),\n }\n checkComponents([node], ctx.components)\n\n return () => renderField(ctx, node, [], 0)\n },\n})\n","import { checkComponents, selectFields } from '@whatsaaaa/form-builder-schema'\nimport { defineComponent, type PropType } from 'vue'\nimport type { FieldComponents } from './field-components'\nimport { type RenderContext, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\n/**\n * Renders the fields of a `useJsonForm` form with your components, one per field type. It renders\n * nothing of its own, so put it inside Formisch's `<Form>` with your own submit button. `only` and\n * `except` are read when it sets up.\n */\nexport const JsonFields = defineComponent({\n name: 'JsonFields',\n props: {\n /** What `useJsonForm` returned. */\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n /** Defaults to the components `useJsonForm` got. */\n components: { type: Object as PropType<FieldComponents> },\n /** Renders only these fields, by path, in the order of the definition. */\n only: { type: Array as PropType<readonly string[]> },\n /** Leaves out these fields, by path, e.g. the ones you place with `<JsonField>`. */\n except: { type: Array as PropType<readonly string[]> },\n },\n setup(props) {\n const ctx: RenderContext = {\n of: props.of,\n components: resolveComponents(props.components, props.of),\n }\n const nodes = selectFields(props.of.fields, { only: props.only, except: props.except })\n checkComponents(nodes, ctx.components)\n\n return () => renderFields(ctx, nodes, [])\n },\n})\n","import { type FormSchema, type FormStore, useForm, type ValidationMode } from '@formisch/vue'\nimport {\n createSchemaBuilder,\n type FieldNode,\n type JsonForm,\n type JsonFormSchema,\n type SchemaBuilder,\n type TypedJsonFormSchema,\n} from '@whatsaaaa/form-builder-schema'\nimport type { FieldComponents } from './field-components'\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 /** 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 definition's fields as a tree, the way `<JsonFields>` renders them. */\n readonly fields: readonly FieldNode[]\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. The definition is read once: give the form a new `:key` to use\n * another one.\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 const builder = options.builder ?? createSchemaBuilder()\n const form = useForm({\n schema: builder.build(definition),\n initialInput: builder.getInitialInput(definition),\n validate: options.validate,\n revalidate: options.revalidate,\n })\n const { components } = options\n return { form, definition, builder, components, fields: builder.getFieldTree(definition) }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/json-form-steps.ts","../src/array-field.ts","../src/value-field.ts","../src/render-fields.ts","../src/json-field.ts","../src/render-layout.ts","../src/json-fields.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/vue'\nimport {\n type FieldPath,\n fieldPathsOf,\n type FormLayout,\n issuesOfStep,\n type LayoutStep,\n type StepIssue,\n} from '@whatsaaaa/form-builder-schema'\nimport { computed, getCurrentScope, nextTick, onScopeDispose, ref, watch } from 'vue'\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\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.\n */\nexport function useJsonFormSteps(\n form: FormStore,\n layout: FormLayout,\n modes: {\n validate?: ValidationMode | undefined\n revalidate?: Exclude<ValidationMode, 'initial'> | undefined\n },\n): JsonFormSteps {\n const list = layout.steps\n const index = ref(0)\n const current = computed(() => list[index.value] as LayoutStep)\n // The errors each step had when the user left it, by the JSON of each field's path.\n const left = new Map<number, Map<string, Errors>>()\n // The steps that failed Next, and haven't passed since.\n const failed = new Set<number>()\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 <= index.value) {\n move(target)\n return true\n }\n const { issues } = await validate(form)\n for (let at = index.value; at < target; at++) {\n const found = issuesOfStep(list[at] as LayoutStep, issues)\n if (found.length) {\n await stop(at, found)\n return false\n }\n failed.delete(at)\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 = 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 index.value = 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(current.value, getInput(form))\n const errors = paths.map((path): [string, Errors] => [JSON.stringify(path), errorsAt(path)])\n left.set(index.value, new Map(errors))\n }\n\n const errorsAt = (path: FieldPath): Errors => getErrors(form, { path } as never)\n\n /** Stays on, or goes to, the step at `at`, with its issues shown. */\n async function stop(at: number, found: readonly StepIssue[]) {\n failed.add(at)\n if (at !== index.value) leave()\n index.value = at\n await nextTick()\n // A group or an array has no element: the first field that has one gets the focus.\n const first = found.find((issue) => issue.field.role === 'value')\n if (first) focus(form, { path: first.path as never })\n }\n\n const steps: JsonFormSteps = {\n list,\n get index() {\n return index.value\n },\n get current() {\n return current.value\n },\n get isFirst() {\n return index.value === 0\n },\n get isLast() {\n return index.value === list.length - 1\n },\n next: () => (steps.isLast ? Promise.resolve(false) : goTo(index.value + 1)),\n back: () => {\n if (index.value > 0) move(index.value - 1)\n },\n goTo,\n }\n if (list.length > 1) watchSteps()\n return steps\n\n function watchSteps() {\n fieldEvents.set(form, (event) => {\n // In the other modes, Formisch revalidates a field with errors itself.\n const validateMode = modes.validate ?? 'submit'\n if (!failed.has(index.value) || validateMode !== 'submit' || form.isSubmitted) return\n if (event === (modes.revalidate ?? 'input')) void validate(form)\n })\n\n // A submit that fails because of a step before the last, e.g. on a form rule, goes back to it.\n watch(\n () => form.isSubmitting,\n async (submitting, was) => {\n if (submitting || !was || form.isValid) return\n const { issues } = await validate(form)\n const at = list.findIndex((step) => issuesOfStep(step, issues).length)\n if (at !== -1 && at < index.value)\n await stop(at, issuesOfStep(list[at] as LayoutStep, issues))\n },\n )\n\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 if (typeof document === 'undefined') return\n const onSubmit = (event: Event) => {\n if (event.target !== formElementOf(form) || steps.isLast) return\n event.preventDefault()\n event.stopImmediatePropagation()\n void steps.next()\n }\n document.addEventListener('submit', onSubmit, { capture: true })\n if (getCurrentScope()) {\n onScopeDispose(() => document.removeEventListener('submit', onSubmit, { capture: true }))\n }\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/vue'\nimport type { FieldNode, FieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type Component, defineComponent, h, type PropType, type VNode } from 'vue'\nimport type { ArrayFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\n/** Connects a container of kind `array` to Formisch, and renders your component for it. */\nexport const ArrayField = defineComponent({\n name: 'ArrayField',\n props: {\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n node: { type: Object as PropType<FieldNode>, required: true },\n path: { type: Array as PropType<FieldPath>, required: true },\n component: { type: [Object, Function] as PropType<Component>, required: true },\n /** Renders the fields of the item at `index`. */\n renderItem: { type: Function as PropType<(index: number) => VNode[]>, required: true },\n },\n setup(props) {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const path = () => props.path as never\n const array = useFieldArray(\n () => props.of.form,\n () => ({ path: path() }),\n )\n\n // In a form with steps, a step that failed Next revalidates on a change of its items too.\n const events = fieldEventsOf(props.of.form)\n const methods = {\n // A new item starts with the defaults of its fields, like the items of a new form.\n add: () => {\n insert(props.of.form, {\n path: path(),\n initialInput: props.of.builder.getItemInput(props.node.field) as never,\n })\n events?.('input')\n },\n remove: (index: number) => {\n remove(props.of.form, { path: path(), at: index })\n events?.('input')\n },\n move: (from: number, to: number) => {\n move(props.of.form, { path: path(), from, to })\n events?.('input')\n },\n }\n\n return () => {\n const fieldProps: ArrayFieldProps = {\n field: props.node.field,\n path: props.path,\n items: array.items,\n errors: array.errors,\n ...methods,\n }\n return h(props.component, fieldProps, {\n default: ({ index }: { index: number }) => props.renderItem(index),\n })\n }\n },\n})\n","import { type FieldElementProps, useField } from '@formisch/vue'\nimport {\n coerceInput,\n type FieldNode,\n type FieldPath,\n isRequired,\n} from '@whatsaaaa/form-builder-schema'\nimport { type Component, defineComponent, h, type PropType } from 'vue'\nimport type { ValueFieldProps } from './field-components'\nimport { fieldEventsOf } from './json-form-steps'\nimport type { AnyJsonFormInstance } from './use-json-form'\n\n/** Connects a field with a value to Formisch, and renders your component for it. */\nexport const ValueField = defineComponent({\n name: 'ValueField',\n props: {\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n node: { type: Object as PropType<FieldNode>, required: true },\n path: { type: Array as PropType<FieldPath>, required: true },\n component: { type: [Object, Function] as PropType<Component>, required: true },\n },\n setup(props) {\n // The path comes from the definition at runtime, so the schema's types can't check it.\n const field = useField(\n () => props.of.form,\n () => ({ path: props.path as never }),\n )\n\n // In a form with steps, a step that failed Next revalidates on these events too.\n const events = fieldEventsOf(props.of.form)\n const setInput = (input: unknown) => {\n field.input = coerceInput(input, props.node) as never\n events?.('input')\n }\n const inputProps: FieldElementProps = events\n ? {\n get name() {\n return field.props.name\n },\n get autofocus() {\n return field.props.autofocus\n },\n ref: (element) => field.props.ref(element),\n onFocus: () => {\n field.props.onFocus()\n events('touch')\n },\n onChange: (event) => {\n field.props.onChange(event)\n events('change')\n },\n onBlur: () => {\n field.props.onBlur()\n events('blur')\n },\n }\n : field.props\n\n return () => {\n const fieldProps: ValueFieldProps = {\n field: props.node.field,\n path: props.path,\n kind: props.node.kind,\n required: isRequired(props.node.field),\n input: field.input,\n errors: field.errors,\n inputProps,\n setInput,\n }\n return h(props.component, fieldProps)\n }\n },\n})\n","import { dotPathOf, type FieldNode, toFieldPath } from '@whatsaaaa/form-builder-schema'\nimport { type Component, h, type VNode } from 'vue'\nimport { ArrayField } from './array-field'\nimport type { FieldComponents } 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): VNode[] {\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[]) => VNode[],\n): VNode {\n const component = ctx.components[node.type] as Component\n const path = toFieldPath(node.dataPath, indexes)\n switch (node.role) {\n case 'display':\n return h(component, { key, field: node.field })\n case 'group':\n return h(component, { key, field: node.field, path }, { default: () => children(indexes) })\n case 'array':\n return h(ArrayField, {\n key,\n of: ctx.of,\n node,\n path,\n component,\n renderItem: (index: number) => children([...indexes, index]),\n })\n case 'value':\n return h(ValueField, { key, of: ctx.of, node, path, component })\n }\n}\n","import { checkComponents, dotPathOf, findField } from '@whatsaaaa/form-builder-schema'\nimport { defineComponent, type PropType } from 'vue'\nimport type { FieldComponents } from './field-components'\nimport { type RenderContext, renderField, renderFields, resolveComponents } from './render-fields'\nimport type { AnyJsonFormInstance } from './use-json-form'\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 const JsonField = defineComponent({\n name: 'JsonField',\n props: {\n /** What `useJsonForm` returned. */\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n /** The field's path: `'email'`, or `'address.city'` for a field of a group. */\n path: { type: String, required: true },\n /** Defaults to the components `useJsonForm` got. */\n components: { type: Object as PropType<FieldComponents> },\n },\n setup(props) {\n const node = findField(props.of.fields, props.path, { placed: true })\n const ctx: RenderContext = {\n of: props.of,\n components: resolveComponents(props.components, props.of),\n }\n checkComponents([node], ctx.components)\n\n return () =>\n renderField(ctx, node, [], dotPathOf(node) ?? '#0', (indexes) =>\n renderFields(ctx, node.children, indexes),\n )\n },\n})\n","import { type IsFieldShown, type LayoutNode, shownNodes } from '@whatsaaaa/form-builder-schema'\nimport { type Component, Fragment, h, type Slots, type VNode } from 'vue'\nimport type { LayoutComponents } 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: Slots\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): VNode[] {\n return shownNodes(nodes, indexes, ctx.isShown).map((node) => renderNode(ctx, node, indexes))\n}\n\nfunction renderNode(ctx: LayoutContext, node: LayoutNode, indexes: readonly number[]): VNode {\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 return h(\n ctx.layoutComponents.section as Component,\n { key, section: node.section },\n { default: () => renderLayout(ctx, node.children, indexes) },\n )\n case 'row':\n return h(\n ctx.layoutComponents.row as Component,\n { key, row: node.row, spans: node.children.map((cell) => cell.span) },\n {\n default: ({ index }: { index: number }) => [\n renderNode(ctx, node.children[index] as LayoutNode, indexes),\n ],\n },\n )\n case 'slot':\n return h(Fragment, { key }, ctx.slots[node.name]?.())\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 { defineComponent, Fragment, h, type PropType } from 'vue'\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\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. Its named\n * slots fill the layout's slots. `only` and `except` are read when it sets up.\n */\nexport const JsonFields = defineComponent({\n name: 'JsonFields',\n props: {\n /** What `useJsonForm` returned. */\n of: { type: Object as PropType<AnyJsonFormInstance>, required: true },\n /** Defaults to the components `useJsonForm` got. */\n components: { type: Object as PropType<FieldComponents> },\n /** Defaults to the layout components `useJsonForm` got. */\n layoutComponents: { type: Object as PropType<LayoutComponents> },\n /** Renders only these fields, by path, in the order of the definition, without the layout. */\n only: { type: Array as PropType<readonly string[]> },\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: { type: Array as PropType<readonly string[]> },\n /** Renders the step at this index instead of the current one, e.g. on a review page. */\n step: { type: Number },\n },\n setup(props, { slots }) {\n const ctx: RenderContext = {\n of: props.of,\n components: resolveComponents(props.components, props.of),\n }\n const nodes = selectFields(props.of.fields, { only: props.only, except: props.except })\n // Without a layout, it renders the fields the way it always did, and so with `only`.\n if (props.only || !props.of.definition.layout) {\n checkComponents(nodes, ctx.components)\n return () => renderFields(ctx, nodes, [])\n }\n\n const { layout, steps } = props.of\n const except = new Set(props.except)\n const layoutCtx: LayoutContext = {\n ...ctx,\n layoutComponents: props.layoutComponents ?? props.of.layoutComponents ?? {},\n slots,\n isShown: (node) => !except.has(dotPathOf(node) ?? ''),\n }\n checkComponents(\n layout.steps.flatMap((step) => fieldsOf(step.children)),\n ctx.components,\n )\n checkLayoutComponents(layout, layoutCtx.layoutComponents, slots)\n\n return () => {\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 h(Fragment, { key: index }, renderLayout(layoutCtx, step.children, []))\n }\n },\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/vue'\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 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. The definition is read once: give the form a new `:key` to use\n * another one.\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 const builder = options.builder ?? createSchemaBuilder()\n const form = useForm({\n schema: builder.build(definition),\n initialInput: builder.getInitialInput(definition),\n validate: options.validate,\n revalidate: options.revalidate,\n })\n const layout = builder.getLayout(definition)\n return {\n form,\n definition,\n builder,\n components: options.components,\n layoutComponents: options.layoutComponents,\n fields: builder.getFieldTree(definition),\n layout,\n steps: useJsonFormSteps(form, layout, options),\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatsaaaa/form-builder-vue",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Render JSON form definitions with Formisch and your own Vue 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.13.0"
36
+ "@whatsaaaa/form-builder-schema": "^0.14.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@arethetypeswrong/cli": "^0.18.5",