@remoteoss/json-schema-form 0.1.1-dev.20230616112349 → 0.2.0-beta.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/CHANGELOG.md +14 -0
- package/dist/index.cjs +4 -5
- package/dist/index.js +4 -5
- package/dist/standalone.js +359 -788
- package/json-schema-form.d.ts +115 -0
- package/package.json +4 -3
- package/src/tests/createHeadlessForm.test.js +29 -19
- package/src/tests/helpers.js +15 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shorthand to lookup for keys with `x-jsf-*` preffix.
|
|
3
|
+
*/
|
|
4
|
+
export function pickXKey(node: Object, key: 'presentation' | 'errorMessage'): Object | undefined;
|
|
5
|
+
|
|
6
|
+
type ValidationTypes =
|
|
7
|
+
| 'type'
|
|
8
|
+
| 'minimum'
|
|
9
|
+
| 'maximum'
|
|
10
|
+
| 'minLength'
|
|
11
|
+
| 'maxLength'
|
|
12
|
+
| 'pattern'
|
|
13
|
+
| 'maxFileSize'
|
|
14
|
+
| 'accept'
|
|
15
|
+
| 'required';
|
|
16
|
+
|
|
17
|
+
type JSFConfig = {
|
|
18
|
+
/**
|
|
19
|
+
* Initial json values to prefill the form fields.
|
|
20
|
+
* This influences the initial visibility of conditional fields.
|
|
21
|
+
*/
|
|
22
|
+
initialValues?: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* It inforces all json properties (fields) to have x-jsf-presentation.inputType.
|
|
25
|
+
@default true
|
|
26
|
+
*/
|
|
27
|
+
strictInputType?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Customize the output of a given named Field.
|
|
30
|
+
* Useful if you want to enhance the UI/UX of a particular field.
|
|
31
|
+
* @example
|
|
32
|
+
* { has_pet: { "optionsDirection": "horizontal" } }
|
|
33
|
+
*/
|
|
34
|
+
customProperties?: {
|
|
35
|
+
description?: (description: string, field: $TSFixMe) => string | string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Customize the config for each inputType
|
|
40
|
+
*/
|
|
41
|
+
inputTypes?: {
|
|
42
|
+
/**
|
|
43
|
+
* Customize the error error message of this input type.
|
|
44
|
+
* @example
|
|
45
|
+
* { required: "This number is required." }
|
|
46
|
+
*/
|
|
47
|
+
errorMessage?: Partial<Record<ValidationTypes, string>>;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
type Fields = Record<string, unknown>[]; //TODO: Type the field based on the given JSON Schema properties.
|
|
51
|
+
|
|
52
|
+
type $TSFixMe = any;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns the Yup schema structure of given fields.
|
|
56
|
+
* These fields must be the same from
|
|
57
|
+
* const { fields } = createHeadlessForm()
|
|
58
|
+
*/
|
|
59
|
+
export function buildCompleteYupSchema(fields: Fields, config: JSFConfig): $TSFixMe; //TODO: We need to update Yup to 1.0 which supports TS.
|
|
60
|
+
|
|
61
|
+
type HeadlessFormOutput = {
|
|
62
|
+
/**
|
|
63
|
+
* List of Fields. Each Field corresponds to a form input from the json schema.
|
|
64
|
+
* @example
|
|
65
|
+
* [
|
|
66
|
+
{
|
|
67
|
+
name: "has_pet",
|
|
68
|
+
label: "Has Pet",
|
|
69
|
+
options: [
|
|
70
|
+
{ label: "Yes", value: "yes" },
|
|
71
|
+
{ label: "No", value: "no" }
|
|
72
|
+
],
|
|
73
|
+
required: true,
|
|
74
|
+
inputType: "radio",
|
|
75
|
+
jsonType: "string",
|
|
76
|
+
description: "Do you have a pet?",
|
|
77
|
+
errorMessage: {},
|
|
78
|
+
isVisible: true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
label: "Pet's name",
|
|
83
|
+
description: "What's your pet's name?",
|
|
84
|
+
required: false,
|
|
85
|
+
inputType: "text",
|
|
86
|
+
jsonType: "string",
|
|
87
|
+
isVisible: false
|
|
88
|
+
}
|
|
89
|
+
* ]
|
|
90
|
+
*/
|
|
91
|
+
fields: Fields;
|
|
92
|
+
/**
|
|
93
|
+
* Validates given values and returns the respective errors.
|
|
94
|
+
* @example
|
|
95
|
+
* const { formErrors } = handleValidation({ has_pet: "yes" });
|
|
96
|
+
* console.log(formErrors) // { pet_name: "Required field." }
|
|
97
|
+
*/
|
|
98
|
+
handleValidation: (values: Record<string, unknown>) => {
|
|
99
|
+
yupError: $TSFixMe;
|
|
100
|
+
formErrors: $TSFixMe;
|
|
101
|
+
};
|
|
102
|
+
isError: boolean;
|
|
103
|
+
error?: Error;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type JSONSchemaObjectType = Record<string, unknown>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generates the Headless form based on the provided JSON schema
|
|
110
|
+
*/
|
|
111
|
+
export function createHeadlessForm(
|
|
112
|
+
/** A JSON Schema of type object */
|
|
113
|
+
jsonSchema: JSONSchemaObjectType,
|
|
114
|
+
customConfig?: JSFConfig
|
|
115
|
+
): HeadlessFormOutput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remoteoss/json-schema-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "Headless UI form powered by JSON Schemas",
|
|
5
5
|
"author": "Remote.com <engineering@remote.com> (https://remote.com/)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"main": "dist/index.cjs",
|
|
13
13
|
"module": "dist/index.js",
|
|
14
|
+
"typings": "./json-schema-form.d.ts",
|
|
14
15
|
"files": [
|
|
15
16
|
"dist",
|
|
16
17
|
"src/tests",
|
|
17
18
|
"json-schema-form.schema.json",
|
|
19
|
+
"json-schema-form.d.ts",
|
|
18
20
|
"README.md",
|
|
19
21
|
"CHANGELOG.md",
|
|
20
22
|
"LICENSE.md"
|
|
@@ -22,7 +24,6 @@
|
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "node scripts/build.js",
|
|
24
26
|
"test": "jest",
|
|
25
|
-
"test:watch": "jest --watchAll",
|
|
26
27
|
"lint": "eslint \"src/**/*.{js,ts}\"",
|
|
27
28
|
"format": "npm run format:prettier && npm run format:eslint",
|
|
28
29
|
"prepare": "husky install",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"lodash": "^4.17.21",
|
|
49
50
|
"randexp": "^0.5.3",
|
|
50
|
-
"yup": "^0.
|
|
51
|
+
"yup": "^0.29.1"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@babel/core": "^7.21.5",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
schemaInputTypeSelectMultiple,
|
|
17
17
|
schemaInputTypeSelectMultipleOptional,
|
|
18
18
|
schemaInputTypeNumber,
|
|
19
|
+
schemaInputTypeNumberZeroMaximum,
|
|
19
20
|
schemaInputTypeDate,
|
|
20
21
|
schemaInputTypeEmail,
|
|
21
22
|
schemaInputWithStatement,
|
|
@@ -419,7 +420,7 @@ describe('createHeadlessForm', () => {
|
|
|
419
420
|
|
|
420
421
|
describe('field support', () => {
|
|
421
422
|
it('support "text" field type', () => {
|
|
422
|
-
const { fields
|
|
423
|
+
const { fields } = createHeadlessForm(schemaInputTypeText);
|
|
423
424
|
|
|
424
425
|
expect(fields[0]).toMatchObject({
|
|
425
426
|
description: 'The number of your national identification (max 10 digits)',
|
|
@@ -435,14 +436,10 @@ describe('createHeadlessForm', () => {
|
|
|
435
436
|
});
|
|
436
437
|
|
|
437
438
|
const fieldValidator = fields[0].schema;
|
|
438
|
-
expect(fieldValidator.isValidSync('CI007'
|
|
439
|
-
expect(fieldValidator.isValidSync(true
|
|
440
|
-
expect(fieldValidator.isValidSync(1
|
|
441
|
-
expect(fieldValidator.isValidSync(0
|
|
442
|
-
|
|
443
|
-
expect(handleValidation({ id_number: 1 }).formErrors).toEqual({
|
|
444
|
-
id_number: 'id_number must be a `string` type, but the final value was: `1`.',
|
|
445
|
-
});
|
|
439
|
+
expect(fieldValidator.isValidSync('CI007')).toBe(true);
|
|
440
|
+
expect(fieldValidator.isValidSync(true)).toBe(true); // @BUG RMT-446 - cannot be a bool
|
|
441
|
+
expect(fieldValidator.isValidSync(1)).toBe(true); // @BUG RMT-446 - cannot be a number
|
|
442
|
+
expect(fieldValidator.isValidSync(0)).toBe(true); // @BUG RMT-446 - cannot be a number
|
|
446
443
|
|
|
447
444
|
expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
|
|
448
445
|
});
|
|
@@ -1843,6 +1840,19 @@ describe('createHeadlessForm', () => {
|
|
|
1843
1840
|
).resolves.toEqual(assertObj);
|
|
1844
1841
|
});
|
|
1845
1842
|
});
|
|
1843
|
+
describe('and maximum is set to zero', () => {
|
|
1844
|
+
it('shows the correct validation', () => {
|
|
1845
|
+
const { handleValidation } = createHeadlessForm(schemaInputTypeNumberZeroMaximum);
|
|
1846
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1847
|
+
|
|
1848
|
+
expect(validateForm({ tabs: '0' })).toBeUndefined();
|
|
1849
|
+
expect(validateForm({ tabs: '-10' })).toBeUndefined();
|
|
1850
|
+
|
|
1851
|
+
expect(validateForm({ tabs: 1 })).toEqual({
|
|
1852
|
+
tabs: 'Must be smaller or equal to 0',
|
|
1853
|
+
});
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1846
1856
|
});
|
|
1847
1857
|
|
|
1848
1858
|
describe('when a field has a maxLength of 10', () => {
|
|
@@ -2317,7 +2327,7 @@ describe('createHeadlessForm', () => {
|
|
|
2317
2327
|
validateForm({
|
|
2318
2328
|
validate_tabs: 'no',
|
|
2319
2329
|
a_fieldset: {
|
|
2320
|
-
id_number:
|
|
2330
|
+
id_number: 123,
|
|
2321
2331
|
},
|
|
2322
2332
|
mandatory_group_array: 'no',
|
|
2323
2333
|
})
|
|
@@ -2332,7 +2342,7 @@ describe('createHeadlessForm', () => {
|
|
|
2332
2342
|
validateForm({
|
|
2333
2343
|
validate_tabs: 'yes',
|
|
2334
2344
|
a_fieldset: {
|
|
2335
|
-
id_number:
|
|
2345
|
+
id_number: 123,
|
|
2336
2346
|
},
|
|
2337
2347
|
mandatory_group_array: 'no',
|
|
2338
2348
|
})
|
|
@@ -2348,7 +2358,7 @@ describe('createHeadlessForm', () => {
|
|
|
2348
2358
|
validateForm({
|
|
2349
2359
|
validate_tabs: 'yes',
|
|
2350
2360
|
a_fieldset: {
|
|
2351
|
-
id_number:
|
|
2361
|
+
id_number: 123,
|
|
2352
2362
|
},
|
|
2353
2363
|
mandatory_group_array: 'yes',
|
|
2354
2364
|
a_group_array: [{ full_name: 'adfs' }],
|
|
@@ -2359,7 +2369,7 @@ describe('createHeadlessForm', () => {
|
|
|
2359
2369
|
validateForm({
|
|
2360
2370
|
validate_tabs: 'yes',
|
|
2361
2371
|
a_fieldset: {
|
|
2362
|
-
id_number:
|
|
2372
|
+
id_number: 123,
|
|
2363
2373
|
tabs: 2,
|
|
2364
2374
|
},
|
|
2365
2375
|
mandatory_group_array: 'no',
|
|
@@ -2450,7 +2460,7 @@ describe('createHeadlessForm', () => {
|
|
|
2450
2460
|
validateForm({
|
|
2451
2461
|
validate_fieldset: ['id_number'],
|
|
2452
2462
|
a_fieldset: {
|
|
2453
|
-
id_number:
|
|
2463
|
+
id_number: 123,
|
|
2454
2464
|
},
|
|
2455
2465
|
})
|
|
2456
2466
|
).toBeUndefined();
|
|
@@ -2459,7 +2469,7 @@ describe('createHeadlessForm', () => {
|
|
|
2459
2469
|
validateForm({
|
|
2460
2470
|
validate_fieldset: ['id_number', 'all'],
|
|
2461
2471
|
a_fieldset: {
|
|
2462
|
-
id_number:
|
|
2472
|
+
id_number: 123,
|
|
2463
2473
|
},
|
|
2464
2474
|
})
|
|
2465
2475
|
).toEqual({
|
|
@@ -2472,7 +2482,7 @@ describe('createHeadlessForm', () => {
|
|
|
2472
2482
|
validateForm({
|
|
2473
2483
|
validate_fieldset: ['id_number', 'all'],
|
|
2474
2484
|
a_fieldset: {
|
|
2475
|
-
id_number:
|
|
2485
|
+
id_number: 123,
|
|
2476
2486
|
tabs: 2,
|
|
2477
2487
|
},
|
|
2478
2488
|
})
|
|
@@ -2487,7 +2497,7 @@ describe('createHeadlessForm', () => {
|
|
|
2487
2497
|
validateForm({
|
|
2488
2498
|
validate_fieldset: ['id_number'],
|
|
2489
2499
|
a_fieldset: {
|
|
2490
|
-
id_number:
|
|
2500
|
+
id_number: 123,
|
|
2491
2501
|
},
|
|
2492
2502
|
})
|
|
2493
2503
|
).toBeUndefined();
|
|
@@ -2496,7 +2506,7 @@ describe('createHeadlessForm', () => {
|
|
|
2496
2506
|
validateForm({
|
|
2497
2507
|
validate_fieldset: ['id_number', 'all'],
|
|
2498
2508
|
a_fieldset: {
|
|
2499
|
-
id_number:
|
|
2509
|
+
id_number: 123,
|
|
2500
2510
|
},
|
|
2501
2511
|
})
|
|
2502
2512
|
).toEqual({
|
|
@@ -2509,7 +2519,7 @@ describe('createHeadlessForm', () => {
|
|
|
2509
2519
|
validateForm({
|
|
2510
2520
|
validate_fieldset: ['id_number', 'all'],
|
|
2511
2521
|
a_fieldset: {
|
|
2512
|
-
id_number:
|
|
2522
|
+
id_number: 123,
|
|
2513
2523
|
tabs: 2,
|
|
2514
2524
|
},
|
|
2515
2525
|
})
|
package/src/tests/helpers.js
CHANGED
|
@@ -46,6 +46,21 @@ export const mockNumberInput = {
|
|
|
46
46
|
type: 'number',
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
export const schemaInputTypeNumberZeroMaximum = {
|
|
50
|
+
properties: {
|
|
51
|
+
tabs: {
|
|
52
|
+
title: 'Tabs',
|
|
53
|
+
description: 'How many open tabs do you have?',
|
|
54
|
+
'x-jsf-presentation': {
|
|
55
|
+
inputType: 'number',
|
|
56
|
+
},
|
|
57
|
+
minimum: -100,
|
|
58
|
+
maximum: 0,
|
|
59
|
+
type: 'number',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
49
64
|
export const mockNumberInputWithPercentage = {
|
|
50
65
|
title: 'Shares',
|
|
51
66
|
description: 'What % of shares do you own?',
|