@nolwenture/formik-jsonschema-form 0.1.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.
@@ -0,0 +1,273 @@
1
+ import { Schema, UiSchema } from "./types";
2
+
3
+ export type SimpleFormDataType = {
4
+ firstName: string;
5
+ lastName: string;
6
+ };
7
+
8
+ export const simpleSchema: Schema = {
9
+ title: "TITLE",
10
+ properties: {
11
+ firstName: { type: "string", title: "First name" },
12
+ lastName: { type: "string", title: "Last name" },
13
+ },
14
+ };
15
+
16
+ export const simpleUiSchema: UiSchema = {
17
+ "ui:description": "Description text",
18
+ };
19
+
20
+ export const completeSchema: Schema = {
21
+ title: "Inventory",
22
+ type: "object",
23
+ required: ["string", "number", "boolean", "enum"],
24
+ properties: {
25
+ string: {
26
+ type: "string",
27
+ title: "String",
28
+ description: "String description",
29
+ },
30
+ number: {
31
+ type: "number",
32
+ title: "Number",
33
+ description: "Number description",
34
+ },
35
+ date: {
36
+ type: "string",
37
+ title: "Date",
38
+ description: "Date description",
39
+ },
40
+ boolean: {
41
+ type: "boolean",
42
+ title: "Boolean",
43
+ description: "Boolean description",
44
+ default: false,
45
+ },
46
+ enum: {
47
+ type: "number",
48
+ title: "Enum",
49
+ description: "Enum description",
50
+ enum: [1, 2, 3, 4],
51
+ },
52
+ enumOneOf: {
53
+ type: "number",
54
+ title: "Enum oneOf (non-standard)",
55
+ description: "Enum oneOf description",
56
+ oneOf: [
57
+ { const: 1, title: "one" },
58
+ { const: 2, title: "two" },
59
+ { const: 3, title: "three" },
60
+ { const: 4, title: "four" },
61
+ ],
62
+ },
63
+ enumAnyOf: {
64
+ type: "number",
65
+ title: "Enum anyOf (non-standard)",
66
+ description: "Enum anyOf description",
67
+ anyOf: [
68
+ { const: 1, title: "one" },
69
+ { const: 2, title: "two" },
70
+ { const: 3, title: "three" },
71
+ { const: 4, title: "four" },
72
+ ],
73
+ },
74
+ null: { type: "null", title: "Null", description: "Null description" },
75
+ nested: {
76
+ type: "object",
77
+ title: "Nested object",
78
+ description: "Nested object description",
79
+ properties: {
80
+ string: {
81
+ type: "string",
82
+ title: "Nested String",
83
+ description: "Nested String description",
84
+ },
85
+ number: {
86
+ type: "number",
87
+ title: "Nested Number",
88
+ description: "Nested Number description",
89
+ },
90
+ date: {
91
+ type: "string",
92
+ title: "Date",
93
+ description: "Date description",
94
+ },
95
+ boolean: {
96
+ type: "boolean",
97
+ title: "Nested Boolean",
98
+ description: "Nested Boolean description",
99
+ default: false,
100
+ },
101
+ enum: {
102
+ type: "number",
103
+ title: "Nested Enum",
104
+ description: "Nested Enum description",
105
+ enum: [1, 2, 3, 4],
106
+ },
107
+ null: {
108
+ type: "null",
109
+ title: "Nested Null",
110
+ description: "Nested Null description",
111
+ },
112
+ },
113
+ additionalProperties: {
114
+ type: "string",
115
+ },
116
+ },
117
+ array: {
118
+ type: "array",
119
+ title: "Array",
120
+ description: "Array description",
121
+ additionalItems: {
122
+ type: "string",
123
+ },
124
+ },
125
+ partiallyFixedArray: {
126
+ type: "array",
127
+ title: "Partially fixed array",
128
+ description: "Partially fixed array description",
129
+ items: [{ type: "string", default: "Hello" }, { type: "string" }],
130
+ additionalItems: {
131
+ type: "string",
132
+ default: "defaultForAdditional",
133
+ },
134
+ },
135
+ enumArray: {
136
+ type: "array",
137
+ title: "Enum array",
138
+ description: "Enum array description",
139
+ additionalItems: {
140
+ type: "number",
141
+ enum: [1, 2, 3, 4],
142
+ },
143
+ },
144
+ objectArray: {
145
+ type: "array",
146
+ title: "Object array",
147
+ description: "Object array description",
148
+ additionalItems: {
149
+ type: "object",
150
+ title: "Array object",
151
+ description: "Array object description",
152
+ properties: {
153
+ string: {
154
+ type: "string",
155
+ title: "Array String",
156
+ description: "Array String description",
157
+ },
158
+ number: {
159
+ type: "number",
160
+ title: "Array Number",
161
+ description: "Array Number description",
162
+ },
163
+ date: {
164
+ type: "string",
165
+ title: "Date",
166
+ description: "Date description",
167
+ },
168
+ boolean: {
169
+ type: "boolean",
170
+ title: "Array Boolean",
171
+ description: "Array Boolean description",
172
+ default: false,
173
+ },
174
+ enum: {
175
+ type: "array",
176
+ title: "Array Enum",
177
+ description: "Array Enum description",
178
+ items: {
179
+ type: "number",
180
+ enum: [1, 2, 3, 4],
181
+ },
182
+ },
183
+ null: {
184
+ type: "null",
185
+ title: "Array Null",
186
+ description: "Array Null description",
187
+ },
188
+ },
189
+ },
190
+ },
191
+ },
192
+ };
193
+
194
+ export const completeUiSchema: UiSchema = {
195
+ string: {
196
+ "ui:help": "String help",
197
+ "ui:options": {
198
+ placeholder: "String placeholder",
199
+ },
200
+ },
201
+ number: {
202
+ "ui:help": "Number help",
203
+ "ui:options": {
204
+ placeholder: "Number placeholder",
205
+ },
206
+ },
207
+ date: {
208
+ "ui:field": "DateField",
209
+ "ui:help": "Date help",
210
+ },
211
+ boolean: {
212
+ "ui:help": "Boolean help",
213
+ "ui:options": {
214
+ valueDescription: "Boolean specific label",
215
+ },
216
+ },
217
+ enum: {
218
+ "ui:help": "Enum help",
219
+ },
220
+ null: {
221
+ "ui:help": "Null help",
222
+ },
223
+ nested: {
224
+ string: {
225
+ "ui:help": "String help",
226
+ "ui:options": {
227
+ placeholder: "String placeholder",
228
+ },
229
+ },
230
+ number: {
231
+ "ui:help": "Number help",
232
+ "ui:options": {
233
+ placeholder: "Number placeholder",
234
+ },
235
+ },
236
+ date: {
237
+ "ui:field": "DateField",
238
+ "ui:help": "Date help",
239
+ },
240
+ boolean: {
241
+ "ui:help": "Boolean help",
242
+ "ui:options": {
243
+ valueDescription: "Boolean specific label",
244
+ },
245
+ },
246
+ enum: {
247
+ "ui:help": "Enum help",
248
+ },
249
+ null: {
250
+ "ui:help": "Null help",
251
+ },
252
+ },
253
+ };
254
+
255
+ export const generatedSchema: Schema = {
256
+ type: "object",
257
+ properties: {
258
+ object: {
259
+ type: "object",
260
+ additionalProperties: {},
261
+ },
262
+ value: {},
263
+ array: {
264
+ type: "array",
265
+ additionalItems: {},
266
+ },
267
+ property: {
268
+ type: "string",
269
+ },
270
+ },
271
+ required: ["object", "value", "property"],
272
+ additionalProperties: false,
273
+ };
@@ -0,0 +1,35 @@
1
+ import { useMemo } from "react";
2
+ import { JSONSchema7, JSONSchema7TypeName } from "json-schema";
3
+ import { useState } from "react";
4
+ import { FieldProps } from "../types";
5
+ import { SchemaField } from "./SchemaField";
6
+
7
+ import { COMPONENT_TYPES } from "./types";
8
+
9
+ export function AnyField(props: FieldProps) {
10
+ const [type, setType] = useState<JSONSchema7TypeName>("string");
11
+ const { name, schema, uiSchema } = props;
12
+ const enrichedSchema = useMemo<JSONSchema7>(() => {
13
+ switch (type) {
14
+ case "object":
15
+ return { ...(schema || {}), type, additionalProperties: {} };
16
+ case "array":
17
+ return { ...(schema || {}), type, additionalItems: {} };
18
+ default:
19
+ return { ...(schema || {}), type };
20
+ }
21
+ }, [type, schema]);
22
+ return (
23
+ <div>
24
+ <select
25
+ value={type}
26
+ onChange={(event) => setType(event.target.value as JSONSchema7TypeName)}
27
+ >
28
+ {Object.keys(COMPONENT_TYPES).map((type) => (
29
+ <option value={type}>{type}</option>
30
+ ))}
31
+ </select>
32
+ <SchemaField name={name} schema={enrichedSchema} uiSchema={uiSchema} />
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,177 @@
1
+ import { useContext, useMemo } from "react";
2
+ import _ from "lodash";
3
+ import { FormRegistry } from "../Form";
4
+ import { useField } from "formik";
5
+ import { ArrayFieldButtonPurpose, FieldProps, Schema } from "../types";
6
+ import { useExtractUiSchema } from "../utils";
7
+
8
+ function appendArray(values: any[] | undefined): any[] {
9
+ return values ? [...values, ""] : [""];
10
+ }
11
+
12
+ function removeFromArray(values: any[], index: number) {
13
+ return [...values.slice(0, index), ...values.slice(index + 1)];
14
+ }
15
+ function moveInArray(values: any[], index: number, direction: -1 | 1) {
16
+ if (direction === -1) {
17
+ return [
18
+ ...values.slice(0, index - 1),
19
+ values[index],
20
+ values[index - 1],
21
+ ...values.slice(index + 1),
22
+ ];
23
+ } else {
24
+ return [
25
+ ...values.slice(0, index),
26
+ values[index + 1],
27
+ values[index],
28
+ ...values.slice(index + 2),
29
+ ];
30
+ }
31
+ }
32
+
33
+ export function ArrayField(props: FieldProps) {
34
+ const {
35
+ fields: { SchemaField },
36
+ templates: { ArrayFieldTemplate },
37
+ components: {
38
+ ArrayFieldItem,
39
+ ArrayFieldList,
40
+ ArrayFieldItemButtons,
41
+ ArrayFieldButton,
42
+ },
43
+ } = useContext(FormRegistry);
44
+ const { name, schema, uiSchema } = props;
45
+ const [{ value }, _, { setValue }] = useField<any[]>(name);
46
+
47
+ const {
48
+ items,
49
+ additionalItems,
50
+ }: { items: Schema[]; additionalItems: Schema | undefined } = useMemo(() => {
51
+ let items =
52
+ schema.items && typeof schema.items !== "boolean" ? schema.items : [];
53
+ if (!Array.isArray(items)) {
54
+ items = [items];
55
+ }
56
+ return {
57
+ items: items.filter((item) => typeof item !== "boolean") as Schema[],
58
+ additionalItems:
59
+ typeof schema.additionalItems !== "boolean"
60
+ ? schema.additionalItems
61
+ : undefined,
62
+ };
63
+ }, [schema]);
64
+
65
+ //This whole mess would give backwards compatibility, but maybe we don't do it
66
+ // const {
67
+ // items,
68
+ // additionalItems,
69
+ // }: {
70
+ // items: Schema[];
71
+ // additionalItems: Schema | undefined;
72
+ // } = useMemo(
73
+ // () =>
74
+ // !schema.additionalItems
75
+ // ? {
76
+ // additionalItems:
77
+ // typeof schema.items !== "boolean"
78
+ // ? Array.isArray(schema.items)
79
+ // ? typeof schema.items[0] !== "boolean"
80
+ // ? schema.items[0]
81
+ // : undefined
82
+ // : typeof schema.items !== "boolean"
83
+ // ? schema.items
84
+ // : undefined
85
+ // : undefined,
86
+ // items: [],
87
+ // }
88
+ // : {
89
+ // additionalItems:
90
+ // typeof schema.additionalItems !== "boolean"
91
+ // ? schema.additionalItems
92
+ // : undefined,
93
+ // items: schema.items
94
+ // ? Array.isArray(schema.items)
95
+ // ? (schema.items.filter(
96
+ // (item) => typeof item !== "boolean"
97
+ // ) as Schema[])
98
+ // : typeof schema.items !== "boolean"
99
+ // ? [schema.items]
100
+ // : []
101
+ // : [],
102
+ // },
103
+ // [schema]
104
+ // );
105
+ // Bacwards compatibility ends
106
+
107
+ const { props: inputProps, disabled } = useExtractUiSchema(uiSchema);
108
+
109
+ return (
110
+ <ArrayFieldTemplate {...props}>
111
+ <ArrayFieldList>
112
+ {(items || []).map((itemSchema, index) => (
113
+ <ArrayFieldItem>
114
+ <SchemaField
115
+ key={index}
116
+ name={`${name}[${index}]`}
117
+ schema={typeof itemSchema !== "boolean" ? itemSchema : {}}
118
+ uiSchema={uiSchema?.[index] || uiSchema?.items}
119
+ />
120
+ </ArrayFieldItem>
121
+ ))}
122
+ {(value?.slice(items.length) || []).map((_, index) => (
123
+ <ArrayFieldItem>
124
+ <SchemaField
125
+ key={index}
126
+ {...{
127
+ disabled,
128
+ ...inputProps,
129
+ }}
130
+ name={`${name}[${index + items.length}]`}
131
+ schema={
132
+ additionalItems && typeof additionalItems !== "boolean"
133
+ ? additionalItems
134
+ : {}
135
+ }
136
+ uiSchema={{
137
+ disabled,
138
+ ...(uiSchema?.[index + items.length] || uiSchema?.items),
139
+ }}
140
+ />
141
+ <ArrayFieldItemButtons>
142
+ <ArrayFieldButton
143
+ purpose={ArrayFieldButtonPurpose.REMOVE}
144
+ onClick={() =>
145
+ setValue(removeFromArray(value, index + items.length))
146
+ }
147
+ />
148
+
149
+ {value.length > 1 && index > 0 && (
150
+ <ArrayFieldButton
151
+ purpose={ArrayFieldButtonPurpose.MOVE_UP}
152
+ onClick={() =>
153
+ setValue(moveInArray(value, index + items.length, -1))
154
+ }
155
+ />
156
+ )}
157
+ {value.length > 1 && index < value.length - 1 && (
158
+ <ArrayFieldButton
159
+ purpose={ArrayFieldButtonPurpose.MOVE_DOWN}
160
+ onClick={() =>
161
+ setValue(moveInArray(value, index + items.length, 1))
162
+ }
163
+ />
164
+ )}
165
+ </ArrayFieldItemButtons>
166
+ </ArrayFieldItem>
167
+ ))}
168
+ </ArrayFieldList>
169
+ {additionalItems && (
170
+ <ArrayFieldButton
171
+ purpose={ArrayFieldButtonPurpose.ADD}
172
+ onClick={() => setValue(appendArray(value))}
173
+ />
174
+ )}
175
+ </ArrayFieldTemplate>
176
+ );
177
+ }
@@ -0,0 +1,23 @@
1
+ import { useContext } from "react";
2
+ import { FormRegistry } from "../Form";
3
+ import { FieldProps } from "../types";
4
+ import { useExtractUiSchema, useSetDefault } from "../utils";
5
+
6
+ export function BooleanField(props: FieldProps) {
7
+ const {
8
+ templates: { FieldTemplate },
9
+ components: { FieldInput },
10
+ } = useContext(FormRegistry);
11
+ useSetDefault(props);
12
+ const { name, uiSchema } = props;
13
+ const { label, props: inputProps } = useExtractUiSchema(uiSchema);
14
+
15
+ return (
16
+ <FieldTemplate {...props}>
17
+ <>
18
+ <FieldInput {...inputProps} name={name} type="checkbox" />
19
+ {label}
20
+ </>
21
+ </FieldTemplate>
22
+ );
23
+ }
@@ -0,0 +1,20 @@
1
+ import { useContext } from "react";
2
+ import { FormRegistry } from "../Form";
3
+ import { FieldProps } from "../types";
4
+ import { useExtractUiSchema, useSetDefault } from "../utils";
5
+
6
+ export function DateField(props: FieldProps) {
7
+ const {
8
+ templates: { FieldTemplate },
9
+ components: { FieldInput },
10
+ } = useContext(FormRegistry);
11
+ useSetDefault(props);
12
+ const { name, uiSchema } = props;
13
+ const { props: inputProps } = useExtractUiSchema(uiSchema);
14
+
15
+ return (
16
+ <FieldTemplate {...props}>
17
+ <FieldInput {...inputProps} name={name} type="date" />
18
+ </FieldTemplate>
19
+ );
20
+ }
@@ -0,0 +1,12 @@
1
+ import { useContext } from "react";
2
+ import { FormRegistry } from "../Form";
3
+ import { FieldProps } from "../types";
4
+
5
+ export function NullField(props: FieldProps) {
6
+ const {
7
+ templates: { FieldTemplate },
8
+ } = useContext(FormRegistry);
9
+ const { name } = props;
10
+
11
+ return <FieldTemplate {...props}></FieldTemplate>;
12
+ }
@@ -0,0 +1,14 @@
1
+ import { FieldProps } from "../types";
2
+ import { StringField } from "./StringField";
3
+
4
+ export function NumberField(props: FieldProps) {
5
+ return (
6
+ <StringField
7
+ {...props}
8
+ uiSchema={{
9
+ ...props.uiSchema,
10
+ "ui:props": { ...(props.uiSchema?.["ui:props"] || {}), type: "number" },
11
+ }}
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,68 @@
1
+ import { useField } from "formik";
2
+ import { useContext, useMemo } from "react";
3
+ import { FormRegistry } from "../Form";
4
+ import { FieldProps, ObjectFieldButtonPurpose } from "../types";
5
+
6
+ export function ObjectField(props: FieldProps) {
7
+ const {
8
+ fields: { SchemaField },
9
+ templates: { ObjectFieldTemplate },
10
+ components: {
11
+ ObjectFieldItem,
12
+ ObjectFieldList,
13
+ ObjectFieldItemButtons,
14
+ ObjectFieldButton,
15
+ },
16
+ } = useContext(FormRegistry);
17
+ const { name, schema, uiSchema } = props;
18
+ const [{ value }, _, { setValue }] = useField<Record<string, any>>(name);
19
+ const { properties, additionalProperties } = schema;
20
+ const propertyKeys = useMemo(
21
+ () => Object.keys(properties || {}),
22
+ [properties]
23
+ );
24
+
25
+ const additionalValues = useMemo(
26
+ () =>
27
+ Object.fromEntries(
28
+ Object.entries(value || {}).filter(
29
+ ([key, value]) => !propertyKeys.includes(key)
30
+ )
31
+ ),
32
+ [value, propertyKeys]
33
+ );
34
+
35
+ return (
36
+ <ObjectFieldTemplate {...props}>
37
+ {Object.entries(properties || {}).map(([childName, childSchema]) => (
38
+ <SchemaField
39
+ key={childName}
40
+ name={name ? [name, childName].join(".") : childName}
41
+ schema={typeof childSchema !== "boolean" ? childSchema : {}}
42
+ uiSchema={uiSchema?.[childName]}
43
+ />
44
+ ))}
45
+ {Object.entries(additionalValues || {}).map(
46
+ ([childName, childSchema]) => (
47
+ <SchemaField
48
+ key={childName}
49
+ name={name ? [name, childName].join(".") : childName}
50
+ schema={typeof childSchema !== "boolean" ? childSchema : {}}
51
+ uiSchema={uiSchema?.[childName]}
52
+ />
53
+ )
54
+ )}
55
+ {additionalProperties && (
56
+ <ObjectFieldButton
57
+ purpose={ObjectFieldButtonPurpose.ADD}
58
+ onClick={() => {
59
+ const key = prompt("Give key");
60
+ if (key) {
61
+ setValue({ ...value, [key]: undefined });
62
+ }
63
+ }}
64
+ />
65
+ )}
66
+ </ObjectFieldTemplate>
67
+ );
68
+ }
@@ -0,0 +1,37 @@
1
+ import { useContext } from "react";
2
+ import { Field, FieldProps, Schema, UiSchema } from "../types";
3
+ import { useExtractUiSchema, getSchemaType } from "../utils";
4
+ import { FormRegistry } from "../Form";
5
+
6
+ import { COMPONENT_TYPES } from "./types";
7
+
8
+ function getFieldComponent(
9
+ schema: Schema,
10
+ uiSchema: UiSchema,
11
+ fields: Record<string, Field>
12
+ ) {
13
+ const { field } = useExtractUiSchema(uiSchema);
14
+ if (typeof field === "function") {
15
+ return field;
16
+ }
17
+ if (typeof field === "string" && field in fields) {
18
+ return fields[field];
19
+ }
20
+ let schemaType = getSchemaType(schema);
21
+ if (Array.isArray(schemaType)) {
22
+ schemaType = schemaType[0];
23
+ }
24
+ const componentName = COMPONENT_TYPES[schemaType || "any"];
25
+
26
+ return componentName in fields
27
+ ? fields[componentName]
28
+ : fields.UnsupportedField;
29
+ }
30
+
31
+ export function SchemaField(props: FieldProps) {
32
+ const { schema, uiSchema } = props;
33
+ const { fields } = useContext(FormRegistry);
34
+ const FieldComponent = getFieldComponent(schema, uiSchema, fields);
35
+
36
+ return <FieldComponent {...props} />;
37
+ }