@nolwenture/formik-jsonschema-form 0.10.1 → 0.11.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 +86 -13
- package/dist/Form.js +3 -0
- package/dist/Form.stories.d.ts +1 -0
- package/dist/Form.stories.js +7 -3
- package/dist/fields/BooleanField.js +3 -3
- package/dist/fields/ObjectField.js +6 -3
- package/dist/fields/SchemaField.js +4 -4
- package/dist/fields/stories/ObjectField.stories.js +20 -2
- package/dist/sampleData/collapsibleSchema.d.ts +3 -0
- package/dist/sampleData/collapsibleSchema.js +27 -0
- package/dist/sampleData/index.d.ts +3 -0
- package/dist/sampleData/index.js +3 -0
- package/dist/sampleData/refDefSchema.js +78 -9
- package/dist/styledComponents.d.ts +8 -1
- package/dist/styledComponents.js +26 -0
- package/dist/templates/CollapsibleObjectFieldTemplate.d.ts +3 -0
- package/dist/templates/CollapsibleObjectFieldTemplate.js +20 -0
- package/dist/templates/index.d.ts +1 -0
- package/dist/templates/index.js +1 -0
- package/dist/types.d.ts +27 -1
- package/dist/utils/__tests/solveReferences.test.js +18 -4
- package/dist/utils/__tests/testSchemas.d.ts +2 -1
- package/dist/utils/__tests/testSchemas.js +9 -0
- package/dist/utils/createInitialValues.js +2 -2
- package/dist/utils/solveReferences.d.ts +10 -2
- package/dist/utils/solveReferences.js +8 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -518,12 +518,33 @@ RadioButtonField.styles = {
|
|
|
518
518
|
};
|
|
519
519
|
```
|
|
520
520
|
|
|
521
|
+
### Collapsible object field
|
|
522
|
+
|
|
523
|
+
Fields of `type: "object"` can be made collapsible via the respective uiSchema setting.
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
const collapsibleSchema: Schema = {
|
|
527
|
+
title: "FORM WITH COLLAPSIBLE OBJECT",
|
|
528
|
+
properties: {
|
|
529
|
+
colObject: {
|
|
530
|
+
type: "object",
|
|
531
|
+
title: "Collapsible Object",
|
|
532
|
+
properties: { summer: { type: "string" }, winter: { type: "string" } },
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const collapsibleUiSchema: UiSchema = {
|
|
538
|
+
colObject: { props: { isCollapsible: true } },
|
|
539
|
+
};
|
|
540
|
+
```
|
|
541
|
+
|
|
521
542
|
### Conditional fields
|
|
522
543
|
|
|
523
544
|
Conditional fields depending on a parent field input can be given with an `if-then-else` logic inside the schema making use of `allOf`. While `if`and `then` are required to make the condition work, `else` is optional.
|
|
524
545
|
|
|
525
546
|
```tsx
|
|
526
|
-
|
|
547
|
+
const conditionsSchema = {
|
|
527
548
|
type: "object",
|
|
528
549
|
properties: {
|
|
529
550
|
animal: {
|
|
@@ -598,7 +619,7 @@ Dependencies can either be triggered by the conditional-giving property being pr
|
|
|
598
619
|
To have value-dependent conditionals, the `dependencies`-object needs to have an `oneOf`-array listing the possible conditional-giving values matching the enum-values of the conditional-giving field. Each case in in the `oneOf` array can either have no conditional fields, then the only key-value pair given inside `properties` is the matching conditional (see "no type"), or an unlimited amount of conditional fields listed after the matching conditional.
|
|
599
620
|
|
|
600
621
|
```tsx
|
|
601
|
-
|
|
622
|
+
const dependenciesSchema: Schema = {
|
|
602
623
|
type: "object",
|
|
603
624
|
properties: {
|
|
604
625
|
creditCard: {
|
|
@@ -689,29 +710,81 @@ Using `if-else-then` or `dependencies` depends mostly on the use case. For recur
|
|
|
689
710
|
Schemas can be referenced. A referenced schema can be given to a property using `$ref` keyword. The definition for the referenced schema needs to be inside `definitions` object inside the main schema. The value-string given to $ref-key must always look like this:
|
|
690
711
|
`'#/definitions/{addMatchingKeyHere}'`
|
|
691
712
|
|
|
713
|
+
In order to give uiSchema to reused or recursive fields defined in `schema.definitons`, the uiSchema needs to be given to the matching prop of `definitions`-object. This way, the given uiProps are added to the referencing field, no matter how deeply nested it is inside the schema.
|
|
714
|
+
|
|
692
715
|
```tsx
|
|
693
|
-
const
|
|
694
|
-
type: "
|
|
695
|
-
title: "
|
|
716
|
+
const familyObjectField: Schema = {
|
|
717
|
+
type: "object",
|
|
718
|
+
title: "My family member",
|
|
719
|
+
properties: {
|
|
720
|
+
relationship: { type: "string", title: "Relationship status" },
|
|
721
|
+
names: { $ref: "#/definitions/names" },
|
|
722
|
+
},
|
|
696
723
|
};
|
|
697
724
|
|
|
698
|
-
|
|
725
|
+
const refDefSchema: Schema = {
|
|
699
726
|
title: "References and Definitions",
|
|
700
727
|
type: "object",
|
|
701
728
|
properties: {
|
|
702
|
-
|
|
703
|
-
$ref: "#/definitions/
|
|
729
|
+
names: {
|
|
730
|
+
$ref: "#/definitions/names",
|
|
704
731
|
},
|
|
705
|
-
|
|
706
|
-
$ref: "#/definitions/
|
|
732
|
+
birthDate: {
|
|
733
|
+
$ref: "#/definitions/birthDate",
|
|
734
|
+
},
|
|
735
|
+
family: {
|
|
736
|
+
type: "object",
|
|
737
|
+
title: "Family",
|
|
738
|
+
properties: {
|
|
739
|
+
mother: {
|
|
740
|
+
type: "array",
|
|
741
|
+
title: "Maternal family",
|
|
742
|
+
additionalItems: familyObjectField,
|
|
743
|
+
},
|
|
744
|
+
father: {
|
|
745
|
+
type: "array",
|
|
746
|
+
title: "Paternal family",
|
|
747
|
+
additionalItems: familyObjectField,
|
|
748
|
+
},
|
|
749
|
+
},
|
|
707
750
|
},
|
|
708
751
|
},
|
|
752
|
+
|
|
709
753
|
definitions: {
|
|
710
|
-
|
|
711
|
-
type: "
|
|
754
|
+
names: {
|
|
755
|
+
type: "object",
|
|
712
756
|
title: "Name",
|
|
757
|
+
description:
|
|
758
|
+
"The schema for this object comes from the 'definitions' object inside the main schema",
|
|
759
|
+
properties: {
|
|
760
|
+
firstName: { type: "string", title: "First name" },
|
|
761
|
+
middleNames: {
|
|
762
|
+
type: "array",
|
|
763
|
+
title: "Middle names",
|
|
764
|
+
additionalItems: { $ref: "#/definitions/middleName" },
|
|
765
|
+
},
|
|
766
|
+
lastName: { type: "string", title: "Last name" },
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
birthDate: { type: "string", title: "When is your birthday?" },
|
|
770
|
+
middleName: { type: "string", title: " Middle name" },
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const refDefUiSchema: UiSchema = {
|
|
775
|
+
definitions: {
|
|
776
|
+
birthDate: { "ui:field": "DateField" },
|
|
777
|
+
names: {
|
|
778
|
+
history: { "ui:field": "TextAreaField" },
|
|
779
|
+
middleNames: { "ui:info": { content: "You can leave those blank" } },
|
|
780
|
+
},
|
|
781
|
+
middleName: {
|
|
782
|
+
props: {
|
|
783
|
+
title: {
|
|
784
|
+
hidden: true,
|
|
785
|
+
},
|
|
786
|
+
},
|
|
713
787
|
},
|
|
714
|
-
age: ageFieldDefinition,
|
|
715
788
|
},
|
|
716
789
|
};
|
|
717
790
|
```
|
package/dist/Form.js
CHANGED
|
@@ -10,6 +10,7 @@ export const FormRegistry = React.createContext({
|
|
|
10
10
|
templates: defaultTemplates,
|
|
11
11
|
components: defaultComponents,
|
|
12
12
|
definitions: {},
|
|
13
|
+
uiSchemaDefinitions: {},
|
|
13
14
|
formContext: {},
|
|
14
15
|
useItemsAsAdditionalItems: false,
|
|
15
16
|
hideUnmarkedAdditionalProperties: false,
|
|
@@ -31,11 +32,13 @@ export const Form = (props) => {
|
|
|
31
32
|
const { isSubmitting } = useFormikContext();
|
|
32
33
|
const { schema, uiSchema = {}, className, children, fields = {}, templates = {}, components = {}, formContext = {}, useItemsAsAdditionalItems = false, hideUnmarkedAdditionalProperties = false, } = props;
|
|
33
34
|
const schemaDefinitions = schema.definitions;
|
|
35
|
+
const uiSchemaDefinitions = uiSchema.definitions;
|
|
34
36
|
const registry = {
|
|
35
37
|
fields: Object.assign(Object.assign({}, defaultFields), fields),
|
|
36
38
|
templates: Object.assign(Object.assign({}, defaultTemplates), templates),
|
|
37
39
|
components: Object.assign(Object.assign({}, defaultComponents), components),
|
|
38
40
|
definitions: schemaDefinitions,
|
|
41
|
+
uiSchemaDefinitions,
|
|
39
42
|
formContext,
|
|
40
43
|
useItemsAsAdditionalItems,
|
|
41
44
|
hideUnmarkedAdditionalProperties,
|
package/dist/Form.stories.d.ts
CHANGED
package/dist/Form.stories.js
CHANGED
|
@@ -23,10 +23,8 @@ import { action } from "@storybook/addon-actions";
|
|
|
23
23
|
import styled from "styled-components";
|
|
24
24
|
import { Formik, useFormikContext } from "formik";
|
|
25
25
|
import { InfoFieldButton, SearchableMultiTagSelectField, } from "./styledComponents";
|
|
26
|
-
import { completeSchema, completeUiSchema, simpleSchema, simpleUiSchema, generatedSchema, requiredInputSchema, requiredInputUiSchema, conditionsSchema, conditionsUiSchema, } from "./sampleData";
|
|
26
|
+
import { completeSchema, completeUiSchema, simpleSchema, simpleUiSchema, generatedSchema, requiredInputSchema, requiredInputUiSchema, conditionsSchema, conditionsUiSchema, refDefSchema, refDefUiSchema, multipleChoiceSchema, multipleChoiceUiSchema, collapsibleSchema, collapsibleUiSchema, } from "./sampleData";
|
|
27
27
|
import { Form, styledComponents } from "./";
|
|
28
|
-
import { refDefSchema, refDefUiSchema } from "./sampleData/refDefSchema";
|
|
29
|
-
import { multipleChoiceSchema, multipleChoiceUiSchema, } from "./sampleData/multipleChoiceSchema";
|
|
30
28
|
const meta = {
|
|
31
29
|
title: "Form",
|
|
32
30
|
component: Form,
|
|
@@ -198,3 +196,9 @@ export const MultipleChoice = {
|
|
|
198
196
|
uiSchema: multipleChoiceUiSchema,
|
|
199
197
|
},
|
|
200
198
|
};
|
|
199
|
+
export const Collapsible = {
|
|
200
|
+
args: {
|
|
201
|
+
schema: collapsibleSchema,
|
|
202
|
+
uiSchema: collapsibleUiSchema,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useContext } from "react";
|
|
3
3
|
import { FormRegistry } from "../Form";
|
|
4
4
|
import { useExtractUiSchema, useSetDefault } from "../hooks";
|
|
5
5
|
export const BooleanField = (props) => {
|
|
6
|
-
const { templates: { FieldTemplate }, components: { FieldInput }, } = useContext(FormRegistry);
|
|
6
|
+
const { templates: { FieldTemplate }, components: { FieldInput, BooleanFieldWrapper }, } = useContext(FormRegistry);
|
|
7
7
|
useSetDefault(props);
|
|
8
8
|
const { name, uiSchema } = props;
|
|
9
9
|
const { label, props: inputProps } = useExtractUiSchema(uiSchema);
|
|
10
|
-
return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(
|
|
10
|
+
return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(BooleanFieldWrapper, { children: [_jsx(FieldInput, Object.assign({}, inputProps, { name: name, type: "checkbox" })), label] }) })));
|
|
11
11
|
};
|
|
@@ -26,8 +26,8 @@ const useObjectField = (name) => {
|
|
|
26
26
|
return { value, setValue };
|
|
27
27
|
};
|
|
28
28
|
export const ObjectField = (props) => {
|
|
29
|
-
var _a;
|
|
30
|
-
const { fields: { SchemaField }, templates: { ObjectFieldTemplate }, components: { ObjectFieldItem, ObjectFieldItemButtons }, hideUnmarkedAdditionalProperties, } = useContext(FormRegistry);
|
|
29
|
+
var _a, _b, _c;
|
|
30
|
+
const { fields: { SchemaField }, templates: { ObjectFieldTemplate, CollapsibleObjectFieldTemplate }, components: { ObjectFieldItem, ObjectFieldItemButtons }, hideUnmarkedAdditionalProperties, } = useContext(FormRegistry);
|
|
31
31
|
useSetDefault(props);
|
|
32
32
|
const { name, schema, uiSchema } = props;
|
|
33
33
|
const { value, setValue } = useObjectField(name);
|
|
@@ -45,7 +45,10 @@ export const ObjectField = (props) => {
|
|
|
45
45
|
}, [valuesToRemove]);
|
|
46
46
|
const propertyKeys = useMemo(() => Object.keys(properties || {}), [properties]);
|
|
47
47
|
const additionalValues = useMemo(() => Object.fromEntries(Object.entries(value || {}).filter(([key, value]) => !propertyKeys.includes(key))), [value, propertyKeys]);
|
|
48
|
-
|
|
48
|
+
const FieldTemplateComponent = ((_b = (_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.isCollapsible)
|
|
49
|
+
? CollapsibleObjectFieldTemplate
|
|
50
|
+
: ObjectFieldTemplate;
|
|
51
|
+
return (_jsxs(FieldTemplateComponent, Object.assign({}, props, { children: [((_c = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema["ui:order"]) !== null && _c !== void 0 ? _c : Object.keys(properties || {}))
|
|
49
52
|
.map((childName) => {
|
|
50
53
|
const childSchema = properties === null || properties === void 0 ? void 0 : properties[childName];
|
|
51
54
|
if (!childSchema)
|
|
@@ -23,9 +23,9 @@ const useFieldComponent = (schema, uiSchema, fields) => {
|
|
|
23
23
|
};
|
|
24
24
|
export const SchemaField = (props) => {
|
|
25
25
|
const { schema, uiSchema } = props;
|
|
26
|
-
const { fields, definitions } = useContext(FormRegistry);
|
|
27
|
-
const schemaSolvedForReferences = useMemo(() => solveReferences(schema, definitions), [schema, definitions]);
|
|
28
|
-
const FieldComponent = useFieldComponent(schemaSolvedForReferences, uiSchema, fields);
|
|
29
|
-
const newProps = Object.assign(Object.assign({}, props), { schema: schemaSolvedForReferences });
|
|
26
|
+
const { fields, definitions, uiSchemaDefinitions } = useContext(FormRegistry);
|
|
27
|
+
const schemaSolvedForReferences = useMemo(() => solveReferences(schema, definitions, uiSchema, uiSchemaDefinitions), [schema, definitions]);
|
|
28
|
+
const FieldComponent = useFieldComponent(schemaSolvedForReferences.schema, schemaSolvedForReferences.uiSchema, fields);
|
|
29
|
+
const newProps = Object.assign(Object.assign({}, props), { schema: schemaSolvedForReferences.schema, uiSchema: schemaSolvedForReferences.uiSchema });
|
|
30
30
|
return _jsx(FieldComponent, Object.assign({}, newProps));
|
|
31
31
|
};
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
-
import { userEvent, expect } from "@storybook/test";
|
|
11
|
+
import { within, userEvent, expect, waitFor } from "@storybook/test";
|
|
12
12
|
import { Formik, Form } from "formik";
|
|
13
13
|
import { ObjectField } from "./../ObjectField";
|
|
14
14
|
export default {
|
|
@@ -55,22 +55,40 @@ export const Default = {
|
|
|
55
55
|
title: "nullField",
|
|
56
56
|
description: "Null description",
|
|
57
57
|
},
|
|
58
|
+
collapsibleObject: {
|
|
59
|
+
type: "object",
|
|
60
|
+
title: "nestedCollapsibleObject",
|
|
61
|
+
properties: {
|
|
62
|
+
insideCollapsibleObject: { type: "string" },
|
|
63
|
+
alsoInsideCollapsibleObject: { type: "number" },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
58
66
|
},
|
|
59
67
|
},
|
|
60
|
-
uiSchema: {},
|
|
68
|
+
uiSchema: { collapsibleObject: { props: { isCollapsible: true } } },
|
|
61
69
|
},
|
|
62
70
|
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
71
|
+
const canvas = within(canvasElement);
|
|
63
72
|
const stringField = canvasElement.querySelector('input[name="object.string"]');
|
|
64
73
|
const numberField = canvasElement.querySelector('input[name="object.number"]');
|
|
65
74
|
const booleanField = canvasElement.querySelector('input[name="object.boolean"]');
|
|
66
75
|
const enumField = canvasElement.querySelector('select[name="object.enum[0]"]');
|
|
76
|
+
const collapsibleObjectButton = canvas.getByLabelText("object.collapsibleObject");
|
|
77
|
+
expect(collapsibleObjectButton).toBeInTheDocument();
|
|
67
78
|
yield userEvent.type(stringField, "string");
|
|
68
79
|
yield userEvent.type(numberField, "123");
|
|
69
80
|
yield userEvent.click(booleanField);
|
|
70
81
|
yield userEvent.selectOptions(enumField, "2");
|
|
82
|
+
yield userEvent.click(collapsibleObjectButton);
|
|
83
|
+
const stringFieldInCollapsibleObject = (yield waitFor(() => canvasElement.querySelector('input[name="object.collapsibleObject.insideCollapsibleObject"]')));
|
|
84
|
+
const numberFieldInCollapsibleObject = (yield waitFor(() => canvasElement.querySelector('input[name="object.collapsibleObject.alsoInsideCollapsibleObject"]')));
|
|
85
|
+
yield userEvent.type(stringFieldInCollapsibleObject, "string in collapsible object");
|
|
86
|
+
yield userEvent.type(numberFieldInCollapsibleObject, "313");
|
|
71
87
|
expect(stringField).toHaveValue("string");
|
|
72
88
|
expect(numberField).toHaveValue(123);
|
|
73
89
|
expect(booleanField).toBeChecked();
|
|
74
90
|
expect(enumField).toHaveValue("2");
|
|
91
|
+
expect(stringFieldInCollapsibleObject).toHaveValue("string in collapsible object");
|
|
92
|
+
expect(numberFieldInCollapsibleObject).toHaveValue(313);
|
|
75
93
|
}),
|
|
76
94
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const collapsibleSchema = {
|
|
2
|
+
title: "TITLE",
|
|
3
|
+
properties: {
|
|
4
|
+
colObject: {
|
|
5
|
+
type: "object",
|
|
6
|
+
title: "Collapsible Object",
|
|
7
|
+
properties: { summer: { type: "string" }, winter: { type: "string" } },
|
|
8
|
+
},
|
|
9
|
+
secondColObject: {
|
|
10
|
+
type: "object",
|
|
11
|
+
title: "Second collapsible Object",
|
|
12
|
+
properties: { spring: { type: "string" }, fall: { type: "string" } },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export const collapsibleUiSchema = {
|
|
17
|
+
"ui:description": "Description text",
|
|
18
|
+
colObject: { props: { isCollapsible: true, isCollapsedOnLoad: false } },
|
|
19
|
+
secondColObject: {
|
|
20
|
+
props: {
|
|
21
|
+
isCollapsible: true,
|
|
22
|
+
isCollapsedOnLoad: true,
|
|
23
|
+
openIcon: "-",
|
|
24
|
+
collapsedIcon: "+",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
package/dist/sampleData/index.js
CHANGED
|
@@ -1,15 +1,52 @@
|
|
|
1
1
|
import { ageFieldDefinition } from "./ageFieldDefinition";
|
|
2
|
+
const familyObjectField = {
|
|
3
|
+
type: "object",
|
|
4
|
+
title: "My family member",
|
|
5
|
+
properties: {
|
|
6
|
+
relationship: { type: "string", title: "Relationship status" },
|
|
7
|
+
names: { $ref: "#/definitions/names" },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
2
10
|
export const refDefSchema = {
|
|
3
11
|
title: "References and Definitions",
|
|
4
12
|
type: "object",
|
|
5
|
-
required: ["
|
|
13
|
+
required: ["names", "fieldType"],
|
|
6
14
|
properties: {
|
|
7
|
-
|
|
8
|
-
$ref: "#/definitions/
|
|
15
|
+
names: {
|
|
16
|
+
$ref: "#/definitions/names",
|
|
17
|
+
},
|
|
18
|
+
family: {
|
|
19
|
+
type: "object",
|
|
20
|
+
title: "Family",
|
|
21
|
+
properties: {
|
|
22
|
+
mother: {
|
|
23
|
+
type: "array",
|
|
24
|
+
title: "Maternal family",
|
|
25
|
+
additionalItems: familyObjectField,
|
|
26
|
+
},
|
|
27
|
+
father: {
|
|
28
|
+
type: "array",
|
|
29
|
+
title: "Paternal family",
|
|
30
|
+
additionalItems: familyObjectField,
|
|
31
|
+
},
|
|
32
|
+
siblings: {
|
|
33
|
+
type: "array",
|
|
34
|
+
title: "Siblings",
|
|
35
|
+
additionalItems: familyObjectField,
|
|
36
|
+
},
|
|
37
|
+
children: {
|
|
38
|
+
type: "array",
|
|
39
|
+
title: "Own children (and grandchildren)",
|
|
40
|
+
additionalItems: familyObjectField,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
9
43
|
},
|
|
10
44
|
age: {
|
|
11
45
|
$ref: "#/definitions/age",
|
|
12
46
|
},
|
|
47
|
+
birthDate: {
|
|
48
|
+
$ref: "#/definitions/birthDate",
|
|
49
|
+
},
|
|
13
50
|
creditCard: {
|
|
14
51
|
type: "number",
|
|
15
52
|
title: "Credit card number",
|
|
@@ -127,7 +164,10 @@ export const refDefSchema = {
|
|
|
127
164
|
{
|
|
128
165
|
properties: {
|
|
129
166
|
boolean: true,
|
|
130
|
-
|
|
167
|
+
feedback: {
|
|
168
|
+
type: "string",
|
|
169
|
+
title: "Feedback on your coffee experience",
|
|
170
|
+
},
|
|
131
171
|
},
|
|
132
172
|
},
|
|
133
173
|
],
|
|
@@ -187,18 +227,32 @@ export const refDefSchema = {
|
|
|
187
227
|
},
|
|
188
228
|
],
|
|
189
229
|
definitions: {
|
|
190
|
-
|
|
191
|
-
type: "
|
|
230
|
+
names: {
|
|
231
|
+
type: "object",
|
|
192
232
|
title: "Name",
|
|
193
|
-
description: "The schema for this
|
|
233
|
+
description: "The schema for this object comes from the 'definitions' object inside the main schema",
|
|
234
|
+
properties: {
|
|
235
|
+
firstName: { type: "string", title: "First name" },
|
|
236
|
+
middleNames: {
|
|
237
|
+
type: "array",
|
|
238
|
+
title: "Middle names",
|
|
239
|
+
additionalItems: { $ref: "#/definitions/middleName" },
|
|
240
|
+
},
|
|
241
|
+
lastName: { type: "string", title: "Last name" },
|
|
242
|
+
history: { type: "string", title: "History of these names" },
|
|
243
|
+
},
|
|
194
244
|
},
|
|
195
245
|
age: ageFieldDefinition,
|
|
246
|
+
birthDate: { type: "string", title: "When is your birthday?" },
|
|
247
|
+
middleName: { type: "string", title: " Middle name" },
|
|
196
248
|
},
|
|
197
249
|
};
|
|
198
250
|
export const refDefUiSchema = {
|
|
199
251
|
"ui:order": [
|
|
200
|
-
"
|
|
252
|
+
"names",
|
|
201
253
|
"age",
|
|
254
|
+
"birthDate",
|
|
255
|
+
"family",
|
|
202
256
|
"creditCard",
|
|
203
257
|
"billing_address",
|
|
204
258
|
"fieldType",
|
|
@@ -206,7 +260,7 @@ export const refDefUiSchema = {
|
|
|
206
260
|
"objectField",
|
|
207
261
|
"arrayField",
|
|
208
262
|
"boolean",
|
|
209
|
-
"
|
|
263
|
+
"feedback",
|
|
210
264
|
"animal",
|
|
211
265
|
"food",
|
|
212
266
|
"water",
|
|
@@ -216,4 +270,19 @@ export const refDefUiSchema = {
|
|
|
216
270
|
props: { disabled: false, hidden: false },
|
|
217
271
|
},
|
|
218
272
|
},
|
|
273
|
+
feedback: { "ui:field": "TextAreaField" },
|
|
274
|
+
definitions: {
|
|
275
|
+
birthDate: { "ui:field": "DateField" },
|
|
276
|
+
names: {
|
|
277
|
+
history: { "ui:field": "TextAreaField" },
|
|
278
|
+
middleNames: { "ui:info": { content: "You can leave those blank" } },
|
|
279
|
+
},
|
|
280
|
+
middleName: {
|
|
281
|
+
props: {
|
|
282
|
+
title: {
|
|
283
|
+
hidden: true,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
219
288
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="hoist-non-react-statics" />
|
|
2
|
-
import type { ArrayFieldButtonProps, InfoFieldButtonProps, ObjectFieldButtonProps, RadioButtonFieldProps, ToggleSwitchFieldProps, SelectFieldProps, MultipleChoiceFieldProps, TextAreaInputFieldProps } from "./types";
|
|
2
|
+
import type { ArrayFieldButtonProps, InfoFieldButtonProps, ObjectFieldButtonProps, RadioButtonFieldProps, ToggleSwitchFieldProps, SelectFieldProps, MultipleChoiceFieldProps, TextAreaInputFieldProps, ClickableProps } from "./types";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import * as formik from "formik";
|
|
5
5
|
export declare const Form: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref"> & React.RefAttributes<HTMLFormElement>, "ref"> & {
|
|
@@ -208,4 +208,11 @@ export declare const TextAreaFieldInput: import("styled-components").IStyledComp
|
|
|
208
208
|
ref?: unknown;
|
|
209
209
|
}, never>> & Omit<({ formikAs, ...props }: Omit<formik.FieldAttributes<any>, "as">) => import("react/jsx-runtime").JSX.Element, keyof React.Component<any, {}, any>>, keyof React.Component<any, {}, any>>;
|
|
210
210
|
export declare const TextAreaInputField: ({ inputProps, name, className, }: TextAreaInputFieldProps) => import("react/jsx-runtime").JSX.Element;
|
|
211
|
+
export declare const BooleanFieldWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
|
|
212
|
+
export declare const Clickable: ({ children, onClick, onMouseUp, onMouseDown, innerRef, className, ariaLabel, ariaExpanded, ariaControls, id, }: ClickableProps) => import("react/jsx-runtime").JSX.Element;
|
|
213
|
+
export declare const CollapsibleToggle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<ClickableProps, never>> & Omit<({ children, onClick, onMouseUp, onMouseDown, innerRef, className, ariaLabel, ariaExpanded, ariaControls, id, }: ClickableProps) => import("react/jsx-runtime").JSX.Element, keyof React.Component<any, {}, any>>;
|
|
214
|
+
export declare const Icon: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
|
|
215
|
+
export declare const CollapsibleChildWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
|
|
216
|
+
export declare const CollapsibleObjectFieldWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>, never>>;
|
|
217
|
+
export declare const CollapsibleObjectFieldTitle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>, never>>;
|
|
211
218
|
export {};
|
package/dist/styledComponents.js
CHANGED
|
@@ -393,3 +393,29 @@ export const TextAreaFieldInput = styled(FieldInput) `
|
|
|
393
393
|
export const TextAreaInputField = ({ inputProps, name, className, }) => {
|
|
394
394
|
return _jsx(TextAreaFieldInput, Object.assign({}, inputProps, { name: name, formikAs: "textarea" }));
|
|
395
395
|
};
|
|
396
|
+
export const BooleanFieldWrapper = styled.div `
|
|
397
|
+
display: flex;
|
|
398
|
+
`;
|
|
399
|
+
const ClickableEl = styled.button `
|
|
400
|
+
background: none;
|
|
401
|
+
color: inherit;
|
|
402
|
+
border: none;
|
|
403
|
+
padding: 0;
|
|
404
|
+
font: inherit;
|
|
405
|
+
outline: inherit;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
`;
|
|
408
|
+
export const Clickable = ({ children, onClick, onMouseUp = () => { }, onMouseDown = () => { }, innerRef = () => { }, className, ariaLabel, ariaExpanded = false, ariaControls, id, }) => {
|
|
409
|
+
return (_jsx(ClickableEl, { type: "button", onClick: onClick, onMouseUp: onMouseUp, onMouseDown: onMouseDown, ref: (r) => innerRef(r), className: className, "aria-expanded": ariaExpanded, "aria-label": ariaLabel, "aria-controls": ariaControls, id: id, children: children }));
|
|
410
|
+
};
|
|
411
|
+
export const CollapsibleToggle = styled(Clickable) `
|
|
412
|
+
display: flex;
|
|
413
|
+
flex-direction: column;
|
|
414
|
+
width: 100%;
|
|
415
|
+
`;
|
|
416
|
+
export const Icon = styled.div `
|
|
417
|
+
padding: 0 0.5rem;
|
|
418
|
+
`;
|
|
419
|
+
export const CollapsibleChildWrapper = styled.div ``;
|
|
420
|
+
export const CollapsibleObjectFieldWrapper = ObjectFieldWrapper;
|
|
421
|
+
export const CollapsibleObjectFieldTitle = ObjectFieldTitle;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useState } from "react";
|
|
3
|
+
import { FormRegistry } from "../Form";
|
|
4
|
+
import { getDescription, getTitle } from "../utils";
|
|
5
|
+
import { useExtractUiSchema } from "../hooks";
|
|
6
|
+
import { CollapsibleChildWrapper, CollapsibleToggle, Icon, RequiredIndicator, } from "../styledComponents";
|
|
7
|
+
export const CollapsibleObjectFieldTemplate = (props) => {
|
|
8
|
+
const { components: { FieldDescription, CollapsibleObjectFieldTitle, CollapsibleObjectFieldWrapper, FieldHelp, InfoFieldButton, }, } = useContext(FormRegistry);
|
|
9
|
+
const { children, name, uiSchema } = props;
|
|
10
|
+
const title = getTitle(props);
|
|
11
|
+
const description = getDescription(props);
|
|
12
|
+
const { help, info,
|
|
13
|
+
//TODO: This is not right
|
|
14
|
+
props: { wrapper: wrapperProps, title: titleProps, description: descriptionProps, help: helpProps, isCollapsedOnLoad = true, openIcon, collapsedIcon, ariaLabel,
|
|
15
|
+
// error: errorProps,
|
|
16
|
+
} = {}, } = useExtractUiSchema(uiSchema);
|
|
17
|
+
const [collapsed, setCollapsed] = useState(isCollapsedOnLoad);
|
|
18
|
+
const hide = collapsed;
|
|
19
|
+
return (_jsxs(CollapsibleObjectFieldWrapper, Object.assign({}, wrapperProps, { children: [title && (_jsx(CollapsibleToggle, { onClick: () => setCollapsed(!collapsed), ariaExpanded: !hide, ariaLabel: ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : name, children: _jsxs(CollapsibleObjectFieldTitle, Object.assign({ style: { display: "flex", alignItems: "center" } }, titleProps, { children: [title, (props === null || props === void 0 ? void 0 : props.required) && _jsx(RequiredIndicator, { children: "*" }), _jsx(Icon, { children: hide ? collapsedIcon !== null && collapsedIcon !== void 0 ? collapsedIcon : "▸" : openIcon !== null && openIcon !== void 0 ? openIcon : "▾" })] })) })), info && _jsx(InfoFieldButton, Object.assign({}, info)), description && (_jsx(FieldDescription, Object.assign({}, descriptionProps, { children: description }))), !hide && _jsx(CollapsibleChildWrapper, { children: children }), help && _jsx(FieldHelp, Object.assign({}, helpProps, { children: help }))] })));
|
|
20
|
+
};
|
package/dist/templates/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { MouseEventHandler } from "react";
|
|
3
3
|
export type FormProps<T> = React.PropsWithChildren<{
|
|
4
4
|
/** If true, disabled prop is passed down to each field on the form */
|
|
5
5
|
disabled?: boolean | undefined;
|
|
@@ -51,6 +51,9 @@ export type FormRegistryContextValue = {
|
|
|
51
51
|
definitions: {
|
|
52
52
|
[key: string]: JSONSchema7Definition;
|
|
53
53
|
} | undefined;
|
|
54
|
+
uiSchemaDefinitions: {
|
|
55
|
+
[key: string]: JSONSchema7Definition;
|
|
56
|
+
} | undefined;
|
|
54
57
|
formContext: Record<string, any>;
|
|
55
58
|
useItemsAsAdditionalItems: boolean;
|
|
56
59
|
hideUnmarkedAdditionalProperties: boolean;
|
|
@@ -90,10 +93,18 @@ export type ObjectFieldTemplateProps = {
|
|
|
90
93
|
required?: boolean;
|
|
91
94
|
children: React.ReactNode;
|
|
92
95
|
};
|
|
96
|
+
export type CollapsibleObjectFieldTemplateProps = {
|
|
97
|
+
schema: Schema;
|
|
98
|
+
uiSchema?: UiSchema;
|
|
99
|
+
required?: boolean;
|
|
100
|
+
children: React.ReactNode;
|
|
101
|
+
name?: string;
|
|
102
|
+
};
|
|
93
103
|
export type TemplatesType = {
|
|
94
104
|
FieldTemplate: React.FunctionComponent<FieldTemplateProps>;
|
|
95
105
|
ObjectFieldTemplate: React.FunctionComponent<ObjectFieldTemplateProps>;
|
|
96
106
|
ArrayFieldTemplate: React.FunctionComponent<ArrayFieldTemplateProps>;
|
|
107
|
+
CollapsibleObjectFieldTemplate: React.FunctionComponent<ObjectFieldTemplateProps>;
|
|
97
108
|
};
|
|
98
109
|
/*** COMPONENTS ***/
|
|
99
110
|
export type ComponentsType = {
|
|
@@ -133,6 +144,9 @@ export type ComponentsType = {
|
|
|
133
144
|
ToggleSwitchField: React.FunctionComponent<any>;
|
|
134
145
|
SearchableMultiTagSelectField: React.FunctionComponent<any>;
|
|
135
146
|
TextAreaInputField: React.FunctionComponent<any>;
|
|
147
|
+
BooleanFieldWrapper: React.FunctionComponent<any>;
|
|
148
|
+
CollapsibleObjectFieldWrapper: React.FunctionComponent<any>;
|
|
149
|
+
CollapsibleObjectFieldTitle: React.FunctionComponent<any>;
|
|
136
150
|
};
|
|
137
151
|
export declare enum ObjectFieldButtonPurpose {
|
|
138
152
|
ADD = 0,
|
|
@@ -205,6 +219,18 @@ export type SelectFieldProps = {
|
|
|
205
219
|
closeElement?: React.ReactNode;
|
|
206
220
|
className?: string;
|
|
207
221
|
};
|
|
222
|
+
export type ClickableProps = {
|
|
223
|
+
children: React.ReactNode;
|
|
224
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
225
|
+
onMouseDown?: MouseEventHandler<HTMLButtonElement>;
|
|
226
|
+
onMouseUp?: MouseEventHandler<HTMLButtonElement>;
|
|
227
|
+
innerRef?: (r: HTMLButtonElement | null) => void;
|
|
228
|
+
className?: string;
|
|
229
|
+
ariaLabel?: string;
|
|
230
|
+
ariaExpanded?: boolean;
|
|
231
|
+
ariaControls?: string;
|
|
232
|
+
id?: string;
|
|
233
|
+
};
|
|
208
234
|
/*** ***/
|
|
209
235
|
export type AjvError = {
|
|
210
236
|
message: string;
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { solveReferences } from "../solveReferences";
|
|
2
|
-
import { refDefSchema } from "./testSchemas";
|
|
2
|
+
import { refDefSchema, refDefUiSchema } from "./testSchemas";
|
|
3
3
|
describe("solveReferences", () => {
|
|
4
4
|
it("should resolve simple references correctly", () => {
|
|
5
5
|
var _a;
|
|
6
|
-
const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.name, refDefSchema.definitions);
|
|
6
|
+
const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.name, refDefSchema.definitions, {}, {});
|
|
7
7
|
expect(schemaSolvedForReferences).toEqual({
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
schema: {
|
|
9
|
+
type: "string",
|
|
10
|
+
title: "Name",
|
|
11
|
+
},
|
|
12
|
+
uiSchema: {},
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
it("should resolve simple references with uiSchema correctly", () => {
|
|
16
|
+
var _a;
|
|
17
|
+
const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.birthday, refDefSchema.definitions, refDefUiSchema, refDefUiSchema.definitions);
|
|
18
|
+
expect(schemaSolvedForReferences).toEqual({
|
|
19
|
+
schema: {
|
|
20
|
+
type: "string",
|
|
21
|
+
title: "Birthday",
|
|
22
|
+
},
|
|
23
|
+
uiSchema: { "ui:field": "DateField" },
|
|
10
24
|
});
|
|
11
25
|
});
|
|
12
26
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Schema } from "../../types";
|
|
1
|
+
import { Schema, UiSchema } from "../../types";
|
|
2
2
|
export declare const refDefSchema: Schema;
|
|
3
|
+
export declare const refDefUiSchema: UiSchema;
|
|
3
4
|
export declare const simpleConditionalSchema: Schema;
|
|
4
5
|
export declare const schemaWithDependenciesAndOneOf: Schema;
|
|
@@ -6,9 +6,18 @@ export const refDefSchema = {
|
|
|
6
6
|
name: {
|
|
7
7
|
$ref: "#/definitions/name",
|
|
8
8
|
},
|
|
9
|
+
birthday: {
|
|
10
|
+
$ref: "#/definitions/birthday",
|
|
11
|
+
},
|
|
9
12
|
},
|
|
10
13
|
definitions: {
|
|
11
14
|
name: { type: "string", title: "Name" },
|
|
15
|
+
birthday: { type: "string", title: "Birthday" },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export const refDefUiSchema = {
|
|
19
|
+
definitions: {
|
|
20
|
+
birthday: { "ui:field": "DateField" },
|
|
12
21
|
},
|
|
13
22
|
};
|
|
14
23
|
export const simpleConditionalSchema = {
|
|
@@ -28,7 +28,7 @@ export function createInitialValues(schema, definitions = {}) {
|
|
|
28
28
|
return Object.keys(schema === null || schema === void 0 ? void 0 : schema.properties).reduce((acc, key) => {
|
|
29
29
|
var _a;
|
|
30
30
|
const value = (_a = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _a === void 0 ? void 0 : _a[key];
|
|
31
|
-
const solvedValue = solveReferences(value, schemaDefinitions);
|
|
32
|
-
return Object.assign(Object.assign({}, acc), { [key]: getValue(solvedValue) });
|
|
31
|
+
const solvedValue = solveReferences(value, schemaDefinitions, {}, {});
|
|
32
|
+
return Object.assign(Object.assign({}, acc), { [key]: getValue(solvedValue.schema) });
|
|
33
33
|
}, {});
|
|
34
34
|
}
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
2
|
+
import { Schema, UiSchema } from "../types";
|
|
3
|
+
export declare function solveReferences(schema: Schema, definitions: {
|
|
4
|
+
[key: string]: JSONSchema7Definition;
|
|
5
|
+
} | undefined, uiSchema: UiSchema, uiSchemaDefinitions: {
|
|
6
|
+
[key: string]: JSONSchema7Definition;
|
|
7
|
+
} | undefined): {
|
|
8
|
+
schema: JSONSchema7;
|
|
9
|
+
uiSchema: UiSchema;
|
|
10
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
export function solveReferences(schema, definitions) {
|
|
2
|
-
var _a, _b;
|
|
3
|
-
|
|
1
|
+
export function solveReferences(schema, definitions, uiSchema, uiSchemaDefinitions) {
|
|
2
|
+
var _a, _b, _c;
|
|
3
|
+
let solvedSchema = schema;
|
|
4
|
+
let solvedUiSchema = uiSchema;
|
|
5
|
+
if (typeof schema === "object" && schema["$ref"] && definitions) {
|
|
4
6
|
const schemaKey = (_b = (_a = schema["$ref"]) === null || _a === void 0 ? void 0 : _a.substring(14)) !== null && _b !== void 0 ? _b : "";
|
|
5
|
-
|
|
7
|
+
solvedSchema = definitions[schemaKey];
|
|
8
|
+
solvedUiSchema = (_c = uiSchemaDefinitions === null || uiSchemaDefinitions === void 0 ? void 0 : uiSchemaDefinitions[schemaKey]) !== null && _c !== void 0 ? _c : {};
|
|
6
9
|
}
|
|
7
|
-
return schema;
|
|
10
|
+
return { schema: solvedSchema, uiSchema: solvedUiSchema };
|
|
8
11
|
}
|