@onehat/ui 0.4.80 → 0.4.82

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.80",
3
+ "version": "0.4.82",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -78,10 +78,10 @@ function Container(props) {
78
78
  id = props.id || props.self?.path,
79
79
  canResize = CURRENT_MODE === UI_MODE_WEB,
80
80
  [isReady, setIsReady] = useState(false),
81
- [localIsNorthCollapsed, setLocalIsNorthCollapsedRaw] = useState(north ? north.props.startsCollapsed : false),
82
- [localIsSouthCollapsed, setLocalIsSouthCollapsedRaw] = useState(south ? south.props.startsCollapsed : false),
83
- [localIsEastCollapsed, setLocalIsEastCollapsedRaw] = useState(east ? east.props.startsCollapsed : false),
84
- [localIsWestCollapsed, setLocalIsWestCollapsedRaw] = useState(west ? west.props.startsCollapsed : false),
81
+ [localIsNorthCollapsed, setLocalIsNorthCollapsedRaw] = useState(north ? !!north.props.startsCollapsed : false),
82
+ [localIsSouthCollapsed, setLocalIsSouthCollapsedRaw] = useState(south ? !!south.props.startsCollapsed : false),
83
+ [localIsEastCollapsed, setLocalIsEastCollapsedRaw] = useState(east ? !!east.props.startsCollapsed : false),
84
+ [localIsWestCollapsed, setLocalIsWestCollapsedRaw] = useState(west ? !!west.props.startsCollapsed : false),
85
85
  [northHeight, setNorthHeightRaw] = useState(north ? north.props.h : 0),
86
86
  [southHeight, setSouthHeightRaw] = useState(south ? south.props.h : 0),
87
87
  [eastWidth, setEastWidthRaw] = useState(east ? east.props.w : 0),
@@ -773,6 +773,9 @@ export const ComboComponent = forwardRef((props, ref) => {
773
773
  if (CURRENT_MODE === UI_MODE_NATIVE) {
774
774
  gridClassName += ' h-[400px] max-h-[100%]';
775
775
  }
776
+ if (gridProps.className) {
777
+ gridClassName += ' ' + gridProps.className;
778
+ }
776
779
  grid = <WhichGrid
777
780
  showHeaders={false}
778
781
  showHovers={true}
@@ -886,9 +889,9 @@ export const ComboComponent = forwardRef((props, ref) => {
886
889
  }}
887
890
  reference="grid"
888
891
  parent={self}
889
- className={gridClassName}
890
892
  style={gridStyle}
891
893
  {...gridProps}
894
+ className={gridClassName}
892
895
  {..._editor}
893
896
  />;
894
897
  if (CURRENT_MODE === UI_MODE_WEB) {
@@ -962,7 +965,7 @@ export const ComboComponent = forwardRef((props, ref) => {
962
965
  top,
963
966
  left,
964
967
  width,
965
- height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
968
+ // height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
966
969
  minWidth: 100,
967
970
  }}
968
971
  >
@@ -22,6 +22,7 @@ function MeterTypesCombo(props) {
22
22
  reference="MeterTypeCombo"
23
23
  data={data}
24
24
  disableDirectEntry={true}
25
+ menuHeight={100}
25
26
  {...props}
26
27
  />;
27
28
  }
@@ -601,7 +601,7 @@ export const DateElement = forwardRef((props, ref) => {
601
601
  styles.FORM_DATE_CLASSNAME,
602
602
  );
603
603
  if (props.className) {
604
- className += props.className;
604
+ className += ' ' + props.className;
605
605
  }
606
606
  if (minimizeForRow) {
607
607
  className += ' h-auto min-h-0 max-h-[50px]';
@@ -12,7 +12,11 @@ export default function PageSizeSelect(props) {
12
12
  } = props;
13
13
 
14
14
  return useMemo(() => {
15
- return <HStack className="PageSizeSelect-HStack w-[70px]">
15
+ return <HStack
16
+ className={clsx(
17
+ 'PageSizeSelect-HStack',
18
+ )}
19
+ >
16
20
  <Select
17
21
  data={[
18
22
  // [ 1, '1', ],
@@ -26,6 +30,7 @@ export default function PageSizeSelect(props) {
26
30
  onChangeValue={(value) => Repository.setPageSize(value)}
27
31
  tooltip="Page Size"
28
32
  tooltipClassName="w-[70px]"
33
+ fixedWidth={false}
29
34
  />
30
35
  </HStack>;
31
36
  }, [
@@ -1,6 +1,7 @@
1
1
  import { forwardRef, useState, useEffect, useRef, } from 'react';
2
2
  import {
3
3
  Select, SelectBackdrop, SelectContent, SelectDragIndicator, SelectDragIndicatorWrapper, SelectInput, SelectIcon, SelectItem, SelectPortal, SelectTrigger,
4
+ Text,
4
5
  } from '@project-components/Gluestack';
5
6
  import clsx from 'clsx';
6
7
  import {
@@ -15,54 +16,22 @@ import CaretDown from '../../../Icons/CaretDown.js';
15
16
  import _ from 'lodash';
16
17
 
17
18
  const SelectElement = forwardRef((props, ref) => {
18
- let { // so localValue can be changed, if needed
19
+ const {
19
20
  data = [], // in format [ [ value, label, ], ... ]
20
21
  value,
21
22
  setValue,
22
23
  onKeyPress,
23
24
  placeholder,
24
25
  disableAutoFlex = false,
26
+ fixedWidth = true,
25
27
  ...propsToPass
26
28
  } = props,
27
29
  styles = UiGlobals.styles,
28
- // debouncedSetValueRef = useRef(),
29
- // [localValue, setLocalValue] = useState(value),
30
- // onKeyPressLocal = (e) => {
31
- // if (e.key === 'Enter') {
32
- // debouncedSetValueRef.current?.cancel();
33
- // setValue(localValue);
34
- // }
35
- // if (onKeyPress) {
36
- // onKeyPress(e, localValue);
37
- // }
38
- // },
30
+ style = props.style || {},
39
31
  items = data.map(([ value, label, ], key) => {
40
32
  return <SelectItem key={key} label={label} value={value} />;
41
33
  });
42
34
 
43
- // useEffect(() => {
44
-
45
- // // Set up debounce fn
46
- // // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
47
- // debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
48
- // debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
49
-
50
- // }, [setValue]);
51
-
52
- // useEffect(() => {
53
-
54
- // if (value !== localValue) {
55
- // // Make local value conform to externally changed value
56
- // setLocalValue(value);
57
- // }
58
-
59
- // }, [value]);
60
-
61
- // if (localValue === null || typeof localValue === 'undefined') {
62
- // localValue = ''; // If the value is null or undefined, don't let this be an uncontrolled select
63
- // }
64
-
65
- const style = props.style || {};
66
35
  // auto-set width to flex if it's not already set another way
67
36
  if (!disableAutoFlex && !hasWidth(props) && !hasFlex(props)) {
68
37
  style.flex = 1;
@@ -70,13 +39,12 @@ const SelectElement = forwardRef((props, ref) => {
70
39
  let className = clsx(
71
40
  'Select',
72
41
  'min-h-[40px]',
73
- 'w-full',
74
42
  'text-left',
75
43
  'rounded-lg',
76
44
  styles.FORM_SELECT_CLASSNAME,
77
45
  );
78
46
  if (props.className) {
79
- className += props.className;
47
+ className += ' ' + props.className;
80
48
  }
81
49
 
82
50
  return <Select
@@ -89,7 +57,15 @@ const SelectElement = forwardRef((props, ref) => {
89
57
  style={style}
90
58
  >
91
59
  <SelectTrigger variant="outline" size="md" className="SelectTrigger" >
92
- <SelectInput placeholder={placeholder} className="SelectInput" />
60
+ {fixedWidth ?
61
+ <SelectInput
62
+ placeholder={placeholder}
63
+ className={clsx(
64
+ 'SelectInput',
65
+ )}
66
+ /> :
67
+ <Text className="SelectText p-2">{value}</Text>}
68
+
93
69
  <SelectIcon className="mr-3" as={CaretDown} />
94
70
  </SelectTrigger>
95
71
  <SelectPortal className="SelectPortal">
@@ -1,18 +1,26 @@
1
- import { useRef, } from 'react';
1
+ import { useRef, useState, useEffect, } from 'react';
2
2
  import {
3
3
  HStack,
4
4
  VStackNative,
5
5
  } from '@project-components/Gluestack';
6
6
  import clsx from 'clsx';
7
+ import * as yup from 'yup'; // https://github.com/jquense/yup#string
8
+ import oneHatData from '@onehat/data';
7
9
  import {
8
10
  EDITOR_TYPE__WINDOWED,
9
11
  } from '../../../../Constants/Editor.js';
12
+ import {
13
+ EDITOR_TYPE__PLAIN,
14
+ } from '../../../../Constants/Editor.js';
15
+ import Form from '../../Form.js';
16
+ import Viewer from '../../../Viewer/Viewer.js';
10
17
  import withAlert from '../../../Hoc/withAlert.js';
11
18
  import withComponent from '../../../Hoc/withComponent.js';
12
19
  import withData from '../../../Hoc/withData.js';
13
20
  import withModal from '../../../Hoc/withModal.js';
14
21
  import withValue from '../../../Hoc/withValue.js';
15
22
  import ValueBox from './ValueBox.js';
23
+ import Inflector from 'inflector-js';
16
24
  import Combo, { ComboEditor } from '../Combo/Combo.js';
17
25
  import UiGlobals from '../../../../UiGlobals.js';
18
26
  import _ from 'lodash';
@@ -27,8 +35,12 @@ function TagComponent(props) {
27
35
  minimizeForRow = false,
28
36
  Editor,
29
37
  _combo = {},
38
+ SourceRepository,
39
+ joinDataConfig,
40
+ outerValueId, // for recursion only. See note in useEffect
30
41
  tooltip,
31
42
  testID,
43
+ getBaseParams,
32
44
 
33
45
  // parent Form
34
46
  onChangeValue,
@@ -39,6 +51,10 @@ function TagComponent(props) {
39
51
  // withComponent
40
52
  self,
41
53
 
54
+ // withData
55
+ Repository: TargetRepository,
56
+ setBaseParams,
57
+
42
58
  // withFilters
43
59
  isInFilter,
44
60
 
@@ -53,11 +69,22 @@ function TagComponent(props) {
53
69
  ...propsToPass // break connection between Tag and Combo props
54
70
  } = props,
55
71
  styles = UiGlobals.styles,
72
+ propertyDef = SourceRepository?.getSchema().getPropertyDefinition(self.reference),
73
+ hasJoinData = propertyDef?.joinData?.length,
74
+ [JoinRepository] = useState(() => {
75
+ if (hasJoinData) {
76
+ return oneHatData.getRepository(propertyDef.joinModel, true);
77
+ }
78
+ return null;
79
+ }),
80
+ [isInited, setIsInited] = useState(false),
81
+ modelFieldStartsWith = hasJoinData ? Inflector.underscore(JoinRepository.getSchema().name) + '__' : '',
56
82
  valueRef = useRef(value),
57
83
  onView = async (item, e) => {
84
+ // This method shows the record viewer
58
85
  const
59
86
  id = item.id,
60
- repository = propsToPass.Repository;
87
+ repository = TargetRepository;
61
88
  if (repository.isLoading) {
62
89
  await repository.waitUntilDoneLoading();
63
90
  }
@@ -117,7 +144,6 @@ function TagComponent(props) {
117
144
  // The value we get from combo is a simple int
118
145
  // Convert this to id and displayValue from either Repository or data array.
119
146
  const
120
- Repository = props.Repository,
121
147
  data = props.data,
122
148
  idIx = props.idIx,
123
149
  displayIx = props.displayIx,
@@ -127,9 +153,9 @@ function TagComponent(props) {
127
153
 
128
154
  if (!id) {
129
155
  displayValue = '';
130
- } else if (Repository) {
131
- if (!Repository.isDestroyed) {
132
- item = Repository.getById(id);
156
+ } else if (TargetRepository) {
157
+ if (!TargetRepository.isDestroyed) {
158
+ item = TargetRepository.getById(id);
133
159
  if (!item) {
134
160
  throw Error('item not found');
135
161
  }
@@ -143,13 +169,42 @@ function TagComponent(props) {
143
169
  displayValue = item[displayIx];
144
170
  }
145
171
 
172
+ let joinData = {};
173
+ if (hasJoinData) {
174
+ // build up the default starting values,
175
+ // first with schema defaultValues...
176
+ const
177
+ allSchemaDefaults = JoinRepository.getSchema().getDefaultValues(),
178
+ modelSchemaDefaults = _.pickBy(allSchemaDefaults, (value, key) => {
179
+ return key.startsWith(modelFieldStartsWith);
180
+ }),
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
+ }
192
+ });
193
+ joinData = { ...strippedSchemaDefaultValues, ...joinData };
194
+ }
195
+
146
196
 
147
197
  // add new value
148
- const newValue = [...value]; // clone, so we trigger a re-render
149
- newValue.push({
150
- id,
151
- text: displayValue,
152
- })
198
+ const
199
+ newValue = [...value], // clone, so we trigger a re-render
200
+ newItem = {
201
+ id,
202
+ text: displayValue,
203
+ };
204
+ if (hasJoinData) {
205
+ newItem.joinData = joinData;
206
+ }
207
+ newValue.push(newItem);
153
208
  setValue(newValue);
154
209
  clearComboValue();
155
210
  },
@@ -160,6 +215,105 @@ function TagComponent(props) {
160
215
  });
161
216
  setValue(newValue);
162
217
  },
218
+ onJoin = async (item, e) => {
219
+ // This method shows the joinData viewer/editor
220
+
221
+ /* item format:
222
+ item = {
223
+ id: 3,
224
+ text: "1000HR PM",
225
+ joinData: {
226
+ hide_every_n: 0,
227
+ also_resets: '[]',
228
+ },
229
+ }
230
+ */
231
+
232
+ // prepend 'model_name__' to the field names, so they match the JoinRepository property names
233
+ const
234
+ record = _.mapKeys(item.joinData, (value, key) => {
235
+ return modelFieldStartsWith + key;
236
+ }),
237
+ items = propertyDef.joinData.map((fieldName) => {
238
+ let obj = {
239
+ name: modelFieldStartsWith + fieldName,
240
+ };
241
+ // add in any config from joinDataConfig for this field
242
+ if (joinDataConfig?.[fieldName]) {
243
+ const jdcf = _.clone(joinDataConfig[fieldName]); // don't mutate original
244
+ jdcf.outerValueId = item.id;
245
+ obj = {
246
+ ...obj,
247
+ ...jdcf,
248
+ };
249
+ }
250
+
251
+ return obj;
252
+ });
253
+
254
+ let height = 300;
255
+ let body;
256
+ if (isViewOnly) {
257
+ // show Viewer
258
+ body = <Viewer
259
+ record={record}
260
+ Repository={JoinRepository}
261
+ items={items}
262
+ columnDefaults={{
263
+ labelWidth: 200,
264
+ }}
265
+ />;
266
+ } else {
267
+ switch (items.length) {
268
+ case 1: height = 250; break;
269
+ case 2: height = 400; break;
270
+ default: height = 600; break;
271
+ }
272
+ body = <Form
273
+ editorType={EDITOR_TYPE__PLAIN}
274
+ isEditorViewOnly={false}
275
+ record={record}
276
+ Repository={JoinRepository}
277
+ items={items}
278
+ additionalFooterButtons={[
279
+ {
280
+ text: 'Cancel',
281
+ onPress: hideModal,
282
+ skipSubmit: true,
283
+ variant: 'outline',
284
+ }
285
+ ]}
286
+ onSave={(values)=> {
287
+
288
+ // strip the 'model__' prefix from the field names
289
+ values = _.mapKeys(values, (value, key) => {
290
+ return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
291
+ });
292
+
293
+ // Put these values back on joinData
294
+ item.joinData = values;
295
+ const newValue = [...valueRef.current]; // clone
296
+ const ix = _.findIndex(newValue, (val) => {
297
+ return val.id === item.id;
298
+ });
299
+ newValue[ix] = item;
300
+ setValue(newValue);
301
+
302
+ hideModal();
303
+ }}
304
+ />;
305
+ }
306
+
307
+ showModal({
308
+ title: 'Extra data for "' + item.text + '"',
309
+ w: 400,
310
+ h: height,
311
+ canClose: true,
312
+ includeReset: false,
313
+ includeCancel: false,
314
+ body,
315
+ });
316
+ },
163
317
  onGridAdd = (selection) => {
164
318
  // underlying GridEditor added a record.
165
319
  // add it to this Tag's value
@@ -226,12 +380,60 @@ function TagComponent(props) {
226
380
  key={ix}
227
381
  text={val.text}
228
382
  onView={() => onView(val)}
229
- onDelete={!isViewOnly ? () => onDelete(val) : null}
230
383
  showEye={showEye}
384
+ onJoin={() => onJoin(val)}
385
+ showJoin={hasJoinData}
386
+ onDelete={!isViewOnly ? () => onDelete(val) : null}
231
387
  minimizeForRow={minimizeForRow}
232
388
  />;
233
389
  });
234
390
 
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) {
428
+ TargetRepository.setBaseParams(getBaseParams(value, outerValueId));
429
+ }
430
+ setIsInited(true);
431
+ }, [value]);
432
+
433
+ if (!isInited) {
434
+ return null;
435
+ }
436
+
235
437
  valueRef.current = value; // the onGrid* methods were dealing with stale data, so use a ref, and update it here
236
438
 
237
439
  let WhichCombo = Combo;
@@ -309,7 +511,7 @@ function TagComponent(props) {
309
511
 
310
512
  {!isViewOnly &&
311
513
  <WhichCombo
312
- Repository={props.Repository}
514
+ Repository={TargetRepository}
313
515
  Editor={props.Editor}
314
516
  onSubmit={onChangeComboValue}
315
517
  parent={self}
@@ -6,6 +6,7 @@ import clsx from 'clsx';
6
6
  import testProps from '../../../../Functions/testProps.js';
7
7
  import IconButton from '../../../Buttons/IconButton.js';
8
8
  import Eye from '../../../Icons/Eye.js';
9
+ import Edit from '../../../Icons/Edit.js';
9
10
  import Xmark from '../../../Icons/Xmark.js';
10
11
  import UiGlobals from '../../../../UiGlobals.js';
11
12
  import _ from 'lodash';
@@ -14,8 +15,10 @@ export default function ValueBox(props) {
14
15
  const {
15
16
  text,
16
17
  onView,
18
+ showEye = false,
19
+ onJoin,
20
+ showJoin = false,
17
21
  onDelete,
18
- showEye,
19
22
  minimizeForRow = false,
20
23
  } = props,
21
24
  styles = UiGlobals.styles;
@@ -49,6 +52,22 @@ export default function ValueBox(props) {
49
52
  styles.FORM_TAG_BTN_CLASSNAME,
50
53
  )}
51
54
  />}
55
+ {showJoin &&
56
+ <IconButton
57
+ {...testProps('joinBtn')}
58
+ icon={Edit}
59
+ _icon={{
60
+ size: styles.FORM_TAG_VALUEBOX_ICON_SIZE,
61
+ className: 'text-grey-600',
62
+ }}
63
+ onPress={onJoin}
64
+ className={clsx(
65
+ 'ValueBox-joinBtn',
66
+ 'h-full',
67
+ minimizeForRow ? 'py-0' : '',
68
+ styles.FORM_TAG_BTN_CLASSNAME,
69
+ )}
70
+ />}
52
71
  <Text
53
72
  className={clsx(
54
73
  'ValueBox-Text',
@@ -183,7 +183,7 @@ function Form(props) {
183
183
  pointerEvents: fabOpacity.value > 0 ? 'auto' : 'none', // Disable interaction when invisible
184
184
  };
185
185
  }),
186
- initialValues = _.merge(startingValues, (record && !record.isDestroyed ? record.submitValues : {})),
186
+ initialValues = _.merge(startingValues, ((record && !record.isDestroyed) ? (record.submitValues || record) : {})),
187
187
  defaultValues = isMultiple ? getNullFieldValues(initialValues, Repository) : initialValues, // when multiple entities, set all default values to null
188
188
  validatorToUse = (() => {
189
189
  // If a custom validator is provided, use it
@@ -440,6 +440,7 @@ function Form(props) {
440
440
  }
441
441
  if (type.match(/Tag/)) {
442
442
  elementClassName += ' overflow-auto';
443
+ configPropsToPass.SourceRepository = Repository;
443
444
  }
444
445
  if (!type.match(/Toggle/)) {
445
446
  elementClassName += ' h-full';
@@ -540,8 +541,8 @@ function Form(props) {
540
541
  if (isHidden) {
541
542
  return null;
542
543
  }
543
- if (type === 'DisplayField') {
544
- isEditable = false;
544
+ if (type === 'DisplayField' || type?.match(/Grid/)) {
545
+ isEditable = false; // this merely disables the FormController for this element
545
546
  }
546
547
  if (!itemPropsToPass.className) {
547
548
  itemPropsToPass.className = '';
@@ -667,16 +668,19 @@ function Form(props) {
667
668
  if (isEditorViewOnly || !isEditable) {
668
669
  let value = null;
669
670
  if (isSingle) {
670
- value = record?.properties[name]?.displayValue || null;
671
- if (_.isNil(value) && record && record[name]) {
671
+ value = record?.properties?.[name]?.displayValue || null;
672
+ if (_.isNil(value) && !_.isNil(record?.[name])) {
672
673
  value = record[name];
673
674
  }
674
- if (_.isNil(value) && startingValues && startingValues[name]) {
675
+ if (_.isNil(value) && !_.isNil(startingValues?.[name])) {
675
676
  value = startingValues[name];
676
677
  }
677
678
  }
679
+ if (type.match(/Tag/)) {
680
+ itemPropsToPass.SourceRepository = Repository;
681
+ }
678
682
 
679
- let elementClassName = 'field-' + name;
683
+ let elementClassName = name ? 'field-' + name : '';
680
684
  const defaultsClassName = defaults.className;
681
685
  if (defaultsClassName) {
682
686
  elementClassName += ' ' + defaultsClassName;
@@ -689,7 +693,6 @@ function Form(props) {
689
693
  if (viewerTypeClassName) {
690
694
  elementClassName += ' ' + viewerTypeClassName;
691
695
  }
692
-
693
696
  let element = <Element
694
697
  {...testProps('field-' + name)}
695
698
  value={value}
@@ -787,6 +790,9 @@ function Form(props) {
787
790
  if (getDynamicProps) {
788
791
  dynamicProps = getDynamicProps({ fieldState, formSetValue, formGetValues, formState });
789
792
  }
793
+ if (type.match(/Tag/)) {
794
+ itemPropsToPass.SourceRepository = Repository;
795
+ }
790
796
 
791
797
  let elementClassName = 'Form-Element field-' + name + ' w-full';
792
798
  const defaultsClassName = defaults.className;
@@ -886,7 +892,8 @@ function Form(props) {
886
892
  >*</Text>;
887
893
  }
888
894
  }
889
- if (!disableLabels && label && editorType !== EDITOR_TYPE__INLINE) {
895
+ const labelToUse = dynamicProps.label || label;
896
+ if (!disableLabels && labelToUse && editorType !== EDITOR_TYPE__INLINE) {
890
897
  const style = {};
891
898
  if (defaults?.labelWidth) {
892
899
  style.width = defaults.labelWidth;
@@ -901,7 +908,7 @@ function Form(props) {
901
908
  element = <HStack className="Form-HStack8 w-full">
902
909
  <Label style={style}>
903
910
  {requiredIndicator}
904
- {label}
911
+ {labelToUse}
905
912
  </Label>
906
913
  {element}
907
914
  </HStack>;
@@ -909,7 +916,7 @@ function Form(props) {
909
916
  element = <VStack className="Form-VStack9 w-full mt-3">
910
917
  <Label style={style}>
911
918
  {requiredIndicator}
912
- {label}
919
+ {labelToUse}
913
920
  </Label>
914
921
  {element}
915
922
  </VStack>;
@@ -1485,8 +1492,13 @@ function Form(props) {
1485
1492
 
1486
1493
  } // END if (containerWidth)
1487
1494
 
1488
- let className = props.className || '';
1489
- className += ' Form-VStackNative';
1495
+ let className = clsx(
1496
+ 'Form-VStackNative',
1497
+ '[transform:translateZ(0)]', // so embedded FAB will be relative to this container, not to viewport
1498
+ );
1499
+ if (props.className) {
1500
+ className += ' ' + props.className;
1501
+ }
1490
1502
  const scrollToTopAnchor = <Box ref={(el) => (ancillaryItemsRef.current[0] = el)} className="h-0" />;
1491
1503
  return <VStackNative
1492
1504
  ref={formRef}