@rjsf/utils 6.6.2 → 6.7.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.
Files changed (79) hide show
  1. package/LICENSE.md +201 -0
  2. package/dist/index.cjs +229 -108
  3. package/dist/index.cjs.map +4 -4
  4. package/dist/utils.esm.js +229 -108
  5. package/dist/utils.esm.js.map +4 -4
  6. package/dist/utils.umd.js +260 -136
  7. package/lib/SelectedOptionDescription.d.ts +4 -0
  8. package/lib/SelectedOptionDescription.js +24 -0
  9. package/lib/SelectedOptionDescription.js.map +1 -0
  10. package/lib/constants.d.ts +8 -5
  11. package/lib/constants.js +10 -7
  12. package/lib/constants.js.map +1 -1
  13. package/lib/createSchemaUtils.js +1 -1
  14. package/lib/createSchemaUtils.js.map +1 -1
  15. package/lib/enums.d.ts +4 -0
  16. package/lib/enums.js +4 -0
  17. package/lib/enums.js.map +1 -1
  18. package/lib/getSubmitButtonOptions.js +1 -1
  19. package/lib/getSubmitButtonOptions.js.map +1 -1
  20. package/lib/getWidget.js +2 -1
  21. package/lib/getWidget.js.map +1 -1
  22. package/lib/hashForSchema.d.ts +2 -0
  23. package/lib/hashForSchema.js +3 -1
  24. package/lib/hashForSchema.js.map +1 -1
  25. package/lib/index.d.ts +5 -2
  26. package/lib/index.js +4 -2
  27. package/lib/index.js.map +1 -1
  28. package/lib/isFixedItems.d.ts +2 -2
  29. package/lib/isFixedItems.js +3 -3
  30. package/lib/isFixedItems.js.map +1 -1
  31. package/lib/isRootSchema.js +2 -3
  32. package/lib/isRootSchema.js.map +1 -1
  33. package/lib/logUnsupportedDefaultForEnum.d.ts +9 -0
  34. package/lib/logUnsupportedDefaultForEnum.js +18 -0
  35. package/lib/logUnsupportedDefaultForEnum.js.map +1 -0
  36. package/lib/mergeSchemas.js +8 -1
  37. package/lib/mergeSchemas.js.map +1 -1
  38. package/lib/optionsList.js +22 -21
  39. package/lib/optionsList.js.map +1 -1
  40. package/lib/resolveUiSchema.js.map +1 -1
  41. package/lib/schema/getDefaultFormState.d.ts +1 -1
  42. package/lib/schema/getDefaultFormState.js +54 -17
  43. package/lib/schema/getDefaultFormState.js.map +1 -1
  44. package/lib/schema/getDisplayLabel.js +2 -3
  45. package/lib/schema/getDisplayLabel.js.map +1 -1
  46. package/lib/schema/omitExtraData.js +11 -0
  47. package/lib/schema/omitExtraData.js.map +1 -1
  48. package/lib/schema/retrieveSchema.d.ts +5 -1
  49. package/lib/schema/retrieveSchema.js +49 -14
  50. package/lib/schema/retrieveSchema.js.map +1 -1
  51. package/lib/schemaRequiresTrueValue.js +4 -3
  52. package/lib/schemaRequiresTrueValue.js.map +1 -1
  53. package/lib/tsconfig.tsbuildinfo +1 -1
  54. package/lib/types.d.ts +24 -0
  55. package/lib/useAltDateWidgetProps.d.ts +1 -1
  56. package/lib/useFileWidgetProps.js +2 -2
  57. package/lib/useFileWidgetProps.js.map +1 -1
  58. package/package.json +19 -19
  59. package/src/SelectedOptionDescription.tsx +48 -0
  60. package/src/constants.ts +10 -7
  61. package/src/createSchemaUtils.ts +1 -1
  62. package/src/enums.ts +4 -0
  63. package/src/getSubmitButtonOptions.ts +1 -1
  64. package/src/getWidget.tsx +1 -1
  65. package/src/hashForSchema.ts +3 -1
  66. package/src/index.ts +6 -0
  67. package/src/isFixedItems.ts +3 -3
  68. package/src/isRootSchema.ts +2 -4
  69. package/src/logUnsupportedDefaultForEnum.ts +28 -0
  70. package/src/mergeSchemas.ts +9 -2
  71. package/src/optionsList.ts +20 -22
  72. package/src/resolveUiSchema.ts +12 -4
  73. package/src/schema/getDefaultFormState.ts +80 -22
  74. package/src/schema/getDisplayLabel.ts +3 -4
  75. package/src/schema/omitExtraData.ts +11 -0
  76. package/src/schema/retrieveSchema.ts +59 -13
  77. package/src/schemaRequiresTrueValue.ts +3 -3
  78. package/src/types.ts +31 -0
  79. package/src/useFileWidgetProps.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/utils",
3
- "version": "6.6.2",
3
+ "version": "6.7.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -42,20 +42,6 @@
42
42
  "engines": {
43
43
  "node": ">=20"
44
44
  },
45
- "scripts": {
46
- "compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
47
- "build:ts": "npm run compileReplacer && rimraf ./lib && tsc -b tsconfig.build.json && tsc-alias -p tsconfig.build.json",
48
- "build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --sourcemap --packages=external --format=cjs",
49
- "build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/utils.esm.js --sourcemap --packages=external --format=esm",
50
- "build:umd": "rollup dist/utils.esm.js --format=umd --file=dist/utils.umd.js --name=@rjsf/utils",
51
- "build": "npm run build:ts && npm run build:cjs && npm run build:esm && npm run build:umd",
52
- "cs-check": "oxfmt --check \"{src,test}/**/*.{ts,tsx}\"",
53
- "cs-format": "oxfmt \"{src,test}/**/*.{ts,tsx}\"",
54
- "lint": "oxlint src test",
55
- "precommit": "lint-staged",
56
- "test": "vitest run",
57
- "test:debug": "node --inspect-brk ../../node_modules/.bin/vitest --pool=forks"
58
- },
59
45
  "lint-staged": {
60
46
  "{src,test}/**/*.{ts,tsx}": [
61
47
  "oxlint --fix",
@@ -68,7 +54,7 @@
68
54
  "dependencies": {
69
55
  "@x0k/json-schema-merge": "^1.0.3",
70
56
  "fast-equals": "^6.0.0",
71
- "fast-uri": "^3.1.2",
57
+ "fast-uri": "^4.0.0",
72
58
  "jsonpointer": "^5.0.1",
73
59
  "lodash": "^4.18.1",
74
60
  "lodash-es": "^4.18.1",
@@ -77,7 +63,7 @@
77
63
  "devDependencies": {
78
64
  "@types/json-schema": "^7.0.15",
79
65
  "@types/react-is": "^18.3.1",
80
- "deep-freeze-es6": "^4.0.1"
66
+ "deep-freeze-es6": "^5.0.0"
81
67
  },
82
68
  "publishConfig": {
83
69
  "access": "public"
@@ -96,5 +82,19 @@
96
82
  "type": "git",
97
83
  "url": "git+https://github.com/rjsf-team/react-jsonschema-form.git"
98
84
  },
99
- "license": "Apache-2.0"
100
- }
85
+ "license": "Apache-2.0",
86
+ "scripts": {
87
+ "compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
88
+ "build:ts": "pnpm run compileReplacer && rimraf ./lib && tsc -b tsconfig.build.json && tsc-alias -p tsconfig.build.json",
89
+ "build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --sourcemap --packages=external --format=cjs",
90
+ "build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/utils.esm.js --sourcemap --packages=external --format=esm",
91
+ "build:umd": "rollup dist/utils.esm.js --format=umd --file=dist/utils.umd.js --name=@rjsf/utils",
92
+ "build": "pnpm run build:ts && pnpm run build:cjs && pnpm run build:esm && pnpm run build:umd",
93
+ "cs-check": "oxfmt --check \"{src,test}/**/*.{ts,tsx}\"",
94
+ "cs-format": "oxfmt \"{src,test}/**/*.{ts,tsx}\"",
95
+ "lint": "oxlint src test",
96
+ "precommit": "lint-staged",
97
+ "test": "vitest run",
98
+ "test:debug": "node --inspect-brk ../../node_modules/.bin/vitest --pool=forks"
99
+ }
100
+ }
@@ -0,0 +1,48 @@
1
+ import enumOptionsIsSelected from './enumOptionsIsSelected';
2
+ import getTemplate from './getTemplate';
3
+ import getUiOptions from './getUiOptions';
4
+ import { descriptionId } from './idGenerators';
5
+ import type { FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps } from './types';
6
+
7
+ export type SelectedOptionDescriptionProps<
8
+ T = any,
9
+ S extends StrictRJSFSchema = RJSFSchema,
10
+ F extends FormContextType = any,
11
+ > = Pick<WidgetProps<T, S, F>, 'id' | 'multiple' | 'options' | 'registry' | 'uiSchema' | 'value' | 'hideLabel'>;
12
+
13
+ /** Renders the description associated with the selected oneOf or anyOf option in a single-select widget. */
14
+ export default function SelectedOptionDescription<
15
+ T = any,
16
+ S extends StrictRJSFSchema = RJSFSchema,
17
+ F extends FormContextType = any,
18
+ >({ hideLabel, id, multiple, options, registry, uiSchema, value }: SelectedOptionDescriptionProps<T, S, F>) {
19
+ if (multiple || hideLabel) {
20
+ return null;
21
+ }
22
+
23
+ const { label = true } = getUiOptions<T, S, F>(uiSchema, registry.globalUiOptions);
24
+ if (!label) {
25
+ return null;
26
+ }
27
+
28
+ const option = options.enumOptions?.find(({ value: enumValue }) => enumOptionsIsSelected(enumValue, value));
29
+ const description = option?.schema?.description;
30
+ if (!description) {
31
+ return null;
32
+ }
33
+
34
+ const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>(
35
+ 'DescriptionFieldTemplate',
36
+ registry,
37
+ options,
38
+ );
39
+ return (
40
+ <DescriptionFieldTemplate
41
+ id={descriptionId(id)}
42
+ description={description}
43
+ schema={option.schema!}
44
+ uiSchema={uiSchema}
45
+ registry={registry}
46
+ />
47
+ );
48
+ }
package/src/constants.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /** Below are the list of all the keys into various elements of a RJSFSchema or UiSchema that are used by the various
2
- * utility functions. In addition to those keys, there are the special `ADDITIONAL_PROPERTY_FLAG` and
3
- * `RJSF_ADDITIONAL_PROPERTIES_FLAG` flags that is added to a schema under certain conditions by the `retrieveSchema()`
4
- * utility.
2
+ * utility functions. In addition to those keys, there are the special `ADDITIONAL_PROPERTY_FLAG`,
3
+ * `RJSF_REF_KEY`, and `RJSF_REF_CYCLE_KEY` Symbols that are added to a schema under certain conditions by the
4
+ * `retrieveSchema()` utility. Using Symbols keeps these internal markers invisible to JSON serialisation and
5
+ * JSON Schema validators (AJV, ATA, etc.), which only enumerate string-keyed properties.
5
6
  */
6
- export const ADDITIONAL_PROPERTY_FLAG = '__additional_property';
7
+ export const ADDITIONAL_PROPERTY_FLAG = Symbol('__additional_property');
7
8
  export const ADDITIONAL_PROPERTIES_KEY = 'additionalProperties';
8
9
  export const ALL_OF_KEY = 'allOf';
9
10
  export const ANY_OF_KEY = 'anyOf';
@@ -25,7 +26,8 @@ export const READONLY_KEY = 'readonly';
25
26
  export const REQUIRED_KEY = 'required';
26
27
  export const SUBMIT_BTN_OPTIONS_KEY = 'submitButtonOptions';
27
28
  export const REF_KEY = '$ref';
28
- export const RJSF_REF_KEY = '__rjsf_ref';
29
+ export const RJSF_REF_KEY = Symbol('__rjsf_ref');
30
+ export const RJSF_REF_CYCLE_KEY = Symbol('__rjsf_ref_cycle');
29
31
  export const SCHEMA_KEY = '$schema';
30
32
  export const DEFAULT_ID_PREFIX = 'root';
31
33
  export const DEFAULT_ID_SEPARATOR = '_';
@@ -40,8 +42,9 @@ export const FORM_CONTEXT_NAME = 'formContext';
40
42
  /** The name of the `layoutGridLookupMap` attribute in the form context
41
43
  */
42
44
  export const LOOKUP_MAP_NAME = 'layoutGridLookupMap';
43
- export const RJSF_ADDITIONAL_PROPERTIES_FLAG = '__rjsf_additionalProperties';
44
- export const ROOT_SCHEMA_PREFIX = '__rjsf_rootSchema';
45
+ export const RJSF_PREFIX_KEY = '__rjsf';
46
+ export const RJSF_ADDITIONAL_PROPERTIES_FLAG = `${RJSF_PREFIX_KEY}_additionalProperties`;
47
+ export const ROOT_SCHEMA_PREFIX = `${RJSF_PREFIX_KEY}_rootSchema`;
45
48
  export const UI_FIELD_KEY = 'ui:field';
46
49
  export const UI_WIDGET_KEY = 'ui:widget';
47
50
  export const UI_OPTIONS_KEY = 'ui:options';
@@ -63,7 +63,7 @@ class SchemaUtils<
63
63
  experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior,
64
64
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
65
65
  ) {
66
- if (rootSchema && rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) {
66
+ if (rootSchema?.[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) {
67
67
  this.rootSchema = makeAllReferencesAbsolute(rootSchema, get(rootSchema, ID_KEY, '#'));
68
68
  } else {
69
69
  this.rootSchema = rootSchema;
package/src/enums.ts CHANGED
@@ -27,6 +27,8 @@ export enum TranslatableString {
27
27
  AddItemButton = 'Add Item',
28
28
  /** Copy button title, used by IconButton */
29
29
  CopyButton = 'Copy',
30
+ /** Expand cycle button title */
31
+ ExpandButton = 'Expand Cycle',
30
32
  /** Move down button title, used by IconButton */
31
33
  MoveDownButton = 'Move down',
32
34
  /** Move up button title, used by IconButton */
@@ -70,6 +72,8 @@ export enum TranslatableString {
70
72
  KeyLabel = '%1 Key',
71
73
  /** Deprecated label, where %1 will be replaced by the label as provided by SchemaField */
72
74
  DeprecatedLabel = '%1 (deprecated)',
75
+ /** Cyclic schema message */
76
+ CycleDetected = 'Circular reference ($ref cycle) detected for field "%1". You may choose to expand to the next cycle break',
73
77
  // Strings with replaceable parameters AND/OR that support markdown and html
74
78
  /** Invalid object field configuration as provided by the ObjectField.
75
79
  * NOTE: Use markdown notation rather than html tags.
@@ -23,7 +23,7 @@ export default function getSubmitButtonOptions<
23
23
  F extends FormContextType = any,
24
24
  >(uiSchema: UiSchema<T, S, F> = {}): UISchemaSubmitButtonOptions {
25
25
  const uiOptions = getUiOptions<T, S, F>(uiSchema);
26
- if (uiOptions && uiOptions[SUBMIT_BTN_OPTIONS_KEY]) {
26
+ if (uiOptions?.[SUBMIT_BTN_OPTIONS_KEY]) {
27
27
  const options = uiOptions[SUBMIT_BTN_OPTIONS_KEY];
28
28
  return { ...DEFAULT_OPTIONS, ...options };
29
29
  }
package/src/getWidget.tsx CHANGED
@@ -75,7 +75,7 @@ function mergeWidgetOptions<T = any, S extends StrictRJSFSchema = RJSFSchema, F
75
75
  // cache return value as property of widget for proper react reconciliation
76
76
  if (!MergedWidget) {
77
77
  // oxlint-disable-next-line typescript/no-deprecated
78
- const defaultOptions = (AWidget.defaultProps && AWidget.defaultProps.options) || {};
78
+ const defaultOptions = AWidget.defaultProps?.options || {};
79
79
  MergedWidget = ({ options, ...props }) => <AWidget options={{ ...defaultOptions, ...options }} {...props} />;
80
80
  set(AWidget, 'MergedWidget', MergedWidget);
81
81
  }
@@ -47,10 +47,12 @@ export function hashObject(object: unknown): string {
47
47
 
48
48
  /** Stringifies the schema and returns the hash of the resulting string. Sorts schema fields
49
49
  * in consistent order before stringify to prevent different hash ids for the same schema.
50
+ * Symbol-keyed properties (RJSF_REF_KEY, RJSF_REF_CYCLE_KEY, ADDITIONAL_PROPERTY_FLAG) are
51
+ * automatically excluded by JSON.stringify, so no special filtering is needed.
50
52
  *
51
53
  * @param schema - The schema for which the hash is desired
52
54
  * @returns - The string obtained from the hash of the stringified schema
53
55
  */
54
56
  export default function hashForSchema<S extends StrictRJSFSchema = RJSFSchema>(schema: S) {
55
- return hashObject(schema);
57
+ return hashString(sortedJSONStringify(schema));
56
58
  }
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ import isObject from './isObject';
52
52
  import isRootSchema from './isRootSchema';
53
53
  import labelValue from './labelValue';
54
54
  import localToUTC from './localToUTC';
55
+ import logUnsupportedDefaultForEnum from './logUnsupportedDefaultForEnum';
55
56
  import lookupFromFormContext from './lookupFromFormContext';
56
57
  import mergeDefaultsWithFormData from './mergeDefaultsWithFormData';
57
58
  import mergeObjects from './mergeObjects';
@@ -66,6 +67,8 @@ import removeOptionalEmptyObjects from './removeOptionalEmptyObjects';
66
67
  import replaceStringParameters from './replaceStringParameters';
67
68
  import resolveUiSchema from './resolveUiSchema';
68
69
  import schemaRequiresTrueValue from './schemaRequiresTrueValue';
70
+ import SelectedOptionDescription from './SelectedOptionDescription';
71
+ import type { SelectedOptionDescriptionProps } from './SelectedOptionDescription';
69
72
  import shallowEquals from './shallowEquals';
70
73
  import type { ComponentUpdateStrategy } from './shouldRender';
71
74
  import shouldRender from './shouldRender';
@@ -98,6 +101,7 @@ export type {
98
101
  DateElementProp,
99
102
  DateElementProps,
100
103
  FileInfoType,
104
+ SelectedOptionDescriptionProps,
101
105
  UseAltDateWidgetResult,
102
106
  UseFileWidgetPropsResult,
103
107
  };
@@ -154,6 +158,7 @@ export {
154
158
  isRootSchema,
155
159
  labelValue,
156
160
  localToUTC,
161
+ logUnsupportedDefaultForEnum,
157
162
  lookupFromFormContext,
158
163
  mergeDefaultsWithFormData,
159
164
  mergeObjects,
@@ -170,6 +175,7 @@ export {
170
175
  replaceStringParameters,
171
176
  resolveUiSchema,
172
177
  schemaRequiresTrueValue,
178
+ SelectedOptionDescription,
173
179
  shallowEquals,
174
180
  shouldRender,
175
181
  shouldRenderOptionalField,
@@ -1,12 +1,12 @@
1
1
  import isObject from './isObject';
2
2
  import type { RJSFSchema, StrictRJSFSchema } from './types';
3
3
 
4
- /** Detects whether the given `schema` contains fixed items. This is the case when `schema.items` is a non-empty array
5
- * that only contains objects.
4
+ /** Detects whether the given `schema` contains fixed items. This is the case when `schema.items` is an array that only
5
+ * contains objects.
6
6
  *
7
7
  * @param schema - The schema in which to check for fixed items
8
8
  * @returns - True if there are fixed items in the schema, false otherwise
9
9
  */
10
10
  export default function isFixedItems<S extends StrictRJSFSchema = RJSFSchema>(schema: S) {
11
- return Array.isArray(schema.items) && schema.items.length > 0 && schema.items.every((item) => isObject(item));
11
+ return Array.isArray(schema.items) && schema.items.every((item) => isObject(item));
12
12
  }
@@ -1,6 +1,4 @@
1
- import omit from 'lodash/omit';
2
-
3
- import { REF_KEY, RJSF_REF_KEY } from './constants';
1
+ import { REF_KEY } from './constants';
4
2
  import deepEquals from './deepEquals';
5
3
  import type { FormContextType, Registry, RJSFSchema, StrictRJSFSchema } from './types';
6
4
 
@@ -25,7 +23,7 @@ export default function isRootSchema<T = any, S extends StrictRJSFSchema = RJSFS
25
23
  }
26
24
  if (REF_KEY in rootSchema) {
27
25
  const resolvedSchema = schemaUtils.retrieveSchema(rootSchema);
28
- return deepEquals(schemaToCompare, omit(resolvedSchema, RJSF_REF_KEY));
26
+ return deepEquals(schemaToCompare, resolvedSchema);
29
27
  }
30
28
  return false;
31
29
  }
@@ -0,0 +1,28 @@
1
+ import enumOptionsIndexForValue from './enumOptionsIndexForValue';
2
+ import type { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types';
3
+
4
+ /** Logs a warning when a single-select enum widget has a schema default that is not one of its enum options.
5
+ *
6
+ * @param id - The field id used in the warning message
7
+ * @param schema - The schema whose default value is checked
8
+ * @param enumOptions - The enum options available to the widget
9
+ * @param multiple - Whether the widget allows multiple selections
10
+ */
11
+ export default function logUnsupportedDefaultForEnum<S extends StrictRJSFSchema = RJSFSchema>(
12
+ id: string,
13
+ schema: S,
14
+ enumOptions?: EnumOptionsType<S>[],
15
+ multiple = false,
16
+ ) {
17
+ if (
18
+ !multiple &&
19
+ Array.isArray(enumOptions) &&
20
+ schema.default !== undefined &&
21
+ enumOptionsIndexForValue<S>(schema.default, enumOptions, multiple) === undefined
22
+ ) {
23
+ // oxlint-disable-next-line no-console
24
+ console.error(
25
+ `The schema default value "${schema.default}" is not one of the values in the enum options for "${id}"`,
26
+ );
27
+ }
28
+ }
@@ -3,7 +3,7 @@ import union from 'lodash/union';
3
3
  import { REQUIRED_KEY } from './constants';
4
4
  import getSchemaType from './getSchemaType';
5
5
  import isObject from './isObject';
6
- import type { GenericObjectType } from './types';
6
+ import type { GenericObjectType, GenericSymbolObjectType } from './types';
7
7
 
8
8
  /** Recursively merge deeply nested schemas. The difference between `mergeSchemas` and `mergeObjects` is that
9
9
  * `mergeSchemas` only concats arrays for values under the 'required' keyword, and when it does, it doesn't include
@@ -15,7 +15,7 @@ import type { GenericObjectType } from './types';
15
15
  */
16
16
  export default function mergeSchemas(obj1: GenericObjectType, obj2: GenericObjectType) {
17
17
  const acc = { ...obj1 }; // Prevent mutation of source object.
18
- return Object.keys(obj2).reduce((accumulator, key) => {
18
+ const result = Object.keys(obj2).reduce((accumulator, key) => {
19
19
  const left = obj1 ? obj1[key] : {},
20
20
  right = obj2[key];
21
21
  if (obj1 && key in obj1 && isObject(right)) {
@@ -35,4 +35,11 @@ export default function mergeSchemas(obj1: GenericObjectType, obj2: GenericObjec
35
35
  }
36
36
  return accumulator;
37
37
  }, acc);
38
+ // Copy own Symbol-keyed properties from obj2 (Object.keys skips them).
39
+ for (const sym of Object.getOwnPropertySymbols(obj2)) {
40
+ // To avoid issues with symbols, we cast them to GenericSymbolObjectType. If we make the mergeSchema function take
41
+ // in GenericSymbolObjectType rather than GenericObjectType, it causes a bunch of type errors downstream
42
+ (result as GenericSymbolObjectType)[sym] = (obj2 as GenericSymbolObjectType)[sym];
43
+ }
44
+ return result;
38
45
  }
@@ -79,26 +79,24 @@ export default function optionsList<T = any, S extends StrictRJSFSchema = RJSFSc
79
79
  const { optionsSchemaSelector = selectorField } = getUiOptions<T, S, F>(uiSchema);
80
80
  selectorField = optionsSchemaSelector;
81
81
  }
82
- return (
83
- altSchemas &&
84
- altSchemas.map((aSchemaDef, index) => {
85
- const { title } = getUiOptions<T, S, F>(altUiSchemas?.[index]);
86
- const aSchema = aSchemaDef as S;
87
- let value: EnumOptionsType<S>['value'];
88
- let label = title;
89
- if (selectorField) {
90
- const innerSchema: S = get(aSchema, [PROPERTIES_KEY, selectorField], {}) as S;
91
- value = get(innerSchema, DEFAULT_KEY, get(innerSchema, CONST_KEY));
92
- label = label || innerSchema?.title || aSchema.title || String(value);
93
- } else {
94
- value = toConstant(aSchema);
95
- label = label || aSchema.title || String(value);
96
- }
97
- return {
98
- schema: aSchema,
99
- label,
100
- value,
101
- };
102
- })
103
- );
82
+ return altSchemas?.map((aSchemaDef, index) => {
83
+ const { title } = getUiOptions<T, S, F>(altUiSchemas?.[index]);
84
+ const aSchema = aSchemaDef as S;
85
+ let value: EnumOptionsType<S>['value'];
86
+ let label = title;
87
+ if (selectorField) {
88
+ const innerSchema: S = get(aSchema, [PROPERTIES_KEY, selectorField], {}) as S;
89
+ value = get(innerSchema, DEFAULT_KEY, get(innerSchema, CONST_KEY));
90
+ // Use nullish coalescing so that an explicitly empty string title is preserved
91
+ label = label ?? innerSchema?.title ?? aSchema.title ?? String(value);
92
+ } else {
93
+ value = toConstant(aSchema);
94
+ label = label ?? aSchema.title ?? String(value);
95
+ }
96
+ return {
97
+ schema: aSchema,
98
+ label,
99
+ value,
100
+ };
101
+ });
104
102
  }
@@ -3,7 +3,15 @@ import isEmpty from 'lodash/isEmpty';
3
3
  import { ANY_OF_KEY, ONE_OF_KEY, REF_KEY, RJSF_REF_KEY } from './constants';
4
4
  import findSchemaDefinition from './findSchemaDefinition';
5
5
  import mergeObjects from './mergeObjects';
6
- import type { FormContextType, GenericObjectType, Registry, RJSFSchema, StrictRJSFSchema, UiSchema } from './types';
6
+ import type {
7
+ FormContextType,
8
+ GenericObjectType,
9
+ Registry,
10
+ RJSFMarkedSchema,
11
+ RJSFSchema,
12
+ StrictRJSFSchema,
13
+ UiSchema,
14
+ } from './types';
7
15
 
8
16
  /** Resolves the uiSchema for a given schema, considering `ui:definitions` stored in the registry.
9
17
  *
@@ -26,7 +34,7 @@ export default function resolveUiSchema<
26
34
  S extends StrictRJSFSchema = RJSFSchema,
27
35
  F extends FormContextType = any,
28
36
  >(schema: S, localUiSchema: UiSchema<T, S, F> | undefined, registry: Registry<T, S, F>): UiSchema<T, S, F> {
29
- const ref = ((schema as GenericObjectType)[RJSF_REF_KEY] ?? schema[REF_KEY]) as string | undefined;
37
+ const ref = ((schema as RJSFMarkedSchema)[RJSF_REF_KEY] ?? schema[REF_KEY]) as string | undefined;
30
38
  const definitions = registry.uiSchemaDefinitions;
31
39
  const definitionUiSchema = ref && definitions ? definitions[ref] : undefined;
32
40
 
@@ -47,7 +55,7 @@ export default function resolveUiSchema<
47
55
  // can read dropdown option titles at the parent level.
48
56
  if (definitions) {
49
57
  let resolvedSchema: S = schema;
50
- if (ref && schema[REF_KEY] && !(schema as GenericObjectType)[RJSF_REF_KEY]) {
58
+ if (ref && schema[REF_KEY] && !(schema as RJSFMarkedSchema)[RJSF_REF_KEY]) {
51
59
  try {
52
60
  resolvedSchema = findSchemaDefinition<S>(ref, registry.rootSchema);
53
61
  } catch (e) {
@@ -66,7 +74,7 @@ export default function resolveUiSchema<
66
74
  let hasExpanded = false;
67
75
  for (let i = 0; i < schemaOptions.length; i++) {
68
76
  const option = schemaOptions[i] as GenericObjectType | undefined;
69
- const optionRef = (option?.[RJSF_REF_KEY] ?? option?.[REF_KEY]) as string | undefined;
77
+ const optionRef = ((option as RJSFMarkedSchema)?.[RJSF_REF_KEY] ?? option?.[REF_KEY]) as string | undefined;
70
78
  if (optionRef && optionRef in definitions) {
71
79
  const optionUiSchema = (uiSchemaArray[i] || {}) as GenericObjectType;
72
80
  uiSchemaArray[i] = mergeObjects(definitions[optionRef] as GenericObjectType, optionUiSchema) as UiSchema<
@@ -232,16 +232,18 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
232
232
  rootSchema = {} as S,
233
233
  includeUndefinedValues = false,
234
234
  _recurseList = [],
235
- experimental_defaultFormStateBehavior = undefined,
236
- experimental_customMergeAllOf = undefined,
235
+ experimental_defaultFormStateBehavior,
236
+ experimental_customMergeAllOf,
237
237
  required,
238
238
  shouldMergeDefaultsIntoFormData = false,
239
239
  initialDefaultsGenerated,
240
240
  } = computeDefaultsProps;
241
241
  let formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
242
242
  const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
243
- // Compute the defaults recursively: give highest priority to deepest nodes.
243
+ // Compute the defaults recursively: give highest priority to deepest nodes unless nestedDefaultsPrecedence is ancestorWins.
244
244
  let defaults: T | T[] | undefined = parentDefaults;
245
+ const preferParentDefaults =
246
+ defaults && experimental_defaultFormStateBehavior?.nestedDefaultsPrecedence === 'ancestorWins';
245
247
  // If we get a new schema, then we need to recompute defaults again for the new schema found.
246
248
  let schemaToCompute: S | null = null;
247
249
  let experimental_dfsb_to_compute = experimental_defaultFormStateBehavior;
@@ -259,12 +261,25 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
259
261
  !schema[ONE_OF_KEY] &&
260
262
  !schema[REF_KEY]
261
263
  ) {
262
- // For object defaults, only override parent defaults that are defined in
263
- // schema.default. Skip this for anyOf/oneOf/$ref schemas - they need special handling.
264
- defaults = mergeObjects(defaults, schema.default as GenericObjectType) as T;
265
- } else if (DEFAULT_KEY in schema && !schema[ANY_OF_KEY] && !schema[ONE_OF_KEY] && !schema[REF_KEY]) {
266
- // If the schema has a default value, then we should use it as the default.
267
- // And if the schema does not have anyOf or oneOf, this is done because we need to merge the defaults with the formData.
264
+ // For object defaults, merge defaults by precedence setting.
265
+ // Skip this for anyOf/oneOf/$ref schemas - they need special handling.
266
+ if (preferParentDefaults) {
267
+ // Use schema.default as the base and only override values that are defined in parent defaults.
268
+ defaults = mergeObjects(schema.default as GenericObjectType, defaults) as T;
269
+ } else {
270
+ // Only override parent defaults that are defined in schema.default.
271
+ defaults = mergeObjects(defaults, schema.default as GenericObjectType) as T;
272
+ }
273
+ } else if (
274
+ DEFAULT_KEY in schema &&
275
+ !preferParentDefaults &&
276
+ !schema[ANY_OF_KEY] &&
277
+ !schema[ONE_OF_KEY] &&
278
+ !schema[REF_KEY]
279
+ ) {
280
+ // If the schema has a default value and parentDefaults does not have precedence
281
+ // And if the schema does not have anyOf or oneOf (since we need to merge the defaults with the formData)
282
+ // Then we should use it as the default.
268
283
  defaults = schema.default as unknown as T;
269
284
  } else if (REF_KEY in schema) {
270
285
  const refName = schema[REF_KEY];
@@ -305,7 +320,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
305
320
  experimental_customMergeAllOf,
306
321
  );
307
322
  [schemaToCompute] = resolvedSchema; // pick the first element from resolve dependencies
308
- } else if (isFixedItems(schema)) {
323
+ } else if (isFixedItems(schema) && !preferParentDefaults) {
324
+ // If the schema contains fixed items and parentDefaults does not have precedence
325
+ // Then construct defaults from defaults of array items.
309
326
  defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) =>
310
327
  computeDefaults<T, S>(validator, itemSchema, {
311
328
  rootSchema,
@@ -443,19 +460,44 @@ export function ensureFormDataMatchingSchema<
443
460
  experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior,
444
461
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
445
462
  ): T | T[] | undefined {
463
+ const shouldRetrieveAllOf =
464
+ experimental_defaultFormStateBehavior?.allOf === 'populateDefaults' && ALL_OF_KEY in schema;
465
+ const schemaToMatch = shouldRetrieveAllOf
466
+ ? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData, experimental_customMergeAllOf)
467
+ : schema;
446
468
  const isSelectField =
447
- !isConstant<S>(schema) && isSelect<T, S, F>(validator, schema, rootSchema, experimental_customMergeAllOf);
469
+ !isConstant<S>(schemaToMatch) &&
470
+ isSelect<T, S, F>(validator, schemaToMatch, rootSchema, experimental_customMergeAllOf);
448
471
  let validFormData: T | T[] | undefined = formData;
449
472
  if (isSelectField) {
450
- const getOptionsList = optionsList<T, S, F>(schema);
473
+ const getOptionsList = optionsList<T, S, F>(schemaToMatch);
451
474
  const isValid = getOptionsList?.some((option) => deepEquals(option.value, formData));
452
475
  validFormData = isValid ? formData : undefined;
453
476
  }
454
477
 
455
478
  // Override the formData with the const if the constAsDefaults is set to always
456
- const constTakesPrecedence = schema[CONST_KEY] && experimental_defaultFormStateBehavior?.constAsDefaults === 'always';
479
+ const constTakesPrecedence =
480
+ schemaToMatch[CONST_KEY] && experimental_defaultFormStateBehavior?.constAsDefaults === 'always';
457
481
  if (constTakesPrecedence) {
458
- validFormData = schema.const as T;
482
+ validFormData = schemaToMatch.const as T;
483
+ } else if (isObject(validFormData) && isObject(schemaToMatch.properties)) {
484
+ validFormData = Object.keys(schemaToMatch.properties).reduce(
485
+ (acc: GenericObjectType, key: string) => {
486
+ const propertySchema: S = get(schemaToMatch, [PROPERTIES_KEY, key], {}) as S;
487
+ if (key in acc && (shouldRetrieveAllOf || (isObject(propertySchema) && ALL_OF_KEY in propertySchema))) {
488
+ acc[key] = ensureFormDataMatchingSchema<T, S, F>(
489
+ validator,
490
+ propertySchema,
491
+ rootSchema,
492
+ get(acc, key),
493
+ experimental_defaultFormStateBehavior,
494
+ experimental_customMergeAllOf,
495
+ );
496
+ }
497
+ return acc;
498
+ },
499
+ { ...(validFormData as GenericObjectType) },
500
+ ) as T;
459
501
  }
460
502
 
461
503
  return validFormData;
@@ -477,8 +519,8 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
477
519
  rootSchema = {} as S,
478
520
  includeUndefinedValues = false,
479
521
  _recurseList = [],
480
- experimental_defaultFormStateBehavior = undefined,
481
- experimental_customMergeAllOf = undefined,
522
+ experimental_defaultFormStateBehavior,
523
+ experimental_customMergeAllOf,
482
524
  required,
483
525
  shouldMergeDefaultsIntoFormData,
484
526
  initialDefaultsGenerated,
@@ -605,8 +647,8 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
605
647
  rawFormData,
606
648
  rootSchema = {} as S,
607
649
  _recurseList = [],
608
- experimental_defaultFormStateBehavior = undefined,
609
- experimental_customMergeAllOf = undefined,
650
+ experimental_defaultFormStateBehavior,
651
+ experimental_customMergeAllOf,
610
652
  required,
611
653
  requiredAsRoot = false,
612
654
  shouldMergeDefaultsIntoFormData,
@@ -674,7 +716,16 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
674
716
  }
675
717
  }
676
718
 
719
+ const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
720
+
677
721
  if (neverPopulate) {
722
+ if (shouldMergeDefaultsIntoFormData) {
723
+ if (schema.minItems && schema.minItems > defaultsLength) {
724
+ const fillerEntries = Array.from({ length: schema.minItems - defaultsLength }, () => null) as T[];
725
+ return (defaults ?? []).concat(fillerEntries);
726
+ }
727
+ return defaults;
728
+ }
678
729
  return defaults ?? emptyDefault;
679
730
  }
680
731
  if (ignoreMinItemsFlagSet && !required) {
@@ -684,7 +735,6 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
684
735
  }
685
736
 
686
737
  let arrayDefault: T[] | undefined;
687
- const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
688
738
  if (
689
739
  !schema.minItems ||
690
740
  isMultiSelect<T, S, F>(validator, schema, rootSchema, experimental_customMergeAllOf) ||
@@ -733,8 +783,8 @@ export function getDefaultBasedOnSchemaType<
733
783
  validator: ValidatorType<T, S, F>,
734
784
  rawSchema: S,
735
785
  computeDefaultsProps: ComputeDefaultsProps<T, S> = {},
736
- defaults?: T | T[] | undefined,
737
- ): T | T[] | void {
786
+ defaults?: T | T[],
787
+ ): T | T[] | undefined {
738
788
  switch (getSchemaType<S>(rawSchema)) {
739
789
  // We need to recurse for object schema inner default values.
740
790
  case 'object': {
@@ -808,9 +858,17 @@ export default function getDefaultFormState<
808
858
  if (isObject(formData) || Array.isArray(formData)) {
809
859
  const { mergeDefaultsIntoFormData } = experimental_defaultFormStateBehavior || {};
810
860
  const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined';
861
+ const matchingFormData = ensureFormDataMatchingSchema<T, S, F>(
862
+ validator,
863
+ schema,
864
+ rootSchema ?? schema,
865
+ formData,
866
+ experimental_defaultFormStateBehavior,
867
+ experimental_customMergeAllOf,
868
+ );
811
869
  const result = mergeDefaultsWithFormData<T | T[]>(
812
870
  defaults,
813
- formData,
871
+ matchingFormData,
814
872
  true, // set to true to add any additional default array entries.
815
873
  defaultSupercedesUndefined,
816
874
  true, // set to true to override formData with defaults if they exist.