Skip to content

Commit 704799f

Browse files
Update utils based on core refactor and update package.jsons (rjsf-team#2903)
* Update utils based on core refactor and update package.jsons - Updated the main `package.json` to bump typescript - Updated the utils `package.json` to bump everything but react - Updated `SchemaUtilsType` to add the `getValidator()` and `doesSchemaUtilsDiffer()` functions - Updated `createSchemaUtils()` to implement the new functions and the tests to validate them - Also updated other types to deal with issues found during core refactor - Changed `FormValidation` to `ErrorSchema` as needed - Updated the `Registry` type to remove `definitions` as it is never used and added the `xxxTemplate` props - Updated many interfaces to make previously required props to be optional - Switched to using the `React.ComponentType` which incorporates the `FunctionComponent` and `ClassComponent` both - Fixed a bug in `getDefaultFormState()` by making the `array` defaults use effectively the same logic as it did in `core` but refactored to a function for type safety - Updated the tests to add one that verifies the bug is fixed * - rollback typescript to previous version due to `fluent-ui` issue * - Fixed bug in `getSchemaType()` related to incorrectly defaulting to `string` when no type exists * - Made `uiSchema` optional in `canExpand()`, `getSubmitButtonOptions()` and `getUiOptions()` - Updated the required-ness of a smattering of props in interfaces * - Made all callbacks be required again * - Made label required again * - More updates to make `uiSchema` optional * - Added new `processSelectValue()` utility, refactored from core's `SelectWidget`, with full tests - Also updated the `UIOptionsType` to add optional `title` and `description` props, typed to `string` to eliminate the need to type cast them * - Added documentation for the `[key: string]` prop * - Responded to reviewer feedback
1 parent 188e038 commit 704799f

25 files changed

+16545
-14571
lines changed

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-jsonschema-form",
3-
"version": "2.0.0-alpha.1",
3+
"version": "4.2.0",
44
"private": true,
55
"description": "monorepo for react-jsonschema-form and its themes",
66
"scripts": {

packages/utils/package-lock.json

+16,178-14,447
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/utils/package.json

+17-18
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,39 @@
2121
"react": ">=16 || >=17"
2222
},
2323
"dependencies": {
24-
"json-schema-merge-allof": "^0.6.0",
24+
"json-schema-merge-allof": "^0.8.1",
2525
"jsonpointer": "^5.0.0",
2626
"lodash": "^4.17.15",
2727
"lodash-es": "^4.17.15",
28-
"react-is": "^16.9.0"
28+
"react-is": "^18.2.0"
2929
},
3030
"devDependencies": {
31-
"@babel/core": "^7.16.0",
32-
"@babel/plugin-proposal-class-properties": "^7.16.0",
33-
"@babel/plugin-transform-modules-commonjs": "^7.16.0",
34-
"@babel/plugin-transform-react-jsx": "^7.16.0",
35-
"@babel/preset-env": "^7.16.4",
36-
"@babel/preset-react": "^7.16.0",
31+
"@babel/core": "^7.18.6",
32+
"@babel/plugin-proposal-class-properties": "^7.18.6",
33+
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
34+
"@babel/plugin-transform-react-jsx": "^7.18.6",
35+
"@babel/preset-env": "^7.18.6",
36+
"@babel/preset-react": "^7.18.6",
3737
"@types/jest": "^25.2.3",
38-
"@types/jest-expect-message": "^1.0.3",
38+
"@types/jest-expect-message": "^1.0.4",
3939
"@types/json-schema": "^7.0.9",
4040
"@types/json-schema-merge-allof": "^0.6.1",
4141
"@types/lodash": "^4.14.182",
4242
"@types/react": "^16.14.25",
43-
"@types/react-is": "^16.7.2",
43+
"@types/react-is": "^17.0.3",
4444
"@types/react-test-renderer": "^16.9.5",
45-
"@types/sinon": "^9.0.0",
46-
"@typescript-eslint/eslint-plugin": "^5.25.0",
47-
"@typescript-eslint/parser": "^5.25.0",
48-
"babel-jest": "^28.1.0",
49-
"babel-preset-jest": "^28.0.2",
50-
"eslint": "^8.15.0",
45+
"@typescript-eslint/eslint-plugin": "^5.30.3",
46+
"@typescript-eslint/parser": "^5.30.3",
47+
"babel-jest": "^28.1.2",
48+
"babel-preset-jest": "^28.1.1",
49+
"eslint": "^8.18.0",
5150
"eslint-plugin-import": "^2.26.0",
52-
"jest": "^28.1.0",
51+
"jest": "^28.1.2",
5352
"jest-expect-message": "^1.0.2",
5453
"react": "^16.14.0",
5554
"react-dom": "^16.14.0",
5655
"react-test-renderer": "^16.14.0",
57-
"rimraf": "^2.6.3",
56+
"rimraf": "^3.0.2",
5857
"tsdx": "^0.14.1"
5958
},
6059
"publishConfig": {

packages/utils/src/canExpand.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import getUiOptions from './getUiOptions';
66
* the `formData` object doesn't already have `schema.maxProperties` elements.
77
*
88
* @param schema - The schema for the field that is being checked
9-
* @param uiSchema - The uiSchema for the field
10-
* @param formData - The formData for the field
9+
* @param [uiSchema={}] - The uiSchema for the field
10+
* @param [formData] - The formData for the field
1111
* @returns - True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit
1212
*/
13-
export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F>, formData: T) {
13+
export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, formData?: T) {
1414
if (!schema.additionalProperties) {
1515
return false;
1616
}
@@ -20,7 +20,7 @@ export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema
2020
}
2121
// if ui:options.expandable was not explicitly set to false, we can add
2222
// another property if we have not exceeded maxProperties yet
23-
if (schema.maxProperties !== undefined) {
23+
if (schema.maxProperties !== undefined && formData) {
2424
return Object.keys(formData).length < schema.maxProperties;
2525
}
2626
return true;

packages/utils/src/createSchemaUtils.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import deepEquals from './deepEquals';
12
import { IdSchema, PathSchema, RJSFSchema, SchemaUtilsType, UiSchema, ValidatorType } from './types';
23
import {
34
getDefaultFormState,
@@ -30,6 +31,29 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
3031
this.validator = validator;
3132
}
3233

34+
/** Returns the `ValidatorType` in the `SchemaUtilsType`
35+
*
36+
* @returns - The `ValidatorType`
37+
*/
38+
getValidator() {
39+
return this.validator;
40+
}
41+
42+
/** Determines whether either the `validator` and `rootSchema` differ from the ones associated with this instance of
43+
* the `SchemaUtilsType`. If either `validator` or `rootSchema` are falsy, then return false to prevent the creation
44+
* of a new `SchemaUtilsType` with incomplete properties.
45+
*
46+
* @param validator - An implementation of the `ValidatorType` interface that will be compared against the current one
47+
* @param rootSchema - The root schema that will be compared against the current one
48+
* @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema`
49+
*/
50+
doesSchemaUtilsDiffer(validator: ValidatorType, rootSchema: RJSFSchema): boolean {
51+
if (!validator || !rootSchema) {
52+
return false;
53+
}
54+
return this.validator !== validator || !deepEquals(this.rootSchema, rootSchema);
55+
}
56+
3357
/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have
3458
* computed to have defaults provided in the `schema`.
3559
*
@@ -46,10 +70,10 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
4670
* should be displayed in a UI.
4771
*
4872
* @param schema - The schema for which the display label flag is desired
49-
* @param uiSchema - The UI schema from which to derive potentially displayable information
73+
* @param [uiSchema] - The UI schema from which to derive potentially displayable information
5074
* @returns - True if the label should be displayed or false if it should not
5175
*/
52-
getDisplayLabel<F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F>) {
76+
getDisplayLabel<F = any>(schema: RJSFSchema, uiSchema?: UiSchema<T, F>) {
5377
return getDisplayLabel<T, F>(this.validator, schema, uiSchema, this.rootSchema);
5478
}
5579

@@ -66,10 +90,10 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
6690
/** Checks to see if the `schema` and `uiSchema` combination represents an array of files
6791
*
6892
* @param schema - The schema for which check for array of files flag is desired
69-
* @param uiSchema - The UI schema from which to check the widget
93+
* @param [uiSchema] - The UI schema from which to check the widget
7094
* @returns - True if schema/uiSchema contains an array of files, otherwise false
7195
*/
72-
isFilesArray<F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F>) {
96+
isFilesArray<F = any>(schema: RJSFSchema, uiSchema?: UiSchema<T, F>) {
7397
return isFilesArray<T, F>(this.validator, schema, uiSchema, this.rootSchema);
7498
}
7599

packages/utils/src/getSchemaType.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { RJSFSchema } from './types';
1010
* - type is an array with a length of 2 and one type is 'null': Returns the other type
1111
*
1212
* @param schema - The schema for which to get the type
13-
* @returns - The type of the schema, defaulting to `string` if not available
13+
* @returns - The type of the schema
1414
*/
15-
export default function getSchemaType(schema: RJSFSchema): string | string[] {
15+
export default function getSchemaType(schema: RJSFSchema): string | string[] | undefined {
1616
let { type } = schema;
1717

1818
if (!type && schema.const) {
@@ -31,5 +31,5 @@ export default function getSchemaType(schema: RJSFSchema): string | string[] {
3131
type = type.find(type => type !== 'null');
3232
}
3333

34-
return type || 'string';
34+
return type;
3535
}

packages/utils/src/getSubmitButtonOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ export const DEFAULT_OPTIONS = {
1414

1515
/** Extracts any `ui:submitButtonOptions` from the `uiSchema` and merges them onto the `DEFAULT_OPTIONS`
1616
*
17-
* @param uiSchema - the UI Schema from which to extract submit button props
17+
* @param [uiSchema={}] - the UI Schema from which to extract submit button props
1818
* @returns - The merging of the `DEFAULT_OPTIONS` with any custom ones
1919
*/
20-
export default function getSubmitButtonOptions<T = any, F = any>(uiSchema: UiSchema<T, F>) {
20+
export default function getSubmitButtonOptions<T = any, F = any>(uiSchema: UiSchema<T, F> = {}) {
2121
const uiOptions = getUiOptions<T, F>(uiSchema);
2222
if (uiOptions && uiOptions[SUBMIT_BTN_OPTIONS_KEY]) {
2323
const options = uiOptions[SUBMIT_BTN_OPTIONS_KEY] as UISchemaSubmitButtonOptions;

packages/utils/src/getUiOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { UIOptionsType, UiSchema } from './types';
55
/** Get all passed options from ui:options, and ui:<optionName>, returning them in an object with the `ui:`
66
* stripped off.
77
*
8-
* @param uiSchema - The UI Schema from which to get any `ui:xxx` options
8+
* @param [uiSchema={}] - The UI Schema from which to get any `ui:xxx` options
99
* @returns - An object containing all of the `ui:xxx` options with the stripped off
1010
*/
11-
export default function getUiOptions<T = any, F = any>(uiSchema: UiSchema<T, F>): UIOptionsType {
11+
export default function getUiOptions<T = any, F = any>(uiSchema: UiSchema<T, F> = {}): UIOptionsType {
1212
return Object.keys(uiSchema)
1313
.filter(key => key.indexOf('ui:') === 0)
1414
.reduce((options, key) => {

packages/utils/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import optionsList from './optionsList';
2323
import orderProperties from './orderProperties';
2424
import pad from './pad';
2525
import parseDateString from './parseDateString';
26+
import processSelectValue from './processSelectValue';
2627
import rangeSpec from './rangeSpec';
2728
import schemaRequiresTrueValue from './schemaRequiresTrueValue';
2829
import shouldRender from './shouldRender';
@@ -61,6 +62,7 @@ export {
6162
orderProperties,
6263
pad,
6364
parseDateString,
65+
processSelectValue,
6466
rangeSpec,
6567
schemaRequiresTrueValue,
6668
shouldRender,
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import get from 'lodash/get';
2+
3+
import { RJSFSchema } from './types';
4+
import asNumber from './asNumber';
5+
import guessType from './guessType';
6+
7+
const nums = new Set<any>(['number', 'integer']);
8+
9+
/** Returns the real value for a select widget due to a silly limitation in the DOM which causes option change event
10+
* values to always be retrieved as strings.
11+
*
12+
* @param schema - The schema to used to determine the value's true type
13+
* @param [value] - The value to convert
14+
*/
15+
export default function processSelectValue(schema: RJSFSchema, value?: any) {
16+
const { enum: schemaEnum, type, items } = schema;
17+
if (value === '') {
18+
return undefined;
19+
}
20+
if (type === 'array' && items && nums.has(get(items, 'type'))) {
21+
return value.map(asNumber);
22+
}
23+
if (type === 'boolean') {
24+
return value === 'true';
25+
}
26+
if (nums.has(type)) {
27+
return asNumber(value);
28+
}
29+
30+
// If type is undefined, but an enum is present, try and infer the type from
31+
// the enum values
32+
if (Array.isArray(schemaEnum)) {
33+
if (schemaEnum.every((x: any) => nums.has(guessType(x)))) {
34+
return asNumber(value);
35+
}
36+
if (schemaEnum.every((x: any) => guessType(x) === 'boolean')) {
37+
return value === 'true';
38+
}
39+
}
40+
41+
return value;
42+
}

packages/utils/src/schema/getDefaultFormState.ts

+36-15
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,43 @@ import { GenericObjectType, RJSFSchema, ValidatorType } from '../types';
2020
import isMultiSelect from './isMultiSelect';
2121
import retrieveSchema, { resolveDependencies } from './retrieveSchema';
2222

23-
/** Given a `schema` will return an inner schema that represents either an element in a `schema.items` array (when
24-
* provided a valid `idx`), `schema.items` if it is not an array, `schema.additionalItems` when it is an object or
25-
* an empty schema if no previous condition passes.
23+
/** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function.
24+
*/
25+
export enum AdditionalItemsHandling {
26+
Ignore,
27+
Invert,
28+
Fallback,
29+
}
30+
31+
/** Given a `schema` will return an inner schema that for an array item. This is computed differently based on the
32+
* `additionalItems` enum and the value of `idx`. There are four possible returns:
33+
* 1. If `idx` is >= 0, then if `schema.items` is an array the `idx`th element of the array is returned if it is a valid
34+
* index and not a boolean, otherwise it falls through to 3.
35+
* 2. If `schema.items` is not an array AND truthy and not a boolean, then `schema.items` is returned since it actually
36+
* is a schema, otherwise it falls through to 3.
37+
* 3. If `additionalItems` is not `AdditionalItemsHandling.Ignore` and `schema.additionalItems` is an object, then
38+
* `schema.additionalItems` is returned since it actually is a schema, otherwise it falls through to 4.
39+
* 4. {} is returned representing an empty schema
2640
*
2741
* @param schema - The schema from which to get the particular item
42+
* @param [additionalItems=AdditionalItemsHandling.Ignore] - How do we want to handle additional items?
2843
* @param [idx=-1] - Index, if non-negative, will be used to return the idx-th element in a `schema.items` array
29-
* @returns - The best fit schema object from the `schema`
44+
* @returns - The best fit schema object from the `schema` given the `additionalItems` and `idx` modifiers
3045
*/
31-
export function getSchemaItem(schema: RJSFSchema, idx = -1) {
32-
if (Array.isArray(schema.items) && idx >= 0 && idx < schema.items.length) {
33-
return schema.items[idx] as RJSFSchema;
34-
}
35-
if (schema.items && !Array.isArray(schema.items)) {
36-
return schema.items as RJSFSchema;
46+
export function getInnerSchemaForArrayItem(
47+
schema: RJSFSchema, additionalItems: AdditionalItemsHandling = AdditionalItemsHandling.Ignore, idx = -1
48+
): RJSFSchema {
49+
if (idx >= 0) {
50+
if (Array.isArray(schema.items) && idx < schema.items.length) {
51+
const item = schema.items[idx];
52+
if (typeof item !== 'boolean') {
53+
return item;
54+
}
55+
}
56+
} else if (schema.items && !Array.isArray(schema.items) && typeof schema.items !== 'boolean') {
57+
return schema.items;
3758
}
38-
if (isObject(schema.additionalItems)) {
59+
if (additionalItems !== AdditionalItemsHandling.Ignore && isObject(schema.additionalItems)) {
3960
return schema.additionalItems as RJSFSchema;
4061
}
4162
return {};
@@ -140,7 +161,7 @@ export function computeDefaults<T = any>(
140161
// Inject defaults into existing array defaults
141162
if (Array.isArray(defaults)) {
142163
defaults = defaults.map((item, idx) => {
143-
const schemaItem = getSchemaItem(schema, idx);
164+
const schemaItem: RJSFSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Fallback, idx);
144165
return computeDefaults<T>(
145166
validator,
146167
schemaItem,
@@ -152,10 +173,11 @@ export function computeDefaults<T = any>(
152173

153174
// Deeply inject defaults into already existing form data
154175
if (Array.isArray(rawFormData)) {
176+
const schemaItem: RJSFSchema = getInnerSchemaForArrayItem(schema);
155177
defaults = rawFormData.map((item: T, idx: number) => {
156178
return computeDefaults<T>(
157179
validator,
158-
getSchemaItem(schema, idx),
180+
schemaItem,
159181
get(defaults, [idx]),
160182
rootSchema,
161183
item
@@ -168,14 +190,13 @@ export function computeDefaults<T = any>(
168190
if (schema.minItems > defaultsLength) {
169191
const defaultEntries: T[] = (defaults || []) as T[];
170192
// populate the array with the defaults
171-
const fillerSchema: RJSFSchema = getSchemaItem(schema);
193+
const fillerSchema: RJSFSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Invert);
172194
const fillerDefault = fillerSchema.default;
173195
const fillerEntries: T[] = fill(
174196
new Array(schema.minItems - defaultsLength),
175197
computeDefaults<any>(validator, fillerSchema, fillerDefault, rootSchema)
176198
) as T[];
177199
// then fill up the rest with either the item default or empty, up to minItems
178-
179200
return defaultEntries.concat(fillerEntries);
180201
}
181202
}

packages/utils/src/schema/getDisplayLabel.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import isMultiSelect from './isMultiSelect';
1111
*
1212
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
1313
* @param schema - The schema for which the display label flag is desired
14-
* @param uiSchema - The UI schema from which to derive potentially displayable information
14+
* @param [uiSchema={}] - The UI schema from which to derive potentially displayable information
1515
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
1616
* @returns - True if the label should be displayed or false if it should not
1717
*/
1818
export default function getDisplayLabel<T = any, F = any>(
19-
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F>, rootSchema?: RJSFSchema
19+
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, rootSchema?: RJSFSchema
2020
): boolean {
2121
const uiOptions = getUiOptions<T, F>(uiSchema);
2222
const { label = true } = uiOptions;

packages/utils/src/schema/isFilesArray.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import retrieveSchema from './retrieveSchema';
66
*
77
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
88
* @param schema - The schema for which check for array of files flag is desired
9-
* @param uiSchema - The UI schema from which to check the widget
9+
* @param [uiSchema={}] - The UI schema from which to check the widget
1010
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
1111
* @returns - True if schema/uiSchema contains an array of files, otherwise false
1212
*/
1313
export default function isFilesArray<T = any, F = any>(
14-
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F>, rootSchema?: RJSFSchema
14+
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, rootSchema?: RJSFSchema
1515
) {
1616
if (uiSchema[UI_WIDGET_KEY] === 'files') {
1717
return true;

0 commit comments

Comments
 (0)