@nolwenture/formik-jsonschema-form 0.18.0 → 0.20.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.
@@ -9,11 +9,11 @@ export const SearchableSelectField = (props) => {
9
9
  useSetDefault(props);
10
10
  const { name, uiSchema } = props;
11
11
  const { props: inputProps, placeholder, clearButtonText, expandText, notFoundMessage, } = useExtractUiSchema(uiSchema);
12
- const [_, __, { setValue }] = useField(name);
12
+ const [{ value }, __, { setValue }] = useField(name);
13
13
  const enums = useMemo(() => getEnums(props), [props]);
14
14
  const options = (enums === null || enums === void 0 ? void 0 : enums.values) || [];
15
15
  const [showOptions, setShowOptions] = useState(false);
16
- const [searchTerm, setSearchTerm] = useState("");
16
+ const [searchTerm, setSearchTerm] = useState(() => { var _a, _b; return (_b = (_a = options.find((option) => option.const === value)) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : ""; });
17
17
  const [displayedItems, setDisplayedItems] = useState([]);
18
18
  const [itemsToShow, setItemsToShow] = useState(10);
19
19
  const moreRef = useRef(null);
@@ -53,5 +53,5 @@ export const SearchableSelectField = (props) => {
53
53
  const filteredOptions = options.filter((option) => { var _a, _b; return ((_b = (_a = option.title) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "").includes(searchTerm.toLowerCase()); });
54
54
  const showMoreOption = displayedItems.length < filteredOptions.length &&
55
55
  displayedItems.length >= 10;
56
- return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(SearchableSelectContainer, { ref: containerRef, onBlur: handleBlur, children: [_jsxs(SearchInputContainer, { children: [_jsx(SearchInput, Object.assign({}, inputProps, { type: "text", placeholder: placeholder, value: searchTerm, onChange: handleSearchChange, onFocus: handleFocus })), searchTerm && (_jsx(SearchClearButton, { onClick: handleClear, children: clearButtonText !== null && clearButtonText !== void 0 ? clearButtonText : "X" }))] }), showOptions && (_jsxs(SearchDropdownContainer, { role: "dropdown-list", ref: moreRef, children: [displayedItems.length > 0 ? (displayedItems.map((item, index) => (_jsx(SearchListOption, { role: "filtered-list", onClick: () => handleSelect(item), onMouseDown: (e) => e.preventDefault(), children: item.title }, index)))) : (_jsx(SearchNotFound, { onMouseDown: (e) => e.preventDefault(), children: notFoundMessage !== null && notFoundMessage !== void 0 ? notFoundMessage : "No match found" })), showMoreOption && (_jsx(SearchListExpand, { ref: moreRef, onMouseDown: (e) => e.preventDefault(), onClick: loadMoreItems, children: expandText !== null && expandText !== void 0 ? expandText : "More..." }))] }))] }) })));
56
+ return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(SearchableSelectContainer, { ref: containerRef, onBlur: handleBlur, children: [_jsxs(SearchInputContainer, { children: [_jsx(SearchInput, Object.assign({}, inputProps, { name: name, type: "text", placeholder: placeholder, value: searchTerm, onChange: handleSearchChange, onFocus: handleFocus })), searchTerm && (_jsx(SearchClearButton, { onClick: handleClear, children: clearButtonText !== null && clearButtonText !== void 0 ? clearButtonText : "x" }))] }), showOptions && (_jsxs(SearchDropdownContainer, { role: "dropdown-list", ref: moreRef, children: [displayedItems.length > 0 ? (displayedItems.map((item, index) => (_jsx(SearchListOption, { role: "filtered-list", onClick: () => handleSelect(item), onMouseDown: (e) => e.preventDefault(), children: item.title }, index)))) : (_jsx(SearchNotFound, { onMouseDown: (e) => e.preventDefault(), children: notFoundMessage !== null && notFoundMessage !== void 0 ? notFoundMessage : "No match found" })), showMoreOption && (_jsx(SearchListExpand, { ref: moreRef, onMouseDown: (e) => e.preventDefault(), onClick: loadMoreItems, children: expandText !== null && expandText !== void 0 ? expandText : "More..." }))] }))] }) })));
57
57
  };
@@ -318,7 +318,7 @@ export const completeUiSchema = {
318
318
  "ui:field": "SearchableSelectField",
319
319
  "ui:options": {
320
320
  placeholder: "Search...",
321
- clearButtonText: "Clear",
321
+ clearButtonText: "X",
322
322
  expandText: "More results",
323
323
  emptySearchMessage: "...no matching results found",
324
324
  },
@@ -167,6 +167,7 @@ export declare const RadioButtonField: {
167
167
  };
168
168
  export declare const MultipleChoiceField: ({ inputProps, name, className, enums, style, }: MultipleChoiceFieldProps) => import("react/jsx-runtime").JSX.Element;
169
169
  export declare const MultiSelectContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
170
+ export declare const MultiSelectSearchContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
170
171
  export declare const MultiSelectInput: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<Omit<Omit<any, "as">, "ref"> & {
171
172
  ref?: unknown;
172
173
  }, never>, "ref"> & {
@@ -184,6 +185,7 @@ export declare const SearchableMultiTagSelectField: {
184
185
  ({ name, placeholder, enums, emptySearchMessage, closeElement, className, }: SelectFieldProps): import("react/jsx-runtime").JSX.Element;
185
186
  styles: {
186
187
  MultiSelectContainerStyle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
188
+ MultiSelectSearchContainerStyle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
187
189
  MultiSelectInputStyle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<Omit<Omit<any, "as">, "ref"> & {
188
190
  ref?: unknown;
189
191
  }, never>, "ref"> & {
@@ -236,6 +238,6 @@ export declare const SearchInput: import("styled-components").IStyledComponent<"
236
238
  }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<Omit<any, "as">, "ref"> & {
237
239
  ref?: unknown;
238
240
  }, 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>>;
239
- export declare const SearchClearButton: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>>;
241
+ export declare const SearchClearButton: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>>;
240
242
  export declare const SearchInputContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
241
243
  export {};
@@ -293,7 +293,14 @@ export const MultiSelectContainer = styled.div `
293
293
  display: flex;
294
294
  flex-direction: column;
295
295
  `;
296
- export const MultiSelectInput = styled(FieldInput) ``;
296
+ export const MultiSelectSearchContainer = styled.div `
297
+ display: inline-block;
298
+ position: relative;
299
+ width: fit-content;
300
+ `;
301
+ export const MultiSelectInput = styled(FieldInput) `
302
+ width: 100%;
303
+ `;
297
304
  export const MultiSelectBadgeContainer = styled.div `
298
305
  margin: 0.3em 0;
299
306
  display: inline-flex;
@@ -313,13 +320,15 @@ export const MultiSelectBadgeCloseElement = styled.span `
313
320
  font-family: sans-serif;
314
321
  `;
315
322
  export const MultiSelectOptionList = styled.ul `
323
+ width: 100%;
316
324
  padding: unset;
317
325
  margin: unset;
318
326
  border: 1px solid gray;
319
327
  border-radius: 0.2em;
320
328
  position: absolute;
321
329
  background: white;
322
- margin: 1.3em 0;
330
+ top: 100%;
331
+ z-index: 1000;
323
332
  `;
324
333
  export const MultiSelectOptionListItem = styled.li `
325
334
  cursor: pointer;
@@ -345,22 +354,23 @@ export const SearchableMultiTagSelectField = ({ name, placeholder, enums, emptyS
345
354
  };
346
355
  return (_jsx(formik.Field, { className: className, name: name, multiple: true, children: ({ field, form: { setFieldValue }, meta, }) => {
347
356
  var _a;
348
- 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) => {
349
- setSearch(e.currentTarget.value);
350
- }, placeholder: placeholder, value: search }), _jsx(MultiSelectBadgeContainer, { children: ((_a = field.value) !== null && _a !== void 0 ? _a : []).map((value) => {
357
+ return (_jsxs(MultiSelectContainer, { className: className, children: [_jsx(FieldInput, Object.assign({ id: className, type: "hidden" }, field)), _jsxs(MultiSelectSearchContainer, { children: [_jsx(MultiSelectInput, { id: className + "_id", className: className, type: "text", onChange: (e) => {
358
+ setSearch(e.currentTarget.value);
359
+ }, placeholder: placeholder, value: search }), search.length > 0 && (_jsx(MultiSelectOptionList, { children: searchIncludedInEnum() ? (enums === null || enums === void 0 ? void 0 : enums.values.filter((item) => { var _a; return !((_a = field.value) !== null && _a !== void 0 ? _a : []).includes(item.const); }).filter((value) => value.title.toLowerCase().includes(search.toLowerCase())).map((option, index) => (_jsx(MultiSelectOptionListItem, { value: option.const, onClick: () => {
360
+ var _a;
361
+ setFieldValue(name, [
362
+ ...new Set([...((_a = field.value) !== null && _a !== void 0 ? _a : []), option.const]),
363
+ ]);
364
+ setSearch("");
365
+ }, children: option.title }, index)))) : (_jsx(MultiSelectOptionListWithoutItem, { children: emptySearchMessage !== null && emptySearchMessage !== void 0 ? emptySearchMessage : "No match found" })) }))] }), _jsx(MultiSelectBadgeContainer, { children: ((_a = field.value) !== null && _a !== void 0 ? _a : []).map((value) => {
351
366
  var _a;
352
367
  return (_jsxs(MultiSelectBadge, { children: [(_a = enums === null || enums === void 0 ? void 0 : enums.values.find((option) => option.const === value)) === null || _a === void 0 ? void 0 : _a.title, _jsx(MultiSelectBadgeCloseElement, { onClick: () => setFieldValue(name, field.value.filter((v) => v !== value)), children: closeElement !== null && closeElement !== void 0 ? closeElement : "x" })] }, value));
353
- }) }), search.length > 0 && (_jsx(MultiSelectOptionList, { children: searchIncludedInEnum() ? (enums === null || enums === void 0 ? void 0 : enums.values.filter((item) => { var _a; return !((_a = field.value) !== null && _a !== void 0 ? _a : []).includes(item.const); }).filter((value) => value.title.toLowerCase().includes(search.toLowerCase())).map((option, index) => (_jsx(MultiSelectOptionListItem, { value: option.const, onClick: () => {
354
- var _a;
355
- setFieldValue(name, [
356
- ...new Set([...((_a = field.value) !== null && _a !== void 0 ? _a : []), option.const]),
357
- ]);
358
- setSearch("");
359
- }, children: option.title }, index)))) : (_jsx(MultiSelectOptionListWithoutItem, { children: emptySearchMessage !== null && emptySearchMessage !== void 0 ? emptySearchMessage : "No match found" })) })), meta.touched && meta.error && (_jsx("div", { className: "error", children: meta.error }))] }));
368
+ }) }), meta.touched && meta.error && (_jsx("div", { className: "error", children: meta.error }))] }));
360
369
  } }));
361
370
  };
362
371
  SearchableMultiTagSelectField.styles = {
363
372
  MultiSelectContainerStyle: MultiSelectContainer,
373
+ MultiSelectSearchContainerStyle: MultiSelectSearchContainer,
364
374
  MultiSelectInputStyle: MultiSelectInput,
365
375
  MultiSelectBadgeContainerStyle: MultiSelectBadgeContainer,
366
376
  MultiSelectBadgeStyle: MultiSelectBadge,
@@ -402,11 +412,10 @@ export const Icon = styled.div `
402
412
  export const CollapsibleChildWrapper = styled.div ``;
403
413
  export const CollapsibleObjectFieldWrapper = ObjectFieldWrapper;
404
414
  export const CollapsibleObjectFieldTitle = ObjectFieldTitle;
405
- export const SearchableSelectContainer = styled(MultiSelectContainer) ``;
415
+ export const SearchableSelectContainer = styled(MultiSelectSearchContainer) ``;
406
416
  export const SearchDropdownContainer = styled(MultiSelectOptionList) `
407
417
  overflow-y: auto;
408
418
  overflow-x: auto;
409
- max-width: 16rem;
410
419
  max-height: 6rem;
411
420
  white-space: nowrap;
412
421
  `;
@@ -426,7 +435,24 @@ export const SearchNotFound = styled(MultiSelectOptionListItem) `
426
435
  }
427
436
  `;
428
437
  export const SearchInput = styled(FieldInput) `
438
+ width: 100%;
439
+ padding-right: 1.5em;
429
440
  text-overflow: ellipsis;
430
441
  `;
431
- export const SearchClearButton = styled.button ``;
432
- export const SearchInputContainer = styled.div ``;
442
+ export const SearchClearButton = styled.span `
443
+ cursor: pointer;
444
+ position: absolute;
445
+ right: 0.3em;
446
+ bottom: 0;
447
+ top: 0;
448
+ margin: auto;
449
+ background-color: #ccc;
450
+ border-radius: 50%;
451
+ line-height: 1em;
452
+ min-width: 1em;
453
+ height: 1em;
454
+ text-align: center;
455
+ `;
456
+ export const SearchInputContainer = styled.div `
457
+ display: flex;
458
+ `;
@@ -69,6 +69,125 @@ const simpleExampleSchema = {
69
69
  simpleArray: { type: "array", title: "Simple array" },
70
70
  },
71
71
  };
72
+ const dependenciesExampleSchema = {
73
+ properties: {
74
+ dependencyField: { type: "string" },
75
+ dependencySelect: {
76
+ type: "string",
77
+ oneOf: [
78
+ { const: "one", title: "One" },
79
+ { const: "two", title: "Two" },
80
+ ],
81
+ },
82
+ },
83
+ dependencies: {
84
+ dependencyField: {
85
+ properties: {
86
+ dependantField: { type: "string" },
87
+ },
88
+ required: ["dependantField"],
89
+ },
90
+ dependencySelect: {
91
+ oneOf: [
92
+ {
93
+ properties: {
94
+ dependencySelect: {
95
+ enum: ["one"],
96
+ },
97
+ },
98
+ },
99
+ {
100
+ properties: {
101
+ dependencySelect: {
102
+ enum: ["two"],
103
+ },
104
+ dependantOnTwo: {
105
+ type: "string",
106
+ },
107
+ },
108
+ required: ["dependantOnTwo"],
109
+ },
110
+ ],
111
+ },
112
+ },
113
+ };
114
+ const ifThenElseExampleSchema = {
115
+ type: "object",
116
+ properties: {
117
+ allOfBool1: { type: "boolean" },
118
+ allOfBool2: { type: "boolean" },
119
+ },
120
+ allOf: [
121
+ {
122
+ if: {
123
+ properties: {
124
+ allOfBool1: { const: true },
125
+ },
126
+ },
127
+ then: {
128
+ properties: {
129
+ firstTrueRequiredField: { type: "string" },
130
+ },
131
+ required: ["firstTrueRequiredField"],
132
+ },
133
+ else: {
134
+ properties: {
135
+ firstFalseRequiredField: { type: "string" },
136
+ },
137
+ required: ["firstFalseRequiredField"],
138
+ },
139
+ },
140
+ {
141
+ if: {
142
+ properties: {
143
+ allOfBool1: { const: false },
144
+ allOfBool2: { const: false },
145
+ },
146
+ },
147
+ then: {
148
+ properties: {
149
+ bothFalseRequiredField: { type: "string" },
150
+ },
151
+ required: ["bothFalseRequiredField"],
152
+ },
153
+ else: {
154
+ properties: {
155
+ oneOrMoreTrueRequiredField: { type: "string" },
156
+ },
157
+ required: ["oneOrMoreTrueRequiredField"],
158
+ },
159
+ },
160
+ ],
161
+ };
162
+ const nestedRequiredConditionalExampleSchema = {
163
+ type: "object",
164
+ properties: {
165
+ textField: { type: "string" },
166
+ checkbox: { type: "boolean" },
167
+ },
168
+ allOf: [
169
+ {
170
+ if: { properties: { checkbox: { const: true } } },
171
+ then: {
172
+ properties: {
173
+ myObject: {
174
+ type: "object",
175
+ properties: {
176
+ foo: { type: "string" },
177
+ nestedObject: {
178
+ type: "object",
179
+ properties: { nestedFoo: { type: "string" } },
180
+ required: ["nestedFoo"],
181
+ },
182
+ },
183
+ required: ["foo"],
184
+ },
185
+ },
186
+ required: ["myObject"],
187
+ },
188
+ },
189
+ ],
190
+ };
72
191
  describe("Create Validation Schema", () => {
73
192
  it("creates yup validationSchema based on given JSON schema", () => __awaiter(void 0, void 0, void 0, function* () {
74
193
  const result = createValidationSchema(exampleSchema);
@@ -133,4 +252,130 @@ describe("Create Validation Schema", () => {
133
252
  simpleArray: "Hello :)",
134
253
  }, { strict: true })).rejects.toThrow('simpleArray must be a `array` type, but the final value was: `"Hello :)"`.');
135
254
  }));
255
+ describe("Conditionals", () => {
256
+ it("generates validation for dependencies without oneOf", () => {
257
+ const result = createValidationSchema(dependenciesExampleSchema);
258
+ const invalid = result.isValidSync({
259
+ dependencyField: "Hello",
260
+ });
261
+ const valid = result.isValidSync({
262
+ dependencyField: "Hello",
263
+ dependantField: "Hi",
264
+ });
265
+ expect(invalid).toEqual(false);
266
+ expect(valid).toEqual(true);
267
+ });
268
+ it("generates validation for dependencies with oneOf", () => {
269
+ const result = createValidationSchema(dependenciesExampleSchema);
270
+ const noDependantFields = result.isValidSync({
271
+ dependencySelect: "one",
272
+ });
273
+ const invalidWithDependantFields = result.isValidSync({
274
+ dependencySelect: "two",
275
+ });
276
+ const validWithDependantFields = result.isValidSync({
277
+ dependencySelect: "two",
278
+ dependantOnTwo: "value",
279
+ });
280
+ expect(noDependantFields).toEqual(true);
281
+ expect(invalidWithDependantFields).toEqual(false);
282
+ expect(validWithDependantFields).toEqual(true);
283
+ });
284
+ it("generates validation for if-then-else", () => {
285
+ const result = createValidationSchema(ifThenElseExampleSchema);
286
+ const bothFalseInvalid1 = result.isValidSync({
287
+ allOfBool1: false,
288
+ allOfBool2: false,
289
+ });
290
+ const bothFalseInvalid2 = result.isValidSync({
291
+ allOfBool1: false,
292
+ allOfBool2: false,
293
+ firstFalseRequiredField: ".",
294
+ });
295
+ const bothFalseInvalid3 = result.isValidSync({
296
+ allOfBool1: false,
297
+ allOfBool2: false,
298
+ bothFalseRequiredField: ".",
299
+ });
300
+ const bothFalseValid = result.isValidSync({
301
+ allOfBool1: false,
302
+ allOfBool2: false,
303
+ firstFalseRequiredField: ".",
304
+ bothFalseRequiredField: ".",
305
+ });
306
+ const firstFalseInvalid1 = result.isValidSync({
307
+ allOfBool1: false,
308
+ allOfBool2: true,
309
+ });
310
+ const firstFalseInvalid2 = result.isValidSync({
311
+ allOfBool1: false,
312
+ allOfBool2: true,
313
+ firstFalseRequiredField: ".",
314
+ });
315
+ const firstFalseInvalid3 = result.isValidSync({
316
+ allOfBool1: false,
317
+ allOfBool2: true,
318
+ oneOrMoreTrueRequiredField: ".",
319
+ });
320
+ const firstFalseValid = result.isValidSync({
321
+ allOfBool1: false,
322
+ allOfBool2: true,
323
+ firstFalseRequiredField: ".",
324
+ oneOrMoreTrueRequiredField: ".",
325
+ });
326
+ const firstTrueInvalid1 = result.isValidSync({
327
+ allOfBool1: true,
328
+ allOfBool2: false,
329
+ });
330
+ const firstTrueInvalid2 = result.isValidSync({
331
+ allOfBool1: true,
332
+ allOfBool2: false,
333
+ firstTrueRequiredField: ".",
334
+ });
335
+ const firstTrueInvalid3 = result.isValidSync({
336
+ allOfBool1: true,
337
+ allOfBool2: false,
338
+ oneOrMoreTrueRequiredField: ".",
339
+ });
340
+ const firstTrueValid = result.isValidSync({
341
+ allOfBool1: true,
342
+ allOfBool2: false,
343
+ firstTrueRequiredField: ".",
344
+ oneOrMoreTrueRequiredField: ".",
345
+ });
346
+ expect(bothFalseInvalid1).toEqual(false);
347
+ expect(bothFalseInvalid2).toEqual(false);
348
+ expect(bothFalseInvalid3).toEqual(false);
349
+ expect(bothFalseValid).toEqual(true);
350
+ expect(firstFalseInvalid1).toEqual(false);
351
+ expect(firstFalseInvalid2).toEqual(false);
352
+ expect(firstFalseInvalid3).toEqual(false);
353
+ expect(firstFalseValid).toEqual(true);
354
+ expect(firstTrueInvalid1).toEqual(false);
355
+ expect(firstTrueInvalid2).toEqual(false);
356
+ expect(firstTrueInvalid3).toEqual(false);
357
+ expect(firstTrueValid).toEqual(true);
358
+ });
359
+ it("generates validation for conditional objects with nested validations", () => {
360
+ const result = createValidationSchema(nestedRequiredConditionalExampleSchema);
361
+ const isValidWhenConditionNotNet = result.isValidSync({
362
+ checkbox: false,
363
+ });
364
+ const isNotValidWhenConditionMetAndMissingRequiredFields = result.isValidSync({
365
+ checkbox: true,
366
+ });
367
+ const isValidWhenConditionMetAndRequiredFieldsAreFilled = result.isValidSync({
368
+ checkbox: true,
369
+ myObject: {
370
+ foo: ".",
371
+ nestedObject: {
372
+ nestedFoo: ".",
373
+ },
374
+ },
375
+ });
376
+ expect(isValidWhenConditionNotNet).toEqual(true);
377
+ expect(isNotValidWhenConditionMetAndMissingRequiredFields).toEqual(false);
378
+ expect(isValidWhenConditionMetAndRequiredFieldsAreFilled).toEqual(true);
379
+ });
380
+ });
136
381
  });
@@ -1,3 +1,8 @@
1
- import { JSONSchema7 } from "json-schema";
2
1
  import * as Yup from "yup";
3
- export declare function createValidationSchema(schema: JSONSchema7, definitions?: JSONSchema7): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
2
+ import { Schema } from "../types";
3
+ type Condition = {
4
+ dependentKeys: string[];
5
+ conditionFn: (...value: any[]) => boolean;
6
+ };
7
+ export declare function createValidationSchema(schema: Schema, definitions?: Schema, condition?: Condition): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
8
+ export {};
@@ -1,54 +1,163 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { solveReferences } from "./solveReferences";
2
13
  import * as Yup from "yup";
3
- export function createValidationSchema(schema, definitions = {}) {
14
+ import { resolveOneOfValue } from "./resolveSchema";
15
+ function getConditionalValidations(schema) {
16
+ const { dependencies, allOf } = schema;
17
+ let conditionalValidations = [];
18
+ if (dependencies) {
19
+ conditionalValidations = Object.entries(dependencies).reduce((validations, [key, dependency]) => {
20
+ if (typeof dependency === "boolean" || Array.isArray(dependency)) {
21
+ return validations;
22
+ }
23
+ const oneOf = dependency.oneOf;
24
+ if (Array.isArray(oneOf)) {
25
+ oneOf.forEach((oneOfSchema) => {
26
+ var _a, _b;
27
+ if (typeof oneOfSchema === "boolean") {
28
+ return;
29
+ }
30
+ const _c = (_a = oneOfSchema.properties) !== null && _a !== void 0 ? _a : {}, _d = key, propertyToCheck = _c[_d], propertiesSansKey = __rest(_c, [typeof _d === "symbol" ? _d : _d + ""]);
31
+ // Push validation only if there are additions in schema
32
+ if (((_b = Object.keys(propertiesSansKey)) === null || _b === void 0 ? void 0 : _b.length) ||
33
+ Object.keys(oneOfSchema).length > 1) {
34
+ validations.push({
35
+ schema: Object.assign(Object.assign({}, oneOfSchema), { properties: propertiesSansKey }),
36
+ condition: {
37
+ dependentKeys: [key],
38
+ conditionFn: (value) => value === resolveOneOfValue(propertyToCheck),
39
+ },
40
+ });
41
+ }
42
+ });
43
+ return validations;
44
+ }
45
+ // Depends on presence of `key` property value
46
+ validations.push({
47
+ schema: dependency,
48
+ condition: {
49
+ dependentKeys: [key],
50
+ conditionFn: (value) => value !== undefined && value !== "",
51
+ },
52
+ });
53
+ return validations;
54
+ }, conditionalValidations);
55
+ }
56
+ if (allOf) {
57
+ conditionalValidations = allOf.reduce((validations, condition) => {
58
+ if (typeof condition === "boolean") {
59
+ return validations;
60
+ }
61
+ const { if: expression, then, else: otherwise } = condition, schemaWithoutCondition = __rest(condition, ["if", "then", "else"]);
62
+ if (expression &&
63
+ typeof expression !== "boolean" &&
64
+ expression.properties) {
65
+ const keyValueMap = Object.entries(expression.properties).reduce((result, [key, value]) => {
66
+ if (typeof value !== "boolean") {
67
+ return Object.assign(Object.assign({}, result), { [key]: value.const });
68
+ }
69
+ return result;
70
+ }, {});
71
+ const dependentKeys = Object.keys(keyValueMap);
72
+ if (then && typeof then !== "boolean") {
73
+ validations.push({
74
+ schema: then,
75
+ condition: {
76
+ dependentKeys,
77
+ conditionFn: (...args) => args.every((value, index) => keyValueMap[dependentKeys[index]] === value),
78
+ },
79
+ });
80
+ }
81
+ if (otherwise && typeof otherwise !== "boolean") {
82
+ validations.push({
83
+ schema: otherwise,
84
+ condition: {
85
+ dependentKeys,
86
+ conditionFn: (...args) => args.some((value, index) => keyValueMap[dependentKeys[index]] !== value),
87
+ },
88
+ });
89
+ }
90
+ }
91
+ else {
92
+ validations.push({ schema: schemaWithoutCondition });
93
+ }
94
+ return validations;
95
+ }, conditionalValidations);
96
+ }
97
+ return conditionalValidations;
98
+ }
99
+ function getYupType(type) {
100
+ switch (type) {
101
+ case "object":
102
+ return Yup.object();
103
+ case "array":
104
+ return Yup.array();
105
+ case "boolean":
106
+ return Yup.boolean();
107
+ case "integer":
108
+ case "number":
109
+ return Yup.number();
110
+ case "null":
111
+ return Yup.string();
112
+ case "string":
113
+ return Yup.string();
114
+ default:
115
+ throw new Error("Unsupported schema type");
116
+ }
117
+ }
118
+ export function createValidationSchema(schema, definitions = {}, condition) {
4
119
  var _a;
5
- if (!schema || !schema.properties) {
120
+ if (!(schema === null || schema === void 0 ? void 0 : schema.properties)) {
6
121
  return Yup.object().shape({});
7
122
  }
8
123
  const schemaDefinitions = Object.assign(Object.assign({}, definitions), ((_a = schema.definitions) !== null && _a !== void 0 ? _a : {}));
9
124
  const requiredFields = schema.required;
10
125
  function getValue(schema, key) {
126
+ const type = schema.type;
127
+ let yupSchema;
11
128
  if (schema.properties) {
12
- return createValidationSchema(schema, schemaDefinitions);
129
+ yupSchema = createValidationSchema(schema, schemaDefinitions);
13
130
  }
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");
131
+ else {
132
+ yupSchema = getYupType(type);
35
133
  }
36
- if (requiredFields !== undefined && requiredFields.includes(key)) {
134
+ if (type === "null")
135
+ yupSchema.nullable();
136
+ if (requiredFields === null || requiredFields === void 0 ? void 0 : requiredFields.includes(key)) {
37
137
  if (type === "array") {
38
- yupType = yupType
138
+ yupSchema = yupSchema
39
139
  .min(1, "Required")
40
140
  .required("Required");
41
141
  }
42
142
  else {
43
- yupType = yupType["required"]("Required");
143
+ yupSchema = yupSchema["required"]("Required");
44
144
  }
45
145
  }
46
- return yupType;
146
+ return condition
147
+ ? getYupType(type).when(condition.dependentKeys, {
148
+ is: condition.conditionFn,
149
+ then: () => yupSchema,
150
+ })
151
+ : yupSchema;
47
152
  }
48
- return Object.keys(schema.properties).reduce((validationSchema, key) => {
153
+ const baseValidationSchema = Object.keys(schema.properties).reduce((validationSchema, key) => {
49
154
  var _a;
50
155
  const value = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[key];
51
156
  const solvedValue = solveReferences(value, schemaDefinitions, {}, {});
52
157
  return validationSchema.concat(Yup.object().shape({ [key]: getValue(solvedValue.schema, key) }));
53
158
  }, Yup.object().shape({}));
159
+ return getConditionalValidations(schema).reduce((validationSchema, conditionalValidation) => {
160
+ const solvedSchemas = solveReferences(conditionalValidation.schema, schemaDefinitions, {}, {});
161
+ return validationSchema.concat(createValidationSchema(solvedSchemas.schema, schemaDefinitions, conditionalValidation.condition));
162
+ }, baseValidationSchema);
54
163
  }
@@ -1,2 +1,4 @@
1
+ import { JSONSchema7Definition } from "json-schema";
1
2
  import type { Schema } from "../types";
3
+ export declare function resolveOneOfValue(value: JSONSchema7Definition): import("json-schema").JSONSchema7Type | undefined;
2
4
  export declare const resolveSchema: ({ allOf, dependencies, ...schema }: Schema, value: Record<string, any>) => [Schema, string[]];
@@ -19,7 +19,7 @@ const resolveEquality = (value, expression) => {
19
19
  };
20
20
  return deepOverlap(expression, newObject);
21
21
  };
22
- function resolveOneOfValue(value) {
22
+ export function resolveOneOfValue(value) {
23
23
  var _a;
24
24
  if (typeof value !== "boolean" && ((_a = value.enum) === null || _a === void 0 ? void 0 : _a[0]) !== undefined) {
25
25
  return value.enum[0];
@@ -31,14 +31,18 @@ function resolveOneOfValue(value) {
31
31
  return undefined;
32
32
  }
33
33
  const resolveSelectedOneOfValue = (key, value, oneOf) => {
34
- const update = oneOf.findIndex((one) => resolveOneOfValue(one.properties[key]) === value[key]);
35
- const manipulatedUpdateValue = Object.keys(oneOf[update].properties)
34
+ var _a, _b;
35
+ const update = ((_a = oneOf.find((one) => typeof one !== "boolean" && (one === null || one === void 0 ? void 0 : one.properties)
36
+ ? resolveOneOfValue(one.properties[key]) === value[key]
37
+ : false)) !== null && _a !== void 0 ? _a : {});
38
+ const manipulatedUpdateValue = Object.keys((_b = update.properties) !== null && _b !== void 0 ? _b : {})
36
39
  .filter((objKey) => objKey !== key)
37
40
  .reduce((newObj, innerKey) => {
38
- newObj[innerKey] = oneOf[update].properties[innerKey];
41
+ var _a;
42
+ newObj[innerKey] = (_a = update.properties) === null || _a === void 0 ? void 0 : _a[innerKey];
39
43
  return newObj;
40
44
  }, {});
41
- return { properties: manipulatedUpdateValue };
45
+ return Object.assign(Object.assign({}, update), { properties: manipulatedUpdateValue });
42
46
  };
43
47
  export const resolveSchema = (_a, value) => {
44
48
  var _b, _c;
@@ -48,7 +52,10 @@ export const resolveSchema = (_a, value) => {
48
52
  let valuesToRemoveAllOf = [];
49
53
  if (dependencies) {
50
54
  schemaCopy = Object.entries(dependencies).reduce((schema, [key, dependency]) => {
51
- if (value[key] !== undefined && value[key] !== "") {
55
+ if (value[key] !== undefined &&
56
+ value[key] !== "" &&
57
+ typeof dependency !== "boolean" &&
58
+ !Array.isArray(dependency)) {
52
59
  const oneOf = dependency.oneOf;
53
60
  if (Array.isArray(oneOf) && oneOf !== undefined) {
54
61
  const selectedOneOf = resolveSelectedOneOfValue(key, value, oneOf);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [