Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update utils based on core refactor and update package.jsons #2903

Merged
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-jsonschema-form",
"version": "2.0.0-alpha.1",
"version": "4.2.0",
"private": true,
"description": "monorepo for react-jsonschema-form and its themes",
"scripts": {
Expand Down
30,625 changes: 16,178 additions & 14,447 deletions packages/utils/package-lock.json

Large diffs are not rendered by default.

35 changes: 17 additions & 18 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,39 @@
"react": ">=16 || >=17"
},
"dependencies": {
"json-schema-merge-allof": "^0.6.0",
"json-schema-merge-allof": "^0.8.1",
"jsonpointer": "^5.0.0",
"lodash": "^4.17.15",
"lodash-es": "^4.17.15",
"react-is": "^16.9.0"
"react-is": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-transform-modules-commonjs": "^7.16.0",
"@babel/plugin-transform-react-jsx": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/core": "^7.18.6",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@types/jest": "^25.2.3",
"@types/jest-expect-message": "^1.0.3",
"@types/jest-expect-message": "^1.0.4",
"@types/json-schema": "^7.0.9",
"@types/json-schema-merge-allof": "^0.6.1",
"@types/lodash": "^4.14.182",
"@types/react": "^16.14.25",
"@types/react-is": "^16.7.2",
"@types/react-is": "^17.0.3",
"@types/react-test-renderer": "^16.9.5",
"@types/sinon": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"babel-jest": "^28.1.0",
"babel-preset-jest": "^28.0.2",
"eslint": "^8.15.0",
"@typescript-eslint/eslint-plugin": "^5.30.3",
"@typescript-eslint/parser": "^5.30.3",
"babel-jest": "^28.1.2",
"babel-preset-jest": "^28.1.1",
"eslint": "^8.18.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^28.1.0",
"jest": "^28.1.2",
"jest-expect-message": "^1.0.2",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-test-renderer": "^16.14.0",
"rimraf": "^2.6.3",
"rimraf": "^3.0.2",
"tsdx": "^0.14.1"
},
"publishConfig": {
Expand Down
8 changes: 4 additions & 4 deletions packages/utils/src/canExpand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import getUiOptions from './getUiOptions';
* the `formData` object doesn't already have `schema.maxProperties` elements.
*
* @param schema - The schema for the field that is being checked
* @param uiSchema - The uiSchema for the field
* @param formData - The formData for the field
* @param [uiSchema={}] - The uiSchema for the field
* @param [formData] - The formData for the field
* @returns - True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit
*/
export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F>, formData: T) {
export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, formData?: T) {
if (!schema.additionalProperties) {
return false;
}
Expand All @@ -20,7 +20,7 @@ export default function canExpand<T = any, F = any>(schema: RJSFSchema, uiSchema
}
// if ui:options.expandable was not explicitly set to false, we can add
// another property if we have not exceeded maxProperties yet
if (schema.maxProperties !== undefined) {
if (schema.maxProperties !== undefined && formData) {
return Object.keys(formData).length < schema.maxProperties;
}
return true;
Expand Down
32 changes: 28 additions & 4 deletions packages/utils/src/createSchemaUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import deepEquals from './deepEquals';
import { IdSchema, PathSchema, RJSFSchema, SchemaUtilsType, UiSchema, ValidatorType } from './types';
import {
getDefaultFormState,
Expand Down Expand Up @@ -30,6 +31,29 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
this.validator = validator;
}

/** Returns the `ValidatorType` in the `SchemaUtilsType`
*
* @returns - The `ValidatorType`
*/
getValidator() {
return this.validator;
}

/** Determines whether either the `validator` and `rootSchema` differ from the ones associated with this instance of
* the `SchemaUtilsType`. If either `validator` or `rootSchema` are falsy, then return false to prevent the creation
* of a new `SchemaUtilsType` with incomplete properties.
*
* @param validator - An implementation of the `ValidatorType` interface that will be compared against the current one
* @param rootSchema - The root schema that will be compared against the current one
* @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema`
*/
doesSchemaUtilsDiffer(validator: ValidatorType, rootSchema: RJSFSchema): boolean {
if (!validator || !rootSchema) {
return false;
}
return this.validator !== validator || !deepEquals(this.rootSchema, rootSchema);
}

/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have
* computed to have defaults provided in the `schema`.
*
Expand All @@ -46,10 +70,10 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
* should be displayed in a UI.
*
* @param schema - The schema for which the display label flag is desired
* @param uiSchema - The UI schema from which to derive potentially displayable information
* @param [uiSchema] - The UI schema from which to derive potentially displayable information
* @returns - True if the label should be displayed or false if it should not
*/
getDisplayLabel<F = any>(schema: RJSFSchema, uiSchema: UiSchema<T, F>) {
getDisplayLabel<F = any>(schema: RJSFSchema, uiSchema?: UiSchema<T, F>) {
return getDisplayLabel<T, F>(this.validator, schema, uiSchema, this.rootSchema);
}

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

Expand Down
6 changes: 3 additions & 3 deletions packages/utils/src/getSchemaType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { RJSFSchema } from './types';
* - type is an array with a length of 2 and one type is 'null': Returns the other type
*
* @param schema - The schema for which to get the type
* @returns - The type of the schema, defaulting to `string` if not available
* @returns - The type of the schema
*/
export default function getSchemaType(schema: RJSFSchema): string | string[] {
export default function getSchemaType(schema: RJSFSchema): string | string[] | undefined {
let { type } = schema;

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

return type || 'string';
return type;
}
4 changes: 2 additions & 2 deletions packages/utils/src/getSubmitButtonOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export const DEFAULT_OPTIONS = {

/** Extracts any `ui:submitButtonOptions` from the `uiSchema` and merges them onto the `DEFAULT_OPTIONS`
*
* @param uiSchema - the UI Schema from which to extract submit button props
* @param [uiSchema={}] - the UI Schema from which to extract submit button props
* @returns - The merging of the `DEFAULT_OPTIONS` with any custom ones
*/
export default function getSubmitButtonOptions<T = any, F = any>(uiSchema: UiSchema<T, F>) {
export default function getSubmitButtonOptions<T = any, F = any>(uiSchema: UiSchema<T, F> = {}) {
const uiOptions = getUiOptions<T, F>(uiSchema);
if (uiOptions && uiOptions[SUBMIT_BTN_OPTIONS_KEY]) {
const options = uiOptions[SUBMIT_BTN_OPTIONS_KEY] as UISchemaSubmitButtonOptions;
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/getUiOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { UIOptionsType, UiSchema } from './types';
/** Get all passed options from ui:options, and ui:<optionName>, returning them in an object with the `ui:`
* stripped off.
*
* @param uiSchema - The UI Schema from which to get any `ui:xxx` options
* @param [uiSchema={}] - The UI Schema from which to get any `ui:xxx` options
* @returns - An object containing all of the `ui:xxx` options with the stripped off
*/
export default function getUiOptions<T = any, F = any>(uiSchema: UiSchema<T, F>): UIOptionsType {
export default function getUiOptions<T = any, F = any>(uiSchema: UiSchema<T, F> = {}): UIOptionsType {
return Object.keys(uiSchema)
.filter(key => key.indexOf('ui:') === 0)
.reduce((options, key) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import optionsList from './optionsList';
import orderProperties from './orderProperties';
import pad from './pad';
import parseDateString from './parseDateString';
import processSelectValue from './processSelectValue';
import rangeSpec from './rangeSpec';
import schemaRequiresTrueValue from './schemaRequiresTrueValue';
import shouldRender from './shouldRender';
Expand Down Expand Up @@ -61,6 +62,7 @@ export {
orderProperties,
pad,
parseDateString,
processSelectValue,
rangeSpec,
schemaRequiresTrueValue,
shouldRender,
Expand Down
42 changes: 42 additions & 0 deletions packages/utils/src/processSelectValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import get from 'lodash/get';

import { RJSFSchema } from './types';
import asNumber from './asNumber';
import guessType from './guessType';

const nums = new Set<any>(['number', 'integer']);

/** Returns the real value for a select widget due to a silly limitation in the DOM which causes option change event
* values to always be retrieved as strings.
*
* @param schema - The schema to used to determine the value's true type
* @param [value] - The value to convert
*/
export default function processSelectValue(schema: RJSFSchema, value?: any) {
const { enum: schemaEnum, type, items } = schema;
if (value === '') {
return undefined;
}
if (type === 'array' && items && nums.has(get(items, 'type'))) {
return value.map(asNumber);
}
if (type === 'boolean') {
return value === 'true';
}
if (nums.has(type)) {
return asNumber(value);
}

// If type is undefined, but an enum is present, try and infer the type from
// the enum values
if (Array.isArray(schemaEnum)) {
if (schemaEnum.every((x: any) => nums.has(guessType(x)))) {
return asNumber(value);
}
if (schemaEnum.every((x: any) => guessType(x) === 'boolean')) {
return value === 'true';
}
}

return value;
}
51 changes: 36 additions & 15 deletions packages/utils/src/schema/getDefaultFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,43 @@ import { GenericObjectType, RJSFSchema, ValidatorType } from '../types';
import isMultiSelect from './isMultiSelect';
import retrieveSchema, { resolveDependencies } from './retrieveSchema';

/** Given a `schema` will return an inner schema that represents either an element in a `schema.items` array (when
* provided a valid `idx`), `schema.items` if it is not an array, `schema.additionalItems` when it is an object or
* an empty schema if no previous condition passes.
/** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function.
*/
export enum AdditionalItemsHandling {
Ignore,
Invert,
Fallback,
}

/** Given a `schema` will return an inner schema that for an array item. This is computed differently based on the
* `additionalItems` enum and the value of `idx`. There are four possible returns:
* 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
* index and not a boolean, otherwise it falls through to 3.
* 2. If `schema.items` is not an array AND truthy and not a boolean, then `schema.items` is returned since it actually
* is a schema, otherwise it falls through to 3.
* 3. If `additionalItems` is not `AdditionalItemsHandling.Ignore` and `schema.additionalItems` is an object, then
* `schema.additionalItems` is returned since it actually is a schema, otherwise it falls through to 4.
* 4. {} is returned representing an empty schema
*
* @param schema - The schema from which to get the particular item
* @param [additionalItems=AdditionalItemsHandling.Ignore] - How do we want to handle additional items?
* @param [idx=-1] - Index, if non-negative, will be used to return the idx-th element in a `schema.items` array
* @returns - The best fit schema object from the `schema`
* @returns - The best fit schema object from the `schema` given the `additionalItems` and `idx` modifiers
*/
export function getSchemaItem(schema: RJSFSchema, idx = -1) {
if (Array.isArray(schema.items) && idx >= 0 && idx < schema.items.length) {
return schema.items[idx] as RJSFSchema;
}
if (schema.items && !Array.isArray(schema.items)) {
return schema.items as RJSFSchema;
export function getInnerSchemaForArrayItem(
schema: RJSFSchema, additionalItems: AdditionalItemsHandling = AdditionalItemsHandling.Ignore, idx = -1
): RJSFSchema {
if (idx >= 0) {
if (Array.isArray(schema.items) && idx < schema.items.length) {
const item = schema.items[idx];
if (typeof item !== 'boolean') {
return item;
}
}
} else if (schema.items && !Array.isArray(schema.items) && typeof schema.items !== 'boolean') {
return schema.items;
}
if (isObject(schema.additionalItems)) {
if (additionalItems !== AdditionalItemsHandling.Ignore && isObject(schema.additionalItems)) {
return schema.additionalItems as RJSFSchema;
}
return {};
Expand Down Expand Up @@ -140,7 +161,7 @@ export function computeDefaults<T = any>(
// Inject defaults into existing array defaults
if (Array.isArray(defaults)) {
defaults = defaults.map((item, idx) => {
const schemaItem = getSchemaItem(schema, idx);
const schemaItem: RJSFSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Fallback, idx);
return computeDefaults<T>(
validator,
schemaItem,
Expand All @@ -152,10 +173,11 @@ export function computeDefaults<T = any>(

// Deeply inject defaults into already existing form data
if (Array.isArray(rawFormData)) {
const schemaItem: RJSFSchema = getInnerSchemaForArrayItem(schema);
defaults = rawFormData.map((item: T, idx: number) => {
return computeDefaults<T>(
validator,
getSchemaItem(schema, idx),
schemaItem,
get(defaults, [idx]),
rootSchema,
item
Expand All @@ -168,14 +190,13 @@ export function computeDefaults<T = any>(
if (schema.minItems > defaultsLength) {
const defaultEntries: T[] = (defaults || []) as T[];
// populate the array with the defaults
const fillerSchema: RJSFSchema = getSchemaItem(schema);
const fillerSchema: RJSFSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Invert);
const fillerDefault = fillerSchema.default;
const fillerEntries: T[] = fill(
new Array(schema.minItems - defaultsLength),
computeDefaults<any>(validator, fillerSchema, fillerDefault, rootSchema)
) as T[];
// then fill up the rest with either the item default or empty, up to minItems

return defaultEntries.concat(fillerEntries);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/schema/getDisplayLabel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import isMultiSelect from './isMultiSelect';
*
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
* @param schema - The schema for which the display label flag is desired
* @param uiSchema - The UI schema from which to derive potentially displayable information
* @param [uiSchema={}] - The UI schema from which to derive potentially displayable information
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
* @returns - True if the label should be displayed or false if it should not
*/
export default function getDisplayLabel<T = any, F = any>(
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F>, rootSchema?: RJSFSchema
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, rootSchema?: RJSFSchema
): boolean {
const uiOptions = getUiOptions<T, F>(uiSchema);
const { label = true } = uiOptions;
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/schema/isFilesArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import retrieveSchema from './retrieveSchema';
*
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
* @param schema - The schema for which check for array of files flag is desired
* @param uiSchema - The UI schema from which to check the widget
* @param [uiSchema={}] - The UI schema from which to check the widget
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
* @returns - True if schema/uiSchema contains an array of files, otherwise false
*/
export default function isFilesArray<T = any, F = any>(
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F>, rootSchema?: RJSFSchema
validator: ValidatorType, schema: RJSFSchema, uiSchema: UiSchema<T, F> = {}, rootSchema?: RJSFSchema
) {
if (uiSchema[UI_WIDGET_KEY] === 'files') {
return true;
Expand Down
Loading