@rjsf/utils 6.0.0-beta.9 → 6.0.1

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 (113) hide show
  1. package/dist/{index.js → index.cjs} +563 -200
  2. package/dist/index.cjs.map +7 -0
  3. package/dist/utils.esm.js +562 -199
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +533 -193
  6. package/lib/ErrorSchemaBuilder.d.ts +2 -2
  7. package/lib/constants.d.ts +3 -0
  8. package/lib/constants.js +3 -0
  9. package/lib/constants.js.map +1 -1
  10. package/lib/createSchemaUtils.js +25 -18
  11. package/lib/createSchemaUtils.js.map +1 -1
  12. package/lib/enums.d.ts +13 -3
  13. package/lib/enums.js +13 -3
  14. package/lib/enums.js.map +1 -1
  15. package/lib/findSchemaDefinition.d.ts +6 -0
  16. package/lib/findSchemaDefinition.js +44 -3
  17. package/lib/findSchemaDefinition.js.map +1 -1
  18. package/lib/getDateElementProps.d.ts +1 -2
  19. package/lib/getTestIds.js +2 -2
  20. package/lib/getTestIds.js.map +1 -1
  21. package/lib/getUiOptions.js +4 -0
  22. package/lib/getUiOptions.js.map +1 -1
  23. package/lib/getWidget.js +3 -3
  24. package/lib/getWidget.js.map +1 -1
  25. package/lib/guessType.d.ts +1 -1
  26. package/lib/idGenerators.d.ts +22 -15
  27. package/lib/idGenerators.js +17 -8
  28. package/lib/idGenerators.js.map +1 -1
  29. package/lib/index.d.ts +16 -6
  30. package/lib/index.js +13 -4
  31. package/lib/index.js.map +1 -1
  32. package/lib/isFormDataAvailable.d.ts +7 -0
  33. package/lib/isFormDataAvailable.js +13 -0
  34. package/lib/isFormDataAvailable.js.map +1 -0
  35. package/lib/isRootSchema.d.ts +13 -0
  36. package/lib/isRootSchema.js +25 -0
  37. package/lib/isRootSchema.js.map +1 -0
  38. package/lib/mergeDefaultsWithFormData.js +14 -2
  39. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  40. package/lib/nameGenerators.d.ts +13 -0
  41. package/lib/nameGenerators.js +30 -0
  42. package/lib/nameGenerators.js.map +1 -0
  43. package/lib/schema/getDefaultFormState.d.ts +17 -3
  44. package/lib/schema/getDefaultFormState.js +66 -26
  45. package/lib/schema/getDefaultFormState.js.map +1 -1
  46. package/lib/schema/getDisplayLabel.js +2 -2
  47. package/lib/schema/getDisplayLabel.js.map +1 -1
  48. package/lib/schema/index.d.ts +1 -2
  49. package/lib/schema/index.js +1 -2
  50. package/lib/schema/index.js.map +1 -1
  51. package/lib/schema/retrieveSchema.d.ts +10 -5
  52. package/lib/schema/retrieveSchema.js +40 -17
  53. package/lib/schema/retrieveSchema.js.map +1 -1
  54. package/lib/shallowEquals.d.ts +8 -0
  55. package/lib/shallowEquals.js +36 -0
  56. package/lib/shallowEquals.js.map +1 -0
  57. package/lib/shouldRender.d.ts +8 -2
  58. package/lib/shouldRender.js +17 -2
  59. package/lib/shouldRender.js.map +1 -1
  60. package/lib/shouldRenderOptionalField.d.ts +18 -0
  61. package/lib/shouldRenderOptionalField.js +47 -0
  62. package/lib/shouldRenderOptionalField.js.map +1 -0
  63. package/lib/toFieldPathId.d.ts +14 -0
  64. package/lib/toFieldPathId.js +26 -0
  65. package/lib/toFieldPathId.js.map +1 -0
  66. package/lib/tsconfig.tsbuildinfo +1 -1
  67. package/lib/types.d.ts +196 -105
  68. package/lib/useAltDateWidgetProps.d.ts +39 -0
  69. package/lib/useAltDateWidgetProps.js +71 -0
  70. package/lib/useAltDateWidgetProps.js.map +1 -0
  71. package/lib/useDeepCompareMemo.d.ts +8 -0
  72. package/lib/useDeepCompareMemo.js +17 -0
  73. package/lib/useDeepCompareMemo.js.map +1 -0
  74. package/lib/useFileWidgetProps.d.ts +29 -0
  75. package/lib/useFileWidgetProps.js +119 -0
  76. package/lib/useFileWidgetProps.js.map +1 -0
  77. package/lib/validationDataMerge.d.ts +2 -1
  78. package/lib/validationDataMerge.js +3 -2
  79. package/lib/validationDataMerge.js.map +1 -1
  80. package/package.json +13 -14
  81. package/src/ErrorSchemaBuilder.ts +2 -2
  82. package/src/constants.ts +3 -0
  83. package/src/createSchemaUtils.ts +25 -26
  84. package/src/enums.ts +13 -3
  85. package/src/findSchemaDefinition.ts +51 -3
  86. package/src/getDateElementProps.ts +1 -1
  87. package/src/getTestIds.ts +2 -2
  88. package/src/getUiOptions.ts +4 -0
  89. package/src/getWidget.tsx +3 -3
  90. package/src/idGenerators.ts +35 -25
  91. package/src/index.ts +36 -5
  92. package/src/isFormDataAvailable.ts +13 -0
  93. package/src/isRootSchema.ts +30 -0
  94. package/src/mergeDefaultsWithFormData.ts +16 -2
  95. package/src/nameGenerators.ts +43 -0
  96. package/src/schema/getDefaultFormState.ts +87 -31
  97. package/src/schema/getDisplayLabel.ts +2 -2
  98. package/src/schema/index.ts +0 -2
  99. package/src/schema/retrieveSchema.ts +43 -7
  100. package/src/shallowEquals.ts +41 -0
  101. package/src/shouldRender.ts +27 -2
  102. package/src/shouldRenderOptionalField.ts +56 -0
  103. package/src/toFieldPathId.ts +34 -0
  104. package/src/types.ts +229 -113
  105. package/src/useAltDateWidgetProps.tsx +163 -0
  106. package/src/useDeepCompareMemo.ts +17 -0
  107. package/src/useFileWidgetProps.ts +155 -0
  108. package/src/validationDataMerge.ts +7 -1
  109. package/dist/index.js.map +0 -7
  110. package/lib/schema/toIdSchema.d.ts +0 -14
  111. package/lib/schema/toIdSchema.js +0 -62
  112. package/lib/schema/toIdSchema.js.map +0 -1
  113. package/src/schema/toIdSchema.ts +0 -131
@@ -0,0 +1,163 @@
1
+ import { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import dateRangeOptions from './dateRangeOptions';
4
+ import getDateElementProps, { DateElementFormat, DateElementProp } from './getDateElementProps';
5
+ import { ariaDescribedByIds } from './idGenerators';
6
+ import parseDateString from './parseDateString';
7
+ import toDateString from './toDateString';
8
+ import { DateObject, FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps } from './types';
9
+
10
+ /** Function that checks to see if a `DateObject` is ready for the onChange callback to be triggered
11
+ *
12
+ * @param state - The current `DateObject`
13
+ * @returns - True if the `state` is ready to trigger an onChange
14
+ */
15
+ function readyForChange(state: DateObject) {
16
+ return Object.values(state).every((value) => value !== -1);
17
+ }
18
+
19
+ /** The Props for the `DateElement` component */
20
+ export type DateElementProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> = Pick<
21
+ WidgetProps<T, S, F>,
22
+ 'value' | 'name' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus' | 'className'
23
+ > & {
24
+ /** The root id of the field */
25
+ rootId: string;
26
+ /** The selector function for a specific prop within the `DateObject`, for a value */
27
+ select: (property: keyof DateObject, value: any) => void;
28
+ /** The type of the date element */
29
+ type: DateElementProp['type'];
30
+ /** The range for the date element */
31
+ range: DateElementProp['range'];
32
+ };
33
+
34
+ /** The `DateElement` component renders one of the 6 date element selectors for an `AltDateWidget`, using the `select`
35
+ * widget from the registry.
36
+ *
37
+ * @param props - The `DateElementProps` for the date element
38
+ */
39
+ export function DateElement<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
40
+ props: DateElementProps<T, S, F>,
41
+ ) {
42
+ const {
43
+ className = 'form-control',
44
+ type,
45
+ range,
46
+ value,
47
+ select,
48
+ rootId,
49
+ name,
50
+ disabled,
51
+ readonly,
52
+ autofocus,
53
+ registry,
54
+ onBlur,
55
+ onFocus,
56
+ } = props;
57
+ const id = `${rootId}_${type}`;
58
+ const { SelectWidget } = registry.widgets;
59
+ const onChange = useCallback((value: any) => select(type as keyof DateObject, value), [select, type]);
60
+ return (
61
+ <SelectWidget
62
+ schema={{ type: 'integer' } as S}
63
+ id={id}
64
+ name={name}
65
+ className={className}
66
+ options={{ enumOptions: dateRangeOptions<S>(range[0], range[1]) }}
67
+ placeholder={type}
68
+ value={value}
69
+ disabled={disabled}
70
+ readonly={readonly}
71
+ autofocus={autofocus}
72
+ onChange={onChange}
73
+ onBlur={onBlur}
74
+ onFocus={onFocus}
75
+ registry={registry}
76
+ label=''
77
+ aria-describedby={ariaDescribedByIds(rootId)}
78
+ />
79
+ );
80
+ }
81
+
82
+ /** The result of a call to the `useAltDateWidgetProps()` hook */
83
+ export type UseAltDateWidgetResult = {
84
+ /** The list of `DateElementProp` data to render for the `AltDateWidget` */
85
+ elements: DateElementProp[];
86
+ /** The callback that handles the changing of DateElement components */
87
+ handleChange: (property: keyof DateObject, value?: string) => void;
88
+ /** The callback that will clear the `AltDateWidget` when a button is clicked */
89
+ handleClear: (event: MouseEvent) => void;
90
+ /** The callback that will set the `AltDateWidget` to NOW when a button is clicked */
91
+ handleSetNow: (event: MouseEvent) => void;
92
+ };
93
+
94
+ /** Hook which encapsulates the logic needed to render an `AltDateWidget` with optional `time` elements. It contains
95
+ * the `state` of the current date(/time) selections in the widget. It returns a `UseAltDateWidgetResult` object
96
+ * that contains the `elements: DateElementProp[]` and three callbacks needed to change one of the rendered `elements`,
97
+ * and to handle the clicking of the `clear` and `setNow` buttons.
98
+ *
99
+ * @param props - The `WidgetProps` for the `AltDateWidget`
100
+ */
101
+ export default function useAltDateWidgetProps<
102
+ T = any,
103
+ S extends StrictRJSFSchema = RJSFSchema,
104
+ F extends FormContextType = any,
105
+ >(props: WidgetProps<T, S, F>): UseAltDateWidgetResult {
106
+ const { time = false, disabled = false, readonly = false, options, onChange, value } = props;
107
+ const [state, setState] = useState(parseDateString(value, time));
108
+
109
+ useEffect(() => {
110
+ setState(parseDateString(value, time));
111
+ }, [time, value]);
112
+
113
+ const handleChange = useCallback(
114
+ (property: keyof DateObject, value?: string) => {
115
+ const nextState = {
116
+ ...state,
117
+ [property]: typeof value === 'undefined' ? -1 : value,
118
+ };
119
+
120
+ if (readyForChange(nextState)) {
121
+ onChange(toDateString(nextState, time));
122
+ } else {
123
+ setState(nextState);
124
+ }
125
+ },
126
+ [state, onChange, time],
127
+ );
128
+
129
+ const handleClear = useCallback(
130
+ (event: MouseEvent) => {
131
+ event.preventDefault();
132
+ if (disabled || readonly) {
133
+ return;
134
+ }
135
+ onChange(undefined);
136
+ },
137
+ [disabled, readonly, onChange],
138
+ );
139
+
140
+ const handleSetNow = useCallback(
141
+ (event: MouseEvent) => {
142
+ event.preventDefault();
143
+ if (disabled || readonly) {
144
+ return;
145
+ }
146
+ const nextState = parseDateString(new Date().toJSON(), time);
147
+ onChange(toDateString(nextState, time));
148
+ },
149
+ [disabled, readonly, time, onChange],
150
+ );
151
+
152
+ const elements = useMemo(
153
+ () =>
154
+ getDateElementProps(
155
+ state,
156
+ time,
157
+ options.yearsRange as [number, number] | undefined,
158
+ options.format as DateElementFormat | undefined,
159
+ ),
160
+ [state, time, options.yearsRange, options.format],
161
+ );
162
+ return { elements, handleChange, handleClear, handleSetNow };
163
+ }
@@ -0,0 +1,17 @@
1
+ import { useRef } from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+
4
+ /** Hook that stores and returns a `T` value. If `newValue` is the same as the stored one, then the stored one is
5
+ * returned to avoid having a component rerender due it being a different object. Otherwise, the `newValue` is stored
6
+ * and returned.
7
+ *
8
+ * @param newValue - The potential new `T` value
9
+ * @returns - The latest stored `T` value
10
+ */
11
+ export default function useDeepCompareMemo<T = unknown>(newValue: T): T {
12
+ const valueRef = useRef<T>(newValue);
13
+ if (!isEqual(newValue, valueRef.current)) {
14
+ valueRef.current = newValue;
15
+ }
16
+ return valueRef.current;
17
+ }
@@ -0,0 +1,155 @@
1
+ import { useCallback, useMemo } from 'react';
2
+
3
+ import dataURItoBlob from './dataURItoBlob';
4
+
5
+ /** The information about files used by a FileWidget */
6
+ export type FileInfoType = {
7
+ /** The url of the data containing the file */
8
+ dataURL?: string | null;
9
+ /** The name of the file */
10
+ name: string;
11
+ /** The size of the file */
12
+ size: number;
13
+ /** The type of the file */
14
+ type: string;
15
+ };
16
+
17
+ export interface UseFileWidgetPropsResult {
18
+ /** The list of FileInfoType contained within the FileWidget */
19
+ filesInfo: FileInfoType[];
20
+ /** The callback handler to pass to the onChange of the input */
21
+ handleChange: (files: FileList) => void;
22
+ /** The callback handler to pass in order to delete a file */
23
+ handleRemove: (index: number) => void;
24
+ }
25
+
26
+ /** Updated the given `dataUrl` to add the `name` to it
27
+ *
28
+ * @param dataURL - The url description string
29
+ * @param name - The name of the file to add to the dataUrl
30
+ * @returns - The `dataUrl` updated to include the name
31
+ */
32
+ function addNameToDataURL(dataURL: string, name: string) {
33
+ return dataURL.replace(';base64', `;name=${encodeURIComponent(name)};base64`);
34
+ }
35
+
36
+ /** Returns a promise that will read the file from the browser and return it as the result of the promise.
37
+ *
38
+ * @param file - The `File` information to read
39
+ * @returns - A promise that resolves to the read file.
40
+ */
41
+ function processFile(file: File): Promise<FileInfoType> {
42
+ const { name, size, type } = file;
43
+ return new Promise((resolve, reject) => {
44
+ const reader = new window.FileReader();
45
+ reader.onerror = reject;
46
+ reader.onload = (event) => {
47
+ if (typeof event.target?.result === 'string') {
48
+ resolve({
49
+ dataURL: addNameToDataURL(event.target.result, name),
50
+ name,
51
+ size,
52
+ type,
53
+ });
54
+ } else {
55
+ resolve({
56
+ dataURL: null,
57
+ name,
58
+ size,
59
+ type,
60
+ });
61
+ }
62
+ };
63
+ reader.readAsDataURL(file);
64
+ });
65
+ }
66
+
67
+ /** Reads a list of files from the browser, returning the results of the promises of each individual file read.
68
+ *
69
+ * @param files - The list of files to read
70
+ * @returns - The list of read files
71
+ */
72
+ function processFiles(files: FileList) {
73
+ return Promise.all(Array.from(files).map(processFile));
74
+ }
75
+
76
+ /** Extracts the file information from the data URLs
77
+ *
78
+ * @param dataURLs - The information about the files
79
+ * @returns - The list of `FileInfoType` objects extracted from the data urls
80
+ */
81
+ function extractFileInfo(dataURLs: string[]): FileInfoType[] {
82
+ return dataURLs.reduce((acc, dataURL) => {
83
+ if (!dataURL) {
84
+ return acc;
85
+ }
86
+ try {
87
+ const { blob, name } = dataURItoBlob(dataURL);
88
+ return [
89
+ ...acc,
90
+ {
91
+ dataURL,
92
+ name: name,
93
+ size: blob.size,
94
+ type: blob.type,
95
+ },
96
+ ];
97
+ } catch {
98
+ // Invalid dataURI, so just ignore it.
99
+ return acc;
100
+ }
101
+ }, [] as FileInfoType[]);
102
+ }
103
+
104
+ /** Hook which encapsulates the logic needed to read and convert a `value` of `File` or `File[]` into the
105
+ * `filesInfo: FileInfoType[]` and the two callback implementations needed to change the list or to remove a
106
+ * `File` from the list. To be used by theme specific `FileWidget` implementations.
107
+ *
108
+ * @param value - The current value of the `FileWidget`
109
+ * @param onChange - The onChange handler for the `FileWidget`
110
+ * @param [multiple=false] - Flag indicating whether the control supports multiple selections
111
+ * @returns - The `UseFileWidgetPropsResult` to be used within a `FileWidget` implementation
112
+ */
113
+ export default function useFileWidgetProps(
114
+ value: string | string[] | undefined | null,
115
+ onChange: (value?: string | null | (string | null)[]) => void,
116
+ multiple = false,
117
+ ): UseFileWidgetPropsResult {
118
+ const values: (string | null)[] = useMemo(() => {
119
+ if (multiple && value) {
120
+ return Array.isArray(value) ? value : [value];
121
+ }
122
+ return [];
123
+ }, [value, multiple]);
124
+ const filesInfo = useMemo(
125
+ () => (Array.isArray(value) ? extractFileInfo(value) : extractFileInfo([value || ''])),
126
+ [value],
127
+ );
128
+
129
+ const handleChange = useCallback(
130
+ (files: FileList) => {
131
+ processFiles(files).then((filesInfoEvent) => {
132
+ const newValue = filesInfoEvent.map((fileInfo) => fileInfo.dataURL || null);
133
+ if (multiple) {
134
+ onChange(values.concat(...newValue));
135
+ } else {
136
+ onChange(newValue[0]);
137
+ }
138
+ });
139
+ },
140
+ [values, multiple, onChange],
141
+ );
142
+ const handleRemove = useCallback(
143
+ (index: number) => {
144
+ if (multiple) {
145
+ const newValue = values.filter((_, i: number) => i !== index);
146
+ onChange(newValue);
147
+ } else {
148
+ onChange(undefined);
149
+ }
150
+ },
151
+ [values, multiple, onChange],
152
+ );
153
+
154
+ return { filesInfo, handleChange, handleRemove };
155
+ }
@@ -11,11 +11,13 @@ import { ErrorSchema, ValidationData } from './types';
11
11
  *
12
12
  * @param validationData - The current `ValidationData` into which to merge the additional errors
13
13
  * @param [additionalErrorSchema] - The optional additional set of errors in an `ErrorSchema`
14
+ * @param [preventDuplicates=false] - Optional flag, if true, will call `mergeObjects()` with `preventDuplicates`
14
15
  * @returns - The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided.
15
16
  */
16
17
  export default function validationDataMerge<T = any>(
17
18
  validationData: ValidationData<T>,
18
19
  additionalErrorSchema?: ErrorSchema<T>,
20
+ preventDuplicates = false,
19
21
  ): ValidationData<T> {
20
22
  if (!additionalErrorSchema) {
21
23
  return validationData;
@@ -24,7 +26,11 @@ export default function validationDataMerge<T = any>(
24
26
  let errors = toErrorList(additionalErrorSchema);
25
27
  let errorSchema = additionalErrorSchema;
26
28
  if (!isEmpty(oldErrorSchema)) {
27
- errorSchema = mergeObjects(oldErrorSchema, additionalErrorSchema, true) as ErrorSchema<T>;
29
+ errorSchema = mergeObjects(
30
+ oldErrorSchema,
31
+ additionalErrorSchema,
32
+ preventDuplicates ? 'preventDuplicates' : true,
33
+ ) as ErrorSchema<T>;
28
34
  errors = [...oldErrors].concat(errors);
29
35
  }
30
36
  return { errorSchema, errors };