@remoteoss/json-schema-form 0.1.0-dev.20230517211625
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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/index.cjs +1205 -0
- package/dist/index.js +1174 -0
- package/dist/standalone.js +12578 -0
- package/package.json +78 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
1
|
+
|
|
2
|
+
/*!
|
|
3
|
+
Copyright (c) 2023 Remote Technology, Inc.
|
|
4
|
+
NPM Package: @remoteoss/json-schema-form@0.1.0-dev.20230517211625
|
|
5
|
+
Generated: Wed, 17 May 2023 21:17:44 GMT
|
|
6
|
+
|
|
7
|
+
MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2023 Remote Technology, Inc.
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// src/createHeadlessForm.js
|
|
32
|
+
import get3 from "lodash/get";
|
|
33
|
+
import isNil3 from "lodash/isNil";
|
|
34
|
+
import omit3 from "lodash/omit";
|
|
35
|
+
import omitBy2 from "lodash/omitBy";
|
|
36
|
+
import pick2 from "lodash/pick";
|
|
37
|
+
import size from "lodash/size";
|
|
38
|
+
|
|
39
|
+
// src/calculateConditionalProperties.js
|
|
40
|
+
import merge2 from "lodash/merge";
|
|
41
|
+
import omit2 from "lodash/omit";
|
|
42
|
+
|
|
43
|
+
// src/helpers.js
|
|
44
|
+
import get2 from "lodash/get";
|
|
45
|
+
import isNil from "lodash/isNil";
|
|
46
|
+
import omit from "lodash/omit";
|
|
47
|
+
import omitBy from "lodash/omitBy";
|
|
48
|
+
import set from "lodash/set";
|
|
49
|
+
import { lazy } from "yup";
|
|
50
|
+
|
|
51
|
+
// src/internals/helpers.js
|
|
52
|
+
import merge from "lodash/fp/merge";
|
|
53
|
+
import get from "lodash/get";
|
|
54
|
+
import isEmpty from "lodash/isEmpty";
|
|
55
|
+
import isFunction from "lodash/isFunction";
|
|
56
|
+
function pickXKey(node, key) {
|
|
57
|
+
const deprecatedKeys = ["presentation", "errorMessage"];
|
|
58
|
+
return get(node, `x-jsf-${key}`, deprecatedKeys.includes(key) ? node?.[key] : void 0);
|
|
59
|
+
}
|
|
60
|
+
function getFieldDescription(node, customProperties = {}) {
|
|
61
|
+
const nodeDescription = node?.description ? {
|
|
62
|
+
description: node.description
|
|
63
|
+
} : {};
|
|
64
|
+
const customDescription = customProperties?.description ? {
|
|
65
|
+
description: isFunction(customProperties.description) ? customProperties.description(node?.description, {
|
|
66
|
+
...node,
|
|
67
|
+
...customProperties
|
|
68
|
+
}) : customProperties.description
|
|
69
|
+
} : {};
|
|
70
|
+
const nodePresentation = pickXKey(node, "presentation");
|
|
71
|
+
const presentation = !isEmpty(nodePresentation) && {
|
|
72
|
+
presentation: { ...nodePresentation, ...customDescription }
|
|
73
|
+
};
|
|
74
|
+
return merge(nodeDescription, { ...customDescription, ...presentation });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/internals/fields.js
|
|
78
|
+
var jsonTypes = {
|
|
79
|
+
STRING: "string",
|
|
80
|
+
NUMBER: "number",
|
|
81
|
+
INTEGER: "integer",
|
|
82
|
+
OBJECT: "object",
|
|
83
|
+
ARRAY: "array",
|
|
84
|
+
BOOLEAN: "boolean",
|
|
85
|
+
NULL: "null"
|
|
86
|
+
};
|
|
87
|
+
var supportedTypes = {
|
|
88
|
+
TEXT: "text",
|
|
89
|
+
NUMBER: "number",
|
|
90
|
+
SELECT: "select",
|
|
91
|
+
FILE: "file",
|
|
92
|
+
RADIO: "radio",
|
|
93
|
+
GROUP_ARRAY: "group-array",
|
|
94
|
+
EMAIL: "email",
|
|
95
|
+
DATE: "date",
|
|
96
|
+
CHECKBOX: "checkbox",
|
|
97
|
+
FIELDSET: "fieldset"
|
|
98
|
+
};
|
|
99
|
+
var jsonTypeToInputType = {
|
|
100
|
+
[jsonTypes.STRING]: ({ oneOf, format }) => {
|
|
101
|
+
if (format === "email")
|
|
102
|
+
return supportedTypes.EMAIL;
|
|
103
|
+
if (format === "date")
|
|
104
|
+
return supportedTypes.DATE;
|
|
105
|
+
if (format === "data-url")
|
|
106
|
+
return supportedTypes.FILE;
|
|
107
|
+
if (oneOf)
|
|
108
|
+
return supportedTypes.RADIO;
|
|
109
|
+
return supportedTypes.TEXT;
|
|
110
|
+
},
|
|
111
|
+
[jsonTypes.NUMBER]: () => supportedTypes.NUMBER,
|
|
112
|
+
[jsonTypes.INTEGER]: () => supportedTypes.NUMBER,
|
|
113
|
+
[jsonTypes.OBJECT]: () => supportedTypes.FIELDSET,
|
|
114
|
+
[jsonTypes.ARRAY]: ({ items }) => {
|
|
115
|
+
if (items.properties)
|
|
116
|
+
return supportedTypes.GROUP_ARRAY;
|
|
117
|
+
return supportedTypes.SELECT;
|
|
118
|
+
},
|
|
119
|
+
[jsonTypes.BOOLEAN]: () => supportedTypes.CHECKBOX
|
|
120
|
+
};
|
|
121
|
+
function getInputType(fieldProperties, strictInputType, name) {
|
|
122
|
+
const presentation = pickXKey(fieldProperties, "presentation") ?? {};
|
|
123
|
+
const presentationInputType = presentation?.inputType;
|
|
124
|
+
if (presentationInputType) {
|
|
125
|
+
return presentationInputType;
|
|
126
|
+
}
|
|
127
|
+
if (strictInputType) {
|
|
128
|
+
throw Error(`Strict error: Missing inputType to field "${name || fieldProperties.title}".
|
|
129
|
+
You can fix the json schema or skip this error by calling createHeadlessForm(schema, { strictInputType: false })`);
|
|
130
|
+
}
|
|
131
|
+
if (!fieldProperties.type) {
|
|
132
|
+
if (fieldProperties.items?.properties) {
|
|
133
|
+
return supportedTypes.GROUP_ARRAY;
|
|
134
|
+
}
|
|
135
|
+
if (fieldProperties.properties) {
|
|
136
|
+
return supportedTypes.SELECT;
|
|
137
|
+
}
|
|
138
|
+
return jsonTypeToInputType[jsonTypes.STRING](fieldProperties);
|
|
139
|
+
}
|
|
140
|
+
return jsonTypeToInputType[fieldProperties.type]?.(fieldProperties);
|
|
141
|
+
}
|
|
142
|
+
function _composeFieldFile({ name, label, description, accept, required = true, ...attrs }) {
|
|
143
|
+
return {
|
|
144
|
+
type: supportedTypes.FILE,
|
|
145
|
+
name,
|
|
146
|
+
label,
|
|
147
|
+
description,
|
|
148
|
+
required,
|
|
149
|
+
accept,
|
|
150
|
+
...attrs
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function _composeFieldText({ name, label, description, required = true, ...attrs }) {
|
|
154
|
+
return {
|
|
155
|
+
type: supportedTypes.TEXT,
|
|
156
|
+
name,
|
|
157
|
+
label,
|
|
158
|
+
description,
|
|
159
|
+
required,
|
|
160
|
+
...attrs
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function _composeFieldEmail({ name, label, required = true, ...attrs }) {
|
|
164
|
+
return {
|
|
165
|
+
type: supportedTypes.EMAIL,
|
|
166
|
+
name,
|
|
167
|
+
label,
|
|
168
|
+
required,
|
|
169
|
+
...attrs
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function _composeFieldNumber({
|
|
173
|
+
name,
|
|
174
|
+
label,
|
|
175
|
+
percentage = false,
|
|
176
|
+
required = true,
|
|
177
|
+
minimum,
|
|
178
|
+
maximum,
|
|
179
|
+
...attrs
|
|
180
|
+
}) {
|
|
181
|
+
let minValue = minimum;
|
|
182
|
+
let maxValue = maximum;
|
|
183
|
+
if (percentage) {
|
|
184
|
+
minValue = minValue ?? 0;
|
|
185
|
+
maxValue = maxValue ?? 100;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
type: supportedTypes.NUMBER,
|
|
189
|
+
name,
|
|
190
|
+
label,
|
|
191
|
+
percentage,
|
|
192
|
+
required,
|
|
193
|
+
minimum: minValue,
|
|
194
|
+
maximum: maxValue,
|
|
195
|
+
...attrs
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function _composeFieldDate({ name, label, required = true, ...attrs }) {
|
|
199
|
+
return {
|
|
200
|
+
type: supportedTypes.DATE,
|
|
201
|
+
name,
|
|
202
|
+
label,
|
|
203
|
+
required,
|
|
204
|
+
...attrs
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function _composeFieldRadio({ name, label, options, required = true, ...attrs }) {
|
|
208
|
+
return {
|
|
209
|
+
type: supportedTypes.RADIO,
|
|
210
|
+
name,
|
|
211
|
+
label,
|
|
212
|
+
options,
|
|
213
|
+
required,
|
|
214
|
+
...attrs
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function _composeFieldSelect({ name, label, options, required = true, ...attrs }) {
|
|
218
|
+
return {
|
|
219
|
+
type: supportedTypes.SELECT,
|
|
220
|
+
name,
|
|
221
|
+
label,
|
|
222
|
+
options,
|
|
223
|
+
required,
|
|
224
|
+
...attrs
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function _composeNthFieldGroup({ name, label, required, nthFieldGroup, ...attrs }) {
|
|
228
|
+
return [
|
|
229
|
+
{
|
|
230
|
+
...nthFieldGroup,
|
|
231
|
+
type: supportedTypes.GROUP_ARRAY,
|
|
232
|
+
name,
|
|
233
|
+
label,
|
|
234
|
+
required,
|
|
235
|
+
...attrs
|
|
236
|
+
}
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
function _composeFieldCheckbox({
|
|
240
|
+
required = true,
|
|
241
|
+
name,
|
|
242
|
+
label,
|
|
243
|
+
description,
|
|
244
|
+
default: defaultValue,
|
|
245
|
+
checkboxValue,
|
|
246
|
+
...attrs
|
|
247
|
+
}) {
|
|
248
|
+
return {
|
|
249
|
+
type: supportedTypes.CHECKBOX,
|
|
250
|
+
required,
|
|
251
|
+
name,
|
|
252
|
+
label,
|
|
253
|
+
description,
|
|
254
|
+
checkboxValue,
|
|
255
|
+
...defaultValue && { default: defaultValue },
|
|
256
|
+
...attrs
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function _composeFieldset({ name, label, fields, variant, ...attrs }) {
|
|
260
|
+
return {
|
|
261
|
+
type: supportedTypes.FIELDSET,
|
|
262
|
+
name,
|
|
263
|
+
label,
|
|
264
|
+
fields,
|
|
265
|
+
variant,
|
|
266
|
+
...attrs
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
var _composeFieldArbitraryClosure = (inputType) => (attrs) => ({
|
|
270
|
+
type: inputType,
|
|
271
|
+
...attrs
|
|
272
|
+
});
|
|
273
|
+
var inputTypeMap = {
|
|
274
|
+
text: _composeFieldText,
|
|
275
|
+
select: _composeFieldSelect,
|
|
276
|
+
radio: _composeFieldRadio,
|
|
277
|
+
date: _composeFieldDate,
|
|
278
|
+
number: _composeFieldNumber,
|
|
279
|
+
"group-array": _composeNthFieldGroup,
|
|
280
|
+
fieldset: _composeFieldset,
|
|
281
|
+
file: _composeFieldFile,
|
|
282
|
+
email: _composeFieldEmail,
|
|
283
|
+
checkbox: _composeFieldCheckbox
|
|
284
|
+
};
|
|
285
|
+
function _composeFieldCustomClosure(defaultComposeFn) {
|
|
286
|
+
return ({ fieldCustomization, ...attrs }) => {
|
|
287
|
+
const { description, ...restFieldCustomization } = fieldCustomization;
|
|
288
|
+
const fieldDescription = getFieldDescription(attrs, fieldCustomization);
|
|
289
|
+
const { nthFieldGroup, ...restAttrs } = attrs;
|
|
290
|
+
const commonAttrs = {
|
|
291
|
+
...restAttrs,
|
|
292
|
+
...restFieldCustomization,
|
|
293
|
+
...fieldDescription
|
|
294
|
+
};
|
|
295
|
+
if (attrs.inputType === supportedTypes.GROUP_ARRAY) {
|
|
296
|
+
return [
|
|
297
|
+
{
|
|
298
|
+
...nthFieldGroup,
|
|
299
|
+
...commonAttrs
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
...defaultComposeFn(attrs),
|
|
305
|
+
...commonAttrs
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/utils.js
|
|
311
|
+
function convertDiskSizeFromTo(from, to) {
|
|
312
|
+
const units = ["bytes", "kb", "mb"];
|
|
313
|
+
return function convert(value) {
|
|
314
|
+
return value * Math.pow(1024, units.indexOf(from.toLowerCase())) / Math.pow(1024, units.indexOf(to.toLowerCase()));
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function containsHTML(str = "") {
|
|
318
|
+
return /<[a-z][\s\S]*>/i.test(str);
|
|
319
|
+
}
|
|
320
|
+
function wrapWithSpan(html, properties = {}) {
|
|
321
|
+
const attributes = Object.entries(properties).reduce((acc, [key, value]) => `${acc}${key}="${value}" `, "").trim();
|
|
322
|
+
return `<span ${attributes}>${html}</span>`;
|
|
323
|
+
}
|
|
324
|
+
function hasProperty(object2, propertyName) {
|
|
325
|
+
return Object.prototype.hasOwnProperty.call(object2, propertyName);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/yupSchema.js
|
|
329
|
+
import flow from "lodash/flow";
|
|
330
|
+
import noop from "lodash/noop";
|
|
331
|
+
import { randexp } from "randexp";
|
|
332
|
+
import { string, number, boolean, object, array } from "yup";
|
|
333
|
+
var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
|
|
334
|
+
var baseString = string().trim();
|
|
335
|
+
var todayDateHint = (/* @__PURE__ */ new Date()).toISOString().substring(0, 10);
|
|
336
|
+
var convertBytesToKB = convertDiskSizeFromTo("Bytes", "KB");
|
|
337
|
+
var convertKbBytesToMB = convertDiskSizeFromTo("KB", "MB");
|
|
338
|
+
var yupSchemas = {
|
|
339
|
+
text: string().trim().nullable(),
|
|
340
|
+
select: string().trim().nullable(),
|
|
341
|
+
radio: string().trim().nullable(),
|
|
342
|
+
date: string().nullable().trim().matches(
|
|
343
|
+
/(?:\d){4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9]|3[0-1])/,
|
|
344
|
+
`Must be a valid date in ${DEFAULT_DATE_FORMAT.toLocaleLowerCase()} format. e.g. ${todayDateHint}`
|
|
345
|
+
),
|
|
346
|
+
number: number().typeError("The value must be a number").nullable(),
|
|
347
|
+
file: array().nullable(),
|
|
348
|
+
email: string().trim().email("Please enter a valid email address").nullable(),
|
|
349
|
+
fieldset: object().nullable(),
|
|
350
|
+
checkbox: string().trim().nullable(),
|
|
351
|
+
checkboxBool: boolean(),
|
|
352
|
+
multiple: {
|
|
353
|
+
select: array().nullable(),
|
|
354
|
+
"group-array": array().nullable()
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
var yupSchemasToJsonTypes = {
|
|
358
|
+
string: yupSchemas.text,
|
|
359
|
+
number: yupSchemas.number,
|
|
360
|
+
integer: yupSchemas.number,
|
|
361
|
+
object: yupSchemas.fieldset,
|
|
362
|
+
array: yupSchemas.multiple.select,
|
|
363
|
+
boolean: yupSchemas.checkboxBool,
|
|
364
|
+
null: noop
|
|
365
|
+
};
|
|
366
|
+
function getRequiredErrorMessage(inputType, { inlineError, configError }) {
|
|
367
|
+
if (inlineError)
|
|
368
|
+
return inlineError;
|
|
369
|
+
if (configError)
|
|
370
|
+
return configError;
|
|
371
|
+
if (inputType === supportedTypes.CHECKBOX)
|
|
372
|
+
return "Please acknowledge this field";
|
|
373
|
+
return "Required field";
|
|
374
|
+
}
|
|
375
|
+
var getJsonTypeInArray = (jsonType) => Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
|
|
376
|
+
function buildYupSchema(field, config) {
|
|
377
|
+
const { inputType, jsonType: jsonTypeValue, errorMessage = {}, ...propertyFields } = field;
|
|
378
|
+
const isCheckboxBoolean = typeof propertyFields.checkboxValue === "boolean";
|
|
379
|
+
let baseSchema;
|
|
380
|
+
const jsonType = getJsonTypeInArray(jsonTypeValue);
|
|
381
|
+
const errorMessageFromConfig = config?.inputTypes?.[inputType]?.errorMessage || {};
|
|
382
|
+
if (propertyFields.multiple) {
|
|
383
|
+
baseSchema = yupSchemas.multiple[inputType] || yupSchemasToJsonTypes.array;
|
|
384
|
+
} else if (isCheckboxBoolean) {
|
|
385
|
+
baseSchema = yupSchemas.checkboxBool;
|
|
386
|
+
} else {
|
|
387
|
+
baseSchema = yupSchemas[inputType] || yupSchemasToJsonTypes[jsonType];
|
|
388
|
+
}
|
|
389
|
+
if (!baseSchema) {
|
|
390
|
+
return noop;
|
|
391
|
+
}
|
|
392
|
+
const randomPlaceholder = propertyFields.pattern && randexp(propertyFields.pattern);
|
|
393
|
+
const requiredMessage = getRequiredErrorMessage(inputType, {
|
|
394
|
+
inlineError: errorMessage.required,
|
|
395
|
+
configError: errorMessageFromConfig.required
|
|
396
|
+
});
|
|
397
|
+
function withRequired(yupSchema) {
|
|
398
|
+
if (isCheckboxBoolean) {
|
|
399
|
+
return yupSchema.oneOf([true], requiredMessage).required(requiredMessage);
|
|
400
|
+
}
|
|
401
|
+
return yupSchema.required(requiredMessage);
|
|
402
|
+
}
|
|
403
|
+
function withMin(yupSchema) {
|
|
404
|
+
return yupSchema.min(
|
|
405
|
+
propertyFields.minimum,
|
|
406
|
+
(message) => errorMessage.minimum ?? errorMessageFromConfig.minimum ?? `Must be greater or equal to ${message.min}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
function withMinLength(yupSchema) {
|
|
410
|
+
return yupSchema.min(
|
|
411
|
+
propertyFields.minLength,
|
|
412
|
+
(message) => errorMessage.minLength ?? errorMessageFromConfig.minLength ?? `Please insert at least ${message.min} characters`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
function withMax(yupSchema) {
|
|
416
|
+
return yupSchema.max(
|
|
417
|
+
propertyFields.maximum,
|
|
418
|
+
(message) => errorMessage.maximum ?? errorMessageFromConfig.maximum ?? `Must be smaller or equal to ${message.max}`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
function withMaxLength(yupSchema) {
|
|
422
|
+
return yupSchema.max(
|
|
423
|
+
propertyFields.maxLength,
|
|
424
|
+
(message) => errorMessage.maxLength ?? errorMessageFromConfig.maxLength ?? `Please insert up to ${message.max} characters`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
function withMatches(yupSchema) {
|
|
428
|
+
return yupSchema.matches(
|
|
429
|
+
propertyFields.pattern,
|
|
430
|
+
() => errorMessage.pattern ?? errorMessageFromConfig.pattern ?? `Must have a valid format. E.g. ${randomPlaceholder}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
function withMaxFileSize(yupSchema) {
|
|
434
|
+
return yupSchema.test(
|
|
435
|
+
"isValidFileSize",
|
|
436
|
+
errorMessage.maxFileSize ?? errorMessageFromConfig.maxFileSize ?? `File size too large. The limit is ${convertKbBytesToMB(propertyFields.maxFileSize)} MB.`,
|
|
437
|
+
(files) => !files?.some((file) => convertBytesToKB(file.size) > propertyFields.maxFileSize)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
function withFileFormat(yupSchema) {
|
|
441
|
+
return yupSchema.test(
|
|
442
|
+
"isSupportedFormat",
|
|
443
|
+
errorMessage.accept ?? errorMessageFromConfig.accept ?? `Unsupported file format. The acceptable formats are ${propertyFields.accept}.`,
|
|
444
|
+
(files) => files && files?.length > 0 ? files.some((file) => {
|
|
445
|
+
const fileType = file.name.split(".").pop();
|
|
446
|
+
return propertyFields.accept.includes(fileType.toLowerCase());
|
|
447
|
+
}) : true
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function withBaseSchema() {
|
|
451
|
+
const customErrorMsg = errorMessage.type || errorMessageFromConfig.type;
|
|
452
|
+
if (customErrorMsg) {
|
|
453
|
+
return baseSchema.typeError(customErrorMsg);
|
|
454
|
+
}
|
|
455
|
+
return baseSchema;
|
|
456
|
+
}
|
|
457
|
+
function buildFieldSetSchema(innerFields) {
|
|
458
|
+
const fieldSetShape = {};
|
|
459
|
+
innerFields.forEach((fieldSetfield) => {
|
|
460
|
+
if (fieldSetfield.fields) {
|
|
461
|
+
fieldSetShape[fieldSetfield.name] = object().shape(
|
|
462
|
+
buildFieldSetSchema(fieldSetfield.fields)
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
fieldSetShape[fieldSetfield.name] = buildYupSchema(
|
|
466
|
+
{
|
|
467
|
+
...fieldSetfield,
|
|
468
|
+
inputType: fieldSetfield.type
|
|
469
|
+
},
|
|
470
|
+
config
|
|
471
|
+
)();
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
return fieldSetShape;
|
|
475
|
+
}
|
|
476
|
+
function buildGroupArraySchema() {
|
|
477
|
+
return object().shape(
|
|
478
|
+
propertyFields.nthFieldGroup.fields().reduce(
|
|
479
|
+
(schema, groupArrayField) => ({
|
|
480
|
+
...schema,
|
|
481
|
+
[groupArrayField.name]: buildYupSchema(groupArrayField, config)()
|
|
482
|
+
}),
|
|
483
|
+
{}
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const validators = [withBaseSchema];
|
|
488
|
+
if (inputType === supportedTypes.GROUP_ARRAY) {
|
|
489
|
+
validators[0] = () => withBaseSchema().of(buildGroupArraySchema());
|
|
490
|
+
} else if (inputType === supportedTypes.FIELDSET) {
|
|
491
|
+
validators[0] = () => withBaseSchema().shape(buildFieldSetSchema(propertyFields.fields));
|
|
492
|
+
}
|
|
493
|
+
if (propertyFields.required) {
|
|
494
|
+
validators.push(withRequired);
|
|
495
|
+
}
|
|
496
|
+
if (typeof propertyFields.minimum !== "undefined") {
|
|
497
|
+
validators.push(withMin);
|
|
498
|
+
}
|
|
499
|
+
if (typeof propertyFields.minLength !== "undefined") {
|
|
500
|
+
validators.push(withMinLength);
|
|
501
|
+
}
|
|
502
|
+
if (propertyFields.maximum) {
|
|
503
|
+
validators.push(withMax);
|
|
504
|
+
}
|
|
505
|
+
if (propertyFields.maxLength) {
|
|
506
|
+
validators.push(withMaxLength);
|
|
507
|
+
}
|
|
508
|
+
if (propertyFields.pattern) {
|
|
509
|
+
validators.push(withMatches);
|
|
510
|
+
}
|
|
511
|
+
if (propertyFields.maxFileSize) {
|
|
512
|
+
validators.push(withMaxFileSize);
|
|
513
|
+
}
|
|
514
|
+
if (propertyFields.accept) {
|
|
515
|
+
validators.push(withFileFormat);
|
|
516
|
+
}
|
|
517
|
+
return flow(validators);
|
|
518
|
+
}
|
|
519
|
+
function getNoSortEdges(fields = []) {
|
|
520
|
+
return fields.reduce((list, field) => {
|
|
521
|
+
if (field.noSortEdges) {
|
|
522
|
+
list.push(field.name);
|
|
523
|
+
}
|
|
524
|
+
return list;
|
|
525
|
+
}, []);
|
|
526
|
+
}
|
|
527
|
+
function getSchema(fields = [], config) {
|
|
528
|
+
const newSchema = {};
|
|
529
|
+
fields.forEach((field) => {
|
|
530
|
+
if (field.schema) {
|
|
531
|
+
if (field.name) {
|
|
532
|
+
if (field.inputType === supportedTypes.FIELDSET) {
|
|
533
|
+
const fieldsetSchema = buildYupSchema(field, config)();
|
|
534
|
+
newSchema[field.name] = fieldsetSchema;
|
|
535
|
+
} else {
|
|
536
|
+
newSchema[field.name] = field.schema;
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
Object.assign(newSchema, getSchema(field.fields, config));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
return newSchema;
|
|
544
|
+
}
|
|
545
|
+
function buildCompleteYupSchema(fields, config) {
|
|
546
|
+
return object().shape(getSchema(fields, config), getNoSortEdges(fields));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/helpers.js
|
|
550
|
+
function hasType(type, typeName) {
|
|
551
|
+
return Array.isArray(type) ? type.includes(typeName) : type === typeName;
|
|
552
|
+
}
|
|
553
|
+
function getField(fieldName, fields) {
|
|
554
|
+
return fields.find(({ name }) => name === fieldName);
|
|
555
|
+
}
|
|
556
|
+
function validateFieldSchema(field, value) {
|
|
557
|
+
const validator = buildYupSchema(field);
|
|
558
|
+
return validator().isValidSync(value);
|
|
559
|
+
}
|
|
560
|
+
function compareFormValueWithSchemaValue(formValue, schemaValue) {
|
|
561
|
+
const currentPropertyValue = typeof schemaValue === "number" ? schemaValue : schemaValue || void 0;
|
|
562
|
+
return String(formValue) === String(currentPropertyValue);
|
|
563
|
+
}
|
|
564
|
+
function checkIfConditionMatches(node, formValues, formFields) {
|
|
565
|
+
return Object.keys(node.if.properties).every((name) => {
|
|
566
|
+
const currentProperty = node.if.properties[name];
|
|
567
|
+
const value = formValues[name];
|
|
568
|
+
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
569
|
+
value === null;
|
|
570
|
+
const hasIfExplicit = node.if.required?.includes(name);
|
|
571
|
+
if (hasEmptyValue && !hasIfExplicit) {
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
if (hasProperty(currentProperty, "const")) {
|
|
575
|
+
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
576
|
+
}
|
|
577
|
+
if (currentProperty.contains?.pattern) {
|
|
578
|
+
const formValue = value || [];
|
|
579
|
+
if (Array.isArray(formValue)) {
|
|
580
|
+
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
581
|
+
return (value || []).some((item) => pattern.test(item));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (currentProperty.enum) {
|
|
585
|
+
return currentProperty.enum.includes(value);
|
|
586
|
+
}
|
|
587
|
+
const { inputType } = getField(name, formFields);
|
|
588
|
+
return validateFieldSchema({ ...currentProperty, inputType, required: true }, value);
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function isFieldFilled(fieldValue) {
|
|
592
|
+
return Array.isArray(fieldValue) ? fieldValue.length > 0 : !!fieldValue;
|
|
593
|
+
}
|
|
594
|
+
function findFirstAnyOfMatch(nodes, formValues) {
|
|
595
|
+
return nodes.find(
|
|
596
|
+
({ required }) => required?.some((fieldName) => isFieldFilled(formValues[fieldName]))
|
|
597
|
+
) || nodes[0];
|
|
598
|
+
}
|
|
599
|
+
function getPrefillSubFieldValues(field, defaultValues, parentFieldKeyPath) {
|
|
600
|
+
let initialValue = defaultValues ?? {};
|
|
601
|
+
let fieldKeyPath = field.name;
|
|
602
|
+
if (parentFieldKeyPath) {
|
|
603
|
+
fieldKeyPath = fieldKeyPath ? `${parentFieldKeyPath}.${fieldKeyPath}` : parentFieldKeyPath;
|
|
604
|
+
}
|
|
605
|
+
const subFields = field.fields;
|
|
606
|
+
if (Array.isArray(subFields)) {
|
|
607
|
+
const subFieldValues = {};
|
|
608
|
+
subFields.forEach((subField) => {
|
|
609
|
+
Object.assign(
|
|
610
|
+
subFieldValues,
|
|
611
|
+
getPrefillSubFieldValues(subField, initialValue[field.name], fieldKeyPath)
|
|
612
|
+
);
|
|
613
|
+
});
|
|
614
|
+
if (field.inputType === supportedTypes.FIELDSET && field.valueGroupingDisabled) {
|
|
615
|
+
Object.assign(initialValue, subFieldValues);
|
|
616
|
+
} else {
|
|
617
|
+
initialValue[field.name] = subFieldValues;
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
initialValue = getPrefillValues([field], initialValue);
|
|
621
|
+
}
|
|
622
|
+
return initialValue;
|
|
623
|
+
}
|
|
624
|
+
function getPrefillValues(fields, initialValues = {}) {
|
|
625
|
+
fields.forEach((field) => {
|
|
626
|
+
const fieldName = field.name;
|
|
627
|
+
switch (field.type) {
|
|
628
|
+
case supportedTypes.GROUP_ARRAY: {
|
|
629
|
+
initialValues[fieldName] = initialValues[fieldName]?.map(
|
|
630
|
+
(subFieldValues) => getPrefillValues(field.fields(), subFieldValues)
|
|
631
|
+
);
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
case supportedTypes.FIELDSET: {
|
|
635
|
+
const subFieldValues = getPrefillSubFieldValues(field, initialValues);
|
|
636
|
+
Object.assign(initialValues, subFieldValues);
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
default: {
|
|
640
|
+
if (!initialValues[fieldName]) {
|
|
641
|
+
initialValues[fieldName] = field.default;
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
return initialValues;
|
|
648
|
+
}
|
|
649
|
+
function updateField(field, requiredFields, node, formValues) {
|
|
650
|
+
if (!field) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const fieldIsRequired = requiredFields.has(field.name);
|
|
654
|
+
if (node.properties && hasProperty(node.properties, field.name)) {
|
|
655
|
+
field.isVisible = !!node.properties[field.name];
|
|
656
|
+
}
|
|
657
|
+
if (fieldIsRequired) {
|
|
658
|
+
field.isVisible = true;
|
|
659
|
+
}
|
|
660
|
+
const updateValues = (fieldValues) => Object.entries(fieldValues).forEach(([key, value]) => {
|
|
661
|
+
field[key] = typeof value === "function" ? value() : value;
|
|
662
|
+
if (key === "value") {
|
|
663
|
+
const readOnlyPropertyWasUpdated = typeof fieldValues.readOnly !== "undefined";
|
|
664
|
+
const isReadonlyByDefault = field.readOnly;
|
|
665
|
+
const isReadonly = readOnlyPropertyWasUpdated ? fieldValues.readOnly : isReadonlyByDefault;
|
|
666
|
+
if (!isReadonly && (value === null || field.inputType === "checkbox")) {
|
|
667
|
+
field.value = void 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
if (field.calculateConditionalProperties) {
|
|
672
|
+
const newFieldValues = field.calculateConditionalProperties(fieldIsRequired, node);
|
|
673
|
+
updateValues(newFieldValues);
|
|
674
|
+
}
|
|
675
|
+
if (field.calculateCustomValidationProperties) {
|
|
676
|
+
const newFieldValues = field.calculateCustomValidationProperties(
|
|
677
|
+
fieldIsRequired,
|
|
678
|
+
node,
|
|
679
|
+
formValues
|
|
680
|
+
);
|
|
681
|
+
updateValues(newFieldValues);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */ new Set()) {
|
|
685
|
+
const requiredFields = new Set(accRequired);
|
|
686
|
+
Object.keys(node.properties ?? []).forEach((fieldName) => {
|
|
687
|
+
const field = getField(fieldName, formFields);
|
|
688
|
+
updateField(field, requiredFields, node, formValues);
|
|
689
|
+
});
|
|
690
|
+
node.required?.forEach((fieldName) => {
|
|
691
|
+
requiredFields.add(fieldName);
|
|
692
|
+
updateField(getField(fieldName, formFields), requiredFields, node, formValues);
|
|
693
|
+
});
|
|
694
|
+
if (node.if) {
|
|
695
|
+
const matchesCondition = checkIfConditionMatches(node, formValues, formFields);
|
|
696
|
+
if (matchesCondition && node.then) {
|
|
697
|
+
const { required: branchRequired } = processNode(
|
|
698
|
+
node.then,
|
|
699
|
+
formValues,
|
|
700
|
+
formFields,
|
|
701
|
+
requiredFields
|
|
702
|
+
);
|
|
703
|
+
branchRequired.forEach((field) => requiredFields.add(field));
|
|
704
|
+
} else if (node.else) {
|
|
705
|
+
const { required: branchRequired } = processNode(
|
|
706
|
+
node.else,
|
|
707
|
+
formValues,
|
|
708
|
+
formFields,
|
|
709
|
+
requiredFields
|
|
710
|
+
);
|
|
711
|
+
branchRequired.forEach((field) => requiredFields.add(field));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (node.anyOf) {
|
|
715
|
+
const firstMatchOfAnyOf = findFirstAnyOfMatch(node.anyOf, formValues);
|
|
716
|
+
firstMatchOfAnyOf.required?.forEach((fieldName) => {
|
|
717
|
+
requiredFields.add(fieldName);
|
|
718
|
+
});
|
|
719
|
+
node.anyOf.forEach(({ required = [] }) => {
|
|
720
|
+
required.forEach((fieldName) => {
|
|
721
|
+
const field = getField(fieldName, formFields);
|
|
722
|
+
updateField(field, requiredFields, node, formValues);
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (node.allOf) {
|
|
727
|
+
node.allOf.map((allOfNode) => processNode(allOfNode, formValues, formFields, requiredFields)).forEach(({ required: allOfItemRequired }) => {
|
|
728
|
+
allOfItemRequired.forEach(requiredFields.add, requiredFields);
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
if (node.properties) {
|
|
732
|
+
Object.entries(node.properties).forEach(([name, nestedNode]) => {
|
|
733
|
+
const inputType = getInputType(nestedNode);
|
|
734
|
+
if (inputType === supportedTypes.FIELDSET) {
|
|
735
|
+
processNode(nestedNode, formValues[name] || {}, getField(name, formFields).fields);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
required: requiredFields
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function clearValuesIfNotVisible(fields, formValues) {
|
|
744
|
+
fields.forEach(({ isVisible = true, name, inputType, fields: nestedFields }) => {
|
|
745
|
+
if (!isVisible) {
|
|
746
|
+
formValues[name] = null;
|
|
747
|
+
}
|
|
748
|
+
if (inputType === supportedTypes.FIELDSET && nestedFields && formValues[name]) {
|
|
749
|
+
clearValuesIfNotVisible(nestedFields, formValues[name]);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function updateFieldsProperties(fields, formValues, jsonSchema) {
|
|
754
|
+
if (!jsonSchema?.properties) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
processNode(jsonSchema, formValues, fields);
|
|
758
|
+
clearValuesIfNotVisible(fields, formValues);
|
|
759
|
+
}
|
|
760
|
+
var notNullOption = (opt) => opt.const !== null;
|
|
761
|
+
function getFieldOptions(node, presentation) {
|
|
762
|
+
function convertToOptions(nodeOptions) {
|
|
763
|
+
return nodeOptions.filter(notNullOption).map(({ title, const: cons, ...item }) => ({
|
|
764
|
+
label: title,
|
|
765
|
+
value: cons,
|
|
766
|
+
...item
|
|
767
|
+
}));
|
|
768
|
+
}
|
|
769
|
+
if (presentation.options) {
|
|
770
|
+
return presentation.options;
|
|
771
|
+
}
|
|
772
|
+
if (node.oneOf) {
|
|
773
|
+
return convertToOptions(node.oneOf);
|
|
774
|
+
}
|
|
775
|
+
if (node.items?.anyOf) {
|
|
776
|
+
return convertToOptions(node.items.anyOf);
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function extractParametersFromNode(schemaNode) {
|
|
781
|
+
if (!schemaNode) {
|
|
782
|
+
return {};
|
|
783
|
+
}
|
|
784
|
+
const presentation = pickXKey(schemaNode, "presentation") ?? {};
|
|
785
|
+
const errorMessage = pickXKey(schemaNode, "errorMessage") ?? {};
|
|
786
|
+
const node = omit(schemaNode, ["x-jsf-presentation", "presentation"]);
|
|
787
|
+
const description = presentation?.description || node.description;
|
|
788
|
+
const statementDescription = containsHTML(presentation.statement?.description) ? wrapWithSpan(presentation.statement.description, { class: "jsf-statement" }) : presentation.statement?.description;
|
|
789
|
+
return omitBy(
|
|
790
|
+
{
|
|
791
|
+
label: node.title,
|
|
792
|
+
readOnly: node.readOnly,
|
|
793
|
+
...node.deprecated && {
|
|
794
|
+
deprecated: {
|
|
795
|
+
description: presentation.deprecated?.description
|
|
796
|
+
// @TODO/@IDEA These might be useful down the road :thinking:
|
|
797
|
+
// version: presentation.deprecated.version, // e.g. "1.1"
|
|
798
|
+
// replacement: presentation.deprecated.replacement, // e.g. ['contract_duration_type']
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
pattern: node.pattern,
|
|
802
|
+
options: getFieldOptions(node, presentation),
|
|
803
|
+
items: node.items,
|
|
804
|
+
maxLength: node.maxLength,
|
|
805
|
+
minLength: node.minLength,
|
|
806
|
+
minimum: node.minimum,
|
|
807
|
+
maximum: node.maximum,
|
|
808
|
+
maxFileSize: node.maxFileSize,
|
|
809
|
+
// @deprecated in favor of presentation.maxFileSize
|
|
810
|
+
default: node.default,
|
|
811
|
+
// Checkboxes conditions
|
|
812
|
+
// — For checkboxes that only accept one value (string)
|
|
813
|
+
...presentation?.inputType === "checkbox" && { checkboxValue: node.const },
|
|
814
|
+
// - For checkboxes with boolean value
|
|
815
|
+
...presentation?.inputType === "checkbox" && node.type === "boolean" && {
|
|
816
|
+
// true is what describes this checkbox as a boolean, regardless if its required or not
|
|
817
|
+
checkboxValue: true
|
|
818
|
+
},
|
|
819
|
+
...hasType(node.type, "array") && {
|
|
820
|
+
multiple: true
|
|
821
|
+
},
|
|
822
|
+
// Handle [name].presentation
|
|
823
|
+
...presentation,
|
|
824
|
+
description: containsHTML(description) ? wrapWithSpan(description, {
|
|
825
|
+
class: "jsf-description"
|
|
826
|
+
}) : description,
|
|
827
|
+
extra: containsHTML(presentation.extra) ? wrapWithSpan(presentation.extra, { class: "jsf-extra" }) : presentation.extra,
|
|
828
|
+
statement: presentation.statement && {
|
|
829
|
+
...presentation.statement,
|
|
830
|
+
description: statementDescription
|
|
831
|
+
},
|
|
832
|
+
// Support scoped conditions (fieldsets)
|
|
833
|
+
if: node.if,
|
|
834
|
+
then: node.then,
|
|
835
|
+
else: node.else,
|
|
836
|
+
anyOf: node.anyOf,
|
|
837
|
+
allOf: node.allOf,
|
|
838
|
+
errorMessage
|
|
839
|
+
},
|
|
840
|
+
isNil
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
function yupToFormErrors(yupError) {
|
|
844
|
+
if (!yupError) {
|
|
845
|
+
return yupError;
|
|
846
|
+
}
|
|
847
|
+
const errors = {};
|
|
848
|
+
if (yupError.inner) {
|
|
849
|
+
if (yupError.inner.length === 0) {
|
|
850
|
+
return set(errors, yupError.path, yupError.message);
|
|
851
|
+
}
|
|
852
|
+
yupError.inner.forEach((err) => {
|
|
853
|
+
if (!get2(errors, err.path)) {
|
|
854
|
+
set(errors, err.path, err.message);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return errors;
|
|
859
|
+
}
|
|
860
|
+
var handleValuesChange = (fields, jsonSchema, config) => (values) => {
|
|
861
|
+
updateFieldsProperties(fields, values, jsonSchema);
|
|
862
|
+
const lazySchema = lazy(() => buildCompleteYupSchema(fields, config));
|
|
863
|
+
let errors;
|
|
864
|
+
try {
|
|
865
|
+
lazySchema.validateSync(values, {
|
|
866
|
+
abortEarly: false
|
|
867
|
+
});
|
|
868
|
+
} catch (err) {
|
|
869
|
+
if (err.name === "ValidationError") {
|
|
870
|
+
errors = err;
|
|
871
|
+
} else {
|
|
872
|
+
console.warn(`Warning: An unhandled error was caught during validationSchema`, err);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
yupError: errors,
|
|
877
|
+
formErrors: yupToFormErrors(errors)
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// src/calculateConditionalProperties.js
|
|
882
|
+
function isFieldRequired(node, inputName) {
|
|
883
|
+
if (node?.required) {
|
|
884
|
+
return node.required.includes(inputName);
|
|
885
|
+
}
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
function rebuildInnerFieldsRequiredProperty(fields, property) {
|
|
889
|
+
if (property?.properties) {
|
|
890
|
+
return fields.map((field) => {
|
|
891
|
+
if (field.fields) {
|
|
892
|
+
return {
|
|
893
|
+
...field,
|
|
894
|
+
fields: rebuildInnerFieldsRequiredProperty(field.fields, property.properties[field.name])
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
...field,
|
|
899
|
+
required: isFieldRequired(property, field.name)
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return fields.map((field) => ({
|
|
904
|
+
...field,
|
|
905
|
+
required: isFieldRequired(property, field.name)
|
|
906
|
+
}));
|
|
907
|
+
}
|
|
908
|
+
function calculateConditionalProperties(fieldParams, customProperties) {
|
|
909
|
+
return (isRequired, conditionBranch) => {
|
|
910
|
+
const conditionalProperty = conditionBranch?.properties?.[fieldParams.name];
|
|
911
|
+
if (conditionalProperty) {
|
|
912
|
+
const presentation = pickXKey(conditionalProperty, "presentation") ?? {};
|
|
913
|
+
const fieldDescription = getFieldDescription(conditionalProperty, customProperties);
|
|
914
|
+
const newFieldParams = extractParametersFromNode({
|
|
915
|
+
...conditionalProperty,
|
|
916
|
+
...fieldDescription
|
|
917
|
+
});
|
|
918
|
+
let fieldSetFields;
|
|
919
|
+
if (fieldParams.inputType === supportedTypes.FIELDSET) {
|
|
920
|
+
fieldSetFields = rebuildInnerFieldsRequiredProperty(
|
|
921
|
+
fieldParams.fields,
|
|
922
|
+
conditionalProperty
|
|
923
|
+
);
|
|
924
|
+
newFieldParams.fields = fieldSetFields;
|
|
925
|
+
}
|
|
926
|
+
const base = {
|
|
927
|
+
isVisible: true,
|
|
928
|
+
required: isRequired,
|
|
929
|
+
...presentation?.inputType && { type: presentation.inputType },
|
|
930
|
+
schema: buildYupSchema({
|
|
931
|
+
...fieldParams,
|
|
932
|
+
...newFieldParams,
|
|
933
|
+
// If there are inner fields (case of fieldset) they need to be updated based on the condition
|
|
934
|
+
fields: fieldSetFields,
|
|
935
|
+
required: isRequired
|
|
936
|
+
})
|
|
937
|
+
};
|
|
938
|
+
return omit2(merge2(base, presentation, newFieldParams), ["inputType"]);
|
|
939
|
+
}
|
|
940
|
+
const isVisible = isRequired;
|
|
941
|
+
return {
|
|
942
|
+
isVisible,
|
|
943
|
+
required: isRequired,
|
|
944
|
+
schema: buildYupSchema({
|
|
945
|
+
...fieldParams,
|
|
946
|
+
...extractParametersFromNode(conditionBranch),
|
|
947
|
+
required: isRequired
|
|
948
|
+
})
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/calculateCustomValidationProperties.js
|
|
954
|
+
import inRange from "lodash/inRange";
|
|
955
|
+
import isFunction2 from "lodash/isFunction";
|
|
956
|
+
import isNil2 from "lodash/isNil";
|
|
957
|
+
import isObject from "lodash/isObject";
|
|
958
|
+
import mapValues from "lodash/mapValues";
|
|
959
|
+
import pick from "lodash/pick";
|
|
960
|
+
var SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS = ["minimum", "maximum"];
|
|
961
|
+
var isCustomValidationAllowed = (fieldParams) => (customValidation, customValidationKey) => {
|
|
962
|
+
if (isNil2(customValidation)) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
const { minimum, maximum } = fieldParams;
|
|
966
|
+
const isAllowed = inRange(
|
|
967
|
+
customValidation,
|
|
968
|
+
minimum ?? -Infinity,
|
|
969
|
+
maximum ? maximum + 1 : Infinity
|
|
970
|
+
);
|
|
971
|
+
if (!isAllowed) {
|
|
972
|
+
const errorMessage = `Custom validation for ${fieldParams.name} is not allowed because ${customValidationKey}:${customValidation} is less strict than the original range: ${minimum} to ${maximum}`;
|
|
973
|
+
if (true) {
|
|
974
|
+
throw new Error(errorMessage);
|
|
975
|
+
} else {
|
|
976
|
+
console.warn(errorMessage);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return isAllowed;
|
|
980
|
+
};
|
|
981
|
+
function calculateCustomValidationProperties(fieldParams, customProperties) {
|
|
982
|
+
return (isRequired, conditionBranch, formValues) => {
|
|
983
|
+
const params = { ...fieldParams, ...conditionBranch?.properties?.[fieldParams.name] };
|
|
984
|
+
const presentation = pickXKey(params, "presentation") ?? {};
|
|
985
|
+
const supportedParams = pick(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS);
|
|
986
|
+
const checkIfAllowed = isCustomValidationAllowed(params);
|
|
987
|
+
const customErrorMessages = [];
|
|
988
|
+
const fieldParamsWithNewValidation = mapValues(
|
|
989
|
+
supportedParams,
|
|
990
|
+
(customValidationValue, customValidationKey) => {
|
|
991
|
+
const originalValidation = params[customValidationKey];
|
|
992
|
+
const customValidation = isFunction2(customValidationValue) ? customValidationValue(formValues, params) : customValidationValue;
|
|
993
|
+
if (isObject(customValidation)) {
|
|
994
|
+
if (checkIfAllowed(customValidation[customValidationKey], customValidationKey)) {
|
|
995
|
+
customErrorMessages.push(pickXKey(customValidation, "errorMessage"));
|
|
996
|
+
return customValidation[customValidationKey];
|
|
997
|
+
}
|
|
998
|
+
return originalValidation;
|
|
999
|
+
}
|
|
1000
|
+
return checkIfAllowed(customValidation, customValidationKey) ? customValidation : originalValidation;
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
1003
|
+
const errorMessage = Object.assign({ ...params.errorMessage }, ...customErrorMessages);
|
|
1004
|
+
return {
|
|
1005
|
+
...params,
|
|
1006
|
+
...fieldParamsWithNewValidation,
|
|
1007
|
+
type: presentation?.inputType || params.inputType,
|
|
1008
|
+
errorMessage,
|
|
1009
|
+
required: isRequired,
|
|
1010
|
+
schema: buildYupSchema({
|
|
1011
|
+
...params,
|
|
1012
|
+
...fieldParamsWithNewValidation,
|
|
1013
|
+
errorMessage,
|
|
1014
|
+
required: isRequired
|
|
1015
|
+
})
|
|
1016
|
+
};
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/createHeadlessForm.js
|
|
1021
|
+
function sortByOrderOrPosition(a, b, order) {
|
|
1022
|
+
if (order) {
|
|
1023
|
+
return order.indexOf(a.name) - order.indexOf(b.name);
|
|
1024
|
+
}
|
|
1025
|
+
return a.position - b.position;
|
|
1026
|
+
}
|
|
1027
|
+
function removeInvalidAttributes(fields) {
|
|
1028
|
+
return omit3(fields, ["items", "maxFileSize", "isDynamic"]);
|
|
1029
|
+
}
|
|
1030
|
+
function buildFieldParameters(name, fieldProperties, required = [], config = {}) {
|
|
1031
|
+
const { position } = pickXKey(fieldProperties, "presentation") ?? {};
|
|
1032
|
+
let fields;
|
|
1033
|
+
const inputType = getInputType(fieldProperties, config.strictInputType, name);
|
|
1034
|
+
if (inputType === supportedTypes.FIELDSET) {
|
|
1035
|
+
fields = getFieldsFromJSONSchema(fieldProperties, {
|
|
1036
|
+
customProperties: get3(config, `customProperties.${name}`, {})
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
const result = {
|
|
1040
|
+
name,
|
|
1041
|
+
inputType,
|
|
1042
|
+
jsonType: fieldProperties.type,
|
|
1043
|
+
type: inputType,
|
|
1044
|
+
// @deprecated in favor of inputType,
|
|
1045
|
+
required: required?.includes(name) ?? false,
|
|
1046
|
+
fields,
|
|
1047
|
+
position,
|
|
1048
|
+
...extractParametersFromNode(fieldProperties)
|
|
1049
|
+
};
|
|
1050
|
+
return omitBy2(result, isNil3);
|
|
1051
|
+
}
|
|
1052
|
+
function convertJSONSchemaPropertiesToFieldParameters({ properties, required, "x-jsf-order": order }, config = {}) {
|
|
1053
|
+
const sortFields = (a, b) => sortByOrderOrPosition(a, b, order);
|
|
1054
|
+
return Object.entries(properties).filter(([, value]) => typeof value === "object").map(([key, value]) => buildFieldParameters(key, value, required, config)).sort(sortFields).map(({ position, ...fieldParams }) => fieldParams);
|
|
1055
|
+
}
|
|
1056
|
+
function applyFieldsDependencies(fieldsParameters, node) {
|
|
1057
|
+
if (node?.then) {
|
|
1058
|
+
fieldsParameters.filter(
|
|
1059
|
+
({ name }) => node.then?.properties?.[name] || node.then?.required?.includes(name) || node.else?.properties?.[name] || node.else?.required?.includes(name)
|
|
1060
|
+
).forEach((property) => {
|
|
1061
|
+
property.isDynamic = true;
|
|
1062
|
+
});
|
|
1063
|
+
applyFieldsDependencies(fieldsParameters, node.then);
|
|
1064
|
+
}
|
|
1065
|
+
if (node?.anyOf) {
|
|
1066
|
+
fieldsParameters.filter(({ name }) => node.anyOf.some(({ required }) => required?.includes(name))).forEach((property) => {
|
|
1067
|
+
property.isDynamic = true;
|
|
1068
|
+
});
|
|
1069
|
+
applyFieldsDependencies(fieldsParameters, node.then);
|
|
1070
|
+
}
|
|
1071
|
+
if (node?.allOf) {
|
|
1072
|
+
node.allOf.forEach((condition) => {
|
|
1073
|
+
applyFieldsDependencies(fieldsParameters, condition);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function getCustomPropertiesForField(fieldParams, config) {
|
|
1078
|
+
return config?.customProperties?.[fieldParams.name];
|
|
1079
|
+
}
|
|
1080
|
+
function getComposeFunctionForField(fieldParams, hasCustomizations) {
|
|
1081
|
+
const composeFn = inputTypeMap[fieldParams.inputType] || _composeFieldArbitraryClosure(fieldParams.inputType);
|
|
1082
|
+
if (hasCustomizations) {
|
|
1083
|
+
return _composeFieldCustomClosure(composeFn);
|
|
1084
|
+
}
|
|
1085
|
+
return composeFn;
|
|
1086
|
+
}
|
|
1087
|
+
function buildField(fieldParams, config, scopedJsonSchema) {
|
|
1088
|
+
const customProperties = getCustomPropertiesForField(fieldParams, config);
|
|
1089
|
+
const composeFn = getComposeFunctionForField(fieldParams, !!customProperties);
|
|
1090
|
+
const yupSchema = buildYupSchema(fieldParams, config);
|
|
1091
|
+
const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties);
|
|
1092
|
+
const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties(
|
|
1093
|
+
fieldParams,
|
|
1094
|
+
customProperties
|
|
1095
|
+
);
|
|
1096
|
+
const hasCustomValidations = !!customProperties && size(pick2(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS)) > 0;
|
|
1097
|
+
const finalFieldParams = {
|
|
1098
|
+
// invalid attribute cleanup
|
|
1099
|
+
...removeInvalidAttributes(fieldParams),
|
|
1100
|
+
// calculateConditionalProperties function if needed
|
|
1101
|
+
...!!calculateConditionalFieldsClosure && {
|
|
1102
|
+
calculateConditionalProperties: calculateConditionalFieldsClosure
|
|
1103
|
+
},
|
|
1104
|
+
// calculateCustomValidationProperties function if needed
|
|
1105
|
+
...hasCustomValidations && {
|
|
1106
|
+
calculateCustomValidationProperties: calculateCustomValidationPropertiesClosure
|
|
1107
|
+
},
|
|
1108
|
+
// field customization properties
|
|
1109
|
+
...customProperties && { fieldCustomization: customProperties },
|
|
1110
|
+
// base schema
|
|
1111
|
+
schema: yupSchema(),
|
|
1112
|
+
scopedJsonSchema
|
|
1113
|
+
};
|
|
1114
|
+
return composeFn(finalFieldParams);
|
|
1115
|
+
}
|
|
1116
|
+
function getFieldsFromJSONSchema(scopedJsonSchema, config) {
|
|
1117
|
+
if (!scopedJsonSchema) {
|
|
1118
|
+
return [];
|
|
1119
|
+
}
|
|
1120
|
+
const fieldParamsList = convertJSONSchemaPropertiesToFieldParameters(scopedJsonSchema, config);
|
|
1121
|
+
applyFieldsDependencies(fieldParamsList, scopedJsonSchema);
|
|
1122
|
+
const fields = [];
|
|
1123
|
+
fieldParamsList.forEach((fieldParams) => {
|
|
1124
|
+
if (fieldParams.inputType === "group-array") {
|
|
1125
|
+
const groupArrayItems = convertJSONSchemaPropertiesToFieldParameters(fieldParams.items);
|
|
1126
|
+
const groupArrayFields = groupArrayItems.map((groupArrayItem) => {
|
|
1127
|
+
groupArrayItem.nameKey = groupArrayItem.name;
|
|
1128
|
+
const customProperties = null;
|
|
1129
|
+
const composeFn = getComposeFunctionForField(groupArrayItem, !!customProperties);
|
|
1130
|
+
return composeFn(groupArrayItem);
|
|
1131
|
+
});
|
|
1132
|
+
fieldParams.nameKey = fieldParams.name;
|
|
1133
|
+
fieldParams.nthFieldGroup = {
|
|
1134
|
+
name: fieldParams.name,
|
|
1135
|
+
label: fieldParams.label,
|
|
1136
|
+
description: fieldParams.description,
|
|
1137
|
+
fields: () => groupArrayFields,
|
|
1138
|
+
addFieldText: fieldParams.addFieldText
|
|
1139
|
+
};
|
|
1140
|
+
buildField(fieldParams, config, scopedJsonSchema).forEach((groupField) => {
|
|
1141
|
+
fields.push(groupField);
|
|
1142
|
+
});
|
|
1143
|
+
} else {
|
|
1144
|
+
fields.push(buildField(fieldParams, config, scopedJsonSchema));
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
return fields;
|
|
1148
|
+
}
|
|
1149
|
+
function createHeadlessForm(jsonSchema, customConfig = {}) {
|
|
1150
|
+
const config = {
|
|
1151
|
+
strictInputType: true,
|
|
1152
|
+
...customConfig
|
|
1153
|
+
};
|
|
1154
|
+
try {
|
|
1155
|
+
const fields = getFieldsFromJSONSchema(jsonSchema, config);
|
|
1156
|
+
const handleValidation = handleValuesChange(fields, jsonSchema, config);
|
|
1157
|
+
updateFieldsProperties(fields, getPrefillValues(fields, config.initialValues), jsonSchema);
|
|
1158
|
+
return {
|
|
1159
|
+
fields,
|
|
1160
|
+
handleValidation,
|
|
1161
|
+
isError: false
|
|
1162
|
+
};
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
console.error("JSON Schema invalid!", error);
|
|
1165
|
+
return {
|
|
1166
|
+
fields: [],
|
|
1167
|
+
isError: true,
|
|
1168
|
+
error
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
export {
|
|
1173
|
+
createHeadlessForm
|
|
1174
|
+
};
|