@onehat/ui 0.4.82 → 0.4.84

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.
@@ -24,6 +24,7 @@ import testProps from '../../../../Functions/testProps.js';
24
24
  import UiGlobals from '../../../../UiGlobals.js';
25
25
  import Input from '../Input.js';
26
26
  import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
27
+ import useForceUpdate from '../../../../Hooks/useForceUpdate.js';
27
28
  import withAlert from '../../../Hoc/withAlert.js';
28
29
  import withComponent from '../../../Hoc/withComponent.js';
29
30
  import withData from '../../../Hoc/withData.js';
@@ -121,13 +122,16 @@ export const ComboComponent = forwardRef((props, ref) => {
121
122
  setValue,
122
123
  } = props,
123
124
  styles = UiGlobals.styles,
125
+ forceUpdate = useForceUpdate(),
124
126
  inputRef = useRef(),
125
127
  inputCloneRef = useRef(),
126
128
  triggerRef = useRef(),
127
129
  menuRef = useRef(),
128
130
  displayValueRef = useRef(),
129
131
  typingTimeout = useRef(),
130
- [isMenuShown, setIsMenuShown] = useState(false),
132
+ isMenuShown = useRef(false),
133
+ isGridLayoutRunWithRender = useRef(false),
134
+ [isMenuAbove, setIsMenuAbove] = useState(false),
131
135
  [isViewerShown, setIsViewerShown] = useState(false),
132
136
  [viewerSelection, setViewerSelection] = useState([]),
133
137
  [isRendered, setIsRendered] = useState(false),
@@ -140,15 +144,39 @@ export const ComboComponent = forwardRef((props, ref) => {
140
144
  [newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
141
145
  [filteredData, setFilteredData] = useState(data),
142
146
  [inputHeight, setInputHeight] = useState(0),
147
+ [menuRenderedHeight, setMenuRenderedHeight] = useState(0),
143
148
  [width, setWidth] = useState(0),
144
149
  [top, setTop] = useState(0),
145
150
  [left, setLeft] = useState(0),
151
+ getIsMenuShown = () => {
152
+ return isMenuShown.current;
153
+ },
154
+ setIsMenuShown = (bool) => {
155
+ isMenuShown.current = bool;
156
+
157
+ if (!bool) {
158
+ // The menu's onLayout runs every time there's a change in its size or position.
159
+ // We're only interested in the *first* time it runs with a rendered height.
160
+ // So if hiding the menu, reset isGridLayoutRunWithRender here and we'll set it to true
161
+ // the first time onLayout runs with a height.
162
+ setIsGridLayoutRunWithRender(false);
163
+ setIsMenuAbove(false); // reset this so the next time the menu opens, it starts below the input
164
+ }
165
+
166
+ forceUpdate();
167
+ },
168
+ getIsGridLayoutRunWithRender = () => {
169
+ return isGridLayoutRunWithRender.current;
170
+ },
171
+ setIsGridLayoutRunWithRender = (bool) => {
172
+ isGridLayoutRunWithRender.current = bool;
173
+ },
146
174
  onLayout = (e) => {
147
175
  setIsRendered(true);
148
176
  setContainerWidth(e.nativeEvent.layout.width);
149
177
  },
150
178
  showMenu = async () => {
151
- if (isMenuShown) {
179
+ if (getIsMenuShown()) {
152
180
  return;
153
181
  }
154
182
  if (CURRENT_MODE === UI_MODE_WEB && inputRef.current?.getBoundingClientRect) {
@@ -181,13 +209,13 @@ export const ComboComponent = forwardRef((props, ref) => {
181
209
  setIsMenuShown(true);
182
210
  },
183
211
  hideMenu = () => {
184
- if (!isMenuShown) {
212
+ if (!getIsMenuShown()) {
185
213
  return;
186
214
  }
187
215
  setIsMenuShown(false);
188
216
  },
189
217
  toggleMenu = () => {
190
- setIsMenuShown(!isMenuShown);
218
+ setIsMenuShown(!getIsMenuShown());
191
219
  },
192
220
  temporarilySetIsNavigatingViaKeyboard = () => {
193
221
  setIsNavigatingViaKeyboard(true);
@@ -359,14 +387,14 @@ export const ComboComponent = forwardRef((props, ref) => {
359
387
  if (reloadOnTrigger && Repository) {
360
388
  await Repository.reload();
361
389
  }
362
- if (isMenuShown) {
390
+ if (getIsMenuShown()) {
363
391
  hideMenu();
364
392
  } else {
365
393
  showMenu();
366
394
  }
367
395
  },
368
396
  onTriggerBlur = (e) => {
369
- if (!isMenuShown) {
397
+ if (!getIsMenuShown()) {
370
398
  return;
371
399
  }
372
400
 
@@ -409,6 +437,33 @@ export const ComboComponent = forwardRef((props, ref) => {
409
437
  onCheckButtonPress = () => {
410
438
  hideMenu();
411
439
  },
440
+ onGridLayout = (e) => {
441
+ // This method is to determine if we need to flip the grid above the input
442
+ // because the menu is partially offscreen
443
+
444
+ if (CURRENT_MODE !== UI_MODE_WEB || !e.nativeEvent.layout.height) {
445
+ return;
446
+ }
447
+
448
+ // we reach this point only if the grid has rendered with a height.
449
+
450
+ if (!getIsGridLayoutRunWithRender()) {
451
+ // we reach this point only on the *first* time onGridLayout runs with a height.
452
+ // determine if the menu is partially offscreen
453
+ const
454
+ menuRect = menuRef.current.getBoundingClientRect(),
455
+ inputRect = inputRef.current.getBoundingClientRect(),
456
+ menuOverflows = menuRect.bottom > window.innerHeight;
457
+ if (menuOverflows) {
458
+ // flip it
459
+ setIsMenuAbove(true);
460
+ } else {
461
+ setIsMenuAbove(false);
462
+ }
463
+ setMenuRenderedHeight(e.nativeEvent.layout.height);
464
+ setIsGridLayoutRunWithRender(true);
465
+ }
466
+ },
412
467
  isEventStillInComponent = (e) => {
413
468
  const {
414
469
  relatedTarget
@@ -518,7 +573,7 @@ export const ComboComponent = forwardRef((props, ref) => {
518
573
  setFilteredData(found);
519
574
  }
520
575
 
521
- if (!isMenuShown) {
576
+ if (!getIsMenuShown()) {
522
577
  showMenu();
523
578
  }
524
579
  setIsSearchMode(true);
@@ -734,7 +789,7 @@ export const ComboComponent = forwardRef((props, ref) => {
734
789
  </Pressable>;
735
790
  }
736
791
 
737
- if (isMenuShown) {
792
+ if (getIsMenuShown()) {
738
793
  const gridProps = _.pick(props, [
739
794
  'Editor',
740
795
  'model',
@@ -743,6 +798,9 @@ export const ComboComponent = forwardRef((props, ref) => {
743
798
  'idIx',
744
799
  'displayIx',
745
800
  // 'value',
801
+ 'disableAdd',
802
+ 'disableEdit',
803
+ 'disableDelete',
746
804
  'disableView',
747
805
  'disableCopy',
748
806
  'disableDuplicate',
@@ -761,8 +819,9 @@ export const ComboComponent = forwardRef((props, ref) => {
761
819
  if (!Repository) {
762
820
  gridProps.data = filteredData;
763
821
  }
764
- const WhichGrid = isEditor ? WindowedGridEditor : Grid;
765
- const gridStyle = {};
822
+ const
823
+ WhichGrid = isEditor ? WindowedGridEditor : Grid,
824
+ gridStyle = {};
766
825
  if (CURRENT_MODE === UI_MODE_WEB) {
767
826
  gridStyle.height = menuHeight || styles.FORM_COMBO_MENU_HEIGHT;
768
827
  }
@@ -786,6 +845,7 @@ export const ComboComponent = forwardRef((props, ref) => {
786
845
  disablePresetButtons={!isEditor}
787
846
  alternateRowBackgrounds={false}
788
847
  showSelectHandle={false}
848
+ onLayout={onGridLayout}
789
849
  onChangeSelection={(selection) => {
790
850
 
791
851
  if (Repository && selection[0]?.isPhantom) {
@@ -938,7 +998,7 @@ export const ComboComponent = forwardRef((props, ref) => {
938
998
  </Box>;
939
999
  }
940
1000
  dropdownMenu = <Popover
941
- isOpen={isMenuShown}
1001
+ isOpen={getIsMenuShown()}
942
1002
  onClose={() => {
943
1003
  hideMenu();
944
1004
  }}
@@ -962,15 +1022,24 @@ export const ComboComponent = forwardRef((props, ref) => {
962
1022
  'max-w-full',
963
1023
  )}
964
1024
  style={{
965
- top,
1025
+ // If flipped, position above input; otherwise, below
1026
+ top: isMenuAbove
1027
+ ? (top - menuRenderedHeight) // above
1028
+ : top, // below
966
1029
  left,
967
1030
  width,
968
- // height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
969
1031
  minWidth: 100,
970
1032
  }}
971
1033
  >
972
- {inputClone}
973
- {grid}
1034
+ {isMenuAbove ?
1035
+ <>
1036
+ {grid}
1037
+ {inputClone}
1038
+ </> :
1039
+ <>
1040
+ {inputClone}
1041
+ {grid}
1042
+ </>}
974
1043
  </Box>
975
1044
  </Popover>;
976
1045
  }
@@ -0,0 +1,13 @@
1
+ import withComponent from '../../Hoc/withComponent.js';
2
+ import withValue from '../../Hoc/withValue.js';
3
+
4
+ const HiddenElement = (props) => {
5
+
6
+ // This component does not render any visible UI elements,
7
+ // but it acts as a hidden input field for form submissions
8
+ // via the withValue HOC.
9
+
10
+ return null;
11
+ };
12
+
13
+ export default withComponent(withValue(HiddenElement));
@@ -21,6 +21,7 @@ export function JsonElement(props) {
21
21
  tooltip = null,
22
22
  isDisabled = false,
23
23
  isViewOnly = false,
24
+ isCollapsed = true,
24
25
  tooltipPlacement = 'bottom',
25
26
  testID,
26
27
 
@@ -59,7 +60,7 @@ export function JsonElement(props) {
59
60
  editable={!isViewOnly}
60
61
  src={src}
61
62
  enableClipboard={false}
62
- collapsed={true}
63
+ collapsed={isCollapsed}
63
64
  onEdit={(obj) => {
64
65
  setValue(JSON.stringify(obj.updated_src));
65
66
  }}
@@ -12,6 +12,8 @@ import {
12
12
  import {
13
13
  EDITOR_TYPE__PLAIN,
14
14
  } from '../../../../Constants/Editor.js';
15
+ import Button from '../../../Buttons/Button.js';
16
+ import testProps from '../../../../Functions/testProps.js';
15
17
  import Form from '../../Form.js';
16
18
  import Viewer from '../../../Viewer/Viewer.js';
17
19
  import withAlert from '../../../Hoc/withAlert.js';
@@ -36,14 +38,13 @@ function TagComponent(props) {
36
38
  Editor,
37
39
  _combo = {},
38
40
  SourceRepository,
41
+ mustSaveBeforeEditingJoinData = false,
39
42
  joinDataConfig,
40
- outerValueId, // for recursion only. See note in useEffect
43
+ getBaseParams, // See note in useEffect
44
+ outerValueId, // See note in useEffect
41
45
  tooltip,
42
46
  testID,
43
- getBaseParams,
44
-
45
- // parent Form
46
- onChangeValue,
47
+ isDirty = false,
47
48
 
48
49
  // withAlert
49
50
  alert,
@@ -77,11 +78,11 @@ function TagComponent(props) {
77
78
  }
78
79
  return null;
79
80
  }),
80
- [isInited, setIsInited] = useState(false),
81
+ [isInited, setIsInited] = useState(_.isUndefined(getBaseParams)), // default to true unless getBaseParams is defined
81
82
  modelFieldStartsWith = hasJoinData ? Inflector.underscore(JoinRepository.getSchema().name) + '__' : '',
82
83
  valueRef = useRef(value),
83
84
  onView = async (item, e) => {
84
- // This method shows the record viewer
85
+ // show the joined record's viewer
85
86
  const
86
87
  id = item.id,
87
88
  repository = TargetRepository;
@@ -142,7 +143,7 @@ function TagComponent(props) {
142
143
  }
143
144
 
144
145
  // The value we get from combo is a simple int
145
- // Convert this to id and displayValue from either Repository or data array.
146
+ // Convert this to { id, text} from either Repository or data array.
146
147
  const
147
148
  data = props.data,
148
149
  idIx = props.idIx,
@@ -171,32 +172,36 @@ function TagComponent(props) {
171
172
 
172
173
  let joinData = {};
173
174
  if (hasJoinData) {
174
- // build up the default starting values,
175
+ // build up the default starting values for joinData,
175
176
  // first with schema defaultValues...
176
177
  const
177
178
  allSchemaDefaults = JoinRepository.getSchema().getDefaultValues(),
178
179
  modelSchemaDefaults = _.pickBy(allSchemaDefaults, (value, key) => {
179
180
  return key.startsWith(modelFieldStartsWith);
180
181
  }),
181
- joinFieldNames = joinDataConfig.map(fieldConfig => fieldConfig.name || fieldConfig),
182
- schemaDefaultValues = _.pick(modelSchemaDefaults, joinFieldNames),
183
- strippedSchemaDefaultValues = _.mapKeys(schemaDefaultValues, (value, key) => {
184
- return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
185
- });
186
-
187
- // then with default values in joinDataConfig, if they exist
188
- _.each(joinDataConfig, (fieldConfig) => {
189
- if (!_.isNil(fieldConfig.defaultValue)) {
190
- joinData[fieldConfig.name] = fieldConfig.defaultValue;
191
- }
182
+ fullFieldNames = propertyDef.joinData.map((fieldName) => { // add the 'model_name__' prefix so we can get schema default values
183
+ return modelFieldStartsWith + fieldName;
184
+ }),
185
+ schemaDefaultValues = _.pick(modelSchemaDefaults, fullFieldNames);
186
+ joinData = _.mapKeys(schemaDefaultValues, (value, key) => { // strip out the 'model_name__' prefix from field names
187
+ return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
192
188
  });
193
- joinData = { ...strippedSchemaDefaultValues, ...joinData };
189
+
190
+ // then override with default values in joinDataConfig, if they exist
191
+ if (joinDataConfig) {
192
+ _.each(Object.keys(joinDataConfig), (fieldName) => {
193
+ const fieldConfig = joinDataConfig[fieldName];
194
+ if (!_.isUndefined(fieldConfig.defaultValue)) { // null in jsonDataConfig will override a default value in schema!
195
+ joinData[fieldName] = fieldConfig.defaultValue;
196
+ }
197
+ });
198
+ }
194
199
  }
195
200
 
196
201
 
197
202
  // add new value
198
203
  const
199
- newValue = [...value], // clone, so we trigger a re-render
204
+ newValue = [...value], // clone Tag's full current value (array), so we trigger a re-render after adding the new value
200
205
  newItem = {
201
206
  id,
202
207
  text: displayValue,
@@ -215,8 +220,8 @@ function TagComponent(props) {
215
220
  });
216
221
  setValue(newValue);
217
222
  },
218
- onJoin = async (item, e) => {
219
- // This method shows the joinData viewer/editor
223
+ onViewEditJoinData = async (item, e) => {
224
+ // show the joinData viewer/editor
220
225
 
221
226
  /* item format:
222
227
  item = {
@@ -229,22 +234,27 @@ function TagComponent(props) {
229
234
  }
230
235
  */
231
236
 
232
- // prepend 'model_name__' to the field names, so they match the JoinRepository property names
237
+ // Prepare Form to edit the joinData
233
238
  const
234
- record = _.mapKeys(item.joinData, (value, key) => {
239
+ // create the Form.record, format: { meters_pm_schedules__also_resets: null, meters_pm_schedules__hide_every_n: 5 }
240
+ record = _.mapKeys(item.joinData, (value, key) => { // add the 'model_name__' prefix so we can match JoinRepository property names
235
241
  return modelFieldStartsWith + key;
236
242
  }),
243
+ // create the Form.items
237
244
  items = propertyDef.joinData.map((fieldName) => {
238
245
  let obj = {
239
246
  name: modelFieldStartsWith + fieldName,
240
247
  };
241
- // add in any config from joinDataConfig for this field
248
+
249
+ // add in any specific config for joinData[fieldName]], if it exists
250
+ // (The outer *Editor can configure each Tag field's joinData Form item.
251
+ // This moves that configuration down and adds outerValueId)
242
252
  if (joinDataConfig?.[fieldName]) {
243
- const jdcf = _.clone(joinDataConfig[fieldName]); // don't mutate original
244
- jdcf.outerValueId = item.id;
253
+ const joinDataConfigFieldname = _.clone(joinDataConfig[fieldName]); // don't mutate original
254
+ joinDataConfigFieldname.outerValueId = item.id; // so that joinData can be aware of the value of the inspected ValueBox; see note in useEffect, below
245
255
  obj = {
246
256
  ...obj,
247
- ...jdcf,
257
+ ...joinDataConfigFieldname,
248
258
  };
249
259
  }
250
260
 
@@ -253,6 +263,7 @@ function TagComponent(props) {
253
263
 
254
264
  let height = 300;
255
265
  let body;
266
+ const extraModalProps = {};
256
267
  if (isViewOnly) {
257
268
  // show Viewer
258
269
  body = <Viewer
@@ -263,12 +274,17 @@ function TagComponent(props) {
263
274
  labelWidth: 200,
264
275
  }}
265
276
  />;
277
+
278
+ extraModalProps.customButtons = [
279
+ <Button
280
+ {...testProps('closeBtn')}
281
+ key="closeBtn"
282
+ onPress={hideModal}
283
+ text="Close"
284
+ className="text-white"
285
+ />,
286
+ ];
266
287
  } else {
267
- switch (items.length) {
268
- case 1: height = 250; break;
269
- case 2: height = 400; break;
270
- default: height = 600; break;
271
- }
272
288
  body = <Form
273
289
  editorType={EDITOR_TYPE__PLAIN}
274
290
  isEditorViewOnly={false}
@@ -285,7 +301,7 @@ function TagComponent(props) {
285
301
  ]}
286
302
  onSave={(values)=> {
287
303
 
288
- // strip the 'model__' prefix from the field names
304
+ // strip the 'model_name__' prefix from the field names
289
305
  values = _.mapKeys(values, (value, key) => {
290
306
  return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
291
307
  });
@@ -303,6 +319,11 @@ function TagComponent(props) {
303
319
  }}
304
320
  />;
305
321
  }
322
+ switch (items.length) {
323
+ case 1: height = 250; break;
324
+ case 2: height = 400; break;
325
+ default: height = 600; break;
326
+ }
306
327
 
307
328
  showModal({
308
329
  title: 'Extra data for "' + item.text + '"',
@@ -312,6 +333,7 @@ function TagComponent(props) {
312
333
  includeReset: false,
313
334
  includeCancel: false,
314
335
  body,
336
+ ...extraModalProps,
315
337
  });
316
338
  },
317
339
  onGridAdd = (selection) => {
@@ -381,54 +403,47 @@ function TagComponent(props) {
381
403
  text={val.text}
382
404
  onView={() => onView(val)}
383
405
  showEye={showEye}
384
- onJoin={() => onJoin(val)}
385
- showJoin={hasJoinData}
406
+ onViewEditJoinData={() => onViewEditJoinData(val)}
407
+ showJoin={hasJoinData && (!mustSaveBeforeEditingJoinData || !isDirty)}
386
408
  onDelete={!isViewOnly ? () => onDelete(val) : null}
387
409
  minimizeForRow={minimizeForRow}
388
410
  />;
389
411
  });
390
412
 
391
- useEffect(() => {
392
-
393
- // NOTE: This useEffect is so we can set the Target baseParams before it loads
394
- // We did this for cases where the Tag field has joinData that's managing a nested Tag field.
395
- // ... This deals with recursion, so gets "alice in wonderland" quickly!
396
- // If that inner Tag field has getBaseParams defined on the joinDataConfig of the outer Tag,
397
- // then that means it needs to set its baseParams dynamically, based on the values that are
398
- // currently set, as well as the value of the outer ValueBox that was clicked on.
399
-
400
- // For example: in the MetersEditor:
401
- // {
402
- // name: 'meters__pm_schedules',
403
- // parent: self,
404
- // reference: 'meters__pm_schedules',
405
- // joinDataConfig: {
406
- // also_resets: {
407
- // getBaseParams: (values, outerValueId) => {
408
- // const
409
- // baseParams = {
410
- // 'conditions[MetersPmSchedules.meter_id]': meter_id, // limit also_resets to those MetersPmSchedules related to this meter
411
- // },
412
- // ids = values.map((value) => value.id),
413
- // mpsValues = JSON.parse(self.children.meters__pm_schedules?.value || '[]');
414
- // if (outerValueId) {
415
- // ids.push(outerValueId);
416
- // }
417
- // if (!_.isEmpty(ids)) {
418
- // baseParams['conditions[MetersPmSchedules.pm_schedule_id NOT IN]'] = ids;
419
- // }
420
- // return baseParams;
421
- // },
422
- // },
423
- // },
424
- // }
425
-
426
-
427
- if (getBaseParams) {
413
+ if (!_.isUndefined(getBaseParams) && outerValueId) {
414
+ useEffect(() => {
415
+
416
+ // NOTE: This useEffect is so we can dynamically set the TargetRepository's baseParams,
417
+ // based on outerValueId, before it loads.
418
+ // We did this for cases where the Tag field has joinData that's managing a nested Tag field.
419
+ // ... This deals with recursion, so gets "alice in wonderland" quickly!
420
+ // If that inner Tag field has getBaseParams defined on a joinDataConfig field of the outer Tag,
421
+ // then that means it needs to set its baseParams dynamically, based on the value of the outer ValueBox.
422
+
423
+ // For example: in the MetersEditor:
424
+ // {
425
+ // name: 'meters__pm_schedules',
426
+ // mustSaveBeforeEditingJoinData: true,
427
+ // joinDataConfig: {
428
+ // also_resets: {
429
+ // getBaseParams: (values, outerValueId) => {
430
+ // const baseParams = {
431
+ // 'conditions[MetersPmSchedules.meter_id]': meter_id, // limit also_resets to those MetersPmSchedules related to this meter
432
+ // };
433
+ // if (outerValueId) {
434
+ // baseParams['conditions[MetersPmSchedules.id <>]'] = outerValueId; // exclude the ValueBox that was clicked on
435
+ // }
436
+ // return baseParams;
437
+ // },
438
+ // },
439
+ // },
440
+ // }
441
+
428
442
  TargetRepository.setBaseParams(getBaseParams(value, outerValueId));
429
- }
430
- setIsInited(true);
431
- }, [value]);
443
+ setIsInited(true);
444
+
445
+ }, [value]);
446
+ }
432
447
 
433
448
  if (!isInited) {
434
449
  return null;
@@ -16,7 +16,7 @@ export default function ValueBox(props) {
16
16
  text,
17
17
  onView,
18
18
  showEye = false,
19
- onJoin,
19
+ onViewEditJoinData,
20
20
  showJoin = false,
21
21
  onDelete,
22
22
  minimizeForRow = false,
@@ -60,7 +60,7 @@ export default function ValueBox(props) {
60
60
  size: styles.FORM_TAG_VALUEBOX_ICON_SIZE,
61
61
  className: 'text-grey-600',
62
62
  }}
63
- onPress={onJoin}
63
+ onPress={onViewEditJoinData}
64
64
  className={clsx(
65
65
  'ValueBox-joinBtn',
66
66
  'h-full',
@@ -205,7 +205,7 @@ function Form(props) {
205
205
 
206
206
  // Fallback to empty schema that allows any fields and defaults to valid
207
207
  return yup.object().noUnknown(false).default({});
208
- })(),
208
+ })() || yup.object().shape({}), // on rare occasions, validatorToUse was null. This fixes it
209
209
  {
210
210
  control,
211
211
  formState,
@@ -816,6 +816,7 @@ function Form(props) {
816
816
  {...testProps('field-' + name)}
817
817
  name={name}
818
818
  value={value}
819
+ isDirty={isDirty}
819
820
  onChangeValue={(newValue) => {
820
821
  if (newValue === undefined) {
821
822
  newValue = null; // React Hook Form doesn't respond well when setting value to undefined
@@ -1548,7 +1549,7 @@ function Form(props) {
1548
1549
  function disableRequiredYupFields(validator) {
1549
1550
  // based on https://github.com/jquense/yup/issues/1466#issuecomment-944386480
1550
1551
  if (!validator) {
1551
- return null;
1552
+ return yup.object().shape({}); // Return valid empty schema instead of null
1552
1553
  }
1553
1554
 
1554
1555
  const nextSchema = validator.clone();