@remoteoss/json-schema-form 0.9.0-beta.0 → 0.9.1-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 CHANGED
@@ -1,3 +1,9 @@
1
+ #### 0.9.1-beta.0 (2024-03-26)
2
+
3
+ ##### Bug Fixes
4
+
5
+ * **select/radio:** Allow numbers in oneOf ([#49](https://github.com/remoteoss/json-schema-form/pull/49)) ([04c2598a](https://github.com/remoteoss/json-schema-form/commit/04c2598a3f4e31d8a9495e0d69c0427b6b8c9f56))
6
+
1
7
  #### 0.9.0-beta.0 (2024-03-11)
2
8
 
3
9
  ##### Breaking changes
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.9.0-beta.0
5
- Generated: Mon, 11 Mar 2024 14:55:00 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.9.1-beta.0
5
+ Generated: Tue, 26 Mar 2024 17:04:09 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -457,9 +457,18 @@ var validateMaxDate = (value, minDate) => {
457
457
  const compare = compareDates(value, minDate);
458
458
  return compare === "LESSER" || compare === "EQUAL" ? true : false;
459
459
  };
460
+ var validateRadioOrSelectOptions = (value, options) => {
461
+ if (value === void 0)
462
+ return true;
463
+ const exactMatch = options.some((option) => option.value === value);
464
+ if (exactMatch)
465
+ return true;
466
+ const patternMatch = options.some((option) => option.pattern?.test(value));
467
+ return !!patternMatch;
468
+ };
460
469
  var yupSchemas = {
461
470
  text: validateOnlyStrings,
462
- radioOrSelect: (options) => {
471
+ radioOrSelectString: (options) => {
463
472
  return (0, import_yup.string)().nullable().transform((value) => {
464
473
  if (value === "") {
465
474
  return void 0;
@@ -469,28 +478,9 @@ var yupSchemas = {
469
478
  }
470
479
  return value === null ? void 0 : value;
471
480
  }).test(
472
- /*
473
- Custom test determines if the value either:
474
- - Matches a specific option by value
475
- - Matches a pattern
476
- If the option is undefined do not test, to allow for optional fields.
477
- */
478
481
  "matchesOptionOrPattern",
479
482
  ({ value }) => `The option ${JSON.stringify(value)} is not valid.`,
480
- (value) => {
481
- if (value === void 0) {
482
- return true;
483
- }
484
- const exactMatch = options.some((option) => option.value === value);
485
- if (exactMatch) {
486
- return true;
487
- }
488
- const patternMatch = options.some((option) => option.pattern?.test(value));
489
- if (patternMatch) {
490
- return true;
491
- }
492
- return false;
493
- }
483
+ (value) => validateRadioOrSelectOptions(value, options)
494
484
  );
495
485
  },
496
486
  date: ({ minDate, maxDate }) => {
@@ -519,6 +509,22 @@ var yupSchemas = {
519
509
  }
520
510
  return dateString;
521
511
  },
512
+ radioOrSelectNumber: (options) => (0, import_yup.mixed)().typeError("The value must be a number").transform((value) => {
513
+ if (options?.some((option) => option.value === null)) {
514
+ return value;
515
+ }
516
+ return value === null ? void 0 : value;
517
+ }).test(
518
+ "matchesOptionOrPattern",
519
+ ({ value }) => {
520
+ return `The option ${JSON.stringify(value)} is not valid.`;
521
+ },
522
+ (value) => {
523
+ if (value !== void 0 && typeof value !== "number")
524
+ return false;
525
+ return validateRadioOrSelectOptions(value, options);
526
+ }
527
+ ).nullable(),
522
528
  number: (0, import_yup.number)().typeError("The value must be a number").nullable(),
523
529
  file: (0, import_yup.array)().nullable(),
524
530
  email: (0, import_yup.string)().trim().email("Please enter a valid email address").nullable(),
@@ -548,7 +554,9 @@ function getRequiredErrorMessage(inputType, { inlineError, configError }) {
548
554
  return "Please acknowledge this field";
549
555
  return "Required field";
550
556
  }
551
- var getJsonTypeInArray = (jsonType) => Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
557
+ var getJsonTypeInArray = (jsonType) => {
558
+ return Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
559
+ };
552
560
  var getOptions = (field) => {
553
561
  const allValues = field.options?.map((option) => ({
554
562
  value: option.value,
@@ -562,9 +570,16 @@ var getOptions = (field) => {
562
570
  };
563
571
  var getYupSchema = ({ inputType, ...field }) => {
564
572
  const jsonType = getJsonTypeInArray(field.jsonType);
565
- if (field.options?.length > 0) {
573
+ const hasOptions = field.options?.length > 0;
574
+ const generateOptionSchema = (type) => {
566
575
  const optionValues = getOptions(field);
567
- return yupSchemas.radioOrSelect(optionValues);
576
+ return type === "number" ? yupSchemas.radioOrSelectNumber(optionValues) : yupSchemas.radioOrSelectString(optionValues);
577
+ };
578
+ if (hasOptions) {
579
+ if (Array.isArray(field.jsonType)) {
580
+ return field.jsonType.includes("number") ? generateOptionSchema("number") : generateOptionSchema("string");
581
+ }
582
+ return generateOptionSchema(field.jsonType);
568
583
  }
569
584
  if (field.format === "date") {
570
585
  return yupSchemas.date({ minDate: field.minDate, maxDate: field.maxDate });
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.9.0-beta.0
5
- Generated: Mon, 11 Mar 2024 14:55:00 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.9.1-beta.0
5
+ Generated: Tue, 26 Mar 2024 17:04:09 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -386,7 +386,7 @@ import jsonLogic from "json-logic-js";
386
386
  import flow from "lodash/flow";
387
387
  import noop from "lodash/noop";
388
388
  import { randexp } from "randexp";
389
- import { string, number, boolean, object, array } from "yup";
389
+ import { string, number, boolean, object, array, mixed } from "yup";
390
390
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
391
391
  var baseString = string().trim();
392
392
  var todayDateHint = (/* @__PURE__ */ new Date()).toISOString().substring(0, 10);
@@ -421,9 +421,18 @@ var validateMaxDate = (value, minDate) => {
421
421
  const compare = compareDates(value, minDate);
422
422
  return compare === "LESSER" || compare === "EQUAL" ? true : false;
423
423
  };
424
+ var validateRadioOrSelectOptions = (value, options) => {
425
+ if (value === void 0)
426
+ return true;
427
+ const exactMatch = options.some((option) => option.value === value);
428
+ if (exactMatch)
429
+ return true;
430
+ const patternMatch = options.some((option) => option.pattern?.test(value));
431
+ return !!patternMatch;
432
+ };
424
433
  var yupSchemas = {
425
434
  text: validateOnlyStrings,
426
- radioOrSelect: (options) => {
435
+ radioOrSelectString: (options) => {
427
436
  return string().nullable().transform((value) => {
428
437
  if (value === "") {
429
438
  return void 0;
@@ -433,28 +442,9 @@ var yupSchemas = {
433
442
  }
434
443
  return value === null ? void 0 : value;
435
444
  }).test(
436
- /*
437
- Custom test determines if the value either:
438
- - Matches a specific option by value
439
- - Matches a pattern
440
- If the option is undefined do not test, to allow for optional fields.
441
- */
442
445
  "matchesOptionOrPattern",
443
446
  ({ value }) => `The option ${JSON.stringify(value)} is not valid.`,
444
- (value) => {
445
- if (value === void 0) {
446
- return true;
447
- }
448
- const exactMatch = options.some((option) => option.value === value);
449
- if (exactMatch) {
450
- return true;
451
- }
452
- const patternMatch = options.some((option) => option.pattern?.test(value));
453
- if (patternMatch) {
454
- return true;
455
- }
456
- return false;
457
- }
447
+ (value) => validateRadioOrSelectOptions(value, options)
458
448
  );
459
449
  },
460
450
  date: ({ minDate, maxDate }) => {
@@ -483,6 +473,22 @@ var yupSchemas = {
483
473
  }
484
474
  return dateString;
485
475
  },
476
+ radioOrSelectNumber: (options) => mixed().typeError("The value must be a number").transform((value) => {
477
+ if (options?.some((option) => option.value === null)) {
478
+ return value;
479
+ }
480
+ return value === null ? void 0 : value;
481
+ }).test(
482
+ "matchesOptionOrPattern",
483
+ ({ value }) => {
484
+ return `The option ${JSON.stringify(value)} is not valid.`;
485
+ },
486
+ (value) => {
487
+ if (value !== void 0 && typeof value !== "number")
488
+ return false;
489
+ return validateRadioOrSelectOptions(value, options);
490
+ }
491
+ ).nullable(),
486
492
  number: number().typeError("The value must be a number").nullable(),
487
493
  file: array().nullable(),
488
494
  email: string().trim().email("Please enter a valid email address").nullable(),
@@ -512,7 +518,9 @@ function getRequiredErrorMessage(inputType, { inlineError, configError }) {
512
518
  return "Please acknowledge this field";
513
519
  return "Required field";
514
520
  }
515
- var getJsonTypeInArray = (jsonType) => Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
521
+ var getJsonTypeInArray = (jsonType) => {
522
+ return Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
523
+ };
516
524
  var getOptions = (field) => {
517
525
  const allValues = field.options?.map((option) => ({
518
526
  value: option.value,
@@ -526,9 +534,16 @@ var getOptions = (field) => {
526
534
  };
527
535
  var getYupSchema = ({ inputType, ...field }) => {
528
536
  const jsonType = getJsonTypeInArray(field.jsonType);
529
- if (field.options?.length > 0) {
537
+ const hasOptions = field.options?.length > 0;
538
+ const generateOptionSchema = (type) => {
530
539
  const optionValues = getOptions(field);
531
- return yupSchemas.radioOrSelect(optionValues);
540
+ return type === "number" ? yupSchemas.radioOrSelectNumber(optionValues) : yupSchemas.radioOrSelectString(optionValues);
541
+ };
542
+ if (hasOptions) {
543
+ if (Array.isArray(field.jsonType)) {
544
+ return field.jsonType.includes("number") ? generateOptionSchema("number") : generateOptionSchema("string");
545
+ }
546
+ return generateOptionSchema(field.jsonType);
532
547
  }
533
548
  if (field.format === "date") {
534
549
  return yupSchemas.date({ minDate: field.minDate, maxDate: field.maxDate });
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.9.0-beta.0
5
- Generated: Mon, 11 Mar 2024 14:55:00 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.9.1-beta.0
5
+ Generated: Tue, 26 Mar 2024 17:04:09 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -11775,9 +11775,18 @@ var validateMaxDate = (value, minDate) => {
11775
11775
  const compare = compareDates(value, minDate);
11776
11776
  return compare === "LESSER" || compare === "EQUAL" ? true : false;
11777
11777
  };
11778
+ var validateRadioOrSelectOptions = (value, options) => {
11779
+ if (value === void 0)
11780
+ return true;
11781
+ const exactMatch = options.some((option) => option.value === value);
11782
+ if (exactMatch)
11783
+ return true;
11784
+ const patternMatch = options.some((option) => option.pattern?.test(value));
11785
+ return !!patternMatch;
11786
+ };
11778
11787
  var yupSchemas = {
11779
11788
  text: validateOnlyStrings,
11780
- radioOrSelect: (options) => {
11789
+ radioOrSelectString: (options) => {
11781
11790
  return StringSchema().nullable().transform((value) => {
11782
11791
  if (value === "") {
11783
11792
  return void 0;
@@ -11787,28 +11796,9 @@ var yupSchemas = {
11787
11796
  }
11788
11797
  return value === null ? void 0 : value;
11789
11798
  }).test(
11790
- /*
11791
- Custom test determines if the value either:
11792
- - Matches a specific option by value
11793
- - Matches a pattern
11794
- If the option is undefined do not test, to allow for optional fields.
11795
- */
11796
11799
  "matchesOptionOrPattern",
11797
11800
  ({ value }) => `The option ${JSON.stringify(value)} is not valid.`,
11798
- (value) => {
11799
- if (value === void 0) {
11800
- return true;
11801
- }
11802
- const exactMatch = options.some((option) => option.value === value);
11803
- if (exactMatch) {
11804
- return true;
11805
- }
11806
- const patternMatch = options.some((option) => option.pattern?.test(value));
11807
- if (patternMatch) {
11808
- return true;
11809
- }
11810
- return false;
11811
- }
11801
+ (value) => validateRadioOrSelectOptions(value, options)
11812
11802
  );
11813
11803
  },
11814
11804
  date: ({ minDate, maxDate }) => {
@@ -11837,6 +11827,22 @@ var yupSchemas = {
11837
11827
  }
11838
11828
  return dateString;
11839
11829
  },
11830
+ radioOrSelectNumber: (options) => SchemaType().typeError("The value must be a number").transform((value) => {
11831
+ if (options?.some((option) => option.value === null)) {
11832
+ return value;
11833
+ }
11834
+ return value === null ? void 0 : value;
11835
+ }).test(
11836
+ "matchesOptionOrPattern",
11837
+ ({ value }) => {
11838
+ return `The option ${JSON.stringify(value)} is not valid.`;
11839
+ },
11840
+ (value) => {
11841
+ if (value !== void 0 && typeof value !== "number")
11842
+ return false;
11843
+ return validateRadioOrSelectOptions(value, options);
11844
+ }
11845
+ ).nullable(),
11840
11846
  number: NumberSchema().typeError("The value must be a number").nullable(),
11841
11847
  file: array_default().nullable(),
11842
11848
  email: StringSchema().trim().email("Please enter a valid email address").nullable(),
@@ -11866,7 +11872,9 @@ function getRequiredErrorMessage(inputType, { inlineError, configError }) {
11866
11872
  return "Please acknowledge this field";
11867
11873
  return "Required field";
11868
11874
  }
11869
- var getJsonTypeInArray = (jsonType) => Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
11875
+ var getJsonTypeInArray = (jsonType) => {
11876
+ return Array.isArray(jsonType) ? jsonType.find((val) => val !== "null") : jsonType;
11877
+ };
11870
11878
  var getOptions = (field) => {
11871
11879
  const allValues = field.options?.map((option) => ({
11872
11880
  value: option.value,
@@ -11880,9 +11888,16 @@ var getOptions = (field) => {
11880
11888
  };
11881
11889
  var getYupSchema = ({ inputType, ...field }) => {
11882
11890
  const jsonType = getJsonTypeInArray(field.jsonType);
11883
- if (field.options?.length > 0) {
11891
+ const hasOptions = field.options?.length > 0;
11892
+ const generateOptionSchema = (type) => {
11884
11893
  const optionValues = getOptions(field);
11885
- return yupSchemas.radioOrSelect(optionValues);
11894
+ return type === "number" ? yupSchemas.radioOrSelectNumber(optionValues) : yupSchemas.radioOrSelectString(optionValues);
11895
+ };
11896
+ if (hasOptions) {
11897
+ if (Array.isArray(field.jsonType)) {
11898
+ return field.jsonType.includes("number") ? generateOptionSchema("number") : generateOptionSchema("string");
11899
+ }
11900
+ return generateOptionSchema(field.jsonType);
11886
11901
  }
11887
11902
  if (field.format === "date") {
11888
11903
  return yupSchemas.date({ minDate: field.minDate, maxDate: field.maxDate });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remoteoss/json-schema-form",
3
- "version": "0.9.0-beta.0",
3
+ "version": "0.9.1-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",
@@ -193,3 +193,44 @@ describe('const/default with forced values', () => {
193
193
  expect(fields[0]).toMatchObject({ forcedValue: 300 });
194
194
  });
195
195
  });
196
+
197
+ describe('OneOf const', () => {
198
+ it('Validates numbers or strings correctly', () => {
199
+ const { handleValidation } = createHeadlessForm(
200
+ {
201
+ properties: {
202
+ number: { type: 'number', oneOf: [{ const: 0 }, { const: 1 }, { const: 2 }] },
203
+ },
204
+ },
205
+ { strictInputType: false }
206
+ );
207
+ expect(handleValidation({}).formErrors).toEqual(undefined);
208
+ expect(handleValidation({ number: 3 }).formErrors).toEqual({
209
+ number: 'The option 3 is not valid.',
210
+ });
211
+ expect(handleValidation({ number: 2 }).formErrors).toBeUndefined();
212
+ expect(handleValidation({ number: '2' }).formErrors).toEqual({
213
+ number: 'The option "2" is not valid.',
214
+ });
215
+ });
216
+
217
+ it('Validates numbers or strings when type is an array with null', () => {
218
+ const { handleValidation } = createHeadlessForm(
219
+ {
220
+ properties: {
221
+ number: { type: ['number', 'null'], oneOf: [{ const: 0 }, { const: 1 }, { const: 2 }] },
222
+ },
223
+ },
224
+ { strictInputType: false }
225
+ );
226
+ expect(handleValidation({}).formErrors).toEqual(undefined);
227
+ expect(handleValidation({ number: 3 }).formErrors).toEqual({
228
+ number: 'The option 3 is not valid.',
229
+ });
230
+ expect(handleValidation({ number: 2 }).formErrors).toBeUndefined();
231
+ expect(handleValidation({ number: '2' }).formErrors).toEqual({
232
+ number: 'The option "2" is not valid.',
233
+ });
234
+ expect(handleValidation({ number: null }).formErrors).toEqual(undefined);
235
+ });
236
+ });
@@ -2028,6 +2028,45 @@ describe('createHeadlessForm', () => {
2028
2028
  });
2029
2029
  });
2030
2030
 
2031
+ it('supports oneOf number const', () => {
2032
+ const result = createHeadlessForm({
2033
+ type: 'object',
2034
+ additionalProperties: false,
2035
+ properties: {
2036
+ pets: {
2037
+ title: 'How many pets?',
2038
+ oneOf: [
2039
+ {
2040
+ title: 'One',
2041
+ const: 0,
2042
+ },
2043
+ {
2044
+ title: 'Two',
2045
+ const: 2,
2046
+ },
2047
+ {
2048
+ title: 'null',
2049
+ const: 1,
2050
+ },
2051
+ ],
2052
+ 'x-jsf-presentation': {
2053
+ inputType: 'select',
2054
+ },
2055
+ type: ['number', 'null'],
2056
+ },
2057
+ },
2058
+ required: [],
2059
+ 'x-jsf-order': ['pets'],
2060
+ });
2061
+
2062
+ const fieldValidator = result.fields[0].schema;
2063
+
2064
+ expect(fieldValidator.isValidSync(0)).toBe(true);
2065
+ expect(fieldValidator.isValidSync(1)).toBe(true);
2066
+ expect(() => fieldValidator.validateSync('2')).toThrowError('The option "2" is not valid.');
2067
+ expect(fieldValidator.isValidSync(null)).toBe(true);
2068
+ });
2069
+
2031
2070
  describe('x-jsf-presentation attribute', () => {
2032
2071
  it('support field with "x-jsf-presentation.statement"', () => {
2033
2072
  const result = createHeadlessForm(schemaInputWithStatement);
@@ -93,6 +93,7 @@ export const mockTextMaxLengthInput = {
93
93
  export const mockRadioInputDeprecated = {
94
94
  title: 'Has siblings',
95
95
  description: 'Do you have any siblings?',
96
+ type: 'string',
96
97
  enum: ['yes', 'no'],
97
98
  'x-jsf-presentation': {
98
99
  inputType: 'radio',
@@ -386,6 +387,7 @@ export const mockGroupArrayInput = {
386
387
  ],
387
388
  },
388
389
  title: 'Child Sex',
390
+ type: 'string',
389
391
  },
390
392
  },
391
393
  'x-jsf-order': ['full_name', 'birthdate', 'sex'],
@@ -842,7 +844,7 @@ export const mockRadioInputOptionalNull = {
842
844
  { const: 'yes', title: 'Yes' },
843
845
  { const: 'no', title: 'No' },
844
846
  // JSF excludes the null option from the field output
845
- // But keepts null as an accepted value
847
+ // But keeps null as an accepted value
846
848
  { const: null, title: 'N/A' },
847
849
  ],
848
850
  'x-jsf-presentation': { inputType: 'radio' },
@@ -1612,7 +1614,6 @@ export const schemaFieldsetScopedCondition = {
1612
1614
  properties: {
1613
1615
  has_child: {
1614
1616
  description: 'If yes, it will show its age.',
1615
- maximum: 100,
1616
1617
  'x-jsf-presentation': {
1617
1618
  inputType: 'radio',
1618
1619
  options: [
@@ -1627,7 +1628,7 @@ export const schemaFieldsetScopedCondition = {
1627
1628
  ],
1628
1629
  },
1629
1630
  title: 'Do you have a child?',
1630
- type: 'number',
1631
+ type: 'string',
1631
1632
  },
1632
1633
  age: {
1633
1634
  description: 'This age is required, but the "age" at the root level is still optional.',