@nolwenture/formik-jsonschema-form 0.11.0 → 0.13.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 +93 -19
- package/dist/fields/ArrayField.js +7 -3
- package/dist/fields/StringField.js +2 -3
- package/dist/fields/stories/ArrayField.stories.d.ts +1 -0
- package/dist/fields/stories/ArrayField.stories.js +33 -0
- package/dist/fields/stories/MultipleChoiceField.stories.d.ts +6 -0
- package/dist/fields/stories/MultipleChoiceField.stories.js +86 -0
- package/dist/fields/stories/SearchMultiTagSelectField.stories.js +1 -1
- package/dist/sampleData/completeSchema.js +2 -2
- package/dist/sampleData/multipleChoiceSchema.js +3 -3
- package/dist/styledComponents.js +1 -1
- package/dist/utils/__tests/createValidationSchema.test.d.ts +1 -0
- package/dist/utils/__tests/createValidationSchema.test.js +136 -0
- package/dist/utils/createValidationSchema.d.ts +3 -0
- package/dist/utils/createValidationSchema.js +47 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -70,8 +70,7 @@ export function MyFormComponent() {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Pass your uiSchema to the `Form`component
|
|
74
|
-
just like schema. uiSchema will tell the form more about its own layout and styles.
|
|
73
|
+
Pass your uiSchema to the `Form`component, which should be passed as a child to the `Formik`component, just like schema. uiSchema will tell the form more about its own layout and styles.
|
|
75
74
|
|
|
76
75
|
Basic available elements to add to the form or a field of the form via uiSchema are:
|
|
77
76
|
|
|
@@ -131,6 +130,51 @@ export function MyFormComponent() {
|
|
|
131
130
|
}
|
|
132
131
|
```
|
|
133
132
|
|
|
133
|
+
### Using create-functions for initialValues and validationSchema
|
|
134
|
+
|
|
135
|
+
This library offers create-functions that, based on the JSON schema provided, will return a basic object of either initialValues or yup validationSchema.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { Form } from "@nolwenture/formik-jsonschema-form";
|
|
139
|
+
import {
|
|
140
|
+
createInitialValues,
|
|
141
|
+
createValidationSchema,
|
|
142
|
+
} from "@nolwenture/formik-jsonschema-form/dist/utils";
|
|
143
|
+
import { Formik } from "formik";
|
|
144
|
+
|
|
145
|
+
const schema = {
|
|
146
|
+
title: "My form",
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
firstName: { type: "string", title: "First name" },
|
|
150
|
+
lastName: { type: "string", title: "Last name" },
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const uiSchema = {
|
|
155
|
+
"ui:description": "Form description",
|
|
156
|
+
firstName: { "ui:description": "Field description" },
|
|
157
|
+
lastName: {
|
|
158
|
+
"ui:info": {
|
|
159
|
+
label: "?",
|
|
160
|
+
content: "Add your family name",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export function MyFormComponent() {
|
|
166
|
+
return (
|
|
167
|
+
<Formik
|
|
168
|
+
initialValues={createInitialValues(schema)}
|
|
169
|
+
validationSchema={createValidationSchema(schema)}
|
|
170
|
+
onSubmit={handleSubmit}
|
|
171
|
+
>
|
|
172
|
+
<Form schema={schema} uiSchema={uiSchema}></Form>
|
|
173
|
+
</Formik>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
134
178
|
### Using custom components
|
|
135
179
|
|
|
136
180
|
Form components (such as wrappers) can be overridden with custom ones
|
|
@@ -365,6 +409,10 @@ export function MyFormComponent() {
|
|
|
365
409
|
Enum fields are fields with multiple options to either choose `oneOf` or `anyOf` from.
|
|
366
410
|
There is an alternative field implementation for "anyOf" that when given `"ui:field": "SearchMultiTagSelectField"` results in a searchable multi tag select field.
|
|
367
411
|
|
|
412
|
+
The type of the field holding the enum is defined by its return value. `oneOf` enums will only return a single value and therefore, the type of the returned value is used as the field type (in the following example, "number" is the correct field type).
|
|
413
|
+
|
|
414
|
+
`anyOf`enums will return the value(s) inside an array, even if only one option was selected. Therefore, the correct field type for the field holding the `anyOf`-enum is "array".
|
|
415
|
+
|
|
368
416
|
```tsx
|
|
369
417
|
const schema = {
|
|
370
418
|
properties: {
|
|
@@ -384,7 +432,7 @@ const schema = {
|
|
|
384
432
|
],
|
|
385
433
|
},
|
|
386
434
|
enumAnyOf: {
|
|
387
|
-
type: "
|
|
435
|
+
type: "array",
|
|
388
436
|
title: "Enum anyOf (non-standard)",
|
|
389
437
|
anyOf: [
|
|
390
438
|
{ const: 1, title: "one" },
|
|
@@ -394,7 +442,7 @@ const schema = {
|
|
|
394
442
|
],
|
|
395
443
|
},
|
|
396
444
|
enumTagSelectAnyOf: {
|
|
397
|
-
type: "
|
|
445
|
+
type: "array",
|
|
398
446
|
title: "Enum searchable tag select anyOf",
|
|
399
447
|
anyOf: [
|
|
400
448
|
{ const: 1, title: "one" },
|
|
@@ -420,7 +468,7 @@ const uiSchema = {
|
|
|
420
468
|
### Alternative fields
|
|
421
469
|
|
|
422
470
|
There are alternative fields available for certain types.
|
|
423
|
-
For `type: "string"` there is `DateField`, `
|
|
471
|
+
For `type: "string"` there is `DateField`, `TextAreaField` and partly `MultipleChoiceField`.
|
|
424
472
|
|
|
425
473
|
`DateField` has a "dd.mm.yyyy" date input field and a calendar date picker and will save the date input as "yyyy-mm-dd" string.
|
|
426
474
|
|
|
@@ -436,7 +484,28 @@ const uiSchema = {
|
|
|
436
484
|
};
|
|
437
485
|
```
|
|
438
486
|
|
|
439
|
-
`
|
|
487
|
+
`TextAreaField` gives more space for a longer string input.
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
const schema = {
|
|
491
|
+
properties: {
|
|
492
|
+
story: { type: "string", title: "A short story" },
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const uiSchema = {
|
|
497
|
+
story: { "ui:field": "TextAreaField" },
|
|
498
|
+
};
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
`MultipleChoiceField` takes an array of `oneOf` or `anyOf`.
|
|
502
|
+
If you are creating a schema using `oneOf`, the type of the field is the value returned by the chosen enum option (e.g. "string" or "number").
|
|
503
|
+
If you are creating a schema using `anyOf`, the type of the field is "array", since the returned value will be in an array format, even if only one option is selected.
|
|
504
|
+
|
|
505
|
+
The choices provided inside an `oneOf`array will be presented on the UI as Radio button selectables and only one of the given values can be selected.
|
|
506
|
+
|
|
507
|
+
The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.
|
|
508
|
+
|
|
440
509
|
`MultipleChoiceField` comes with a small number of predefined styles, which are `inline`, `oneColumn`, `twoColumns` and `threeColumns`. When no style is given, the choice options are listed in a row under the title.
|
|
441
510
|
|
|
442
511
|
```tsx
|
|
@@ -452,6 +521,19 @@ const schema = {
|
|
|
452
521
|
{ const: "nope", title: "Please don't even ask..." },
|
|
453
522
|
],
|
|
454
523
|
},
|
|
524
|
+
food: {
|
|
525
|
+
type: "array",
|
|
526
|
+
title: "What would you like to eat today? (style: oneColumn)",
|
|
527
|
+
anyOf: [
|
|
528
|
+
{ const: "breakfast", title: "Breakfast" },
|
|
529
|
+
{ const: "secondBreakfast", title: "What about second breakfast?" },
|
|
530
|
+
{ const: "elevenses", title: "Elevenses" },
|
|
531
|
+
{ const: "luncheon", title: "Luncheon" },
|
|
532
|
+
{ const: "afternoonTea", title: "Afternoon tea" },
|
|
533
|
+
{ const: "dinner", title: "Dinner" },
|
|
534
|
+
{ const: "supper", title: "Supper" },
|
|
535
|
+
],
|
|
536
|
+
},
|
|
455
537
|
},
|
|
456
538
|
};
|
|
457
539
|
|
|
@@ -462,21 +544,13 @@ const uiSchema = {
|
|
|
462
544
|
style: "twoColumns",
|
|
463
545
|
},
|
|
464
546
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
```tsx
|
|
471
|
-
const schema = {
|
|
472
|
-
properties: {
|
|
473
|
-
story: { type: "string", title: "A short story" },
|
|
547
|
+
food: {
|
|
548
|
+
"ui:field": "MultipleChoiceField",
|
|
549
|
+
"ui:options": {
|
|
550
|
+
style: "oneColumn",
|
|
551
|
+
},
|
|
474
552
|
},
|
|
475
553
|
};
|
|
476
|
-
|
|
477
|
-
const uiSchema = {
|
|
478
|
-
story: { "ui:field": "TextAreaField" },
|
|
479
|
-
};
|
|
480
554
|
```
|
|
481
555
|
|
|
482
556
|
For `type: "boolean"` there is `RadioField` and `ToggleField`.
|
|
@@ -4,7 +4,9 @@ import { useField } from "formik";
|
|
|
4
4
|
import { FormRegistry } from "../Form";
|
|
5
5
|
import { ArrayFieldButtonPurpose } from "../types";
|
|
6
6
|
import { useExtractUiSchema, useSetDefault } from "../hooks";
|
|
7
|
-
import { GetArrayFieldButton } from "../styledComponents";
|
|
7
|
+
import { FieldInput, GetArrayFieldButton } from "../styledComponents";
|
|
8
|
+
import { FieldTemplate } from "../templates";
|
|
9
|
+
import { getEnums } from "../utils";
|
|
8
10
|
const appendArray = (values) => {
|
|
9
11
|
return values ? [...values, ""] : [""];
|
|
10
12
|
};
|
|
@@ -33,6 +35,7 @@ export const ArrayField = (props) => {
|
|
|
33
35
|
const { fields: { SchemaField }, templates: { ArrayFieldTemplate }, components: { ArrayFieldItem, ArrayFieldList, ArrayFieldItemButtons }, useItemsAsAdditionalItems, } = useContext(FormRegistry);
|
|
34
36
|
useSetDefault(props);
|
|
35
37
|
const { name, schema, uiSchema } = props;
|
|
38
|
+
const { type, props: inputProps } = useExtractUiSchema(uiSchema);
|
|
36
39
|
const [{ value }, _, { setValue }] = useField(name);
|
|
37
40
|
const { items, additionalItems, } = useMemo(() => {
|
|
38
41
|
let items = schema.items && typeof schema.items !== "boolean" ? schema.items : [];
|
|
@@ -51,6 +54,7 @@ export const ArrayField = (props) => {
|
|
|
51
54
|
: undefined,
|
|
52
55
|
};
|
|
53
56
|
}, [schema]);
|
|
57
|
+
const enums = useMemo(() => getEnums(props), [props]);
|
|
54
58
|
//This whole mess would give backwards compatibility, but maybe we don't do it
|
|
55
59
|
// const {
|
|
56
60
|
// items,
|
|
@@ -91,10 +95,10 @@ export const ArrayField = (props) => {
|
|
|
91
95
|
// },
|
|
92
96
|
// [schema]
|
|
93
97
|
// );
|
|
94
|
-
//
|
|
98
|
+
// Backwards compatibility ends
|
|
95
99
|
const { disabled } = useExtractUiSchema(uiSchema);
|
|
96
100
|
const valueArray = Array.isArray(value) ? value : undefined;
|
|
97
|
-
return (_jsxs(ArrayFieldTemplate, Object.assign({}, props, { children: [_jsxs(ArrayFieldList, { children: [(items || []).map((itemSchema, index) => (_jsx(ArrayFieldItem, { children: _jsx(SchemaField, { name: `${name}[${index}]`, schema: typeof itemSchema !== "boolean" ? itemSchema : {}, uiSchema: (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items), forceDefaultValue: true }) }, index))), ((valueArray === null || valueArray === void 0 ? void 0 : valueArray.slice(items.length)) || []).map((_, index) => (_jsxs(ArrayFieldItem, { children: [_jsx(SchemaField, { name: `${name}[${index + items.length}]`, schema: additionalItems && typeof additionalItems !== "boolean"
|
|
101
|
+
return schema.anyOf !== undefined ? (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsx(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", multiple: (enums === null || enums === void 0 ? void 0 : enums.type) === "multiSelect", children: enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index))) })) }))) : (_jsxs(ArrayFieldTemplate, Object.assign({}, props, { children: [_jsxs(ArrayFieldList, { children: [(items || []).map((itemSchema, index) => (_jsx(ArrayFieldItem, { children: _jsx(SchemaField, { name: `${name}[${index}]`, schema: typeof itemSchema !== "boolean" ? itemSchema : {}, uiSchema: (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items), forceDefaultValue: true }) }, index))), ((valueArray === null || valueArray === void 0 ? void 0 : valueArray.slice(items.length)) || []).map((_, index) => (_jsxs(ArrayFieldItem, { children: [_jsx(SchemaField, { name: `${name}[${index + items.length}]`, schema: additionalItems && typeof additionalItems !== "boolean"
|
|
98
102
|
? additionalItems
|
|
99
103
|
: {}, uiSchema: Object.assign({ disabled }, ((uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index + items.length]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items))) }), _jsxs(ArrayFieldItemButtons, { children: [GetArrayFieldButton({
|
|
100
104
|
purpose: ArrayFieldButtonPurpose.REMOVE,
|
|
@@ -12,9 +12,8 @@ export const StringField = (props) => {
|
|
|
12
12
|
const enums = useMemo(() => getEnums(props), [props]);
|
|
13
13
|
switch (enums === null || enums === void 0 ? void 0 : enums.type) {
|
|
14
14
|
case "select":
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hidden: true }, (_a = uiSchema["ui:placeholderOptionOptions"]) === null || _a === void 0 ? void 0 : _a.props, { children: placeholder }))), enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index)))] })) })));
|
|
15
|
+
return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", children: [_jsx("option", Object.assign({ value: "", disabled: true,
|
|
16
|
+
hidden: true }, (_a = uiSchema["ui:placeholderOptionOptions"]) === null || _a === void 0 ? void 0 : _a.props, { children: placeholder })), enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index)))] })) })));
|
|
18
17
|
default:
|
|
19
18
|
return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsx(FieldInput, Object.assign({ type,
|
|
20
19
|
placeholder }, inputProps, { name: name })) })));
|
|
@@ -5,3 +5,4 @@ export default _default;
|
|
|
5
5
|
export declare const BasicArray: StoryObj<typeof ArrayField>;
|
|
6
6
|
export declare const EnumArray: StoryObj<typeof ArrayField>;
|
|
7
7
|
export declare const ObjectArray: StoryObj<typeof ArrayField>;
|
|
8
|
+
export declare const AnyOfArray: StoryObj<typeof ArrayField>;
|
|
@@ -189,3 +189,36 @@ export const ObjectArray = {
|
|
|
189
189
|
expect(allLabelsAfterRemove.length).toEqual(0);
|
|
190
190
|
}),
|
|
191
191
|
};
|
|
192
|
+
export const AnyOfArray = {
|
|
193
|
+
args: {
|
|
194
|
+
name: "anyOf Select",
|
|
195
|
+
schema: {
|
|
196
|
+
type: "array",
|
|
197
|
+
title: "Array with anyOf-enum",
|
|
198
|
+
description: "Array description",
|
|
199
|
+
anyOf: [
|
|
200
|
+
{ const: 1, title: "one" },
|
|
201
|
+
{ const: 2, title: "two" },
|
|
202
|
+
{ const: 3, title: "three" },
|
|
203
|
+
{ const: 4, title: "four" },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
uiSchema: {},
|
|
207
|
+
},
|
|
208
|
+
play: (_d) => __awaiter(void 0, [_d], void 0, function* ({ canvasElement }) {
|
|
209
|
+
const anyOfSelectField = canvasElement.querySelector('select[name="anyOf Select"]');
|
|
210
|
+
const optionOne = canvasElement.querySelector('option[value="1"]');
|
|
211
|
+
const optionTwo = canvasElement.querySelector('option[value="2"]');
|
|
212
|
+
const optionThree = canvasElement.querySelector('option[value="3"]');
|
|
213
|
+
const optionFour = canvasElement.querySelector('option[value="4"]');
|
|
214
|
+
expect(anyOfSelectField).toBeInTheDocument();
|
|
215
|
+
expect(optionOne).toBeInTheDocument();
|
|
216
|
+
expect(optionTwo).toBeInTheDocument();
|
|
217
|
+
expect(optionThree).toBeInTheDocument();
|
|
218
|
+
expect(optionFour).toBeInTheDocument();
|
|
219
|
+
yield userEvent.selectOptions(anyOfSelectField, optionOne);
|
|
220
|
+
yield userEvent.selectOptions(anyOfSelectField, optionThree);
|
|
221
|
+
yield userEvent.click(optionThree);
|
|
222
|
+
yield expect(anyOfSelectField).toHaveValue(["1", "3"]);
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StoryObj } from "@storybook/react";
|
|
2
|
+
import { MultipleChoiceField } from "../MultipleChoiceField";
|
|
3
|
+
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react").ReactRenderer, import("../../types").FieldProps>;
|
|
4
|
+
export default _default;
|
|
5
|
+
export declare const OneOf: StoryObj<typeof MultipleChoiceField>;
|
|
6
|
+
export declare const AnyOf: StoryObj<typeof MultipleChoiceField>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { userEvent, expect } from "@storybook/test";
|
|
12
|
+
import { Formik, Form } from "formik";
|
|
13
|
+
import { MultipleChoiceField } from "../MultipleChoiceField";
|
|
14
|
+
export default {
|
|
15
|
+
title: "Fields/MultipleChoiceField",
|
|
16
|
+
component: MultipleChoiceField,
|
|
17
|
+
decorators: [
|
|
18
|
+
(Story) => (_jsx(Formik, { initialValues: { choicesAnyOf: [], choicesOneOf: "" }, onSubmit: (values) => console.log(values), children: _jsx(Form, { children: Story() }) })),
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
export const OneOf = {
|
|
22
|
+
args: {
|
|
23
|
+
name: "choicesOneOf",
|
|
24
|
+
schema: {
|
|
25
|
+
type: "string",
|
|
26
|
+
oneOf: [
|
|
27
|
+
{ const: "sunny", title: "Sunny" },
|
|
28
|
+
{ const: "cloudy", title: "Cloudy" },
|
|
29
|
+
{ const: "rainy", title: "Rainy" },
|
|
30
|
+
{ const: "windy", title: "Windy" },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
uiSchema: {
|
|
34
|
+
"ui:field": "MultipleChoiceField",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
38
|
+
const optionSunny = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="sunny"]');
|
|
39
|
+
const optionCloudy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="cloudy"]');
|
|
40
|
+
const optionRainy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="rainy"]');
|
|
41
|
+
const optionWindy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="windy"]');
|
|
42
|
+
yield userEvent.click(optionSunny);
|
|
43
|
+
yield expect(optionSunny).toBeChecked();
|
|
44
|
+
yield expect(optionCloudy).not.toBeChecked();
|
|
45
|
+
yield expect(optionRainy).not.toBeChecked();
|
|
46
|
+
yield expect(optionWindy).not.toBeChecked();
|
|
47
|
+
yield userEvent.click(optionCloudy);
|
|
48
|
+
yield expect(optionCloudy).toBeChecked();
|
|
49
|
+
yield expect(optionSunny).not.toBeChecked();
|
|
50
|
+
yield expect(optionRainy).not.toBeChecked();
|
|
51
|
+
yield expect(optionWindy).not.toBeChecked();
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
export const AnyOf = {
|
|
55
|
+
args: {
|
|
56
|
+
name: "choicesAnyOf",
|
|
57
|
+
schema: {
|
|
58
|
+
type: "array",
|
|
59
|
+
anyOf: [
|
|
60
|
+
{ const: "lily", title: "Lily" },
|
|
61
|
+
{ const: "rose", title: "Rose" },
|
|
62
|
+
{ const: "iris", title: "Iris" },
|
|
63
|
+
{ const: "daisy", title: "Daisy" },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
uiSchema: {
|
|
67
|
+
"ui:field": "MultipleChoiceField",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
play: (_b) => __awaiter(void 0, [_b], void 0, function* ({ canvasElement }) {
|
|
71
|
+
const optionLily = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="lily"]');
|
|
72
|
+
const optionRose = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="rose"]');
|
|
73
|
+
const optionIris = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="iris"]');
|
|
74
|
+
const optionDaisy = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="daisy"]');
|
|
75
|
+
yield userEvent.click(optionIris);
|
|
76
|
+
yield expect(optionIris).toBeChecked();
|
|
77
|
+
yield expect(optionRose).not.toBeChecked();
|
|
78
|
+
yield expect(optionLily).not.toBeChecked();
|
|
79
|
+
yield expect(optionDaisy).not.toBeChecked();
|
|
80
|
+
yield userEvent.click(optionRose);
|
|
81
|
+
yield expect(optionIris).toBeChecked();
|
|
82
|
+
yield expect(optionRose).toBeChecked();
|
|
83
|
+
yield expect(optionLily).not.toBeChecked();
|
|
84
|
+
yield expect(optionDaisy).not.toBeChecked();
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
@@ -44,7 +44,7 @@ export const completeSchema = {
|
|
|
44
44
|
],
|
|
45
45
|
},
|
|
46
46
|
enumAnyOf: {
|
|
47
|
-
type: "
|
|
47
|
+
type: "array",
|
|
48
48
|
title: "Enum anyOf (non-standard)",
|
|
49
49
|
description: "Enum anyOf description",
|
|
50
50
|
anyOf: [
|
|
@@ -55,7 +55,7 @@ export const completeSchema = {
|
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
57
|
enumTagSelectAnyOf: {
|
|
58
|
-
type: "
|
|
58
|
+
type: "array",
|
|
59
59
|
title: "Enum searchable tag select anyOf",
|
|
60
60
|
description: "Enum searchable tag select anyOf description",
|
|
61
61
|
anyOf: [
|
|
@@ -23,7 +23,7 @@ export const multipleChoiceSchema = {
|
|
|
23
23
|
],
|
|
24
24
|
},
|
|
25
25
|
food: {
|
|
26
|
-
type: "
|
|
26
|
+
type: "array",
|
|
27
27
|
title: "What would you like to eat today? (style: oneColumn)",
|
|
28
28
|
anyOf: [
|
|
29
29
|
{ const: "breakfast", title: "Breakfast" },
|
|
@@ -36,7 +36,7 @@ export const multipleChoiceSchema = {
|
|
|
36
36
|
],
|
|
37
37
|
},
|
|
38
38
|
drinks: {
|
|
39
|
-
type: "
|
|
39
|
+
type: "array",
|
|
40
40
|
title: "What would you like to drink today? (style: twoColumns)",
|
|
41
41
|
anyOf: [
|
|
42
42
|
{ const: "water", title: "Water" },
|
|
@@ -53,7 +53,7 @@ export const multipleChoiceSchema = {
|
|
|
53
53
|
],
|
|
54
54
|
},
|
|
55
55
|
flowers: {
|
|
56
|
-
type: "
|
|
56
|
+
type: "array",
|
|
57
57
|
title: "Pick some flowers (style: threeColumns)",
|
|
58
58
|
anyOf: [
|
|
59
59
|
{ const: "lily", title: "Lily" },
|
package/dist/styledComponents.js
CHANGED
|
@@ -362,7 +362,7 @@ export const SearchableMultiTagSelectField = ({ name, placeholder, enums, emptyS
|
|
|
362
362
|
};
|
|
363
363
|
return (_jsx(formik.Field, { className: className, name: name, multiple: true, children: ({ field, form: { setFieldValue }, meta, }) => {
|
|
364
364
|
var _a;
|
|
365
|
-
return (_jsxs(MultiSelectContainer, { className: className, children: [_jsx(FieldInput, Object.assign({ type: "hidden" }, field)), _jsx(MultiSelectInput, { className: className, type: "text", onChange: (e) => {
|
|
365
|
+
return (_jsxs(MultiSelectContainer, { className: className, children: [_jsx(FieldInput, Object.assign({ id: className, type: "hidden" }, field)), _jsx(MultiSelectInput, { id: className + "_id", className: className, type: "text", onChange: (e) => {
|
|
366
366
|
setSearch(e.currentTarget.value);
|
|
367
367
|
}, placeholder: placeholder, value: search }), _jsx(MultiSelectBadgeContainer, { children: ((_a = field.value) !== null && _a !== void 0 ? _a : []).map((value) => {
|
|
368
368
|
var _a;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { ageFieldDefinition } from "../../sampleData/ageFieldDefinition";
|
|
11
|
+
import { createValidationSchema } from "../createValidationSchema";
|
|
12
|
+
const exampleSchema = {
|
|
13
|
+
title: "TITLE",
|
|
14
|
+
required: ["name", "age", "feeling"],
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: "string", title: "Full name" },
|
|
17
|
+
age: { $ref: "#/definitions/age" },
|
|
18
|
+
feeling: { type: "boolean", title: "Feeling happy today?" },
|
|
19
|
+
arrayOfObject: {
|
|
20
|
+
type: "array",
|
|
21
|
+
title: "Array of objects",
|
|
22
|
+
items: {
|
|
23
|
+
type: "object",
|
|
24
|
+
title: "Favourites",
|
|
25
|
+
required: ["favouriteFood"],
|
|
26
|
+
properties: {
|
|
27
|
+
favouriteFood: { type: "string", title: "Favourite food" },
|
|
28
|
+
favouriteSong: { type: "string", title: "Favourite song" },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
additionalItems: {
|
|
32
|
+
type: "object",
|
|
33
|
+
title: "Second favourites",
|
|
34
|
+
required: ["anotherFavouriteSong"],
|
|
35
|
+
properties: {
|
|
36
|
+
anotherFavouriteFood: {
|
|
37
|
+
type: "string",
|
|
38
|
+
title: "Another favourite food",
|
|
39
|
+
},
|
|
40
|
+
anotherFavouriteSong: {
|
|
41
|
+
type: "string",
|
|
42
|
+
title: "Another favourite song",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
nestedObject: {
|
|
48
|
+
type: "object",
|
|
49
|
+
title: "Nested Object",
|
|
50
|
+
required: ["tasks"],
|
|
51
|
+
properties: {
|
|
52
|
+
hobbies: { type: "string", title: "Hobbies" },
|
|
53
|
+
tasks: { type: "string", title: "Tasks" },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
definitions: {
|
|
58
|
+
age: ageFieldDefinition,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const simpleExampleSchema = {
|
|
62
|
+
title: "TITLE",
|
|
63
|
+
required: ["lastName"],
|
|
64
|
+
properties: {
|
|
65
|
+
firstName: { type: "string", title: "First name" },
|
|
66
|
+
lastName: { type: "string", title: "Last name" },
|
|
67
|
+
age: { type: "number", title: "Age" },
|
|
68
|
+
drinksCoffee: { type: "boolean", title: "Do you drink coffee?" },
|
|
69
|
+
simpleArray: { type: "array", title: "Simple array" },
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
describe("Create Validation Schema", () => {
|
|
73
|
+
it("creates yup validationSchema based on given JSON schema", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
+
const result = createValidationSchema(exampleSchema);
|
|
75
|
+
const expected = result.isValidSync({
|
|
76
|
+
name: "Jane",
|
|
77
|
+
age: 54,
|
|
78
|
+
feeling: false,
|
|
79
|
+
arrayOfObject: [
|
|
80
|
+
{ favouriteFood: "Pizza!", favouriteSong: undefined },
|
|
81
|
+
{
|
|
82
|
+
anotherFavouriteFood: undefined,
|
|
83
|
+
anotherFavouriteSong: "On the turning away",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
nestedObject: {
|
|
87
|
+
hobbies: undefined,
|
|
88
|
+
tasks: "buy groceries",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
expect(expected).toEqual(true);
|
|
92
|
+
}));
|
|
93
|
+
it("throws error when required input is missing", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
+
const result = createValidationSchema(simpleExampleSchema);
|
|
95
|
+
yield expect(() => result.validate({
|
|
96
|
+
firstName: "Jane",
|
|
97
|
+
lastName: undefined,
|
|
98
|
+
age: 55,
|
|
99
|
+
})).rejects.toThrow("Required");
|
|
100
|
+
}));
|
|
101
|
+
it("throws error when given input is not matching defined type", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
+
const result = createValidationSchema(simpleExampleSchema);
|
|
103
|
+
// for 'number' type
|
|
104
|
+
yield expect(() => result.validate({
|
|
105
|
+
firstName: "Ella",
|
|
106
|
+
lastName: "Myller",
|
|
107
|
+
age: true,
|
|
108
|
+
drinksCoffee: false,
|
|
109
|
+
simpleArray: [],
|
|
110
|
+
}, { strict: true })).rejects.toThrow("age must be a `number` type, but the final value was: `true`.");
|
|
111
|
+
// for 'string' type
|
|
112
|
+
yield expect(() => result.validate({
|
|
113
|
+
firstName: 123,
|
|
114
|
+
lastName: "Myller",
|
|
115
|
+
age: 17,
|
|
116
|
+
drinksCoffee: false,
|
|
117
|
+
simpleArray: [],
|
|
118
|
+
}, { strict: true })).rejects.toThrow("firstName must be a `string` type, but the final value was: `123`.");
|
|
119
|
+
// for 'boolean' type
|
|
120
|
+
yield expect(() => result.validate({
|
|
121
|
+
firstName: "Ella",
|
|
122
|
+
lastName: "Myller",
|
|
123
|
+
age: 17,
|
|
124
|
+
drinksCoffee: "Well, sometimes...",
|
|
125
|
+
simpleArray: [],
|
|
126
|
+
}, { strict: true })).rejects.toThrow('drinksCoffee must be a `boolean` type, but the final value was: `"Well, sometimes..."`.');
|
|
127
|
+
// for 'array' type
|
|
128
|
+
yield expect(() => result.validate({
|
|
129
|
+
firstName: "Ella",
|
|
130
|
+
lastName: "Myller",
|
|
131
|
+
age: 17,
|
|
132
|
+
drinksCoffee: false,
|
|
133
|
+
simpleArray: "Hello :)",
|
|
134
|
+
}, { strict: true })).rejects.toThrow('simpleArray must be a `array` type, but the final value was: `"Hello :)"`.');
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { solveReferences } from "./solveReferences";
|
|
2
|
+
import * as Yup from "yup";
|
|
3
|
+
export function createValidationSchema(schema, definitions = {}) {
|
|
4
|
+
var _a;
|
|
5
|
+
if (!schema || !schema.properties) {
|
|
6
|
+
return Yup.object().shape({});
|
|
7
|
+
}
|
|
8
|
+
const schemaDefinitions = Object.assign(Object.assign({}, definitions), ((_a = schema.definitions) !== null && _a !== void 0 ? _a : {}));
|
|
9
|
+
const requiredFields = schema.required;
|
|
10
|
+
function getValue(schema, key) {
|
|
11
|
+
if (schema.properties) {
|
|
12
|
+
return createValidationSchema(schema, schemaDefinitions);
|
|
13
|
+
}
|
|
14
|
+
const type = schema.type;
|
|
15
|
+
let yupType;
|
|
16
|
+
switch (type) {
|
|
17
|
+
case "array":
|
|
18
|
+
yupType = Yup.array();
|
|
19
|
+
break;
|
|
20
|
+
case "boolean":
|
|
21
|
+
yupType = Yup.boolean();
|
|
22
|
+
break;
|
|
23
|
+
case "integer":
|
|
24
|
+
case "number":
|
|
25
|
+
yupType = Yup.number();
|
|
26
|
+
break;
|
|
27
|
+
case "null":
|
|
28
|
+
yupType = Yup.string().nullable();
|
|
29
|
+
break;
|
|
30
|
+
case "string":
|
|
31
|
+
yupType = Yup.string();
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
throw new Error("Unsupported schema type");
|
|
35
|
+
}
|
|
36
|
+
if (requiredFields !== undefined && requiredFields.includes(key)) {
|
|
37
|
+
yupType = yupType["required"]("Required");
|
|
38
|
+
}
|
|
39
|
+
return yupType;
|
|
40
|
+
}
|
|
41
|
+
return Object.keys(schema.properties).reduce((validationSchema, key) => {
|
|
42
|
+
var _a;
|
|
43
|
+
const value = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[key];
|
|
44
|
+
const solvedValue = solveReferences(value, schemaDefinitions, {}, {});
|
|
45
|
+
return validationSchema.concat(Yup.object().shape({ [key]: getValue(solvedValue.schema, key) }));
|
|
46
|
+
}, Yup.object().shape({}));
|
|
47
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export * from "./mergeSchemas";
|
|
|
6
6
|
export * from "./resolveSchema";
|
|
7
7
|
export * from "./solveReferences";
|
|
8
8
|
export * from "./createInitialValues";
|
|
9
|
+
export * from "./createValidationSchema";
|
|
9
10
|
export declare const warnFromUnsupportedKeys: (componentName: string, data: Record<string, any>, supportedKeys: string[]) => void;
|
package/dist/utils/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./mergeSchemas";
|
|
|
6
6
|
export * from "./resolveSchema";
|
|
7
7
|
export * from "./solveReferences";
|
|
8
8
|
export * from "./createInitialValues";
|
|
9
|
+
export * from "./createValidationSchema";
|
|
9
10
|
//TODO: REMOVE WHEN ALL SUPPORTED
|
|
10
11
|
export const warnFromUnsupportedKeys = (componentName, data, supportedKeys) => {
|
|
11
12
|
Object.keys(data).forEach((key) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nolwenture/formik-jsonschema-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Formik form generation from json schema",
|
|
5
5
|
"author": "Antti Vikman <antti.vikman@nolwenture.com>",
|
|
6
6
|
"contributors": [
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"json-schema": "^0.4.0",
|
|
40
40
|
"react": "^18.2.0",
|
|
41
41
|
"styled-components": "^6.1.8",
|
|
42
|
-
"typescript": "^5.4.3"
|
|
42
|
+
"typescript": "^5.4.3",
|
|
43
|
+
"yup": "^1.4.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@babel/preset-react": "^7.24.1",
|