@nolwenture/formik-jsonschema-form 0.11.0 → 0.12.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
@@ -365,6 +365,10 @@ export function MyFormComponent() {
365
365
  Enum fields are fields with multiple options to either choose `oneOf` or `anyOf` from.
366
366
  There is an alternative field implementation for "anyOf" that when given `"ui:field": "SearchMultiTagSelectField"` results in a searchable multi tag select field.
367
367
 
368
+ 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).
369
+
370
+ `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".
371
+
368
372
  ```tsx
369
373
  const schema = {
370
374
  properties: {
@@ -384,7 +388,7 @@ const schema = {
384
388
  ],
385
389
  },
386
390
  enumAnyOf: {
387
- type: "number",
391
+ type: "array",
388
392
  title: "Enum anyOf (non-standard)",
389
393
  anyOf: [
390
394
  { const: 1, title: "one" },
@@ -394,7 +398,7 @@ const schema = {
394
398
  ],
395
399
  },
396
400
  enumTagSelectAnyOf: {
397
- type: "string",
401
+ type: "array",
398
402
  title: "Enum searchable tag select anyOf",
399
403
  anyOf: [
400
404
  { const: 1, title: "one" },
@@ -420,7 +424,7 @@ const uiSchema = {
420
424
  ### Alternative fields
421
425
 
422
426
  There are alternative fields available for certain types.
423
- For `type: "string"` there is `DateField`, `MultipleChoiceField` and `TextAreaField`.
427
+ For `type: "string"` there is `DateField`, `TextAreaField` and partly `MultipleChoiceField`.
424
428
 
425
429
  `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
430
 
@@ -436,7 +440,28 @@ const uiSchema = {
436
440
  };
437
441
  ```
438
442
 
439
- `MultipleChoiceField` takes an array of `oneOf` or `anyOf`. 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. The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.
443
+ `TextAreaField` gives more space for a longer string input.
444
+
445
+ ```tsx
446
+ const schema = {
447
+ properties: {
448
+ story: { type: "string", title: "A short story" },
449
+ },
450
+ };
451
+
452
+ const uiSchema = {
453
+ story: { "ui:field": "TextAreaField" },
454
+ };
455
+ ```
456
+
457
+ `MultipleChoiceField` takes an array of `oneOf` or `anyOf`.
458
+ 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").
459
+ 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.
460
+
461
+ 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.
462
+
463
+ The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.
464
+
440
465
  `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
466
 
442
467
  ```tsx
@@ -452,6 +477,19 @@ const schema = {
452
477
  { const: "nope", title: "Please don't even ask..." },
453
478
  ],
454
479
  },
480
+ food: {
481
+ type: "array",
482
+ title: "What would you like to eat today? (style: oneColumn)",
483
+ anyOf: [
484
+ { const: "breakfast", title: "Breakfast" },
485
+ { const: "secondBreakfast", title: "What about second breakfast?" },
486
+ { const: "elevenses", title: "Elevenses" },
487
+ { const: "luncheon", title: "Luncheon" },
488
+ { const: "afternoonTea", title: "Afternoon tea" },
489
+ { const: "dinner", title: "Dinner" },
490
+ { const: "supper", title: "Supper" },
491
+ ],
492
+ },
455
493
  },
456
494
  };
457
495
 
@@ -462,21 +500,13 @@ const uiSchema = {
462
500
  style: "twoColumns",
463
501
  },
464
502
  },
465
- };
466
- ```
467
-
468
- `TextAreaField` gives more space for a longer string input.
469
-
470
- ```tsx
471
- const schema = {
472
- properties: {
473
- story: { type: "string", title: "A short story" },
503
+ food: {
504
+ "ui:field": "MultipleChoiceField",
505
+ "ui:options": {
506
+ style: "oneColumn",
507
+ },
474
508
  },
475
509
  };
476
-
477
- const uiSchema = {
478
- story: { "ui:field": "TextAreaField" },
479
- };
480
510
  ```
481
511
 
482
512
  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
- // Bacwards compatibility ends
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
- case "multiSelect":
16
- return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", multiple: enums.type === "multiSelect", children: [enums.type !== "multiSelect" && (_jsx("option", Object.assign({ value: "", disabled: true,
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
+ };
@@ -22,7 +22,7 @@ export const Default = {
22
22
  args: {
23
23
  name: "tags",
24
24
  schema: {
25
- type: "string",
25
+ type: "array",
26
26
  title: "Enum searchable tag select anyOf",
27
27
  anyOf: [
28
28
  { const: "1", title: "First tag" },
@@ -44,7 +44,7 @@ export const completeSchema = {
44
44
  ],
45
45
  },
46
46
  enumAnyOf: {
47
- type: "number",
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: "string",
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: "string",
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: "string",
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: "string",
56
+ type: "array",
57
57
  title: "Pick some flowers (style: threeColumns)",
58
58
  anyOf: [
59
59
  { const: "lily", title: "Lily" },
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [