@rjsf/core 5.11.2 → 5.12.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 (232) hide show
  1. package/dist/core.umd.js +3464 -0
  2. package/dist/index.esm.js +3814 -0
  3. package/dist/index.esm.js.map +7 -0
  4. package/dist/index.js +3714 -5
  5. package/dist/index.js.map +7 -0
  6. package/{dist/index.d.ts → lib/components/Form.d.ts} +321 -337
  7. package/lib/components/Form.js +474 -0
  8. package/lib/components/Form.js.map +1 -0
  9. package/lib/components/fields/ArrayField.d.ts +179 -0
  10. package/lib/components/fields/ArrayField.js +568 -0
  11. package/lib/components/fields/ArrayField.js.map +1 -0
  12. package/lib/components/fields/BooleanField.d.ts +9 -0
  13. package/lib/components/fields/BooleanField.js +62 -0
  14. package/lib/components/fields/BooleanField.js.map +1 -0
  15. package/lib/components/fields/MultiSchemaField.d.ts +47 -0
  16. package/lib/components/fields/MultiSchemaField.js +129 -0
  17. package/lib/components/fields/MultiSchemaField.js.map +1 -0
  18. package/lib/components/fields/NullField.d.ts +8 -0
  19. package/lib/components/fields/NullField.js +17 -0
  20. package/lib/components/fields/NullField.js.map +1 -0
  21. package/lib/components/fields/NumberField.d.ts +21 -0
  22. package/lib/components/fields/NumberField.js +70 -0
  23. package/lib/components/fields/NumberField.js.map +1 -0
  24. package/lib/components/fields/ObjectField.d.ts +73 -0
  25. package/lib/components/fields/ObjectField.js +222 -0
  26. package/lib/components/fields/ObjectField.js.map +1 -0
  27. package/lib/components/fields/SchemaField.d.ts +10 -0
  28. package/lib/components/fields/SchemaField.js +172 -0
  29. package/lib/components/fields/SchemaField.js.map +1 -0
  30. package/lib/components/fields/StringField.d.ts +8 -0
  31. package/lib/components/fields/StringField.js +25 -0
  32. package/lib/components/fields/StringField.js.map +1 -0
  33. package/lib/components/fields/index.d.ts +3 -0
  34. package/lib/components/fields/index.js +24 -0
  35. package/lib/components/fields/index.js.map +1 -0
  36. package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +8 -0
  37. package/lib/components/templates/ArrayFieldDescriptionTemplate.js +18 -0
  38. package/lib/components/templates/ArrayFieldDescriptionTemplate.js.map +1 -0
  39. package/lib/components/templates/ArrayFieldItemTemplate.d.ts +7 -0
  40. package/lib/components/templates/ArrayFieldItemTemplate.js +20 -0
  41. package/lib/components/templates/ArrayFieldItemTemplate.js.map +1 -0
  42. package/lib/components/templates/ArrayFieldTemplate.d.ts +7 -0
  43. package/lib/components/templates/ArrayFieldTemplate.js +22 -0
  44. package/lib/components/templates/ArrayFieldTemplate.js.map +1 -0
  45. package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +8 -0
  46. package/lib/components/templates/ArrayFieldTitleTemplate.js +18 -0
  47. package/lib/components/templates/ArrayFieldTitleTemplate.js.map +1 -0
  48. package/lib/components/templates/BaseInputTemplate.d.ts +9 -0
  49. package/lib/components/templates/BaseInputTemplate.js +39 -0
  50. package/lib/components/templates/BaseInputTemplate.js.map +1 -0
  51. package/lib/components/templates/ButtonTemplates/AddButton.d.ts +5 -0
  52. package/lib/components/templates/ButtonTemplates/AddButton.js +10 -0
  53. package/lib/components/templates/ButtonTemplates/AddButton.js.map +1 -0
  54. package/lib/components/templates/ButtonTemplates/IconButton.d.ts +7 -0
  55. package/lib/components/templates/ButtonTemplates/IconButton.js +24 -0
  56. package/lib/components/templates/ButtonTemplates/IconButton.js.map +1 -0
  57. package/lib/components/templates/ButtonTemplates/SubmitButton.d.ts +5 -0
  58. package/lib/components/templates/ButtonTemplates/SubmitButton.js +12 -0
  59. package/lib/components/templates/ButtonTemplates/SubmitButton.js.map +1 -0
  60. package/lib/components/templates/ButtonTemplates/index.d.ts +3 -0
  61. package/lib/components/templates/ButtonTemplates/index.js +15 -0
  62. package/lib/components/templates/ButtonTemplates/index.js.map +1 -0
  63. package/lib/components/templates/DescriptionField.d.ts +7 -0
  64. package/lib/components/templates/DescriptionField.js +18 -0
  65. package/lib/components/templates/DescriptionField.js.map +1 -0
  66. package/lib/components/templates/ErrorList.d.ts +7 -0
  67. package/lib/components/templates/ErrorList.js +13 -0
  68. package/lib/components/templates/ErrorList.js.map +1 -0
  69. package/lib/components/templates/FieldErrorTemplate.d.ts +7 -0
  70. package/lib/components/templates/FieldErrorTemplate.js +19 -0
  71. package/lib/components/templates/FieldErrorTemplate.js.map +1 -0
  72. package/lib/components/templates/FieldHelpTemplate.d.ts +7 -0
  73. package/lib/components/templates/FieldHelpTemplate.js +18 -0
  74. package/lib/components/templates/FieldHelpTemplate.js.map +1 -0
  75. package/lib/components/templates/FieldTemplate/FieldTemplate.d.ts +8 -0
  76. package/lib/components/templates/FieldTemplate/FieldTemplate.js +18 -0
  77. package/lib/components/templates/FieldTemplate/FieldTemplate.js.map +1 -0
  78. package/lib/components/templates/FieldTemplate/Label.d.ts +14 -0
  79. package/lib/components/templates/FieldTemplate/Label.js +14 -0
  80. package/lib/components/templates/FieldTemplate/Label.js.map +1 -0
  81. package/lib/components/templates/FieldTemplate/index.d.ts +2 -0
  82. package/lib/components/templates/FieldTemplate/index.js +3 -0
  83. package/lib/components/templates/FieldTemplate/index.js.map +1 -0
  84. package/lib/components/templates/ObjectFieldTemplate.d.ts +9 -0
  85. package/lib/components/templates/ObjectFieldTemplate.js +18 -0
  86. package/lib/components/templates/ObjectFieldTemplate.js.map +1 -0
  87. package/lib/components/templates/TitleField.d.ts +7 -0
  88. package/lib/components/templates/TitleField.js +11 -0
  89. package/lib/components/templates/TitleField.js.map +1 -0
  90. package/lib/components/templates/UnsupportedField.d.ts +9 -0
  91. package/lib/components/templates/UnsupportedField.js +28 -0
  92. package/lib/components/templates/UnsupportedField.js.map +1 -0
  93. package/lib/components/templates/WrapIfAdditionalTemplate.d.ts +8 -0
  94. package/lib/components/templates/WrapIfAdditionalTemplate.js +21 -0
  95. package/lib/components/templates/WrapIfAdditionalTemplate.js.map +1 -0
  96. package/lib/components/templates/index.d.ts +3 -0
  97. package/lib/components/templates/index.js +36 -0
  98. package/lib/components/templates/index.js.map +1 -0
  99. package/lib/components/widgets/AltDateTimeWidget.d.ts +9 -0
  100. package/lib/components/widgets/AltDateTimeWidget.js +14 -0
  101. package/lib/components/widgets/AltDateTimeWidget.js.map +1 -0
  102. package/lib/components/widgets/AltDateWidget.d.ts +7 -0
  103. package/lib/components/widgets/AltDateWidget.js +77 -0
  104. package/lib/components/widgets/AltDateWidget.js.map +1 -0
  105. package/lib/components/widgets/CheckboxWidget.d.ts +9 -0
  106. package/lib/components/widgets/CheckboxWidget.js +23 -0
  107. package/lib/components/widgets/CheckboxWidget.js.map +1 -0
  108. package/lib/components/widgets/CheckboxesWidget.d.ts +9 -0
  109. package/lib/components/widgets/CheckboxesWidget.js +31 -0
  110. package/lib/components/widgets/CheckboxesWidget.js.map +1 -0
  111. package/lib/components/widgets/ColorWidget.d.ts +8 -0
  112. package/lib/components/widgets/ColorWidget.js +13 -0
  113. package/lib/components/widgets/ColorWidget.js.map +1 -0
  114. package/lib/components/widgets/DateTimeWidget.d.ts +8 -0
  115. package/lib/components/widgets/DateTimeWidget.js +13 -0
  116. package/lib/components/widgets/DateTimeWidget.js.map +1 -0
  117. package/lib/components/widgets/DateWidget.d.ts +8 -0
  118. package/lib/components/widgets/DateWidget.js +15 -0
  119. package/lib/components/widgets/DateWidget.js.map +1 -0
  120. package/lib/components/widgets/EmailWidget.d.ts +7 -0
  121. package/lib/components/widgets/EmailWidget.js +12 -0
  122. package/lib/components/widgets/EmailWidget.js.map +1 -0
  123. package/lib/components/widgets/FileWidget.d.ts +8 -0
  124. package/lib/components/widgets/FileWidget.js +105 -0
  125. package/lib/components/widgets/FileWidget.js.map +1 -0
  126. package/lib/components/widgets/HiddenWidget.d.ts +9 -0
  127. package/lib/components/widgets/HiddenWidget.js +11 -0
  128. package/lib/components/widgets/HiddenWidget.js.map +1 -0
  129. package/lib/components/widgets/PasswordWidget.d.ts +7 -0
  130. package/lib/components/widgets/PasswordWidget.js +12 -0
  131. package/lib/components/widgets/PasswordWidget.js.map +1 -0
  132. package/lib/components/widgets/RadioWidget.d.ts +9 -0
  133. package/lib/components/widgets/RadioWidget.js +24 -0
  134. package/lib/components/widgets/RadioWidget.js.map +1 -0
  135. package/lib/components/widgets/RangeWidget.d.ts +8 -0
  136. package/lib/components/widgets/RangeWidget.js +11 -0
  137. package/lib/components/widgets/RangeWidget.js.map +1 -0
  138. package/lib/components/widgets/SelectWidget.d.ts +9 -0
  139. package/lib/components/widgets/SelectWidget.js +41 -0
  140. package/lib/components/widgets/SelectWidget.js.map +1 -0
  141. package/lib/components/widgets/TextWidget.d.ts +7 -0
  142. package/lib/components/widgets/TextWidget.js +12 -0
  143. package/lib/components/widgets/TextWidget.js.map +1 -0
  144. package/lib/components/widgets/TextareaWidget.d.ts +14 -0
  145. package/lib/components/widgets/TextareaWidget.js +19 -0
  146. package/lib/components/widgets/TextareaWidget.js.map +1 -0
  147. package/lib/components/widgets/TimeWidget.d.ts +8 -0
  148. package/lib/components/widgets/TimeWidget.js +15 -0
  149. package/lib/components/widgets/TimeWidget.js.map +1 -0
  150. package/lib/components/widgets/URLWidget.d.ts +7 -0
  151. package/lib/components/widgets/URLWidget.js +12 -0
  152. package/lib/components/widgets/URLWidget.js.map +1 -0
  153. package/lib/components/widgets/UpDownWidget.d.ts +7 -0
  154. package/lib/components/widgets/UpDownWidget.js +12 -0
  155. package/lib/components/widgets/UpDownWidget.js.map +1 -0
  156. package/lib/components/widgets/index.d.ts +3 -0
  157. package/lib/components/widgets/index.js +44 -0
  158. package/lib/components/widgets/index.js.map +1 -0
  159. package/lib/getDefaultRegistry.d.ts +6 -0
  160. package/lib/getDefaultRegistry.js +19 -0
  161. package/lib/getDefaultRegistry.js.map +1 -0
  162. package/lib/index.d.ts +6 -0
  163. package/lib/index.js +6 -0
  164. package/lib/index.js.map +1 -0
  165. package/lib/withTheme.d.ts +9 -0
  166. package/lib/withTheme.js +16 -0
  167. package/lib/withTheme.js.map +1 -0
  168. package/package.json +23 -16
  169. package/src/components/Form.tsx +853 -0
  170. package/src/components/fields/ArrayField.tsx +875 -0
  171. package/src/components/fields/BooleanField.tsx +114 -0
  172. package/src/components/fields/MultiSchemaField.tsx +221 -0
  173. package/src/components/fields/NullField.tsx +22 -0
  174. package/src/components/fields/NumberField.tsx +86 -0
  175. package/src/components/fields/ObjectField.tsx +331 -0
  176. package/src/components/fields/SchemaField.tsx +360 -0
  177. package/src/components/fields/StringField.tsx +71 -0
  178. package/src/components/fields/index.ts +31 -0
  179. package/src/components/templates/ArrayFieldDescriptionTemplate.tsx +41 -0
  180. package/src/components/templates/ArrayFieldItemTemplate.tsx +90 -0
  181. package/src/components/templates/ArrayFieldTemplate.tsx +88 -0
  182. package/src/components/templates/ArrayFieldTitleTemplate.tsx +43 -0
  183. package/src/components/templates/BaseInputTemplate.tsx +102 -0
  184. package/src/components/templates/ButtonTemplates/AddButton.tsx +29 -0
  185. package/src/components/templates/ButtonTemplates/IconButton.tsx +77 -0
  186. package/src/components/templates/ButtonTemplates/SubmitButton.tsx +21 -0
  187. package/src/components/templates/ButtonTemplates/index.ts +22 -0
  188. package/src/components/templates/DescriptionField.tsx +29 -0
  189. package/src/components/templates/ErrorList.tsx +35 -0
  190. package/src/components/templates/FieldErrorTemplate.tsx +33 -0
  191. package/src/components/templates/FieldHelpTemplate.tsx +29 -0
  192. package/src/components/templates/FieldTemplate/FieldTemplate.tsx +41 -0
  193. package/src/components/templates/FieldTemplate/Label.tsx +27 -0
  194. package/src/components/templates/FieldTemplate/index.ts +3 -0
  195. package/src/components/templates/ObjectFieldTemplate.tsx +83 -0
  196. package/src/components/templates/TitleField.tsx +19 -0
  197. package/src/components/templates/UnsupportedField.tsx +37 -0
  198. package/src/components/templates/WrapIfAdditionalTemplate.tsx +80 -0
  199. package/src/components/templates/index.ts +43 -0
  200. package/src/components/widgets/AltDateTimeWidget.tsx +16 -0
  201. package/src/components/widgets/AltDateWidget.tsx +198 -0
  202. package/src/components/widgets/CheckboxWidget.tsx +92 -0
  203. package/src/components/widgets/CheckboxesWidget.tsx +92 -0
  204. package/src/components/widgets/ColorWidget.tsx +14 -0
  205. package/src/components/widgets/DateTimeWidget.tsx +31 -0
  206. package/src/components/widgets/DateWidget.tsx +17 -0
  207. package/src/components/widgets/EmailWidget.tsx +13 -0
  208. package/src/components/widgets/FileWidget.tsx +178 -0
  209. package/src/components/widgets/HiddenWidget.tsx +15 -0
  210. package/src/components/widgets/PasswordWidget.tsx +15 -0
  211. package/src/components/widgets/RadioWidget.tsx +88 -0
  212. package/src/components/widgets/RangeWidget.tsx +23 -0
  213. package/src/components/widgets/SelectWidget.tsx +100 -0
  214. package/src/components/widgets/TextWidget.tsx +13 -0
  215. package/src/components/widgets/TextareaWidget.tsx +61 -0
  216. package/src/components/widgets/TimeWidget.tsx +17 -0
  217. package/src/components/widgets/URLWidget.tsx +13 -0
  218. package/src/components/widgets/UpDownWidget.tsx +13 -0
  219. package/src/components/widgets/index.ts +51 -0
  220. package/src/getDefaultRegistry.ts +24 -0
  221. package/src/index.ts +8 -0
  222. package/src/withTheme.tsx +42 -0
  223. package/dist/core.cjs.development.js +0 -4403
  224. package/dist/core.cjs.development.js.map +0 -1
  225. package/dist/core.cjs.production.min.js +0 -2
  226. package/dist/core.cjs.production.min.js.map +0 -1
  227. package/dist/core.esm.js +0 -4383
  228. package/dist/core.esm.js.map +0 -1
  229. package/dist/core.umd.development.js +0 -4393
  230. package/dist/core.umd.development.js.map +0 -1
  231. package/dist/core.umd.production.min.js +0 -2
  232. package/dist/core.umd.production.min.js.map +0 -1
package/dist/core.esm.js DELETED
@@ -1,4383 +0,0 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Component, useState, useCallback, useEffect, useReducer, createRef, forwardRef } from 'react';
3
- import { isFixedItems, allowAdditionalItems, ITEMS_KEY, TranslatableString, getUiOptions, getTemplate, isCustomWidget, getWidget, optionsList, deepEquals, getDiscriminatorFieldFromSchema, ERRORS_KEY, ADDITIONAL_PROPERTY_FLAG, mergeSchemas, asNumber, REF_KEY, orderProperties, PROPERTIES_KEY, ANY_OF_KEY, ONE_OF_KEY, mergeObjects, UI_OPTIONS_KEY, getSchemaType, descriptionId, ID_KEY, hasWidget, titleId, getInputProps, examplesId, ariaDescribedByIds, getSubmitButtonOptions, errorId, helpId, canExpand, parseDateString, toDateString, pad, schemaRequiresTrueValue, labelValue, enumOptionsValueForIndex, enumOptionsIsSelected, optionId, enumOptionsSelectValue, enumOptionsDeselectValue, utcToLocal, localToUTC, dataURItoBlob, enumOptionsIndexForValue, englishStringTranslator, isObject as isObject$1, validationDataMerge, toErrorList, createSchemaUtils, shouldRender, UI_GLOBAL_OPTIONS_KEY, RJSF_ADDITONAL_PROPERTIES_FLAG, NAME_KEY, SUBMIT_BTN_OPTIONS_KEY } from '@rjsf/utils';
4
- import get from 'lodash-es/get';
5
- import isEmpty from 'lodash-es/isEmpty';
6
- import _pick from 'lodash-es/pick';
7
- import _toPath from 'lodash-es/toPath';
8
- import cloneDeep from 'lodash-es/cloneDeep';
9
- import isObject from 'lodash-es/isObject';
10
- import set from 'lodash-es/set';
11
- import { nanoid } from 'nanoid';
12
- import omit from 'lodash-es/omit';
13
- import unset from 'lodash-es/unset';
14
- import Markdown from 'markdown-to-jsx';
15
- import has from 'lodash-es/has';
16
-
17
- /** Used to generate a unique ID for an element in a row */
18
- function generateRowId() {
19
- return nanoid();
20
- }
21
- /** Converts the `formData` into `KeyedFormDataType` data, using the `generateRowId()` function to create the key
22
- *
23
- * @param formData - The data for the form
24
- * @returns - The `formData` converted into a `KeyedFormDataType` element
25
- */
26
- function generateKeyedFormData(formData) {
27
- return !Array.isArray(formData) ? [] : formData.map(item => {
28
- return {
29
- key: generateRowId(),
30
- item
31
- };
32
- });
33
- }
34
- /** Converts `KeyedFormDataType` data into the inner `formData`
35
- *
36
- * @param keyedFormData - The `KeyedFormDataType` to be converted
37
- * @returns - The inner `formData` item(s) in the `keyedFormData`
38
- */
39
- function keyedToPlainFormData(keyedFormData) {
40
- if (Array.isArray(keyedFormData)) {
41
- return keyedFormData.map(keyedItem => keyedItem.item);
42
- }
43
- return [];
44
- }
45
- /** The `ArrayField` component is used to render a field in the schema that is of type `array`. It supports both normal
46
- * and fixed array, allowing user to add and remove elements from the array data.
47
- */
48
- class ArrayField extends Component {
49
- /** Constructs an `ArrayField` from the `props`, generating the initial keyed data from the `formData`
50
- *
51
- * @param props - The `FieldProps` for this template
52
- */
53
- constructor(props) {
54
- super(props);
55
- /** Returns the default form information for an item based on the schema for that item. Deals with the possibility
56
- * that the schema is fixed and allows additional items.
57
- */
58
- this._getNewFormDataRow = () => {
59
- const {
60
- schema,
61
- registry
62
- } = this.props;
63
- const {
64
- schemaUtils
65
- } = registry;
66
- let itemSchema = schema.items;
67
- if (isFixedItems(schema) && allowAdditionalItems(schema)) {
68
- itemSchema = schema.additionalItems;
69
- }
70
- // Cast this as a T to work around schema utils being for T[] caused by the FieldProps<T[], S, F> call on the class
71
- return schemaUtils.getDefaultFormState(itemSchema);
72
- };
73
- /** Callback handler for when the user clicks on the add button. Creates a new row of keyed form data at the end of
74
- * the list, adding it into the state, and then returning `onChange()` with the plain form data converted from the
75
- * keyed data
76
- *
77
- * @param event - The event for the click
78
- */
79
- this.onAddClick = event => {
80
- this._handleAddClick(event);
81
- };
82
- /** Callback handler for when the user clicks on the add button on an existing array element. Creates a new row of
83
- * keyed form data inserted at the `index`, adding it into the state, and then returning `onChange()` with the plain
84
- * form data converted from the keyed data
85
- *
86
- * @param index - The index at which the add button is clicked
87
- */
88
- this.onAddIndexClick = index => {
89
- return event => {
90
- this._handleAddClick(event, index);
91
- };
92
- };
93
- /** Callback handler for when the user clicks on the copy button on an existing array element. Clones the row of
94
- * keyed form data at the `index` into the next position in the state, and then returning `onChange()` with the plain
95
- * form data converted from the keyed data
96
- *
97
- * @param index - The index at which the copy button is clicked
98
- */
99
- this.onCopyIndexClick = index => {
100
- return event => {
101
- if (event) {
102
- event.preventDefault();
103
- }
104
- const {
105
- onChange
106
- } = this.props;
107
- const {
108
- keyedFormData
109
- } = this.state;
110
- const newKeyedFormDataRow = {
111
- key: generateRowId(),
112
- item: cloneDeep(keyedFormData[index].item)
113
- };
114
- const newKeyedFormData = [...keyedFormData];
115
- if (index !== undefined) {
116
- newKeyedFormData.splice(index + 1, 0, newKeyedFormDataRow);
117
- } else {
118
- newKeyedFormData.push(newKeyedFormDataRow);
119
- }
120
- this.setState({
121
- keyedFormData: newKeyedFormData,
122
- updatedKeyedFormData: true
123
- }, () => onChange(keyedToPlainFormData(newKeyedFormData)));
124
- };
125
- };
126
- /** Callback handler for when the user clicks on the remove button on an existing array element. Removes the row of
127
- * keyed form data at the `index` in the state, and then returning `onChange()` with the plain form data converted
128
- * from the keyed data
129
- *
130
- * @param index - The index at which the remove button is clicked
131
- */
132
- this.onDropIndexClick = index => {
133
- return event => {
134
- if (event) {
135
- event.preventDefault();
136
- }
137
- const {
138
- onChange,
139
- errorSchema
140
- } = this.props;
141
- const {
142
- keyedFormData
143
- } = this.state;
144
- // refs #195: revalidate to ensure properly reindexing errors
145
- let newErrorSchema;
146
- if (errorSchema) {
147
- newErrorSchema = {};
148
- for (const idx in errorSchema) {
149
- const i = parseInt(idx);
150
- if (i < index) {
151
- set(newErrorSchema, [i], errorSchema[idx]);
152
- } else if (i > index) {
153
- set(newErrorSchema, [i - 1], errorSchema[idx]);
154
- }
155
- }
156
- }
157
- const newKeyedFormData = keyedFormData.filter((_, i) => i !== index);
158
- this.setState({
159
- keyedFormData: newKeyedFormData,
160
- updatedKeyedFormData: true
161
- }, () => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema));
162
- };
163
- };
164
- /** Callback handler for when the user clicks on one of the move item buttons on an existing array element. Moves the
165
- * row of keyed form data at the `index` to the `newIndex` in the state, and then returning `onChange()` with the
166
- * plain form data converted from the keyed data
167
- *
168
- * @param index - The index of the item to move
169
- * @param newIndex - The index to where the item is to be moved
170
- */
171
- this.onReorderClick = (index, newIndex) => {
172
- return event => {
173
- if (event) {
174
- event.preventDefault();
175
- event.currentTarget.blur();
176
- }
177
- const {
178
- onChange,
179
- errorSchema
180
- } = this.props;
181
- let newErrorSchema;
182
- if (errorSchema) {
183
- newErrorSchema = {};
184
- for (const idx in errorSchema) {
185
- const i = parseInt(idx);
186
- if (i == index) {
187
- set(newErrorSchema, [newIndex], errorSchema[index]);
188
- } else if (i == newIndex) {
189
- set(newErrorSchema, [index], errorSchema[newIndex]);
190
- } else {
191
- set(newErrorSchema, [idx], errorSchema[i]);
192
- }
193
- }
194
- }
195
- const {
196
- keyedFormData
197
- } = this.state;
198
- function reOrderArray() {
199
- // Copy item
200
- const _newKeyedFormData = keyedFormData.slice();
201
- // Moves item from index to newIndex
202
- _newKeyedFormData.splice(index, 1);
203
- _newKeyedFormData.splice(newIndex, 0, keyedFormData[index]);
204
- return _newKeyedFormData;
205
- }
206
- const newKeyedFormData = reOrderArray();
207
- this.setState({
208
- keyedFormData: newKeyedFormData
209
- }, () => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema));
210
- };
211
- };
212
- /** Callback handler used to deal with changing the value of the data in the array at the `index`. Calls the
213
- * `onChange` callback with the updated form data
214
- *
215
- * @param index - The index of the item being changed
216
- */
217
- this.onChangeForIndex = index => {
218
- return (value, newErrorSchema, id) => {
219
- const {
220
- formData,
221
- onChange,
222
- errorSchema
223
- } = this.props;
224
- const arrayData = Array.isArray(formData) ? formData : [];
225
- const newFormData = arrayData.map((item, i) => {
226
- // We need to treat undefined items as nulls to have validation.
227
- // See https://github.com/tdegrunt/jsonschema/issues/206
228
- const jsonValue = typeof value === 'undefined' ? null : value;
229
- return index === i ? jsonValue : item;
230
- });
231
- onChange(newFormData, errorSchema && errorSchema && {
232
- ...errorSchema,
233
- [index]: newErrorSchema
234
- }, id);
235
- };
236
- };
237
- /** Callback handler used to change the value for a checkbox */
238
- this.onSelectChange = value => {
239
- const {
240
- onChange,
241
- idSchema
242
- } = this.props;
243
- onChange(value, undefined, idSchema && idSchema.$id);
244
- };
245
- const {
246
- formData: _formData = []
247
- } = props;
248
- const _keyedFormData = generateKeyedFormData(_formData);
249
- this.state = {
250
- keyedFormData: _keyedFormData,
251
- updatedKeyedFormData: false
252
- };
253
- }
254
- /** React lifecycle method that is called when the props are about to change allowing the state to be updated. It
255
- * regenerates the keyed form data and returns it
256
- *
257
- * @param nextProps - The next set of props data
258
- * @param prevState - The previous set of state data
259
- */
260
- static getDerivedStateFromProps(nextProps, prevState) {
261
- // Don't call getDerivedStateFromProps if keyed formdata was just updated.
262
- if (prevState.updatedKeyedFormData) {
263
- return {
264
- updatedKeyedFormData: false
265
- };
266
- }
267
- const nextFormData = Array.isArray(nextProps.formData) ? nextProps.formData : [];
268
- const previousKeyedFormData = prevState.keyedFormData || [];
269
- const newKeyedFormData = nextFormData.length === previousKeyedFormData.length ? previousKeyedFormData.map((previousKeyedFormDatum, index) => {
270
- return {
271
- key: previousKeyedFormDatum.key,
272
- item: nextFormData[index]
273
- };
274
- }) : generateKeyedFormData(nextFormData);
275
- return {
276
- keyedFormData: newKeyedFormData
277
- };
278
- }
279
- /** Returns the appropriate title for an item by getting first the title from the schema.items, then falling back to
280
- * the description from the schema.items, and finally the string "Item"
281
- */
282
- get itemTitle() {
283
- const {
284
- schema,
285
- registry
286
- } = this.props;
287
- const {
288
- translateString
289
- } = registry;
290
- return get(schema, [ITEMS_KEY, 'title'], get(schema, [ITEMS_KEY, 'description'], translateString(TranslatableString.ArrayItemTitle)));
291
- }
292
- /** Determines whether the item described in the schema is always required, which is determined by whether any item
293
- * may be null.
294
- *
295
- * @param itemSchema - The schema for the item
296
- * @return - True if the item schema type does not contain the "null" type
297
- */
298
- isItemRequired(itemSchema) {
299
- if (Array.isArray(itemSchema.type)) {
300
- // While we don't yet support composite/nullable jsonschema types, it's
301
- // future-proof to check for requirement against these.
302
- return !itemSchema.type.includes('null');
303
- }
304
- // All non-null array item types are inherently required by design
305
- return itemSchema.type !== 'null';
306
- }
307
- /** Determines whether more items can be added to the array. If the uiSchema indicates the array doesn't allow adding
308
- * then false is returned. Otherwise, if the schema indicates that there are a maximum number of items and the
309
- * `formData` matches that value, then false is returned, otherwise true is returned.
310
- *
311
- * @param formItems - The list of items in the form
312
- * @returns - True if the item is addable otherwise false
313
- */
314
- canAddItem(formItems) {
315
- const {
316
- schema,
317
- uiSchema,
318
- registry
319
- } = this.props;
320
- let {
321
- addable
322
- } = getUiOptions(uiSchema, registry.globalUiOptions);
323
- if (addable !== false) {
324
- // if ui:options.addable was not explicitly set to false, we can add
325
- // another item if we have not exceeded maxItems yet
326
- if (schema.maxItems !== undefined) {
327
- addable = formItems.length < schema.maxItems;
328
- } else {
329
- addable = true;
330
- }
331
- }
332
- return addable;
333
- }
334
- /** Callback handler for when the user clicks on the add or add at index buttons. Creates a new row of keyed form data
335
- * either at the end of the list (when index is not specified) or inserted at the `index` when it is, adding it into
336
- * the state, and then returning `onChange()` with the plain form data converted from the keyed data
337
- *
338
- * @param event - The event for the click
339
- * @param [index] - The optional index at which to add the new data
340
- */
341
- _handleAddClick(event, index) {
342
- if (event) {
343
- event.preventDefault();
344
- }
345
- const {
346
- onChange
347
- } = this.props;
348
- const {
349
- keyedFormData
350
- } = this.state;
351
- const newKeyedFormDataRow = {
352
- key: generateRowId(),
353
- item: this._getNewFormDataRow()
354
- };
355
- const newKeyedFormData = [...keyedFormData];
356
- if (index !== undefined) {
357
- newKeyedFormData.splice(index, 0, newKeyedFormDataRow);
358
- } else {
359
- newKeyedFormData.push(newKeyedFormDataRow);
360
- }
361
- this.setState({
362
- keyedFormData: newKeyedFormData,
363
- updatedKeyedFormData: true
364
- }, () => onChange(keyedToPlainFormData(newKeyedFormData)));
365
- }
366
- /** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
367
- */
368
- render() {
369
- const {
370
- schema,
371
- uiSchema,
372
- idSchema,
373
- registry
374
- } = this.props;
375
- const {
376
- schemaUtils,
377
- translateString
378
- } = registry;
379
- if (!(ITEMS_KEY in schema)) {
380
- const uiOptions = getUiOptions(uiSchema);
381
- const UnsupportedFieldTemplate = getTemplate('UnsupportedFieldTemplate', registry, uiOptions);
382
- return jsx(UnsupportedFieldTemplate, {
383
- schema: schema,
384
- idSchema: idSchema,
385
- reason: translateString(TranslatableString.MissingItems),
386
- registry: registry
387
- });
388
- }
389
- if (schemaUtils.isMultiSelect(schema)) {
390
- // If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
391
- return this.renderMultiSelect();
392
- }
393
- if (isCustomWidget(uiSchema)) {
394
- return this.renderCustomWidget();
395
- }
396
- if (isFixedItems(schema)) {
397
- return this.renderFixedArray();
398
- }
399
- if (schemaUtils.isFilesArray(schema, uiSchema)) {
400
- return this.renderFiles();
401
- }
402
- return this.renderNormalArray();
403
- }
404
- /** Renders a normal array without any limitations of length
405
- */
406
- renderNormalArray() {
407
- const {
408
- schema,
409
- uiSchema = {},
410
- errorSchema,
411
- idSchema,
412
- name,
413
- disabled = false,
414
- readonly = false,
415
- autofocus = false,
416
- required = false,
417
- registry,
418
- onBlur,
419
- onFocus,
420
- idPrefix,
421
- idSeparator = '_',
422
- rawErrors
423
- } = this.props;
424
- const {
425
- keyedFormData
426
- } = this.state;
427
- const title = schema.title === undefined ? name : schema.title;
428
- const {
429
- schemaUtils,
430
- formContext
431
- } = registry;
432
- const uiOptions = getUiOptions(uiSchema);
433
- const _schemaItems = isObject(schema.items) ? schema.items : {};
434
- const itemsSchema = schemaUtils.retrieveSchema(_schemaItems);
435
- const formData = keyedToPlainFormData(this.state.keyedFormData);
436
- const canAdd = this.canAddItem(formData);
437
- const arrayProps = {
438
- canAdd,
439
- items: keyedFormData.map((keyedItem, index) => {
440
- const {
441
- key,
442
- item
443
- } = keyedItem;
444
- // While we are actually dealing with a single item of type T, the types require a T[], so cast
445
- const itemCast = item;
446
- const itemSchema = schemaUtils.retrieveSchema(_schemaItems, itemCast);
447
- const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
448
- const itemIdPrefix = idSchema.$id + idSeparator + index;
449
- const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
450
- return this.renderArrayFieldItem({
451
- key,
452
- index,
453
- name: name && `${name}-${index}`,
454
- canAdd,
455
- canMoveUp: index > 0,
456
- canMoveDown: index < formData.length - 1,
457
- itemSchema,
458
- itemIdSchema,
459
- itemErrorSchema,
460
- itemData: itemCast,
461
- itemUiSchema: uiSchema.items,
462
- autofocus: autofocus && index === 0,
463
- onBlur,
464
- onFocus,
465
- rawErrors,
466
- totalItems: keyedFormData.length
467
- });
468
- }),
469
- className: `field field-array field-array-of-${itemsSchema.type}`,
470
- disabled,
471
- idSchema,
472
- uiSchema,
473
- onAddClick: this.onAddClick,
474
- readonly,
475
- required,
476
- schema,
477
- title,
478
- formContext,
479
- formData,
480
- rawErrors,
481
- registry
482
- };
483
- const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
484
- return jsx(Template, {
485
- ...arrayProps
486
- });
487
- }
488
- /** Renders an array using the custom widget provided by the user in the `uiSchema`
489
- */
490
- renderCustomWidget() {
491
- const {
492
- schema,
493
- idSchema,
494
- uiSchema,
495
- disabled = false,
496
- readonly = false,
497
- autofocus = false,
498
- required = false,
499
- hideError,
500
- placeholder,
501
- onBlur,
502
- onFocus,
503
- formData: items = [],
504
- registry,
505
- rawErrors,
506
- name
507
- } = this.props;
508
- const {
509
- widgets,
510
- formContext,
511
- globalUiOptions,
512
- schemaUtils
513
- } = registry;
514
- const {
515
- widget,
516
- title: uiTitle,
517
- ...options
518
- } = getUiOptions(uiSchema, globalUiOptions);
519
- const Widget = getWidget(schema, widget, widgets);
520
- const label = uiTitle ?? schema.title ?? name;
521
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
522
- return jsx(Widget, {
523
- id: idSchema.$id,
524
- name: name,
525
- multiple: true,
526
- onChange: this.onSelectChange,
527
- onBlur: onBlur,
528
- onFocus: onFocus,
529
- options: options,
530
- schema: schema,
531
- uiSchema: uiSchema,
532
- registry: registry,
533
- value: items,
534
- disabled: disabled,
535
- readonly: readonly,
536
- hideError: hideError,
537
- required: required,
538
- label: label,
539
- hideLabel: !displayLabel,
540
- placeholder: placeholder,
541
- formContext: formContext,
542
- autofocus: autofocus,
543
- rawErrors: rawErrors
544
- });
545
- }
546
- /** Renders an array as a set of checkboxes
547
- */
548
- renderMultiSelect() {
549
- const {
550
- schema,
551
- idSchema,
552
- uiSchema,
553
- formData: items = [],
554
- disabled = false,
555
- readonly = false,
556
- autofocus = false,
557
- required = false,
558
- placeholder,
559
- onBlur,
560
- onFocus,
561
- registry,
562
- rawErrors,
563
- name
564
- } = this.props;
565
- const {
566
- widgets,
567
- schemaUtils,
568
- formContext,
569
- globalUiOptions
570
- } = registry;
571
- const itemsSchema = schemaUtils.retrieveSchema(schema.items, items);
572
- const enumOptions = optionsList(itemsSchema);
573
- const {
574
- widget = 'select',
575
- title: uiTitle,
576
- ...options
577
- } = getUiOptions(uiSchema, globalUiOptions);
578
- const Widget = getWidget(schema, widget, widgets);
579
- const label = uiTitle ?? schema.title ?? name;
580
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
581
- return jsx(Widget, {
582
- id: idSchema.$id,
583
- name: name,
584
- multiple: true,
585
- onChange: this.onSelectChange,
586
- onBlur: onBlur,
587
- onFocus: onFocus,
588
- options: {
589
- ...options,
590
- enumOptions
591
- },
592
- schema: schema,
593
- uiSchema: uiSchema,
594
- registry: registry,
595
- value: items,
596
- disabled: disabled,
597
- readonly: readonly,
598
- required: required,
599
- label: label,
600
- hideLabel: !displayLabel,
601
- placeholder: placeholder,
602
- formContext: formContext,
603
- autofocus: autofocus,
604
- rawErrors: rawErrors
605
- });
606
- }
607
- /** Renders an array of files using the `FileWidget`
608
- */
609
- renderFiles() {
610
- const {
611
- schema,
612
- uiSchema,
613
- idSchema,
614
- name,
615
- disabled = false,
616
- readonly = false,
617
- autofocus = false,
618
- required = false,
619
- onBlur,
620
- onFocus,
621
- registry,
622
- formData: items = [],
623
- rawErrors
624
- } = this.props;
625
- const {
626
- widgets,
627
- formContext,
628
- globalUiOptions,
629
- schemaUtils
630
- } = registry;
631
- const {
632
- widget = 'files',
633
- title: uiTitle,
634
- ...options
635
- } = getUiOptions(uiSchema, globalUiOptions);
636
- const Widget = getWidget(schema, widget, widgets);
637
- const label = uiTitle ?? schema.title ?? name;
638
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
639
- return jsx(Widget, {
640
- options: options,
641
- id: idSchema.$id,
642
- name: name,
643
- multiple: true,
644
- onChange: this.onSelectChange,
645
- onBlur: onBlur,
646
- onFocus: onFocus,
647
- schema: schema,
648
- uiSchema: uiSchema,
649
- value: items,
650
- disabled: disabled,
651
- readonly: readonly,
652
- required: required,
653
- registry: registry,
654
- formContext: formContext,
655
- autofocus: autofocus,
656
- rawErrors: rawErrors,
657
- label: label,
658
- hideLabel: !displayLabel
659
- });
660
- }
661
- /** Renders an array that has a maximum limit of items
662
- */
663
- renderFixedArray() {
664
- const {
665
- schema,
666
- uiSchema = {},
667
- formData = [],
668
- errorSchema,
669
- idPrefix,
670
- idSeparator = '_',
671
- idSchema,
672
- name,
673
- disabled = false,
674
- readonly = false,
675
- autofocus = false,
676
- required = false,
677
- registry,
678
- onBlur,
679
- onFocus,
680
- rawErrors
681
- } = this.props;
682
- const {
683
- keyedFormData
684
- } = this.state;
685
- let {
686
- formData: items = []
687
- } = this.props;
688
- const title = schema.title || name;
689
- const uiOptions = getUiOptions(uiSchema);
690
- const {
691
- schemaUtils,
692
- formContext
693
- } = registry;
694
- const _schemaItems = isObject(schema.items) ? schema.items : [];
695
- const itemSchemas = _schemaItems.map((item, index) => schemaUtils.retrieveSchema(item, formData[index]));
696
- const additionalSchema = isObject(schema.additionalItems) ? schemaUtils.retrieveSchema(schema.additionalItems, formData) : null;
697
- if (!items || items.length < itemSchemas.length) {
698
- // to make sure at least all fixed items are generated
699
- items = items || [];
700
- items = items.concat(new Array(itemSchemas.length - items.length));
701
- }
702
- // These are the props passed into the render function
703
- const canAdd = this.canAddItem(items) && !!additionalSchema;
704
- const arrayProps = {
705
- canAdd,
706
- className: 'field field-array field-array-fixed-items',
707
- disabled,
708
- idSchema,
709
- formData,
710
- items: keyedFormData.map((keyedItem, index) => {
711
- const {
712
- key,
713
- item
714
- } = keyedItem;
715
- // While we are actually dealing with a single item of type T, the types require a T[], so cast
716
- const itemCast = item;
717
- const additional = index >= itemSchemas.length;
718
- const itemSchema = additional && isObject(schema.additionalItems) ? schemaUtils.retrieveSchema(schema.additionalItems, itemCast) : itemSchemas[index];
719
- const itemIdPrefix = idSchema.$id + idSeparator + index;
720
- const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
721
- const itemUiSchema = additional ? uiSchema.additionalItems || {} : Array.isArray(uiSchema.items) ? uiSchema.items[index] : uiSchema.items || {};
722
- const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
723
- return this.renderArrayFieldItem({
724
- key,
725
- index,
726
- name: name && `${name}-${index}`,
727
- canAdd,
728
- canRemove: additional,
729
- canMoveUp: index >= itemSchemas.length + 1,
730
- canMoveDown: additional && index < items.length - 1,
731
- itemSchema,
732
- itemData: itemCast,
733
- itemUiSchema,
734
- itemIdSchema,
735
- itemErrorSchema,
736
- autofocus: autofocus && index === 0,
737
- onBlur,
738
- onFocus,
739
- rawErrors,
740
- totalItems: keyedFormData.length
741
- });
742
- }),
743
- onAddClick: this.onAddClick,
744
- readonly,
745
- required,
746
- registry,
747
- schema,
748
- uiSchema,
749
- title,
750
- formContext,
751
- rawErrors
752
- };
753
- const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
754
- return jsx(Template, {
755
- ...arrayProps
756
- });
757
- }
758
- /** Renders the individual array item using a `SchemaField` along with the additional properties required to be send
759
- * back to the `ArrayFieldItemTemplate`.
760
- *
761
- * @param props - The props for the individual array item to be rendered
762
- */
763
- renderArrayFieldItem(props) {
764
- const {
765
- key,
766
- index,
767
- name,
768
- canAdd,
769
- canRemove = true,
770
- canMoveUp,
771
- canMoveDown,
772
- itemSchema,
773
- itemData,
774
- itemUiSchema,
775
- itemIdSchema,
776
- itemErrorSchema,
777
- autofocus,
778
- onBlur,
779
- onFocus,
780
- rawErrors,
781
- totalItems
782
- } = props;
783
- const {
784
- disabled,
785
- hideError,
786
- idPrefix,
787
- idSeparator,
788
- readonly,
789
- uiSchema,
790
- registry,
791
- formContext
792
- } = this.props;
793
- const {
794
- fields: {
795
- ArraySchemaField,
796
- SchemaField
797
- },
798
- globalUiOptions
799
- } = registry;
800
- const ItemSchemaField = ArraySchemaField || SchemaField;
801
- const {
802
- orderable = true,
803
- removable = true,
804
- copyable = false
805
- } = getUiOptions(uiSchema, globalUiOptions);
806
- const has = {
807
- moveUp: orderable && canMoveUp,
808
- moveDown: orderable && canMoveDown,
809
- copy: copyable && canAdd,
810
- remove: removable && canRemove,
811
- toolbar: false
812
- };
813
- has.toolbar = Object.keys(has).some(key => has[key]);
814
- return {
815
- children: jsx(ItemSchemaField, {
816
- name: name,
817
- index: index,
818
- schema: itemSchema,
819
- uiSchema: itemUiSchema,
820
- formData: itemData,
821
- formContext: formContext,
822
- errorSchema: itemErrorSchema,
823
- idPrefix: idPrefix,
824
- idSeparator: idSeparator,
825
- idSchema: itemIdSchema,
826
- required: this.isItemRequired(itemSchema),
827
- onChange: this.onChangeForIndex(index),
828
- onBlur: onBlur,
829
- onFocus: onFocus,
830
- registry: registry,
831
- disabled: disabled,
832
- readonly: readonly,
833
- hideError: hideError,
834
- autofocus: autofocus,
835
- rawErrors: rawErrors
836
- }),
837
- className: 'array-item',
838
- disabled,
839
- canAdd,
840
- hasCopy: has.copy,
841
- hasToolbar: has.toolbar,
842
- hasMoveUp: has.moveUp,
843
- hasMoveDown: has.moveDown,
844
- hasRemove: has.remove,
845
- index,
846
- totalItems,
847
- key,
848
- onAddIndexClick: this.onAddIndexClick,
849
- onCopyIndexClick: this.onCopyIndexClick,
850
- onDropIndexClick: this.onDropIndexClick,
851
- onReorderClick: this.onReorderClick,
852
- readonly,
853
- registry,
854
- schema: itemSchema,
855
- uiSchema: itemUiSchema
856
- };
857
- }
858
- }
859
-
860
- /** The `BooleanField` component is used to render a field in the schema is boolean. It constructs `enumOptions` for the
861
- * two boolean values based on the various alternatives in the schema.
862
- *
863
- * @param props - The `FieldProps` for this template
864
- */
865
- function BooleanField(props) {
866
- const {
867
- schema,
868
- name,
869
- uiSchema,
870
- idSchema,
871
- formData,
872
- registry,
873
- required,
874
- disabled,
875
- readonly,
876
- autofocus,
877
- onChange,
878
- onFocus,
879
- onBlur,
880
- rawErrors
881
- } = props;
882
- const {
883
- title
884
- } = schema;
885
- const {
886
- widgets,
887
- formContext,
888
- translateString,
889
- globalUiOptions
890
- } = registry;
891
- const {
892
- widget = 'checkbox',
893
- title: uiTitle,
894
- // Unlike the other fields, don't use `getDisplayLabel()` since it always returns false for the boolean type
895
- label: displayLabel = true,
896
- ...options
897
- } = getUiOptions(uiSchema, globalUiOptions);
898
- const Widget = getWidget(schema, widget, widgets);
899
- const yes = translateString(TranslatableString.YesLabel);
900
- const no = translateString(TranslatableString.NoLabel);
901
- let enumOptions;
902
- const label = uiTitle ?? title ?? name;
903
- if (Array.isArray(schema.oneOf)) {
904
- enumOptions = optionsList({
905
- oneOf: schema.oneOf.map(option => {
906
- if (isObject(option)) {
907
- return {
908
- ...option,
909
- title: option.title || (option.const === true ? yes : no)
910
- };
911
- }
912
- return undefined;
913
- }).filter(o => o) // cast away the error that typescript can't grok is fixed
914
- });
915
- } else {
916
- // We deprecated enumNames in v5. It's intentionally omitted from RSJFSchema type, so we need to cast here.
917
- const schemaWithEnumNames = schema;
918
- const enums = schema.enum ?? [true, false];
919
- if (!schemaWithEnumNames.enumNames && enums.length === 2 && enums.every(v => typeof v === 'boolean')) {
920
- enumOptions = [{
921
- value: enums[0],
922
- label: enums[0] ? yes : no
923
- }, {
924
- value: enums[1],
925
- label: enums[1] ? yes : no
926
- }];
927
- } else {
928
- enumOptions = optionsList({
929
- enum: enums,
930
- // NOTE: enumNames is deprecated, but still supported for now.
931
- enumNames: schemaWithEnumNames.enumNames
932
- });
933
- }
934
- }
935
- return jsx(Widget, {
936
- options: {
937
- ...options,
938
- enumOptions
939
- },
940
- schema: schema,
941
- uiSchema: uiSchema,
942
- id: idSchema.$id,
943
- name: name,
944
- onChange: onChange,
945
- onFocus: onFocus,
946
- onBlur: onBlur,
947
- label: label,
948
- hideLabel: !displayLabel,
949
- value: formData,
950
- required: required,
951
- disabled: disabled,
952
- readonly: readonly,
953
- registry: registry,
954
- formContext: formContext,
955
- autofocus: autofocus,
956
- rawErrors: rawErrors
957
- });
958
- }
959
-
960
- /** The `AnyOfField` component is used to render a field in the schema that is an `anyOf`, `allOf` or `oneOf`. It tracks
961
- * the currently selected option and cleans up any irrelevant data in `formData`.
962
- *
963
- * @param props - The `FieldProps` for this template
964
- */
965
- class AnyOfField extends Component {
966
- /** Constructs an `AnyOfField` with the given `props` to initialize the initially selected option in state
967
- *
968
- * @param props - The `FieldProps` for this template
969
- */
970
- constructor(props) {
971
- super(props);
972
- /** Callback handler to remember what the currently selected option is. In addition to that the `formData` is updated
973
- * to remove properties that are not part of the newly selected option schema, and then the updated data is passed to
974
- * the `onChange` handler.
975
- *
976
- * @param option - The new option value being selected
977
- */
978
- this.onOptionChange = option => {
979
- const {
980
- selectedOption,
981
- retrievedOptions
982
- } = this.state;
983
- const {
984
- formData,
985
- onChange,
986
- registry
987
- } = this.props;
988
- const {
989
- schemaUtils
990
- } = registry;
991
- const intOption = option !== undefined ? parseInt(option, 10) : -1;
992
- if (intOption === selectedOption) {
993
- return;
994
- }
995
- const newOption = intOption >= 0 ? retrievedOptions[intOption] : undefined;
996
- const oldOption = selectedOption >= 0 ? retrievedOptions[selectedOption] : undefined;
997
- let newFormData = schemaUtils.sanitizeDataForNewSchema(newOption, oldOption, formData);
998
- if (newFormData && newOption) {
999
- // Call getDefaultFormState to make sure defaults are populated on change. Pass "excludeObjectChildren"
1000
- // so that only the root objects themselves are created without adding undefined children properties
1001
- newFormData = schemaUtils.getDefaultFormState(newOption, newFormData, 'excludeObjectChildren');
1002
- }
1003
- onChange(newFormData, undefined, this.getFieldId());
1004
- this.setState({
1005
- selectedOption: intOption
1006
- });
1007
- };
1008
- const {
1009
- formData: _formData,
1010
- options,
1011
- registry: {
1012
- schemaUtils: _schemaUtils
1013
- }
1014
- } = this.props;
1015
- // cache the retrieved options in state in case they have $refs to save doing it later
1016
- const _retrievedOptions = options.map(opt => _schemaUtils.retrieveSchema(opt, _formData));
1017
- this.state = {
1018
- retrievedOptions: _retrievedOptions,
1019
- selectedOption: this.getMatchingOption(0, _formData, _retrievedOptions)
1020
- };
1021
- }
1022
- /** React lifecycle method that is called when the props and/or state for this component is updated. It recomputes the
1023
- * currently selected option based on the overall `formData`
1024
- *
1025
- * @param prevProps - The previous `FieldProps` for this template
1026
- * @param prevState - The previous `AnyOfFieldState` for this template
1027
- */
1028
- componentDidUpdate(prevProps, prevState) {
1029
- const {
1030
- formData,
1031
- options,
1032
- idSchema
1033
- } = this.props;
1034
- const {
1035
- selectedOption
1036
- } = this.state;
1037
- let newState = this.state;
1038
- if (!deepEquals(prevProps.options, options)) {
1039
- const {
1040
- registry: {
1041
- schemaUtils
1042
- }
1043
- } = this.props;
1044
- // re-cache the retrieved options in state in case they have $refs to save doing it later
1045
- const retrievedOptions = options.map(opt => schemaUtils.retrieveSchema(opt, formData));
1046
- newState = {
1047
- selectedOption,
1048
- retrievedOptions
1049
- };
1050
- }
1051
- if (!deepEquals(formData, prevProps.formData) && idSchema.$id === prevProps.idSchema.$id) {
1052
- const {
1053
- retrievedOptions
1054
- } = newState;
1055
- const matchingOption = this.getMatchingOption(selectedOption, formData, retrievedOptions);
1056
- if (prevState && matchingOption !== selectedOption) {
1057
- newState = {
1058
- selectedOption: matchingOption,
1059
- retrievedOptions
1060
- };
1061
- }
1062
- }
1063
- if (newState !== this.state) {
1064
- this.setState(newState);
1065
- }
1066
- }
1067
- /** Determines the best matching option for the given `formData` and `options`.
1068
- *
1069
- * @param formData - The new formData
1070
- * @param options - The list of options to choose from
1071
- * @return - The index of the `option` that best matches the `formData`
1072
- */
1073
- getMatchingOption(selectedOption, formData, options) {
1074
- const {
1075
- schema,
1076
- registry: {
1077
- schemaUtils
1078
- }
1079
- } = this.props;
1080
- const discriminator = getDiscriminatorFieldFromSchema(schema);
1081
- const option = schemaUtils.getClosestMatchingOption(formData, options, selectedOption, discriminator);
1082
- return option;
1083
- }
1084
- getFieldId() {
1085
- const {
1086
- idSchema,
1087
- schema
1088
- } = this.props;
1089
- return `${idSchema.$id}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`;
1090
- }
1091
- /** Renders the `AnyOfField` selector along with a `SchemaField` for the value of the `formData`
1092
- */
1093
- render() {
1094
- const {
1095
- name,
1096
- disabled = false,
1097
- errorSchema = {},
1098
- formContext,
1099
- onBlur,
1100
- onFocus,
1101
- registry,
1102
- schema,
1103
- uiSchema
1104
- } = this.props;
1105
- const {
1106
- widgets,
1107
- fields,
1108
- translateString,
1109
- globalUiOptions,
1110
- schemaUtils
1111
- } = registry;
1112
- const {
1113
- SchemaField: _SchemaField
1114
- } = fields;
1115
- const {
1116
- selectedOption,
1117
- retrievedOptions
1118
- } = this.state;
1119
- const {
1120
- widget = 'select',
1121
- placeholder,
1122
- autofocus,
1123
- autocomplete,
1124
- title = schema.title,
1125
- ...uiOptions
1126
- } = getUiOptions(uiSchema, globalUiOptions);
1127
- const Widget = getWidget({
1128
- type: 'number'
1129
- }, widget, widgets);
1130
- const rawErrors = get(errorSchema, ERRORS_KEY, []);
1131
- const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]);
1132
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
1133
- const option = selectedOption >= 0 ? retrievedOptions[selectedOption] || null : null;
1134
- let optionSchema;
1135
- if (option) {
1136
- const {
1137
- oneOf,
1138
- anyOf,
1139
- ...remaining
1140
- } = schema;
1141
- // Merge in all the non-oneOf/anyOf properties and also skip the special ADDITIONAL_PROPERTY_FLAG property
1142
- unset(remaining, ADDITIONAL_PROPERTY_FLAG);
1143
- optionSchema = !isEmpty(remaining) ? mergeSchemas(remaining, option) : option;
1144
- }
1145
- const translateEnum = title ? TranslatableString.TitleOptionPrefix : TranslatableString.OptionPrefix;
1146
- const translateParams = title ? [title] : [];
1147
- const enumOptions = retrievedOptions.map((opt, index) => ({
1148
- label: opt.title || translateString(translateEnum, translateParams.concat(String(index + 1))),
1149
- value: index
1150
- }));
1151
- return jsxs("div", {
1152
- className: 'panel panel-default panel-body',
1153
- children: [jsx("div", {
1154
- className: 'form-group',
1155
- children: jsx(Widget, {
1156
- id: this.getFieldId(),
1157
- name: `${name}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`,
1158
- schema: {
1159
- type: 'number',
1160
- default: 0
1161
- },
1162
- onChange: this.onOptionChange,
1163
- onBlur: onBlur,
1164
- onFocus: onFocus,
1165
- disabled: disabled || isEmpty(enumOptions),
1166
- multiple: false,
1167
- rawErrors: rawErrors,
1168
- errorSchema: fieldErrorSchema,
1169
- value: selectedOption >= 0 ? selectedOption : undefined,
1170
- options: {
1171
- enumOptions,
1172
- ...uiOptions
1173
- },
1174
- registry: registry,
1175
- formContext: formContext,
1176
- placeholder: placeholder,
1177
- autocomplete: autocomplete,
1178
- autofocus: autofocus,
1179
- label: title ?? name,
1180
- hideLabel: !displayLabel
1181
- })
1182
- }), option !== null && jsx(_SchemaField, {
1183
- ...this.props,
1184
- schema: optionSchema
1185
- })]
1186
- });
1187
- }
1188
- }
1189
-
1190
- // Matches a string that ends in a . character, optionally followed by a sequence of
1191
- // digits followed by any number of 0 characters up until the end of the line.
1192
- // Ensuring that there is at least one prefixed character is important so that
1193
- // you don't incorrectly match against "0".
1194
- const trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/;
1195
- // This is used for trimming the trailing 0 and . characters without affecting
1196
- // the rest of the string. Its possible to use one RegEx with groups for this
1197
- // functionality, but it is fairly complex compared to simply defining two
1198
- // different matchers.
1199
- const trailingCharMatcher = /[0.]0*$/;
1200
- /**
1201
- * The NumberField class has some special handling for dealing with trailing
1202
- * decimal points and/or zeroes. This logic is designed to allow trailing values
1203
- * to be visible in the input element, but not be represented in the
1204
- * corresponding form data.
1205
- *
1206
- * The algorithm is as follows:
1207
- *
1208
- * 1. When the input value changes the value is cached in the component state
1209
- *
1210
- * 2. The value is then normalized, removing trailing decimal points and zeros,
1211
- * then passed to the "onChange" callback
1212
- *
1213
- * 3. When the component is rendered, the formData value is checked against the
1214
- * value cached in the state. If it matches the cached value, the cached
1215
- * value is passed to the input instead of the formData value
1216
- */
1217
- function NumberField(props) {
1218
- const {
1219
- registry,
1220
- onChange,
1221
- formData,
1222
- value: initialValue
1223
- } = props;
1224
- const [lastValue, setLastValue] = useState(initialValue);
1225
- const {
1226
- StringField
1227
- } = registry.fields;
1228
- let value = formData;
1229
- /** Handle the change from the `StringField` to properly convert to a number
1230
- *
1231
- * @param value - The current value for the change occurring
1232
- */
1233
- const handleChange = useCallback(value => {
1234
- // Cache the original value in component state
1235
- setLastValue(value);
1236
- // Normalize decimals that don't start with a zero character in advance so
1237
- // that the rest of the normalization logic is simpler
1238
- if (`${value}`.charAt(0) === '.') {
1239
- value = `0${value}`;
1240
- }
1241
- // Check that the value is a string (this can happen if the widget used is a
1242
- // <select>, due to an enum declaration etc) then, if the value ends in a
1243
- // trailing decimal point or multiple zeroes, strip the trailing values
1244
- const processed = typeof value === 'string' && value.match(trailingCharMatcherWithPrefix) ? asNumber(value.replace(trailingCharMatcher, '')) : asNumber(value);
1245
- onChange(processed);
1246
- }, [onChange]);
1247
- if (typeof lastValue === 'string' && typeof value === 'number') {
1248
- // Construct a regular expression that checks for a string that consists
1249
- // of the formData value suffixed with zero or one '.' characters and zero
1250
- // or more '0' characters
1251
- const re = new RegExp(`${value}`.replace('.', '\\.') + '\\.?0*$');
1252
- // If the cached "lastValue" is a match, use that instead of the formData
1253
- // value to prevent the input value from changing in the UI
1254
- if (lastValue.match(re)) {
1255
- value = lastValue;
1256
- }
1257
- }
1258
- return jsx(StringField, {
1259
- ...props,
1260
- formData: value,
1261
- onChange: handleChange
1262
- });
1263
- }
1264
-
1265
- /** The `ObjectField` component is used to render a field in the schema that is of type `object`. It tracks whether an
1266
- * additional property key was modified and what it was modified to
1267
- *
1268
- * @param props - The `FieldProps` for this template
1269
- */
1270
- class ObjectField extends Component {
1271
- constructor(...args) {
1272
- super(...args);
1273
- /** Set up the initial state */
1274
- this.state = {
1275
- wasPropertyKeyModified: false,
1276
- additionalProperties: {}
1277
- };
1278
- /** Returns the `onPropertyChange` handler for the `name` field. Handles the special case where a user is attempting
1279
- * to clear the data for a field added as an additional property. Calls the `onChange()` handler with the updated
1280
- * formData.
1281
- *
1282
- * @param name - The name of the property
1283
- * @param addedByAdditionalProperties - Flag indicating whether this property is an additional property
1284
- * @returns - The onPropertyChange callback for the `name` property
1285
- */
1286
- this.onPropertyChange = (name, addedByAdditionalProperties = false) => {
1287
- return (value, newErrorSchema, id) => {
1288
- const {
1289
- formData,
1290
- onChange,
1291
- errorSchema
1292
- } = this.props;
1293
- if (value === undefined && addedByAdditionalProperties) {
1294
- // Don't set value = undefined for fields added by
1295
- // additionalProperties. Doing so removes them from the
1296
- // formData, which causes them to completely disappear
1297
- // (including the input field for the property name). Unlike
1298
- // fields which are "mandated" by the schema, these fields can
1299
- // be set to undefined by clicking a "delete field" button, so
1300
- // set empty values to the empty string.
1301
- value = '';
1302
- }
1303
- const newFormData = {
1304
- ...formData,
1305
- [name]: value
1306
- };
1307
- onChange(newFormData, errorSchema && errorSchema && {
1308
- ...errorSchema,
1309
- [name]: newErrorSchema
1310
- }, id);
1311
- };
1312
- };
1313
- /** Returns a callback to handle the onDropPropertyClick event for the given `key` which removes the old `key` data
1314
- * and calls the `onChange` callback with it
1315
- *
1316
- * @param key - The key for which the drop callback is desired
1317
- * @returns - The drop property click callback
1318
- */
1319
- this.onDropPropertyClick = key => {
1320
- return event => {
1321
- event.preventDefault();
1322
- const {
1323
- onChange,
1324
- formData
1325
- } = this.props;
1326
- const copiedFormData = {
1327
- ...formData
1328
- };
1329
- unset(copiedFormData, key);
1330
- onChange(copiedFormData);
1331
- };
1332
- };
1333
- /** Computes the next available key name from the `preferredKey`, indexing through the already existing keys until one
1334
- * that is already not assigned is found.
1335
- *
1336
- * @param preferredKey - The preferred name of a new key
1337
- * @param [formData] - The form data in which to check if the desired key already exists
1338
- * @returns - The name of the next available key from `preferredKey`
1339
- */
1340
- this.getAvailableKey = (preferredKey, formData) => {
1341
- const {
1342
- uiSchema,
1343
- registry
1344
- } = this.props;
1345
- const {
1346
- duplicateKeySuffixSeparator = '-'
1347
- } = getUiOptions(uiSchema, registry.globalUiOptions);
1348
- let index = 0;
1349
- let newKey = preferredKey;
1350
- while (has(formData, newKey)) {
1351
- newKey = `${preferredKey}${duplicateKeySuffixSeparator}${++index}`;
1352
- }
1353
- return newKey;
1354
- };
1355
- /** Returns a callback function that deals with the rename of a key for an additional property for a schema. That
1356
- * callback will attempt to rename the key and move the existing data to that key, calling `onChange` when it does.
1357
- *
1358
- * @param oldValue - The old value of a field
1359
- * @returns - The key change callback function
1360
- */
1361
- this.onKeyChange = oldValue => {
1362
- return (value, newErrorSchema) => {
1363
- if (oldValue === value) {
1364
- return;
1365
- }
1366
- const {
1367
- formData,
1368
- onChange,
1369
- errorSchema
1370
- } = this.props;
1371
- value = this.getAvailableKey(value, formData);
1372
- const newFormData = {
1373
- ...formData
1374
- };
1375
- const newKeys = {
1376
- [oldValue]: value
1377
- };
1378
- const keyValues = Object.keys(newFormData).map(key => {
1379
- const newKey = newKeys[key] || key;
1380
- return {
1381
- [newKey]: newFormData[key]
1382
- };
1383
- });
1384
- const renamedObj = Object.assign({}, ...keyValues);
1385
- this.setState({
1386
- wasPropertyKeyModified: true
1387
- });
1388
- onChange(renamedObj, errorSchema && errorSchema && {
1389
- ...errorSchema,
1390
- [value]: newErrorSchema
1391
- });
1392
- };
1393
- };
1394
- /** Handles the adding of a new additional property on the given `schema`. Calls the `onChange` callback once the new
1395
- * default data for that field has been added to the formData.
1396
- *
1397
- * @param schema - The schema element to which the new property is being added
1398
- */
1399
- this.handleAddClick = schema => () => {
1400
- if (!schema.additionalProperties) {
1401
- return;
1402
- }
1403
- const {
1404
- formData,
1405
- onChange,
1406
- registry
1407
- } = this.props;
1408
- const newFormData = {
1409
- ...formData
1410
- };
1411
- let type = undefined;
1412
- if (isObject(schema.additionalProperties)) {
1413
- type = schema.additionalProperties.type;
1414
- let apSchema = schema.additionalProperties;
1415
- if (REF_KEY in apSchema) {
1416
- const {
1417
- schemaUtils
1418
- } = registry;
1419
- apSchema = schemaUtils.retrieveSchema({
1420
- $ref: apSchema[REF_KEY]
1421
- }, formData);
1422
- type = apSchema.type;
1423
- }
1424
- if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
1425
- type = 'object';
1426
- }
1427
- }
1428
- const newKey = this.getAvailableKey('newKey', newFormData);
1429
- // Cast this to make the `set` work properly
1430
- set(newFormData, newKey, this.getDefaultValue(type));
1431
- onChange(newFormData);
1432
- };
1433
- }
1434
- /** Returns a flag indicating whether the `name` field is required in the object schema
1435
- *
1436
- * @param name - The name of the field to check for required-ness
1437
- * @returns - True if the field `name` is required, false otherwise
1438
- */
1439
- isRequired(name) {
1440
- const {
1441
- schema
1442
- } = this.props;
1443
- return Array.isArray(schema.required) && schema.required.indexOf(name) !== -1;
1444
- }
1445
- /** Returns a default value to be used for a new additional schema property of the given `type`
1446
- *
1447
- * @param type - The type of the new additional schema property
1448
- */
1449
- getDefaultValue(type) {
1450
- const {
1451
- registry: {
1452
- translateString
1453
- }
1454
- } = this.props;
1455
- switch (type) {
1456
- case 'array':
1457
- return [];
1458
- case 'boolean':
1459
- return false;
1460
- case 'null':
1461
- return null;
1462
- case 'number':
1463
- return 0;
1464
- case 'object':
1465
- return {};
1466
- case 'string':
1467
- default:
1468
- // We don't have a datatype for some reason (perhaps additionalProperties was true)
1469
- return translateString(TranslatableString.NewStringDefault);
1470
- }
1471
- }
1472
- /** Renders the `ObjectField` from the given props
1473
- */
1474
- render() {
1475
- const {
1476
- schema: rawSchema,
1477
- uiSchema = {},
1478
- formData,
1479
- errorSchema,
1480
- idSchema,
1481
- name,
1482
- required = false,
1483
- disabled = false,
1484
- readonly = false,
1485
- hideError,
1486
- idPrefix,
1487
- idSeparator,
1488
- onBlur,
1489
- onFocus,
1490
- registry
1491
- } = this.props;
1492
- const {
1493
- fields,
1494
- formContext,
1495
- schemaUtils,
1496
- translateString,
1497
- globalUiOptions
1498
- } = registry;
1499
- const {
1500
- SchemaField
1501
- } = fields;
1502
- const schema = schemaUtils.retrieveSchema(rawSchema, formData);
1503
- const uiOptions = getUiOptions(uiSchema, globalUiOptions);
1504
- const {
1505
- properties: schemaProperties = {}
1506
- } = schema;
1507
- const title = uiOptions.title ?? schema.title ?? name;
1508
- const description = uiOptions.description ?? schema.description;
1509
- let orderedProperties;
1510
- try {
1511
- const properties = Object.keys(schemaProperties);
1512
- orderedProperties = orderProperties(properties, uiOptions.order);
1513
- } catch (err) {
1514
- return jsxs("div", {
1515
- children: [jsx("p", {
1516
- className: 'config-error',
1517
- style: {
1518
- color: 'red'
1519
- },
1520
- children: jsx(Markdown, {
1521
- children: translateString(TranslatableString.InvalidObjectField, [name || 'root', err.message])
1522
- })
1523
- }), jsx("pre", {
1524
- children: JSON.stringify(schema)
1525
- })]
1526
- });
1527
- }
1528
- const Template = getTemplate('ObjectFieldTemplate', registry, uiOptions);
1529
- const templateProps = {
1530
- // getDisplayLabel() always returns false for object types, so just check the `uiOptions.label`
1531
- title: uiOptions.label === false ? '' : title,
1532
- description: uiOptions.label === false ? undefined : description,
1533
- properties: orderedProperties.map(name => {
1534
- const addedByAdditionalProperties = has(schema, [PROPERTIES_KEY, name, ADDITIONAL_PROPERTY_FLAG]);
1535
- const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[name];
1536
- const hidden = getUiOptions(fieldUiSchema).widget === 'hidden';
1537
- const fieldIdSchema = get(idSchema, [name], {});
1538
- return {
1539
- content: jsx(SchemaField, {
1540
- name: name,
1541
- required: this.isRequired(name),
1542
- schema: get(schema, [PROPERTIES_KEY, name], {}),
1543
- uiSchema: fieldUiSchema,
1544
- errorSchema: get(errorSchema, name),
1545
- idSchema: fieldIdSchema,
1546
- idPrefix: idPrefix,
1547
- idSeparator: idSeparator,
1548
- formData: get(formData, name),
1549
- formContext: formContext,
1550
- wasPropertyKeyModified: this.state.wasPropertyKeyModified,
1551
- onKeyChange: this.onKeyChange(name),
1552
- onChange: this.onPropertyChange(name, addedByAdditionalProperties),
1553
- onBlur: onBlur,
1554
- onFocus: onFocus,
1555
- registry: registry,
1556
- disabled: disabled,
1557
- readonly: readonly,
1558
- hideError: hideError,
1559
- onDropPropertyClick: this.onDropPropertyClick
1560
- }, name),
1561
- name,
1562
- readonly,
1563
- disabled,
1564
- required,
1565
- hidden
1566
- };
1567
- }),
1568
- readonly,
1569
- disabled,
1570
- required,
1571
- idSchema,
1572
- uiSchema,
1573
- errorSchema,
1574
- schema,
1575
- formData,
1576
- formContext,
1577
- registry
1578
- };
1579
- return jsx(Template, {
1580
- ...templateProps,
1581
- onAddClick: this.handleAddClick
1582
- });
1583
- }
1584
- }
1585
-
1586
- /** The map of component type to FieldName */
1587
- const COMPONENT_TYPES = {
1588
- array: 'ArrayField',
1589
- boolean: 'BooleanField',
1590
- integer: 'NumberField',
1591
- number: 'NumberField',
1592
- object: 'ObjectField',
1593
- string: 'StringField',
1594
- null: 'NullField'
1595
- };
1596
- /** Computes and returns which `Field` implementation to return in order to render the field represented by the
1597
- * `schema`. The `uiOptions` are used to alter what potential `Field` implementation is actually returned. If no
1598
- * appropriate `Field` implementation can be found then a wrapper around `UnsupportedFieldTemplate` is used.
1599
- *
1600
- * @param schema - The schema from which to obtain the type
1601
- * @param uiOptions - The UI Options that may affect the component decision
1602
- * @param idSchema - The id that is passed to the `UnsupportedFieldTemplate`
1603
- * @param registry - The registry from which fields and templates are obtained
1604
- * @returns - The `Field` component that is used to render the actual field data
1605
- */
1606
- function getFieldComponent(schema, uiOptions, idSchema, registry) {
1607
- const field = uiOptions.field;
1608
- const {
1609
- fields,
1610
- translateString
1611
- } = registry;
1612
- if (typeof field === 'function') {
1613
- return field;
1614
- }
1615
- if (typeof field === 'string' && field in fields) {
1616
- return fields[field];
1617
- }
1618
- const schemaType = getSchemaType(schema);
1619
- const type = Array.isArray(schemaType) ? schemaType[0] : schemaType || '';
1620
- const schemaId = schema.$id;
1621
- let componentName = COMPONENT_TYPES[type];
1622
- if (schemaId && schemaId in fields) {
1623
- componentName = schemaId;
1624
- }
1625
- // If the type is not defined and the schema uses 'anyOf' or 'oneOf', don't
1626
- // render a field and let the MultiSchemaField component handle the form display
1627
- if (!componentName && (schema.anyOf || schema.oneOf)) {
1628
- return () => null;
1629
- }
1630
- return componentName in fields ? fields[componentName] : () => {
1631
- const UnsupportedFieldTemplate = getTemplate('UnsupportedFieldTemplate', registry, uiOptions);
1632
- return jsx(UnsupportedFieldTemplate, {
1633
- schema: schema,
1634
- idSchema: idSchema,
1635
- reason: translateString(TranslatableString.UnknownFieldType, [String(schema.type)]),
1636
- registry: registry
1637
- });
1638
- };
1639
- }
1640
- /** The `SchemaFieldRender` component is the work-horse of react-jsonschema-form, determining what kind of real field to
1641
- * render based on the `schema`, `uiSchema` and all the other props. It also deals with rendering the `anyOf` and
1642
- * `oneOf` fields.
1643
- *
1644
- * @param props - The `FieldProps` for this component
1645
- */
1646
- function SchemaFieldRender(props) {
1647
- const {
1648
- schema: _schema,
1649
- idSchema: _idSchema,
1650
- uiSchema,
1651
- formData,
1652
- errorSchema,
1653
- idPrefix,
1654
- idSeparator,
1655
- name,
1656
- onChange,
1657
- onKeyChange,
1658
- onDropPropertyClick,
1659
- required,
1660
- registry,
1661
- wasPropertyKeyModified = false
1662
- } = props;
1663
- const {
1664
- formContext,
1665
- schemaUtils,
1666
- globalUiOptions
1667
- } = registry;
1668
- const uiOptions = getUiOptions(uiSchema, globalUiOptions);
1669
- const FieldTemplate = getTemplate('FieldTemplate', registry, uiOptions);
1670
- const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, uiOptions);
1671
- const FieldHelpTemplate = getTemplate('FieldHelpTemplate', registry, uiOptions);
1672
- const FieldErrorTemplate = getTemplate('FieldErrorTemplate', registry, uiOptions);
1673
- const schema = schemaUtils.retrieveSchema(_schema, formData);
1674
- const fieldId = _idSchema[ID_KEY];
1675
- const idSchema = mergeObjects(schemaUtils.toIdSchema(schema, fieldId, formData, idPrefix, idSeparator), _idSchema);
1676
- /** Intermediary `onChange` handler for field components that will inject the `id` of the current field into the
1677
- * `onChange` chain if it is not already being provided from a deeper level in the hierarchy
1678
- */
1679
- const handleFieldComponentChange = useCallback((formData, newErrorSchema, id) => {
1680
- const theId = id || fieldId;
1681
- return onChange(formData, newErrorSchema, theId);
1682
- }, [fieldId, onChange]);
1683
- const FieldComponent = getFieldComponent(schema, uiOptions, idSchema, registry);
1684
- const disabled = Boolean(props.disabled || uiOptions.disabled);
1685
- const readonly = Boolean(props.readonly || uiOptions.readonly || props.schema.readOnly || schema.readOnly);
1686
- const uiSchemaHideError = uiOptions.hideError;
1687
- // Set hideError to the value provided in the uiSchema, otherwise stick with the prop to propagate to children
1688
- const hideError = uiSchemaHideError === undefined ? props.hideError : Boolean(uiSchemaHideError);
1689
- const autofocus = Boolean(props.autofocus || uiOptions.autofocus);
1690
- if (Object.keys(schema).length === 0) {
1691
- return null;
1692
- }
1693
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
1694
- const {
1695
- __errors,
1696
- ...fieldErrorSchema
1697
- } = errorSchema || {};
1698
- // See #439: uiSchema: Don't pass consumed class names or style to child components
1699
- const fieldUiSchema = omit(uiSchema, ['ui:classNames', 'classNames', 'ui:style']);
1700
- if (UI_OPTIONS_KEY in fieldUiSchema) {
1701
- fieldUiSchema[UI_OPTIONS_KEY] = omit(fieldUiSchema[UI_OPTIONS_KEY], ['classNames', 'style']);
1702
- }
1703
- const field = jsx(FieldComponent, {
1704
- ...props,
1705
- onChange: handleFieldComponentChange,
1706
- idSchema: idSchema,
1707
- schema: schema,
1708
- uiSchema: fieldUiSchema,
1709
- disabled: disabled,
1710
- readonly: readonly,
1711
- hideError: hideError,
1712
- autofocus: autofocus,
1713
- errorSchema: fieldErrorSchema,
1714
- formContext: formContext,
1715
- rawErrors: __errors
1716
- });
1717
- const id = idSchema[ID_KEY];
1718
- // If this schema has a title defined, but the user has set a new key/label, retain their input.
1719
- let label;
1720
- if (wasPropertyKeyModified) {
1721
- label = name;
1722
- } else {
1723
- label = ADDITIONAL_PROPERTY_FLAG in schema ? name : uiOptions.title || props.schema.title || schema.title || name;
1724
- }
1725
- const description = uiOptions.description || props.schema.description || schema.description || '';
1726
- const richDescription = uiOptions.enableMarkdownInDescription ? jsx(Markdown, {
1727
- children: description
1728
- }) : description;
1729
- const help = uiOptions.help;
1730
- const hidden = uiOptions.widget === 'hidden';
1731
- const classNames = ['form-group', 'field', `field-${getSchemaType(schema)}`];
1732
- if (!hideError && __errors && __errors.length > 0) {
1733
- classNames.push('field-error has-error has-danger');
1734
- }
1735
- if (uiSchema !== null && uiSchema !== void 0 && uiSchema.classNames) {
1736
- if (process.env.NODE_ENV !== 'production') {
1737
- console.warn("'uiSchema.classNames' is deprecated and may be removed in a major release; Use 'ui:classNames' instead.");
1738
- }
1739
- classNames.push(uiSchema.classNames);
1740
- }
1741
- if (uiOptions.classNames) {
1742
- classNames.push(uiOptions.classNames);
1743
- }
1744
- const helpComponent = jsx(FieldHelpTemplate, {
1745
- help: help,
1746
- idSchema: idSchema,
1747
- schema: schema,
1748
- uiSchema: uiSchema,
1749
- hasErrors: !hideError && __errors && __errors.length > 0,
1750
- registry: registry
1751
- });
1752
- /*
1753
- * AnyOf/OneOf errors handled by child schema
1754
- */
1755
- const errorsComponent = hideError || schema.anyOf || schema.oneOf ? undefined : jsx(FieldErrorTemplate, {
1756
- errors: __errors,
1757
- errorSchema: errorSchema,
1758
- idSchema: idSchema,
1759
- schema: schema,
1760
- uiSchema: uiSchema,
1761
- registry: registry
1762
- });
1763
- const fieldProps = {
1764
- description: jsx(DescriptionFieldTemplate, {
1765
- id: descriptionId(id),
1766
- description: richDescription,
1767
- schema: schema,
1768
- uiSchema: uiSchema,
1769
- registry: registry
1770
- }),
1771
- rawDescription: description,
1772
- help: helpComponent,
1773
- rawHelp: typeof help === 'string' ? help : undefined,
1774
- errors: errorsComponent,
1775
- rawErrors: hideError ? undefined : __errors,
1776
- id,
1777
- label,
1778
- hidden,
1779
- onChange,
1780
- onKeyChange,
1781
- onDropPropertyClick,
1782
- required,
1783
- disabled,
1784
- readonly,
1785
- hideError,
1786
- displayLabel,
1787
- classNames: classNames.join(' ').trim(),
1788
- style: uiOptions.style,
1789
- formContext,
1790
- formData,
1791
- schema,
1792
- uiSchema,
1793
- registry
1794
- };
1795
- const _AnyOfField = registry.fields.AnyOfField;
1796
- const _OneOfField = registry.fields.OneOfField;
1797
- const isReplacingAnyOrOneOf = (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:field']) && (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:fieldReplacesAnyOrOneOf']) === true;
1798
- return jsx(FieldTemplate, {
1799
- ...fieldProps,
1800
- children: jsxs(Fragment, {
1801
- children: [field, schema.anyOf && !isReplacingAnyOrOneOf && !schemaUtils.isSelect(schema) && jsx(_AnyOfField, {
1802
- name: name,
1803
- disabled: disabled,
1804
- readonly: readonly,
1805
- hideError: hideError,
1806
- errorSchema: errorSchema,
1807
- formData: formData,
1808
- formContext: formContext,
1809
- idPrefix: idPrefix,
1810
- idSchema: idSchema,
1811
- idSeparator: idSeparator,
1812
- onBlur: props.onBlur,
1813
- onChange: props.onChange,
1814
- onFocus: props.onFocus,
1815
- options: schema.anyOf.map(_schema => schemaUtils.retrieveSchema(isObject(_schema) ? _schema : {}, formData)),
1816
- registry: registry,
1817
- schema: schema,
1818
- uiSchema: uiSchema
1819
- }), schema.oneOf && !isReplacingAnyOrOneOf && !schemaUtils.isSelect(schema) && jsx(_OneOfField, {
1820
- name: name,
1821
- disabled: disabled,
1822
- readonly: readonly,
1823
- hideError: hideError,
1824
- errorSchema: errorSchema,
1825
- formData: formData,
1826
- formContext: formContext,
1827
- idPrefix: idPrefix,
1828
- idSchema: idSchema,
1829
- idSeparator: idSeparator,
1830
- onBlur: props.onBlur,
1831
- onChange: props.onChange,
1832
- onFocus: props.onFocus,
1833
- options: schema.oneOf.map(_schema => schemaUtils.retrieveSchema(isObject(_schema) ? _schema : {}, formData)),
1834
- registry: registry,
1835
- schema: schema,
1836
- uiSchema: uiSchema
1837
- })]
1838
- })
1839
- });
1840
- }
1841
- /** The `SchemaField` component determines whether it is necessary to rerender the component based on any props changes
1842
- * and if so, calls the `SchemaFieldRender` component with the props.
1843
- */
1844
- class SchemaField extends Component {
1845
- shouldComponentUpdate(nextProps) {
1846
- return !deepEquals(this.props, nextProps);
1847
- }
1848
- render() {
1849
- return jsx(SchemaFieldRender, {
1850
- ...this.props
1851
- });
1852
- }
1853
- }
1854
-
1855
- /** The `StringField` component is used to render a schema field that represents a string type
1856
- *
1857
- * @param props - The `FieldProps` for this template
1858
- */
1859
- function StringField(props) {
1860
- const {
1861
- schema,
1862
- name,
1863
- uiSchema,
1864
- idSchema,
1865
- formData,
1866
- required,
1867
- disabled = false,
1868
- readonly = false,
1869
- autofocus = false,
1870
- onChange,
1871
- onBlur,
1872
- onFocus,
1873
- registry,
1874
- rawErrors
1875
- } = props;
1876
- const {
1877
- title,
1878
- format
1879
- } = schema;
1880
- const {
1881
- widgets,
1882
- formContext,
1883
- schemaUtils,
1884
- globalUiOptions
1885
- } = registry;
1886
- const enumOptions = schemaUtils.isSelect(schema) ? optionsList(schema) : undefined;
1887
- let defaultWidget = enumOptions ? 'select' : 'text';
1888
- if (format && hasWidget(schema, format, widgets)) {
1889
- defaultWidget = format;
1890
- }
1891
- const {
1892
- widget = defaultWidget,
1893
- placeholder = '',
1894
- title: uiTitle,
1895
- ...options
1896
- } = getUiOptions(uiSchema);
1897
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
1898
- const label = uiTitle ?? title ?? name;
1899
- const Widget = getWidget(schema, widget, widgets);
1900
- return jsx(Widget, {
1901
- options: {
1902
- ...options,
1903
- enumOptions
1904
- },
1905
- schema: schema,
1906
- uiSchema: uiSchema,
1907
- id: idSchema.$id,
1908
- name: name,
1909
- label: label,
1910
- hideLabel: !displayLabel,
1911
- value: formData,
1912
- onChange: onChange,
1913
- onBlur: onBlur,
1914
- onFocus: onFocus,
1915
- required: required,
1916
- disabled: disabled,
1917
- readonly: readonly,
1918
- formContext: formContext,
1919
- autofocus: autofocus,
1920
- registry: registry,
1921
- placeholder: placeholder,
1922
- rawErrors: rawErrors
1923
- });
1924
- }
1925
-
1926
- /** The `NullField` component is used to render a field in the schema is null. It also ensures that the `formData` is
1927
- * also set to null if it has no value.
1928
- *
1929
- * @param props - The `FieldProps` for this template
1930
- */
1931
- function NullField(props) {
1932
- const {
1933
- formData,
1934
- onChange
1935
- } = props;
1936
- useEffect(() => {
1937
- if (formData === undefined) {
1938
- onChange(null);
1939
- }
1940
- }, [formData, onChange]);
1941
- return null;
1942
- }
1943
-
1944
- function fields() {
1945
- return {
1946
- AnyOfField: AnyOfField,
1947
- ArrayField: ArrayField,
1948
- // ArrayField falls back to SchemaField if ArraySchemaField is not defined, which it isn't by default
1949
- BooleanField,
1950
- NumberField,
1951
- ObjectField,
1952
- OneOfField: AnyOfField,
1953
- SchemaField,
1954
- StringField,
1955
- NullField
1956
- };
1957
- }
1958
-
1959
- /** The `ArrayFieldDescriptionTemplate` component renders a `DescriptionFieldTemplate` with an `id` derived from
1960
- * the `idSchema`.
1961
- *
1962
- * @param props - The `ArrayFieldDescriptionProps` for the component
1963
- */
1964
- function ArrayFieldDescriptionTemplate(props) {
1965
- const {
1966
- idSchema,
1967
- description,
1968
- registry,
1969
- schema,
1970
- uiSchema
1971
- } = props;
1972
- const options = getUiOptions(uiSchema, registry.globalUiOptions);
1973
- const {
1974
- label: displayLabel = true
1975
- } = options;
1976
- if (!description || !displayLabel) {
1977
- return null;
1978
- }
1979
- const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, options);
1980
- return jsx(DescriptionFieldTemplate, {
1981
- id: descriptionId(idSchema),
1982
- description: description,
1983
- schema: schema,
1984
- uiSchema: uiSchema,
1985
- registry: registry
1986
- });
1987
- }
1988
-
1989
- /** The `ArrayFieldItemTemplate` component is the template used to render an items of an array.
1990
- *
1991
- * @param props - The `ArrayFieldTemplateItemType` props for the component
1992
- */
1993
- function ArrayFieldItemTemplate(props) {
1994
- const {
1995
- children,
1996
- className,
1997
- disabled,
1998
- hasToolbar,
1999
- hasMoveDown,
2000
- hasMoveUp,
2001
- hasRemove,
2002
- hasCopy,
2003
- index,
2004
- onCopyIndexClick,
2005
- onDropIndexClick,
2006
- onReorderClick,
2007
- readonly,
2008
- registry,
2009
- uiSchema
2010
- } = props;
2011
- const {
2012
- CopyButton,
2013
- MoveDownButton,
2014
- MoveUpButton,
2015
- RemoveButton
2016
- } = registry.templates.ButtonTemplates;
2017
- const btnStyle = {
2018
- flex: 1,
2019
- paddingLeft: 6,
2020
- paddingRight: 6,
2021
- fontWeight: 'bold'
2022
- };
2023
- return jsxs("div", {
2024
- className: className,
2025
- children: [jsx("div", {
2026
- className: hasToolbar ? 'col-xs-9' : 'col-xs-12',
2027
- children: children
2028
- }), hasToolbar && jsx("div", {
2029
- className: 'col-xs-3 array-item-toolbox',
2030
- children: jsxs("div", {
2031
- className: 'btn-group',
2032
- style: {
2033
- display: 'flex',
2034
- justifyContent: 'space-around'
2035
- },
2036
- children: [(hasMoveUp || hasMoveDown) && jsx(MoveUpButton, {
2037
- style: btnStyle,
2038
- disabled: disabled || readonly || !hasMoveUp,
2039
- onClick: onReorderClick(index, index - 1),
2040
- uiSchema: uiSchema,
2041
- registry: registry
2042
- }), (hasMoveUp || hasMoveDown) && jsx(MoveDownButton, {
2043
- style: btnStyle,
2044
- disabled: disabled || readonly || !hasMoveDown,
2045
- onClick: onReorderClick(index, index + 1),
2046
- uiSchema: uiSchema,
2047
- registry: registry
2048
- }), hasCopy && jsx(CopyButton, {
2049
- style: btnStyle,
2050
- disabled: disabled || readonly,
2051
- onClick: onCopyIndexClick(index),
2052
- uiSchema: uiSchema,
2053
- registry: registry
2054
- }), hasRemove && jsx(RemoveButton, {
2055
- style: btnStyle,
2056
- disabled: disabled || readonly,
2057
- onClick: onDropIndexClick(index),
2058
- uiSchema: uiSchema,
2059
- registry: registry
2060
- })]
2061
- })
2062
- })]
2063
- });
2064
- }
2065
-
2066
- /** The `ArrayFieldTemplate` component is the template used to render all items in an array.
2067
- *
2068
- * @param props - The `ArrayFieldTemplateItemType` props for the component
2069
- */
2070
- function ArrayFieldTemplate(props) {
2071
- const {
2072
- canAdd,
2073
- className,
2074
- disabled,
2075
- idSchema,
2076
- uiSchema,
2077
- items,
2078
- onAddClick,
2079
- readonly,
2080
- registry,
2081
- required,
2082
- schema,
2083
- title
2084
- } = props;
2085
- const uiOptions = getUiOptions(uiSchema);
2086
- const ArrayFieldDescriptionTemplate = getTemplate('ArrayFieldDescriptionTemplate', registry, uiOptions);
2087
- const ArrayFieldItemTemplate = getTemplate('ArrayFieldItemTemplate', registry, uiOptions);
2088
- const ArrayFieldTitleTemplate = getTemplate('ArrayFieldTitleTemplate', registry, uiOptions);
2089
- // Button templates are not overridden in the uiSchema
2090
- const {
2091
- ButtonTemplates: {
2092
- AddButton
2093
- }
2094
- } = registry.templates;
2095
- return jsxs("fieldset", {
2096
- className: className,
2097
- id: idSchema.$id,
2098
- children: [jsx(ArrayFieldTitleTemplate, {
2099
- idSchema: idSchema,
2100
- title: uiOptions.title || title,
2101
- required: required,
2102
- schema: schema,
2103
- uiSchema: uiSchema,
2104
- registry: registry
2105
- }), jsx(ArrayFieldDescriptionTemplate, {
2106
- idSchema: idSchema,
2107
- description: uiOptions.description || schema.description,
2108
- schema: schema,
2109
- uiSchema: uiSchema,
2110
- registry: registry
2111
- }), jsx("div", {
2112
- className: 'row array-item-list',
2113
- children: items && items.map(({
2114
- key,
2115
- ...itemProps
2116
- }) => jsx(ArrayFieldItemTemplate, {
2117
- ...itemProps
2118
- }, key))
2119
- }), canAdd && jsx(AddButton, {
2120
- className: 'array-item-add',
2121
- onClick: onAddClick,
2122
- disabled: disabled || readonly,
2123
- uiSchema: uiSchema,
2124
- registry: registry
2125
- })]
2126
- });
2127
- }
2128
-
2129
- /** The `ArrayFieldTitleTemplate` component renders a `TitleFieldTemplate` with an `id` derived from
2130
- * the `idSchema`.
2131
- *
2132
- * @param props - The `ArrayFieldTitleProps` for the component
2133
- */
2134
- function ArrayFieldTitleTemplate(props) {
2135
- const {
2136
- idSchema,
2137
- title,
2138
- schema,
2139
- uiSchema,
2140
- required,
2141
- registry
2142
- } = props;
2143
- const options = getUiOptions(uiSchema, registry.globalUiOptions);
2144
- const {
2145
- label: displayLabel = true
2146
- } = options;
2147
- if (!title || !displayLabel) {
2148
- return null;
2149
- }
2150
- const TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, options);
2151
- return jsx(TitleFieldTemplate, {
2152
- id: titleId(idSchema),
2153
- title: title,
2154
- required: required,
2155
- schema: schema,
2156
- uiSchema: uiSchema,
2157
- registry: registry
2158
- });
2159
- }
2160
-
2161
- /** The `BaseInputTemplate` is the template to use to render the basic `<input>` component for the `core` theme.
2162
- * It is used as the template for rendering many of the <input> based widgets that differ by `type` and callbacks only.
2163
- * It can be customized/overridden for other themes or individual implementations as needed.
2164
- *
2165
- * @param props - The `WidgetProps` for this template
2166
- */
2167
- function BaseInputTemplate(props) {
2168
- const {
2169
- id,
2170
- name,
2171
- // remove this from ...rest
2172
- value,
2173
- readonly,
2174
- disabled,
2175
- autofocus,
2176
- onBlur,
2177
- onFocus,
2178
- onChange,
2179
- onChangeOverride,
2180
- options,
2181
- schema,
2182
- uiSchema,
2183
- formContext,
2184
- registry,
2185
- rawErrors,
2186
- type,
2187
- hideLabel,
2188
- // remove this from ...rest
2189
- hideError,
2190
- // remove this from ...rest
2191
- ...rest
2192
- } = props;
2193
- // Note: since React 15.2.0 we can't forward unknown element attributes, so we
2194
- // exclude the "options" and "schema" ones here.
2195
- if (!id) {
2196
- console.log('No id for', props);
2197
- throw new Error(`no id for props ${JSON.stringify(props)}`);
2198
- }
2199
- const inputProps = {
2200
- ...rest,
2201
- ...getInputProps(schema, type, options)
2202
- };
2203
- let inputValue;
2204
- if (inputProps.type === 'number' || inputProps.type === 'integer') {
2205
- inputValue = value || value === 0 ? value : '';
2206
- } else {
2207
- inputValue = value == null ? '' : value;
2208
- }
2209
- const _onChange = useCallback(({
2210
- target: {
2211
- value
2212
- }
2213
- }) => onChange(value === '' ? options.emptyValue : value), [onChange, options]);
2214
- const _onBlur = useCallback(({
2215
- target: {
2216
- value
2217
- }
2218
- }) => onBlur(id, value), [onBlur, id]);
2219
- const _onFocus = useCallback(({
2220
- target: {
2221
- value
2222
- }
2223
- }) => onFocus(id, value), [onFocus, id]);
2224
- return jsxs(Fragment, {
2225
- children: [jsx("input", {
2226
- id: id,
2227
- name: id,
2228
- className: 'form-control',
2229
- readOnly: readonly,
2230
- disabled: disabled,
2231
- autoFocus: autofocus,
2232
- value: inputValue,
2233
- ...inputProps,
2234
- list: schema.examples ? examplesId(id) : undefined,
2235
- onChange: onChangeOverride || _onChange,
2236
- onBlur: _onBlur,
2237
- onFocus: _onFocus,
2238
- "aria-describedby": ariaDescribedByIds(id, !!schema.examples)
2239
- }), Array.isArray(schema.examples) && jsx("datalist", {
2240
- id: examplesId(id),
2241
- children: schema.examples.concat(schema.default && !schema.examples.includes(schema.default) ? [schema.default] : []).map(example => {
2242
- return jsx("option", {
2243
- value: example
2244
- }, example);
2245
- })
2246
- }, `datalist_${id}`)]
2247
- });
2248
- }
2249
-
2250
- /** The `SubmitButton` renders a button that represent the `Submit` action on a form
2251
- */
2252
- function SubmitButton({
2253
- uiSchema
2254
- }) {
2255
- const {
2256
- submitText,
2257
- norender,
2258
- props: submitButtonProps = {}
2259
- } = getSubmitButtonOptions(uiSchema);
2260
- if (norender) {
2261
- return null;
2262
- }
2263
- return jsx("div", {
2264
- children: jsx("button", {
2265
- type: 'submit',
2266
- ...submitButtonProps,
2267
- className: `btn btn-info ${submitButtonProps.className || ''}`,
2268
- children: submitText
2269
- })
2270
- });
2271
- }
2272
-
2273
- function IconButton(props) {
2274
- const {
2275
- iconType = 'default',
2276
- icon,
2277
- className,
2278
- uiSchema,
2279
- registry,
2280
- ...otherProps
2281
- } = props;
2282
- return jsx("button", {
2283
- type: 'button',
2284
- className: `btn btn-${iconType} ${className}`,
2285
- ...otherProps,
2286
- children: jsx("i", {
2287
- className: `glyphicon glyphicon-${icon}`
2288
- })
2289
- });
2290
- }
2291
- function CopyButton(props) {
2292
- const {
2293
- registry: {
2294
- translateString
2295
- }
2296
- } = props;
2297
- return jsx(IconButton, {
2298
- title: translateString(TranslatableString.CopyButton),
2299
- className: 'array-item-copy',
2300
- ...props,
2301
- icon: 'copy'
2302
- });
2303
- }
2304
- function MoveDownButton(props) {
2305
- const {
2306
- registry: {
2307
- translateString
2308
- }
2309
- } = props;
2310
- return jsx(IconButton, {
2311
- title: translateString(TranslatableString.MoveDownButton),
2312
- className: 'array-item-move-down',
2313
- ...props,
2314
- icon: 'arrow-down'
2315
- });
2316
- }
2317
- function MoveUpButton(props) {
2318
- const {
2319
- registry: {
2320
- translateString
2321
- }
2322
- } = props;
2323
- return jsx(IconButton, {
2324
- title: translateString(TranslatableString.MoveUpButton),
2325
- className: 'array-item-move-up',
2326
- ...props,
2327
- icon: 'arrow-up'
2328
- });
2329
- }
2330
- function RemoveButton(props) {
2331
- const {
2332
- registry: {
2333
- translateString
2334
- }
2335
- } = props;
2336
- return jsx(IconButton, {
2337
- title: translateString(TranslatableString.RemoveButton),
2338
- className: 'array-item-remove',
2339
- ...props,
2340
- iconType: 'danger',
2341
- icon: 'remove'
2342
- });
2343
- }
2344
-
2345
- /** The `AddButton` renders a button that represent the `Add` action on a form
2346
- */
2347
- function AddButton({
2348
- className,
2349
- onClick,
2350
- disabled,
2351
- registry
2352
- }) {
2353
- const {
2354
- translateString
2355
- } = registry;
2356
- return jsx("div", {
2357
- className: 'row',
2358
- children: jsx("p", {
2359
- className: `col-xs-3 col-xs-offset-9 text-right ${className}`,
2360
- children: jsx(IconButton, {
2361
- iconType: 'info',
2362
- icon: 'plus',
2363
- className: 'btn-add col-xs-12',
2364
- title: translateString(TranslatableString.AddButton),
2365
- onClick: onClick,
2366
- disabled: disabled,
2367
- registry: registry
2368
- })
2369
- })
2370
- });
2371
- }
2372
-
2373
- function buttonTemplates() {
2374
- return {
2375
- SubmitButton,
2376
- AddButton,
2377
- CopyButton,
2378
- MoveDownButton,
2379
- MoveUpButton,
2380
- RemoveButton
2381
- };
2382
- }
2383
-
2384
- /** The `DescriptionField` is the template to use to render the description of a field
2385
- *
2386
- * @param props - The `DescriptionFieldProps` for this component
2387
- */
2388
- function DescriptionField(props) {
2389
- const {
2390
- id,
2391
- description
2392
- } = props;
2393
- if (!description) {
2394
- return null;
2395
- }
2396
- if (typeof description === 'string') {
2397
- return jsx("p", {
2398
- id: id,
2399
- className: 'field-description',
2400
- children: description
2401
- });
2402
- } else {
2403
- return jsx("div", {
2404
- id: id,
2405
- className: 'field-description',
2406
- children: description
2407
- });
2408
- }
2409
- }
2410
-
2411
- /** The `ErrorList` component is the template that renders the all the errors associated with the fields in the `Form`
2412
- *
2413
- * @param props - The `ErrorListProps` for this component
2414
- */
2415
- function ErrorList({
2416
- errors,
2417
- registry
2418
- }) {
2419
- const {
2420
- translateString
2421
- } = registry;
2422
- return jsxs("div", {
2423
- className: 'panel panel-danger errors',
2424
- children: [jsx("div", {
2425
- className: 'panel-heading',
2426
- children: jsx("h3", {
2427
- className: 'panel-title',
2428
- children: translateString(TranslatableString.ErrorsLabel)
2429
- })
2430
- }), jsx("ul", {
2431
- className: 'list-group',
2432
- children: errors.map((error, i) => {
2433
- return jsx("li", {
2434
- className: 'list-group-item text-danger',
2435
- children: error.stack
2436
- }, i);
2437
- })
2438
- })]
2439
- });
2440
- }
2441
-
2442
- const REQUIRED_FIELD_SYMBOL$1 = '*';
2443
- /** Renders a label for a field
2444
- *
2445
- * @param props - The `LabelProps` for this component
2446
- */
2447
- function Label(props) {
2448
- const {
2449
- label,
2450
- required,
2451
- id
2452
- } = props;
2453
- if (!label) {
2454
- return null;
2455
- }
2456
- return jsxs("label", {
2457
- className: 'control-label',
2458
- htmlFor: id,
2459
- children: [label, required && jsx("span", {
2460
- className: 'required',
2461
- children: REQUIRED_FIELD_SYMBOL$1
2462
- })]
2463
- });
2464
- }
2465
-
2466
- /** The `FieldTemplate` component is the template used by `SchemaField` to render any field. It renders the field
2467
- * content, (label, description, children, errors and help) inside of a `WrapIfAdditional` component.
2468
- *
2469
- * @param props - The `FieldTemplateProps` for this component
2470
- */
2471
- function FieldTemplate(props) {
2472
- const {
2473
- id,
2474
- label,
2475
- children,
2476
- errors,
2477
- help,
2478
- description,
2479
- hidden,
2480
- required,
2481
- displayLabel,
2482
- registry,
2483
- uiSchema
2484
- } = props;
2485
- const uiOptions = getUiOptions(uiSchema);
2486
- const WrapIfAdditionalTemplate = getTemplate('WrapIfAdditionalTemplate', registry, uiOptions);
2487
- if (hidden) {
2488
- return jsx("div", {
2489
- className: 'hidden',
2490
- children: children
2491
- });
2492
- }
2493
- return jsxs(WrapIfAdditionalTemplate, {
2494
- ...props,
2495
- children: [displayLabel && jsx(Label, {
2496
- label: label,
2497
- required: required,
2498
- id: id
2499
- }), displayLabel && description ? description : null, children, errors, help]
2500
- });
2501
- }
2502
-
2503
- /** The `FieldErrorTemplate` component renders the errors local to the particular field
2504
- *
2505
- * @param props - The `FieldErrorProps` for the errors being rendered
2506
- */
2507
- function FieldErrorTemplate(props) {
2508
- const {
2509
- errors = [],
2510
- idSchema
2511
- } = props;
2512
- if (errors.length === 0) {
2513
- return null;
2514
- }
2515
- const id = errorId(idSchema);
2516
- return jsx("div", {
2517
- children: jsx("ul", {
2518
- id: id,
2519
- className: 'error-detail bs-callout bs-callout-info',
2520
- children: errors.filter(elem => !!elem).map((error, index) => {
2521
- return jsx("li", {
2522
- className: 'text-danger',
2523
- children: error
2524
- }, index);
2525
- })
2526
- })
2527
- });
2528
- }
2529
-
2530
- /** The `FieldHelpTemplate` component renders any help desired for a field
2531
- *
2532
- * @param props - The `FieldHelpProps` to be rendered
2533
- */
2534
- function FieldHelpTemplate(props) {
2535
- const {
2536
- idSchema,
2537
- help
2538
- } = props;
2539
- if (!help) {
2540
- return null;
2541
- }
2542
- const id = helpId(idSchema);
2543
- if (typeof help === 'string') {
2544
- return jsx("p", {
2545
- id: id,
2546
- className: 'help-block',
2547
- children: help
2548
- });
2549
- }
2550
- return jsx("div", {
2551
- id: id,
2552
- className: 'help-block',
2553
- children: help
2554
- });
2555
- }
2556
-
2557
- /** The `ObjectFieldTemplate` is the template to use to render all the inner properties of an object along with the
2558
- * title and description if available. If the object is expandable, then an `AddButton` is also rendered after all
2559
- * the properties.
2560
- *
2561
- * @param props - The `ObjectFieldTemplateProps` for this component
2562
- */
2563
- function ObjectFieldTemplate(props) {
2564
- const {
2565
- description,
2566
- disabled,
2567
- formData,
2568
- idSchema,
2569
- onAddClick,
2570
- properties,
2571
- readonly,
2572
- registry,
2573
- required,
2574
- schema,
2575
- title,
2576
- uiSchema
2577
- } = props;
2578
- const options = getUiOptions(uiSchema);
2579
- const TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, options);
2580
- const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, options);
2581
- // Button templates are not overridden in the uiSchema
2582
- const {
2583
- ButtonTemplates: {
2584
- AddButton
2585
- }
2586
- } = registry.templates;
2587
- return jsxs("fieldset", {
2588
- id: idSchema.$id,
2589
- children: [title && jsx(TitleFieldTemplate, {
2590
- id: titleId(idSchema),
2591
- title: title,
2592
- required: required,
2593
- schema: schema,
2594
- uiSchema: uiSchema,
2595
- registry: registry
2596
- }), description && jsx(DescriptionFieldTemplate, {
2597
- id: descriptionId(idSchema),
2598
- description: description,
2599
- schema: schema,
2600
- uiSchema: uiSchema,
2601
- registry: registry
2602
- }), properties.map(prop => prop.content), canExpand(schema, uiSchema, formData) && jsx(AddButton, {
2603
- className: 'object-property-expand',
2604
- onClick: onAddClick(schema),
2605
- disabled: disabled || readonly,
2606
- uiSchema: uiSchema,
2607
- registry: registry
2608
- })]
2609
- });
2610
- }
2611
-
2612
- const REQUIRED_FIELD_SYMBOL = '*';
2613
- /** The `TitleField` is the template to use to render the title of a field
2614
- *
2615
- * @param props - The `TitleFieldProps` for this component
2616
- */
2617
- function TitleField(props) {
2618
- const {
2619
- id,
2620
- title,
2621
- required
2622
- } = props;
2623
- return jsxs("legend", {
2624
- id: id,
2625
- children: [title, required && jsx("span", {
2626
- className: 'required',
2627
- children: REQUIRED_FIELD_SYMBOL
2628
- })]
2629
- });
2630
- }
2631
-
2632
- /** The `UnsupportedField` component is used to render a field in the schema is one that is not supported by
2633
- * react-jsonschema-form.
2634
- *
2635
- * @param props - The `FieldProps` for this template
2636
- */
2637
- function UnsupportedField(props) {
2638
- const {
2639
- schema,
2640
- idSchema,
2641
- reason,
2642
- registry
2643
- } = props;
2644
- const {
2645
- translateString
2646
- } = registry;
2647
- let translateEnum = TranslatableString.UnsupportedField;
2648
- const translateParams = [];
2649
- if (idSchema && idSchema.$id) {
2650
- translateEnum = TranslatableString.UnsupportedFieldWithId;
2651
- translateParams.push(idSchema.$id);
2652
- }
2653
- if (reason) {
2654
- translateEnum = translateEnum === TranslatableString.UnsupportedField ? TranslatableString.UnsupportedFieldWithReason : TranslatableString.UnsupportedFieldWithIdAndReason;
2655
- translateParams.push(reason);
2656
- }
2657
- return jsxs("div", {
2658
- className: 'unsupported-field',
2659
- children: [jsx("p", {
2660
- children: jsx(Markdown, {
2661
- children: translateString(translateEnum, translateParams)
2662
- })
2663
- }), schema && jsx("pre", {
2664
- children: JSON.stringify(schema, null, 2)
2665
- })]
2666
- });
2667
- }
2668
-
2669
- /** The `WrapIfAdditional` component is used by the `FieldTemplate` to rename, or remove properties that are
2670
- * part of an `additionalProperties` part of a schema.
2671
- *
2672
- * @param props - The `WrapIfAdditionalProps` for this component
2673
- */
2674
- function WrapIfAdditionalTemplate(props) {
2675
- const {
2676
- id,
2677
- classNames,
2678
- style,
2679
- disabled,
2680
- label,
2681
- onKeyChange,
2682
- onDropPropertyClick,
2683
- readonly,
2684
- required,
2685
- schema,
2686
- children,
2687
- uiSchema,
2688
- registry
2689
- } = props;
2690
- const {
2691
- templates,
2692
- translateString
2693
- } = registry;
2694
- // Button templates are not overridden in the uiSchema
2695
- const {
2696
- RemoveButton
2697
- } = templates.ButtonTemplates;
2698
- const keyLabel = translateString(TranslatableString.KeyLabel, [label]);
2699
- const additional = (ADDITIONAL_PROPERTY_FLAG in schema);
2700
- if (!additional) {
2701
- return jsx("div", {
2702
- className: classNames,
2703
- style: style,
2704
- children: children
2705
- });
2706
- }
2707
- return jsx("div", {
2708
- className: classNames,
2709
- style: style,
2710
- children: jsxs("div", {
2711
- className: 'row',
2712
- children: [jsx("div", {
2713
- className: 'col-xs-5 form-additional',
2714
- children: jsxs("div", {
2715
- className: 'form-group',
2716
- children: [jsx(Label, {
2717
- label: keyLabel,
2718
- required: required,
2719
- id: `${id}-key`
2720
- }), jsx("input", {
2721
- className: 'form-control',
2722
- type: 'text',
2723
- id: `${id}-key`,
2724
- onBlur: event => onKeyChange(event.target.value),
2725
- defaultValue: label
2726
- })]
2727
- })
2728
- }), jsx("div", {
2729
- className: 'form-additional form-group col-xs-5',
2730
- children: children
2731
- }), jsx("div", {
2732
- className: 'col-xs-2',
2733
- children: jsx(RemoveButton, {
2734
- className: 'array-item-remove btn-block',
2735
- style: {
2736
- border: '0'
2737
- },
2738
- disabled: disabled || readonly,
2739
- onClick: onDropPropertyClick(label),
2740
- uiSchema: uiSchema,
2741
- registry: registry
2742
- })
2743
- })]
2744
- })
2745
- });
2746
- }
2747
-
2748
- function templates() {
2749
- return {
2750
- ArrayFieldDescriptionTemplate,
2751
- ArrayFieldItemTemplate,
2752
- ArrayFieldTemplate,
2753
- ArrayFieldTitleTemplate,
2754
- ButtonTemplates: buttonTemplates(),
2755
- BaseInputTemplate,
2756
- DescriptionFieldTemplate: DescriptionField,
2757
- ErrorListTemplate: ErrorList,
2758
- FieldTemplate,
2759
- FieldErrorTemplate,
2760
- FieldHelpTemplate,
2761
- ObjectFieldTemplate,
2762
- TitleFieldTemplate: TitleField,
2763
- UnsupportedFieldTemplate: UnsupportedField,
2764
- WrapIfAdditionalTemplate
2765
- };
2766
- }
2767
-
2768
- function rangeOptions(start, stop) {
2769
- const options = [];
2770
- for (let i = start; i <= stop; i++) {
2771
- options.push({
2772
- value: i,
2773
- label: pad(i, 2)
2774
- });
2775
- }
2776
- return options;
2777
- }
2778
- function readyForChange(state) {
2779
- return Object.values(state).every(value => value !== -1);
2780
- }
2781
- function dateElementProps(state, time, yearsRange = [1900, new Date().getFullYear() + 2]) {
2782
- const {
2783
- year,
2784
- month,
2785
- day,
2786
- hour,
2787
- minute,
2788
- second
2789
- } = state;
2790
- const data = [{
2791
- type: 'year',
2792
- range: yearsRange,
2793
- value: year
2794
- }, {
2795
- type: 'month',
2796
- range: [1, 12],
2797
- value: month
2798
- }, {
2799
- type: 'day',
2800
- range: [1, 31],
2801
- value: day
2802
- }];
2803
- if (time) {
2804
- data.push({
2805
- type: 'hour',
2806
- range: [0, 23],
2807
- value: hour
2808
- }, {
2809
- type: 'minute',
2810
- range: [0, 59],
2811
- value: minute
2812
- }, {
2813
- type: 'second',
2814
- range: [0, 59],
2815
- value: second
2816
- });
2817
- }
2818
- return data;
2819
- }
2820
- function DateElement({
2821
- type,
2822
- range,
2823
- value,
2824
- select,
2825
- rootId,
2826
- name,
2827
- disabled,
2828
- readonly,
2829
- autofocus,
2830
- registry,
2831
- onBlur,
2832
- onFocus
2833
- }) {
2834
- const id = rootId + '_' + type;
2835
- const {
2836
- SelectWidget
2837
- } = registry.widgets;
2838
- return jsx(SelectWidget, {
2839
- schema: {
2840
- type: 'integer'
2841
- },
2842
- id: id,
2843
- name: name,
2844
- className: 'form-control',
2845
- options: {
2846
- enumOptions: rangeOptions(range[0], range[1])
2847
- },
2848
- placeholder: type,
2849
- value: value,
2850
- disabled: disabled,
2851
- readonly: readonly,
2852
- autofocus: autofocus,
2853
- onChange: value => select(type, value),
2854
- onBlur: onBlur,
2855
- onFocus: onFocus,
2856
- registry: registry,
2857
- label: '',
2858
- "aria-describedby": ariaDescribedByIds(rootId)
2859
- });
2860
- }
2861
- /** The `AltDateWidget` is an alternative widget for rendering date properties.
2862
- * @param props - The `WidgetProps` for this component
2863
- */
2864
- function AltDateWidget({
2865
- time = false,
2866
- disabled = false,
2867
- readonly = false,
2868
- autofocus = false,
2869
- options,
2870
- id,
2871
- name,
2872
- registry,
2873
- onBlur,
2874
- onFocus,
2875
- onChange,
2876
- value
2877
- }) {
2878
- const {
2879
- translateString
2880
- } = registry;
2881
- const [lastValue, setLastValue] = useState(value);
2882
- const [state, setState] = useReducer((state, action) => {
2883
- return {
2884
- ...state,
2885
- ...action
2886
- };
2887
- }, parseDateString(value, time));
2888
- useEffect(() => {
2889
- const stateValue = toDateString(state, time);
2890
- if (readyForChange(state) && stateValue !== value) {
2891
- // The user changed the date to a new valid data via the comboboxes, so call onChange
2892
- onChange(stateValue);
2893
- } else if (lastValue !== value) {
2894
- // We got a new value in the props
2895
- setLastValue(value);
2896
- setState(parseDateString(value, time));
2897
- }
2898
- }, [time, value, onChange, state, lastValue]);
2899
- const handleChange = useCallback((property, value) => {
2900
- setState({
2901
- [property]: value
2902
- });
2903
- }, []);
2904
- const handleSetNow = useCallback(event => {
2905
- event.preventDefault();
2906
- if (disabled || readonly) {
2907
- return;
2908
- }
2909
- const nextState = parseDateString(new Date().toJSON(), time);
2910
- onChange(toDateString(nextState, time));
2911
- }, [disabled, readonly, time]);
2912
- const handleClear = useCallback(event => {
2913
- event.preventDefault();
2914
- if (disabled || readonly) {
2915
- return;
2916
- }
2917
- onChange(undefined);
2918
- }, [disabled, readonly, onChange]);
2919
- return jsxs("ul", {
2920
- className: 'list-inline',
2921
- children: [dateElementProps(state, time, options.yearsRange).map((elemProps, i) => jsx("li", {
2922
- className: 'list-inline-item',
2923
- children: jsx(DateElement, {
2924
- rootId: id,
2925
- name: name,
2926
- select: handleChange,
2927
- ...elemProps,
2928
- disabled: disabled,
2929
- readonly: readonly,
2930
- registry: registry,
2931
- onBlur: onBlur,
2932
- onFocus: onFocus,
2933
- autofocus: autofocus && i === 0
2934
- })
2935
- }, i)), (options.hideNowButton !== 'undefined' ? !options.hideNowButton : true) && jsx("li", {
2936
- className: 'list-inline-item',
2937
- children: jsx("a", {
2938
- href: '#',
2939
- className: 'btn btn-info btn-now',
2940
- onClick: handleSetNow,
2941
- children: translateString(TranslatableString.NowLabel)
2942
- })
2943
- }), (options.hideClearButton !== 'undefined' ? !options.hideClearButton : true) && jsx("li", {
2944
- className: 'list-inline-item',
2945
- children: jsx("a", {
2946
- href: '#',
2947
- className: 'btn btn-warning btn-clear',
2948
- onClick: handleClear,
2949
- children: translateString(TranslatableString.ClearLabel)
2950
- })
2951
- })]
2952
- });
2953
- }
2954
-
2955
- /** The `AltDateTimeWidget` is an alternative widget for rendering datetime properties.
2956
- * It uses the AltDateWidget for rendering, with the `time` prop set to true by default.
2957
- *
2958
- * @param props - The `WidgetProps` for this component
2959
- */
2960
- function AltDateTimeWidget({
2961
- time = true,
2962
- ...props
2963
- }) {
2964
- const {
2965
- AltDateWidget
2966
- } = props.registry.widgets;
2967
- return jsx(AltDateWidget, {
2968
- time: time,
2969
- ...props
2970
- });
2971
- }
2972
-
2973
- /** The `CheckBoxWidget` is a widget for rendering boolean properties.
2974
- * It is typically used to represent a boolean.
2975
- *
2976
- * @param props - The `WidgetProps` for this component
2977
- */
2978
- function CheckboxWidget({
2979
- schema,
2980
- uiSchema,
2981
- options,
2982
- id,
2983
- value,
2984
- disabled,
2985
- readonly,
2986
- label,
2987
- hideLabel,
2988
- autofocus = false,
2989
- onBlur,
2990
- onFocus,
2991
- onChange,
2992
- registry
2993
- }) {
2994
- const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, options);
2995
- // Because an unchecked checkbox will cause html5 validation to fail, only add
2996
- // the "required" attribute if the field value must be "true", due to the
2997
- // "const" or "enum" keywords
2998
- const required = schemaRequiresTrueValue(schema);
2999
- const handleChange = useCallback(event => onChange(event.target.checked), [onChange]);
3000
- const handleBlur = useCallback(event => onBlur(id, event.target.checked), [onBlur, id]);
3001
- const handleFocus = useCallback(event => onFocus(id, event.target.checked), [onFocus, id]);
3002
- const description = options.description ?? schema.description;
3003
- return jsxs("div", {
3004
- className: `checkbox ${disabled || readonly ? 'disabled' : ''}`,
3005
- children: [!hideLabel && !!description && jsx(DescriptionFieldTemplate, {
3006
- id: descriptionId(id),
3007
- description: description,
3008
- schema: schema,
3009
- uiSchema: uiSchema,
3010
- registry: registry
3011
- }), jsxs("label", {
3012
- children: [jsx("input", {
3013
- type: 'checkbox',
3014
- id: id,
3015
- name: id,
3016
- checked: typeof value === 'undefined' ? false : value,
3017
- required: required,
3018
- disabled: disabled || readonly,
3019
- autoFocus: autofocus,
3020
- onChange: handleChange,
3021
- onBlur: handleBlur,
3022
- onFocus: handleFocus,
3023
- "aria-describedby": ariaDescribedByIds(id)
3024
- }), labelValue(jsx("span", {
3025
- children: label
3026
- }), hideLabel)]
3027
- })]
3028
- });
3029
- }
3030
-
3031
- /** The `CheckboxesWidget` is a widget for rendering checkbox groups.
3032
- * It is typically used to represent an array of enums.
3033
- *
3034
- * @param props - The `WidgetProps` for this component
3035
- */
3036
- function CheckboxesWidget({
3037
- id,
3038
- disabled,
3039
- options: {
3040
- inline = false,
3041
- enumOptions,
3042
- enumDisabled,
3043
- emptyValue
3044
- },
3045
- value,
3046
- autofocus = false,
3047
- readonly,
3048
- onChange,
3049
- onBlur,
3050
- onFocus
3051
- }) {
3052
- const checkboxesValues = Array.isArray(value) ? value : [value];
3053
- const handleBlur = useCallback(({
3054
- target: {
3055
- value
3056
- }
3057
- }) => onBlur(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)), [onBlur, id]);
3058
- const handleFocus = useCallback(({
3059
- target: {
3060
- value
3061
- }
3062
- }) => onFocus(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)), [onFocus, id]);
3063
- return jsx("div", {
3064
- className: 'checkboxes',
3065
- id: id,
3066
- children: Array.isArray(enumOptions) && enumOptions.map((option, index) => {
3067
- const checked = enumOptionsIsSelected(option.value, checkboxesValues);
3068
- const itemDisabled = Array.isArray(enumDisabled) && enumDisabled.indexOf(option.value) !== -1;
3069
- const disabledCls = disabled || itemDisabled || readonly ? 'disabled' : '';
3070
- const handleChange = event => {
3071
- if (event.target.checked) {
3072
- onChange(enumOptionsSelectValue(index, checkboxesValues, enumOptions));
3073
- } else {
3074
- onChange(enumOptionsDeselectValue(index, checkboxesValues, enumOptions));
3075
- }
3076
- };
3077
- const checkbox = jsxs("span", {
3078
- children: [jsx("input", {
3079
- type: 'checkbox',
3080
- id: optionId(id, index),
3081
- name: id,
3082
- checked: checked,
3083
- value: String(index),
3084
- disabled: disabled || itemDisabled || readonly,
3085
- autoFocus: autofocus && index === 0,
3086
- onChange: handleChange,
3087
- onBlur: handleBlur,
3088
- onFocus: handleFocus,
3089
- "aria-describedby": ariaDescribedByIds(id)
3090
- }), jsx("span", {
3091
- children: option.label
3092
- })]
3093
- });
3094
- return inline ? jsx("label", {
3095
- className: `checkbox-inline ${disabledCls}`,
3096
- children: checkbox
3097
- }, index) : jsx("div", {
3098
- className: `checkbox ${disabledCls}`,
3099
- children: jsx("label", {
3100
- children: checkbox
3101
- })
3102
- }, index);
3103
- })
3104
- });
3105
- }
3106
-
3107
- /** The `ColorWidget` component uses the `BaseInputTemplate` changing the type to `color` and disables it when it is
3108
- * either disabled or readonly.
3109
- *
3110
- * @param props - The `WidgetProps` for this component
3111
- */
3112
- function ColorWidget(props) {
3113
- const {
3114
- disabled,
3115
- readonly,
3116
- options,
3117
- registry
3118
- } = props;
3119
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3120
- return jsx(BaseInputTemplate, {
3121
- type: 'color',
3122
- ...props,
3123
- disabled: disabled || readonly
3124
- });
3125
- }
3126
-
3127
- /** The `DateWidget` component uses the `BaseInputTemplate` changing the type to `date` and transforms
3128
- * the value to undefined when it is falsy during the `onChange` handling.
3129
- *
3130
- * @param props - The `WidgetProps` for this component
3131
- */
3132
- function DateWidget(props) {
3133
- const {
3134
- onChange,
3135
- options,
3136
- registry
3137
- } = props;
3138
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3139
- const handleChange = useCallback(value => onChange(value || undefined), [onChange]);
3140
- return jsx(BaseInputTemplate, {
3141
- type: 'date',
3142
- ...props,
3143
- onChange: handleChange
3144
- });
3145
- }
3146
-
3147
- /** The `DateTimeWidget` component uses the `BaseInputTemplate` changing the type to `datetime-local` and transforms
3148
- * the value to/from utc using the appropriate utility functions.
3149
- *
3150
- * @param props - The `WidgetProps` for this component
3151
- */
3152
- function DateTimeWidget(props) {
3153
- const {
3154
- onChange,
3155
- value,
3156
- options,
3157
- registry
3158
- } = props;
3159
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3160
- return jsx(BaseInputTemplate, {
3161
- type: 'datetime-local',
3162
- ...props,
3163
- value: utcToLocal(value),
3164
- onChange: value => onChange(localToUTC(value))
3165
- });
3166
- }
3167
-
3168
- /** The `EmailWidget` component uses the `BaseInputTemplate` changing the type to `email`.
3169
- *
3170
- * @param props - The `WidgetProps` for this component
3171
- */
3172
- function EmailWidget(props) {
3173
- const {
3174
- options,
3175
- registry
3176
- } = props;
3177
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3178
- return jsx(BaseInputTemplate, {
3179
- type: 'email',
3180
- ...props
3181
- });
3182
- }
3183
-
3184
- function addNameToDataURL(dataURL, name) {
3185
- if (dataURL === null) {
3186
- return null;
3187
- }
3188
- return dataURL.replace(';base64', `;name=${encodeURIComponent(name)};base64`);
3189
- }
3190
- function processFile(file) {
3191
- const {
3192
- name,
3193
- size,
3194
- type
3195
- } = file;
3196
- return new Promise((resolve, reject) => {
3197
- const reader = new window.FileReader();
3198
- reader.onerror = reject;
3199
- reader.onload = event => {
3200
- var _event$target;
3201
- if (typeof ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.result) === 'string') {
3202
- resolve({
3203
- dataURL: addNameToDataURL(event.target.result, name),
3204
- name,
3205
- size,
3206
- type
3207
- });
3208
- } else {
3209
- resolve({
3210
- dataURL: null,
3211
- name,
3212
- size,
3213
- type
3214
- });
3215
- }
3216
- };
3217
- reader.readAsDataURL(file);
3218
- });
3219
- }
3220
- function processFiles(files) {
3221
- return Promise.all(Array.from(files).map(processFile));
3222
- }
3223
- function FileInfoPreview({
3224
- fileInfo,
3225
- registry
3226
- }) {
3227
- const {
3228
- translateString
3229
- } = registry;
3230
- const {
3231
- dataURL,
3232
- type,
3233
- name
3234
- } = fileInfo;
3235
- if (!dataURL) {
3236
- return null;
3237
- }
3238
- if (type.indexOf('image') !== -1) {
3239
- return jsx("img", {
3240
- src: dataURL,
3241
- style: {
3242
- maxWidth: '100%'
3243
- },
3244
- className: 'file-preview'
3245
- });
3246
- }
3247
- return jsxs(Fragment, {
3248
- children: [' ', jsx("a", {
3249
- download: `preview-${name}`,
3250
- href: dataURL,
3251
- className: 'file-download',
3252
- children: translateString(TranslatableString.PreviewLabel)
3253
- })]
3254
- });
3255
- }
3256
- function FilesInfo({
3257
- filesInfo,
3258
- registry,
3259
- preview
3260
- }) {
3261
- if (filesInfo.length === 0) {
3262
- return null;
3263
- }
3264
- const {
3265
- translateString
3266
- } = registry;
3267
- return jsx("ul", {
3268
- className: 'file-info',
3269
- children: filesInfo.map((fileInfo, key) => {
3270
- const {
3271
- name,
3272
- size,
3273
- type
3274
- } = fileInfo;
3275
- return jsxs("li", {
3276
- children: [jsx(Markdown, {
3277
- children: translateString(TranslatableString.FilesInfo, [name, type, String(size)])
3278
- }), preview && jsx(FileInfoPreview, {
3279
- fileInfo: fileInfo,
3280
- registry: registry
3281
- })]
3282
- }, key);
3283
- })
3284
- });
3285
- }
3286
- function extractFileInfo(dataURLs) {
3287
- return dataURLs.filter(dataURL => dataURL).map(dataURL => {
3288
- const {
3289
- blob,
3290
- name
3291
- } = dataURItoBlob(dataURL);
3292
- return {
3293
- dataURL,
3294
- name: name,
3295
- size: blob.size,
3296
- type: blob.type
3297
- };
3298
- });
3299
- }
3300
- /**
3301
- * The `FileWidget` is a widget for rendering file upload fields.
3302
- * It is typically used with a string property with data-url format.
3303
- */
3304
- function FileWidget(props) {
3305
- const {
3306
- disabled,
3307
- readonly,
3308
- required,
3309
- multiple,
3310
- onChange,
3311
- value,
3312
- options,
3313
- registry
3314
- } = props;
3315
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3316
- const [filesInfo, setFilesInfo] = useState(Array.isArray(value) ? extractFileInfo(value) : extractFileInfo([value]));
3317
- const handleChange = useCallback(event => {
3318
- if (!event.target.files) {
3319
- return;
3320
- }
3321
- // Due to variances in themes, dealing with multiple files for the array case now happens one file at a time.
3322
- // This is because we don't pass `multiple` into the `BaseInputTemplate` anymore. Instead, we deal with the single
3323
- // file in each event and concatenate them together ourselves
3324
- processFiles(event.target.files).then(filesInfoEvent => {
3325
- const newValue = filesInfoEvent.map(fileInfo => fileInfo.dataURL);
3326
- if (multiple) {
3327
- setFilesInfo(filesInfo.concat(filesInfoEvent[0]));
3328
- onChange(value.concat(newValue[0]));
3329
- } else {
3330
- setFilesInfo(filesInfoEvent);
3331
- onChange(newValue[0]);
3332
- }
3333
- });
3334
- }, [multiple, value, filesInfo, onChange]);
3335
- return jsxs("div", {
3336
- children: [jsx(BaseInputTemplate, {
3337
- ...props,
3338
- disabled: disabled || readonly,
3339
- type: 'file',
3340
- required: value ? false : required,
3341
- onChangeOverride: handleChange,
3342
- value: '',
3343
- accept: options.accept ? String(options.accept) : undefined
3344
- }), jsx(FilesInfo, {
3345
- filesInfo: filesInfo,
3346
- registry: registry,
3347
- preview: options.filePreview
3348
- })]
3349
- });
3350
- }
3351
-
3352
- /** The `HiddenWidget` is a widget for rendering a hidden input field.
3353
- * It is typically used by setting type to "hidden".
3354
- *
3355
- * @param props - The `WidgetProps` for this component
3356
- */
3357
- function HiddenWidget({
3358
- id,
3359
- value
3360
- }) {
3361
- return jsx("input", {
3362
- type: 'hidden',
3363
- id: id,
3364
- name: id,
3365
- value: typeof value === 'undefined' ? '' : value
3366
- });
3367
- }
3368
-
3369
- /** The `PasswordWidget` component uses the `BaseInputTemplate` changing the type to `password`.
3370
- *
3371
- * @param props - The `WidgetProps` for this component
3372
- */
3373
- function PasswordWidget(props) {
3374
- const {
3375
- options,
3376
- registry
3377
- } = props;
3378
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3379
- return jsx(BaseInputTemplate, {
3380
- type: 'password',
3381
- ...props
3382
- });
3383
- }
3384
-
3385
- /** The `RadioWidget` is a widget for rendering a radio group.
3386
- * It is typically used with a string property constrained with enum options.
3387
- *
3388
- * @param props - The `WidgetProps` for this component
3389
- */
3390
- function RadioWidget({
3391
- options,
3392
- value,
3393
- required,
3394
- disabled,
3395
- readonly,
3396
- autofocus = false,
3397
- onBlur,
3398
- onFocus,
3399
- onChange,
3400
- id
3401
- }) {
3402
- const {
3403
- enumOptions,
3404
- enumDisabled,
3405
- inline,
3406
- emptyValue
3407
- } = options;
3408
- const handleBlur = useCallback(({
3409
- target: {
3410
- value
3411
- }
3412
- }) => onBlur(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)), [onBlur, id]);
3413
- const handleFocus = useCallback(({
3414
- target: {
3415
- value
3416
- }
3417
- }) => onFocus(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)), [onFocus, id]);
3418
- return jsx("div", {
3419
- className: 'field-radio-group',
3420
- id: id,
3421
- children: Array.isArray(enumOptions) && enumOptions.map((option, i) => {
3422
- const checked = enumOptionsIsSelected(option.value, value);
3423
- const itemDisabled = Array.isArray(enumDisabled) && enumDisabled.indexOf(option.value) !== -1;
3424
- const disabledCls = disabled || itemDisabled || readonly ? 'disabled' : '';
3425
- const handleChange = () => onChange(option.value);
3426
- const radio = jsxs("span", {
3427
- children: [jsx("input", {
3428
- type: 'radio',
3429
- id: optionId(id, i),
3430
- checked: checked,
3431
- name: id,
3432
- required: required,
3433
- value: String(i),
3434
- disabled: disabled || itemDisabled || readonly,
3435
- autoFocus: autofocus && i === 0,
3436
- onChange: handleChange,
3437
- onBlur: handleBlur,
3438
- onFocus: handleFocus,
3439
- "aria-describedby": ariaDescribedByIds(id)
3440
- }), jsx("span", {
3441
- children: option.label
3442
- })]
3443
- });
3444
- return inline ? jsx("label", {
3445
- className: `radio-inline ${disabledCls}`,
3446
- children: radio
3447
- }, i) : jsx("div", {
3448
- className: `radio ${disabledCls}`,
3449
- children: jsx("label", {
3450
- children: radio
3451
- })
3452
- }, i);
3453
- })
3454
- });
3455
- }
3456
-
3457
- /** The `RangeWidget` component uses the `BaseInputTemplate` changing the type to `range` and wrapping the result
3458
- * in a div, with the value along side it.
3459
- *
3460
- * @param props - The `WidgetProps` for this component
3461
- */
3462
- function RangeWidget(props) {
3463
- const {
3464
- value,
3465
- registry: {
3466
- templates: {
3467
- BaseInputTemplate
3468
- }
3469
- }
3470
- } = props;
3471
- return jsxs("div", {
3472
- className: 'field-range-wrapper',
3473
- children: [jsx(BaseInputTemplate, {
3474
- type: 'range',
3475
- ...props
3476
- }), jsx("span", {
3477
- className: 'range-view',
3478
- children: value
3479
- })]
3480
- });
3481
- }
3482
-
3483
- function getValue(event, multiple) {
3484
- if (multiple) {
3485
- return Array.from(event.target.options).slice().filter(o => o.selected).map(o => o.value);
3486
- }
3487
- return event.target.value;
3488
- }
3489
- /** The `SelectWidget` is a widget for rendering dropdowns.
3490
- * It is typically used with string properties constrained with enum options.
3491
- *
3492
- * @param props - The `WidgetProps` for this component
3493
- */
3494
- function SelectWidget({
3495
- schema,
3496
- id,
3497
- options,
3498
- value,
3499
- required,
3500
- disabled,
3501
- readonly,
3502
- multiple = false,
3503
- autofocus = false,
3504
- onChange,
3505
- onBlur,
3506
- onFocus,
3507
- placeholder
3508
- }) {
3509
- const {
3510
- enumOptions,
3511
- enumDisabled,
3512
- emptyValue: optEmptyVal
3513
- } = options;
3514
- const emptyValue = multiple ? [] : '';
3515
- const handleFocus = useCallback(event => {
3516
- const newValue = getValue(event, multiple);
3517
- return onFocus(id, enumOptionsValueForIndex(newValue, enumOptions, optEmptyVal));
3518
- }, [onFocus, id, schema, multiple, options]);
3519
- const handleBlur = useCallback(event => {
3520
- const newValue = getValue(event, multiple);
3521
- return onBlur(id, enumOptionsValueForIndex(newValue, enumOptions, optEmptyVal));
3522
- }, [onBlur, id, schema, multiple, options]);
3523
- const handleChange = useCallback(event => {
3524
- const newValue = getValue(event, multiple);
3525
- return onChange(enumOptionsValueForIndex(newValue, enumOptions, optEmptyVal));
3526
- }, [onChange, schema, multiple, options]);
3527
- const selectedIndexes = enumOptionsIndexForValue(value, enumOptions, multiple);
3528
- return jsxs("select", {
3529
- id: id,
3530
- name: id,
3531
- multiple: multiple,
3532
- className: 'form-control',
3533
- value: typeof selectedIndexes === 'undefined' ? emptyValue : selectedIndexes,
3534
- required: required,
3535
- disabled: disabled || readonly,
3536
- autoFocus: autofocus,
3537
- onBlur: handleBlur,
3538
- onFocus: handleFocus,
3539
- onChange: handleChange,
3540
- "aria-describedby": ariaDescribedByIds(id),
3541
- children: [!multiple && schema.default === undefined && jsx("option", {
3542
- value: '',
3543
- children: placeholder
3544
- }), Array.isArray(enumOptions) && enumOptions.map(({
3545
- value,
3546
- label
3547
- }, i) => {
3548
- const disabled = enumDisabled && enumDisabled.indexOf(value) !== -1;
3549
- return jsx("option", {
3550
- value: String(i),
3551
- disabled: disabled,
3552
- children: label
3553
- }, i);
3554
- })]
3555
- });
3556
- }
3557
-
3558
- /** The `TextareaWidget` is a widget for rendering input fields as textarea.
3559
- *
3560
- * @param props - The `WidgetProps` for this component
3561
- */
3562
- function TextareaWidget({
3563
- id,
3564
- options = {},
3565
- placeholder,
3566
- value,
3567
- required,
3568
- disabled,
3569
- readonly,
3570
- autofocus = false,
3571
- onChange,
3572
- onBlur,
3573
- onFocus
3574
- }) {
3575
- const handleChange = useCallback(({
3576
- target: {
3577
- value
3578
- }
3579
- }) => onChange(value === '' ? options.emptyValue : value), [onChange, options.emptyValue]);
3580
- const handleBlur = useCallback(({
3581
- target: {
3582
- value
3583
- }
3584
- }) => onBlur(id, value), [onBlur, id]);
3585
- const handleFocus = useCallback(({
3586
- target: {
3587
- value
3588
- }
3589
- }) => onFocus(id, value), [id, onFocus]);
3590
- return jsx("textarea", {
3591
- id: id,
3592
- name: id,
3593
- className: 'form-control',
3594
- value: value ? value : '',
3595
- placeholder: placeholder,
3596
- required: required,
3597
- disabled: disabled,
3598
- readOnly: readonly,
3599
- autoFocus: autofocus,
3600
- rows: options.rows,
3601
- onBlur: handleBlur,
3602
- onFocus: handleFocus,
3603
- onChange: handleChange,
3604
- "aria-describedby": ariaDescribedByIds(id)
3605
- });
3606
- }
3607
- TextareaWidget.defaultProps = {
3608
- autofocus: false,
3609
- options: {}
3610
- };
3611
-
3612
- /** The `TextWidget` component uses the `BaseInputTemplate`.
3613
- *
3614
- * @param props - The `WidgetProps` for this component
3615
- */
3616
- function TextWidget(props) {
3617
- const {
3618
- options,
3619
- registry
3620
- } = props;
3621
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3622
- return jsx(BaseInputTemplate, {
3623
- ...props
3624
- });
3625
- }
3626
-
3627
- /** The `TimeWidget` component uses the `BaseInputTemplate` changing the type to `time` and transforms
3628
- * the value to undefined when it is falsy during the `onChange` handling.
3629
- *
3630
- * @param props - The `WidgetProps` for this component
3631
- */
3632
- function TimeWidget(props) {
3633
- const {
3634
- onChange,
3635
- options,
3636
- registry
3637
- } = props;
3638
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3639
- const handleChange = useCallback(value => onChange(value ? `${value}:00` : undefined), [onChange]);
3640
- return jsx(BaseInputTemplate, {
3641
- type: 'time',
3642
- ...props,
3643
- onChange: handleChange
3644
- });
3645
- }
3646
-
3647
- /** The `URLWidget` component uses the `BaseInputTemplate` changing the type to `url`.
3648
- *
3649
- * @param props - The `WidgetProps` for this component
3650
- */
3651
- function URLWidget(props) {
3652
- const {
3653
- options,
3654
- registry
3655
- } = props;
3656
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3657
- return jsx(BaseInputTemplate, {
3658
- type: 'url',
3659
- ...props
3660
- });
3661
- }
3662
-
3663
- /** The `UpDownWidget` component uses the `BaseInputTemplate` changing the type to `number`.
3664
- *
3665
- * @param props - The `WidgetProps` for this component
3666
- */
3667
- function UpDownWidget(props) {
3668
- const {
3669
- options,
3670
- registry
3671
- } = props;
3672
- const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
3673
- return jsx(BaseInputTemplate, {
3674
- type: 'number',
3675
- ...props
3676
- });
3677
- }
3678
-
3679
- function widgets() {
3680
- return {
3681
- AltDateWidget,
3682
- AltDateTimeWidget,
3683
- CheckboxWidget,
3684
- CheckboxesWidget,
3685
- ColorWidget,
3686
- DateWidget,
3687
- DateTimeWidget,
3688
- EmailWidget,
3689
- FileWidget,
3690
- HiddenWidget,
3691
- PasswordWidget,
3692
- RadioWidget,
3693
- RangeWidget,
3694
- SelectWidget,
3695
- TextWidget,
3696
- TextareaWidget,
3697
- TimeWidget,
3698
- UpDownWidget,
3699
- URLWidget
3700
- };
3701
- }
3702
-
3703
- /** The default registry consists of all the fields, templates and widgets provided in the core implementation,
3704
- * plus an empty `rootSchema` and `formContext. We omit schemaUtils here because it cannot be defaulted without a
3705
- * rootSchema and validator. It will be added into the computed registry later in the Form.
3706
- */
3707
- function getDefaultRegistry() {
3708
- return {
3709
- fields: fields(),
3710
- templates: templates(),
3711
- widgets: widgets(),
3712
- rootSchema: {},
3713
- formContext: {},
3714
- translateString: englishStringTranslator
3715
- };
3716
- }
3717
-
3718
- /** The `Form` component renders the outer form and all the fields defined in the `schema` */
3719
- class Form extends Component {
3720
- /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
3721
- * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
3722
- * state construction.
3723
- *
3724
- * @param props - The initial props for the `Form`
3725
- */
3726
- constructor(props) {
3727
- super(props);
3728
- /** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can
3729
- * provide any possible type here
3730
- */
3731
- this.formElement = void 0;
3732
- /** Returns the `formData` with only the elements specified in the `fields` list
3733
- *
3734
- * @param formData - The data for the `Form`
3735
- * @param fields - The fields to keep while filtering
3736
- */
3737
- this.getUsedFormData = (formData, fields) => {
3738
- // For the case of a single input form
3739
- if (fields.length === 0 && typeof formData !== 'object') {
3740
- return formData;
3741
- }
3742
- // _pick has incorrect type definition, it works with string[][], because lodash/hasIn supports it
3743
- const data = _pick(formData, fields);
3744
- if (Array.isArray(formData)) {
3745
- return Object.keys(data).map(key => data[key]);
3746
- }
3747
- return data;
3748
- };
3749
- /** Returns the list of field names from inspecting the `pathSchema` as well as using the `formData`
3750
- *
3751
- * @param pathSchema - The `PathSchema` object for the form
3752
- * @param [formData] - The form data to use while checking for empty objects/arrays
3753
- */
3754
- this.getFieldNames = (pathSchema, formData) => {
3755
- const getAllPaths = (_obj, acc = [], paths = [[]]) => {
3756
- Object.keys(_obj).forEach(key => {
3757
- if (typeof _obj[key] === 'object') {
3758
- const newPaths = paths.map(path => [...path, key]);
3759
- // If an object is marked with additionalProperties, all its keys are valid
3760
- if (_obj[key][RJSF_ADDITONAL_PROPERTIES_FLAG] && _obj[key][NAME_KEY] !== '') {
3761
- acc.push(_obj[key][NAME_KEY]);
3762
- } else {
3763
- getAllPaths(_obj[key], acc, newPaths);
3764
- }
3765
- } else if (key === NAME_KEY && _obj[key] !== '') {
3766
- paths.forEach(path => {
3767
- const formValue = get(formData, path);
3768
- // adds path to fieldNames if it points to a value
3769
- // or an empty object/array
3770
- if (typeof formValue !== 'object' || isEmpty(formValue)) {
3771
- acc.push(path);
3772
- }
3773
- });
3774
- }
3775
- });
3776
- return acc;
3777
- };
3778
- return getAllPaths(pathSchema);
3779
- };
3780
- /** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the
3781
- * `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and
3782
- * then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filterer to remove any extra data not
3783
- * in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
3784
- * updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
3785
- * callback will be called if specified with the updated state.
3786
- *
3787
- * @param formData - The new form data from a change to a field
3788
- * @param newErrorSchema - The new `ErrorSchema` based on the field change
3789
- * @param id - The id of the field that caused the change
3790
- */
3791
- this.onChange = (formData, newErrorSchema, id) => {
3792
- const {
3793
- extraErrors,
3794
- omitExtraData,
3795
- liveOmit,
3796
- noValidate,
3797
- liveValidate,
3798
- onChange
3799
- } = this.props;
3800
- const {
3801
- schemaUtils,
3802
- schema
3803
- } = this.state;
3804
- if (isObject$1(formData) || Array.isArray(formData)) {
3805
- const newState = this.getStateFromProps(this.props, formData);
3806
- formData = newState.formData;
3807
- }
3808
- const mustValidate = !noValidate && liveValidate;
3809
- let state = {
3810
- formData,
3811
- schema
3812
- };
3813
- let newFormData = formData;
3814
- if (omitExtraData === true && liveOmit === true) {
3815
- const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
3816
- const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
3817
- const fieldNames = this.getFieldNames(pathSchema, formData);
3818
- newFormData = this.getUsedFormData(formData, fieldNames);
3819
- state = {
3820
- formData: newFormData
3821
- };
3822
- }
3823
- if (mustValidate) {
3824
- const schemaValidation = this.validate(newFormData);
3825
- let errors = schemaValidation.errors;
3826
- let errorSchema = schemaValidation.errorSchema;
3827
- const schemaValidationErrors = errors;
3828
- const schemaValidationErrorSchema = errorSchema;
3829
- if (extraErrors) {
3830
- const merged = validationDataMerge(schemaValidation, extraErrors);
3831
- errorSchema = merged.errorSchema;
3832
- errors = merged.errors;
3833
- }
3834
- state = {
3835
- formData: newFormData,
3836
- errors,
3837
- errorSchema,
3838
- schemaValidationErrors,
3839
- schemaValidationErrorSchema
3840
- };
3841
- } else if (!noValidate && newErrorSchema) {
3842
- const errorSchema = extraErrors ? mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') : newErrorSchema;
3843
- state = {
3844
- formData: newFormData,
3845
- errorSchema: errorSchema,
3846
- errors: toErrorList(errorSchema)
3847
- };
3848
- }
3849
- this.setState(state, () => onChange && onChange({
3850
- ...this.state,
3851
- ...state
3852
- }, id));
3853
- };
3854
- /**
3855
- * Callback function to handle reset form data.
3856
- * - Reset all fields with default values.
3857
- * - Reset validations and errors
3858
- *
3859
- */
3860
- this.reset = () => {
3861
- const {
3862
- onChange
3863
- } = this.props;
3864
- const newState = this.getStateFromProps(this.props, undefined);
3865
- const newFormData = newState.formData;
3866
- const state = {
3867
- formData: newFormData,
3868
- errorSchema: {},
3869
- errors: [],
3870
- schemaValidationErrors: [],
3871
- schemaValidationErrorSchema: {}
3872
- };
3873
- this.setState(state, () => onChange && onChange({
3874
- ...this.state,
3875
- ...state
3876
- }));
3877
- };
3878
- /** Callback function to handle when a field on the form is blurred. Calls the `onBlur` callback for the `Form` if it
3879
- * was provided.
3880
- *
3881
- * @param id - The unique `id` of the field that was blurred
3882
- * @param data - The data associated with the field that was blurred
3883
- */
3884
- this.onBlur = (id, data) => {
3885
- const {
3886
- onBlur
3887
- } = this.props;
3888
- if (onBlur) {
3889
- onBlur(id, data);
3890
- }
3891
- };
3892
- /** Callback function to handle when a field on the form is focused. Calls the `onFocus` callback for the `Form` if it
3893
- * was provided.
3894
- *
3895
- * @param id - The unique `id` of the field that was focused
3896
- * @param data - The data associated with the field that was focused
3897
- */
3898
- this.onFocus = (id, data) => {
3899
- const {
3900
- onFocus
3901
- } = this.props;
3902
- if (onFocus) {
3903
- onFocus(id, data);
3904
- }
3905
- };
3906
- /** Callback function to handle when the form is submitted. First, it prevents the default event behavior. Nothing
3907
- * happens if the target and currentTarget of the event are not the same. It will omit any extra data in the
3908
- * `formData` in the state if `omitExtraData` is true. It will validate the resulting `formData`, reporting errors
3909
- * via the `onError()` callback unless validation is disabled. Finally, it will add in any `extraErrors` and then call
3910
- * back the `onSubmit` callback if it was provided.
3911
- *
3912
- * @param event - The submit HTML form event
3913
- */
3914
- this.onSubmit = event => {
3915
- event.preventDefault();
3916
- if (event.target !== event.currentTarget) {
3917
- return;
3918
- }
3919
- event.persist();
3920
- const {
3921
- omitExtraData,
3922
- extraErrors,
3923
- noValidate,
3924
- onSubmit
3925
- } = this.props;
3926
- let {
3927
- formData: newFormData
3928
- } = this.state;
3929
- const {
3930
- schema,
3931
- schemaUtils
3932
- } = this.state;
3933
- if (omitExtraData === true) {
3934
- const retrievedSchema = schemaUtils.retrieveSchema(schema, newFormData);
3935
- const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', newFormData);
3936
- const fieldNames = this.getFieldNames(pathSchema, newFormData);
3937
- newFormData = this.getUsedFormData(newFormData, fieldNames);
3938
- }
3939
- if (noValidate || this.validateForm()) {
3940
- // There are no errors generated through schema validation.
3941
- // Check for user provided errors and update state accordingly.
3942
- const errorSchema = extraErrors || {};
3943
- const errors = extraErrors ? toErrorList(extraErrors) : [];
3944
- this.setState({
3945
- formData: newFormData,
3946
- errors,
3947
- errorSchema,
3948
- schemaValidationErrors: [],
3949
- schemaValidationErrorSchema: {}
3950
- }, () => {
3951
- if (onSubmit) {
3952
- onSubmit({
3953
- ...this.state,
3954
- formData: newFormData,
3955
- status: 'submitted'
3956
- }, event);
3957
- }
3958
- });
3959
- }
3960
- };
3961
- if (!props.validator) {
3962
- throw new Error('A validator is required for Form functionality to work');
3963
- }
3964
- this.state = this.getStateFromProps(props, props.formData);
3965
- if (this.props.onChange && !deepEquals(this.state.formData, this.props.formData)) {
3966
- this.props.onChange(this.state);
3967
- }
3968
- this.formElement = /*#__PURE__*/createRef();
3969
- }
3970
- /** React lifecycle method that gets called before new props are provided, updates the state based on new props. It
3971
- * will also call the`onChange` handler if the `formData` is modified to add missing default values as part of the
3972
- * state construction.
3973
- *
3974
- * @param nextProps - The new set of props about to be applied to the `Form`
3975
- */
3976
- UNSAFE_componentWillReceiveProps(nextProps) {
3977
- const nextState = this.getStateFromProps(nextProps, nextProps.formData);
3978
- if (!deepEquals(nextState.formData, nextProps.formData) && !deepEquals(nextState.formData, this.state.formData) && nextProps.onChange) {
3979
- nextProps.onChange(nextState);
3980
- }
3981
- this.setState(nextState);
3982
- }
3983
- /** Extracts the updated state from the given `props` and `inputFormData`. As part of this process, the
3984
- * `inputFormData` is first processed to add any missing required defaults. After that, the data is run through the
3985
- * validation process IF required by the `props`.
3986
- *
3987
- * @param props - The props passed to the `Form`
3988
- * @param inputFormData - The new or current data for the `Form`
3989
- * @returns - The new state for the `Form`
3990
- */
3991
- getStateFromProps(props, inputFormData) {
3992
- const state = this.state || {};
3993
- const schema = 'schema' in props ? props.schema : this.props.schema;
3994
- const uiSchema = ('uiSchema' in props ? props.uiSchema : this.props.uiSchema) || {};
3995
- const edit = typeof inputFormData !== 'undefined';
3996
- const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
3997
- const mustValidate = edit && !props.noValidate && liveValidate;
3998
- const rootSchema = schema;
3999
- const experimental_defaultFormStateBehavior = 'experimental_defaultFormStateBehavior' in props ? props.experimental_defaultFormStateBehavior : this.props.experimental_defaultFormStateBehavior;
4000
- let schemaUtils = state.schemaUtils;
4001
- if (!schemaUtils || schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema, experimental_defaultFormStateBehavior)) {
4002
- schemaUtils = createSchemaUtils(props.validator, rootSchema, experimental_defaultFormStateBehavior);
4003
- }
4004
- const formData = schemaUtils.getDefaultFormState(schema, inputFormData);
4005
- const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
4006
- const getCurrentErrors = () => {
4007
- if (props.noValidate) {
4008
- return {
4009
- errors: [],
4010
- errorSchema: {}
4011
- };
4012
- } else if (!props.liveValidate) {
4013
- return {
4014
- errors: state.schemaValidationErrors || [],
4015
- errorSchema: state.schemaValidationErrorSchema || {}
4016
- };
4017
- }
4018
- return {
4019
- errors: state.errors || [],
4020
- errorSchema: state.errorSchema || {}
4021
- };
4022
- };
4023
- let errors;
4024
- let errorSchema;
4025
- let schemaValidationErrors = state.schemaValidationErrors;
4026
- let schemaValidationErrorSchema = state.schemaValidationErrorSchema;
4027
- if (mustValidate) {
4028
- const schemaValidation = this.validate(formData, schema, schemaUtils);
4029
- errors = schemaValidation.errors;
4030
- errorSchema = schemaValidation.errorSchema;
4031
- schemaValidationErrors = errors;
4032
- schemaValidationErrorSchema = errorSchema;
4033
- } else {
4034
- const currentErrors = getCurrentErrors();
4035
- errors = currentErrors.errors;
4036
- errorSchema = currentErrors.errorSchema;
4037
- }
4038
- if (props.extraErrors) {
4039
- const merged = validationDataMerge({
4040
- errorSchema,
4041
- errors
4042
- }, props.extraErrors);
4043
- errorSchema = merged.errorSchema;
4044
- errors = merged.errors;
4045
- }
4046
- const idSchema = schemaUtils.toIdSchema(retrievedSchema, uiSchema['ui:rootFieldId'], formData, props.idPrefix, props.idSeparator);
4047
- const nextState = {
4048
- schemaUtils,
4049
- schema,
4050
- uiSchema,
4051
- idSchema,
4052
- formData,
4053
- edit,
4054
- errors,
4055
- errorSchema,
4056
- schemaValidationErrors,
4057
- schemaValidationErrorSchema
4058
- };
4059
- return nextState;
4060
- }
4061
- /** React lifecycle method that is used to determine whether component should be updated.
4062
- *
4063
- * @param nextProps - The next version of the props
4064
- * @param nextState - The next version of the state
4065
- * @returns - True if the component should be updated, false otherwise
4066
- */
4067
- shouldComponentUpdate(nextProps, nextState) {
4068
- return shouldRender(this, nextProps, nextState);
4069
- }
4070
- /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the
4071
- * `schemaUtils` in the state), returning the results.
4072
- *
4073
- * @param formData - The new form data to validate
4074
- * @param schema - The schema used to validate against
4075
- * @param altSchemaUtils - The alternate schemaUtils to use for validation
4076
- */
4077
- validate(formData, schema = this.props.schema, altSchemaUtils) {
4078
- const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils;
4079
- const {
4080
- customValidate,
4081
- transformErrors,
4082
- uiSchema
4083
- } = this.props;
4084
- const resolvedSchema = schemaUtils.retrieveSchema(schema, formData);
4085
- return schemaUtils.getValidator().validateFormData(formData, resolvedSchema, customValidate, transformErrors, uiSchema);
4086
- }
4087
- /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
4088
- renderErrors(registry) {
4089
- const {
4090
- errors,
4091
- errorSchema,
4092
- schema,
4093
- uiSchema
4094
- } = this.state;
4095
- const {
4096
- formContext
4097
- } = this.props;
4098
- const options = getUiOptions(uiSchema);
4099
- const ErrorListTemplate = getTemplate('ErrorListTemplate', registry, options);
4100
- if (errors && errors.length) {
4101
- return jsx(ErrorListTemplate, {
4102
- errors: errors,
4103
- errorSchema: errorSchema || {},
4104
- schema: schema,
4105
- uiSchema: uiSchema,
4106
- formContext: formContext,
4107
- registry: registry
4108
- });
4109
- }
4110
- return null;
4111
- }
4112
- /** Returns the registry for the form */
4113
- getRegistry() {
4114
- var _this$props$templates;
4115
- const {
4116
- translateString: customTranslateString,
4117
- uiSchema = {}
4118
- } = this.props;
4119
- const {
4120
- schemaUtils
4121
- } = this.state;
4122
- const {
4123
- fields,
4124
- templates,
4125
- widgets,
4126
- formContext,
4127
- translateString
4128
- } = getDefaultRegistry();
4129
- return {
4130
- fields: {
4131
- ...fields,
4132
- ...this.props.fields
4133
- },
4134
- templates: {
4135
- ...templates,
4136
- ...this.props.templates,
4137
- ButtonTemplates: {
4138
- ...templates.ButtonTemplates,
4139
- ...((_this$props$templates = this.props.templates) === null || _this$props$templates === void 0 ? void 0 : _this$props$templates.ButtonTemplates)
4140
- }
4141
- },
4142
- widgets: {
4143
- ...widgets,
4144
- ...this.props.widgets
4145
- },
4146
- rootSchema: this.props.schema,
4147
- formContext: this.props.formContext || formContext,
4148
- schemaUtils,
4149
- translateString: customTranslateString || translateString,
4150
- globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY]
4151
- };
4152
- }
4153
- /** Provides a function that can be used to programmatically submit the `Form` */
4154
- submit() {
4155
- if (this.formElement.current) {
4156
- this.formElement.current.dispatchEvent(new CustomEvent('submit', {
4157
- cancelable: true
4158
- }));
4159
- this.formElement.current.requestSubmit();
4160
- }
4161
- }
4162
- /** Attempts to focus on the field associated with the `error`. Uses the `property` field to compute path of the error
4163
- * field, then, using the `idPrefix` and `idSeparator` converts that path into an id. Then the input element with that
4164
- * id is attempted to be found using the `formElement` ref. If it is located, then it is focused.
4165
- *
4166
- * @param error - The error on which to focus
4167
- */
4168
- focusOnError(error) {
4169
- const {
4170
- idPrefix = 'root',
4171
- idSeparator = '_'
4172
- } = this.props;
4173
- const {
4174
- property
4175
- } = error;
4176
- const path = _toPath(property);
4177
- if (path[0] === '') {
4178
- // Most of the time the `.foo` property results in the first element being empty, so replace it with the idPrefix
4179
- path[0] = idPrefix;
4180
- } else {
4181
- // Otherwise insert the idPrefix into the first location using unshift
4182
- path.unshift(idPrefix);
4183
- }
4184
- const elementId = path.join(idSeparator);
4185
- let field = this.formElement.current.elements[elementId];
4186
- if (!field) {
4187
- // if not an exact match, try finding an input starting with the element id (like radio buttons or checkboxes)
4188
- field = this.formElement.current.querySelector(`input[id^=${elementId}`);
4189
- }
4190
- if (field && field.length) {
4191
- // If we got a list with length > 0
4192
- field = field[0];
4193
- }
4194
- if (field) {
4195
- field.focus();
4196
- }
4197
- }
4198
- /** Programmatically validate the form. If `onError` is provided, then it will be called with the list of errors the
4199
- * same way as would happen on form submission.
4200
- *
4201
- * @returns - True if the form is valid, false otherwise.
4202
- */
4203
- validateForm() {
4204
- const {
4205
- extraErrors,
4206
- extraErrorsBlockSubmit,
4207
- focusOnFirstError,
4208
- onError
4209
- } = this.props;
4210
- const {
4211
- formData
4212
- } = this.state;
4213
- const schemaValidation = this.validate(formData);
4214
- let errors = schemaValidation.errors;
4215
- let errorSchema = schemaValidation.errorSchema;
4216
- const schemaValidationErrors = errors;
4217
- const schemaValidationErrorSchema = errorSchema;
4218
- if (errors.length > 0 || extraErrors && extraErrorsBlockSubmit) {
4219
- if (extraErrors) {
4220
- const merged = validationDataMerge(schemaValidation, extraErrors);
4221
- errorSchema = merged.errorSchema;
4222
- errors = merged.errors;
4223
- }
4224
- if (focusOnFirstError) {
4225
- if (typeof focusOnFirstError === 'function') {
4226
- focusOnFirstError(errors[0]);
4227
- } else {
4228
- this.focusOnError(errors[0]);
4229
- }
4230
- }
4231
- this.setState({
4232
- errors,
4233
- errorSchema,
4234
- schemaValidationErrors,
4235
- schemaValidationErrorSchema
4236
- }, () => {
4237
- if (onError) {
4238
- onError(errors);
4239
- } else {
4240
- console.error('Form validation failed', errors);
4241
- }
4242
- });
4243
- return false;
4244
- }
4245
- return true;
4246
- }
4247
- /** Renders the `Form` fields inside the <form> | `tagName` or `_internalFormWrapper`, rendering any errors if
4248
- * needed along with the submit button or any children of the form.
4249
- */
4250
- render() {
4251
- const {
4252
- children,
4253
- id,
4254
- idPrefix,
4255
- idSeparator,
4256
- className = '',
4257
- tagName,
4258
- name,
4259
- method,
4260
- target,
4261
- action,
4262
- autoComplete,
4263
- enctype,
4264
- acceptcharset,
4265
- noHtml5Validate = false,
4266
- disabled = false,
4267
- readonly = false,
4268
- formContext,
4269
- showErrorList = 'top',
4270
- _internalFormWrapper
4271
- } = this.props;
4272
- const {
4273
- schema,
4274
- uiSchema,
4275
- formData,
4276
- errorSchema,
4277
- idSchema
4278
- } = this.state;
4279
- const registry = this.getRegistry();
4280
- const {
4281
- SchemaField: _SchemaField
4282
- } = registry.fields;
4283
- const {
4284
- SubmitButton
4285
- } = registry.templates.ButtonTemplates;
4286
- // The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the
4287
- // PropTypes.elementType to use for the inner tag, so we'll need to pass `tagName` along if it is provided.
4288
- // NOTE, the `as` prop is native to `semantic-ui` and is emulated in the `material-ui` theme
4289
- const as = _internalFormWrapper ? tagName : undefined;
4290
- const FormTag = _internalFormWrapper || tagName || 'form';
4291
- let {
4292
- [SUBMIT_BTN_OPTIONS_KEY]: submitOptions = {}
4293
- } = getUiOptions(uiSchema);
4294
- if (disabled) {
4295
- submitOptions = {
4296
- ...submitOptions,
4297
- props: {
4298
- ...submitOptions.props,
4299
- disabled: true
4300
- }
4301
- };
4302
- }
4303
- const submitUiSchema = {
4304
- [UI_OPTIONS_KEY]: {
4305
- [SUBMIT_BTN_OPTIONS_KEY]: submitOptions
4306
- }
4307
- };
4308
- return jsxs(FormTag, {
4309
- className: className ? className : 'rjsf',
4310
- id: id,
4311
- name: name,
4312
- method: method,
4313
- target: target,
4314
- action: action,
4315
- autoComplete: autoComplete,
4316
- encType: enctype,
4317
- acceptCharset: acceptcharset,
4318
- noValidate: noHtml5Validate,
4319
- onSubmit: this.onSubmit,
4320
- as: as,
4321
- ref: this.formElement,
4322
- children: [showErrorList === 'top' && this.renderErrors(registry), jsx(_SchemaField, {
4323
- name: '',
4324
- schema: schema,
4325
- uiSchema: uiSchema,
4326
- errorSchema: errorSchema,
4327
- idSchema: idSchema,
4328
- idPrefix: idPrefix,
4329
- idSeparator: idSeparator,
4330
- formContext: formContext,
4331
- formData: formData,
4332
- onChange: this.onChange,
4333
- onBlur: this.onBlur,
4334
- onFocus: this.onFocus,
4335
- registry: registry,
4336
- disabled: disabled,
4337
- readonly: readonly
4338
- }), children ? children : jsx(SubmitButton, {
4339
- uiSchema: submitUiSchema,
4340
- registry: registry
4341
- }), showErrorList === 'bottom' && this.renderErrors(registry)]
4342
- });
4343
- }
4344
- }
4345
-
4346
- /** A Higher-Order component that creates a wrapper around a `Form` with the overrides from the `WithThemeProps` */
4347
- function withTheme(themeProps) {
4348
- return /*#__PURE__*/forwardRef(({
4349
- fields,
4350
- widgets,
4351
- templates,
4352
- ...directProps
4353
- }, ref) => {
4354
- var _themeProps$templates, _templates;
4355
- fields = {
4356
- ...(themeProps === null || themeProps === void 0 ? void 0 : themeProps.fields),
4357
- ...fields
4358
- };
4359
- widgets = {
4360
- ...(themeProps === null || themeProps === void 0 ? void 0 : themeProps.widgets),
4361
- ...widgets
4362
- };
4363
- templates = {
4364
- ...(themeProps === null || themeProps === void 0 ? void 0 : themeProps.templates),
4365
- ...templates,
4366
- ButtonTemplates: {
4367
- ...(themeProps === null || themeProps === void 0 ? void 0 : (_themeProps$templates = themeProps.templates) === null || _themeProps$templates === void 0 ? void 0 : _themeProps$templates.ButtonTemplates),
4368
- ...((_templates = templates) === null || _templates === void 0 ? void 0 : _templates.ButtonTemplates)
4369
- }
4370
- };
4371
- return jsx(Form, {
4372
- ...themeProps,
4373
- ...directProps,
4374
- fields: fields,
4375
- widgets: widgets,
4376
- templates: templates,
4377
- ref: ref
4378
- });
4379
- });
4380
- }
4381
-
4382
- export { Form as default, getDefaultRegistry, withTheme };
4383
- //# sourceMappingURL=core.esm.js.map