@onehat/ui 0.3.273 → 0.3.276

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.3.273",
3
+ "version": "0.3.276",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "@gluestack-ui/themed": "^1.1.26",
31
31
  "@hookform/resolvers": "^3.3.1",
32
32
  "@k-renwick/colour-mixer": "^1.2.1",
33
- "@onehat/data": "^1.20.0",
33
+ "@onehat/data": "^1.21.0",
34
34
  "@reduxjs/toolkit": "^1.9.5",
35
35
  "inflector-js": "^1.0.1",
36
36
  "js-cookie": "^3.0.5",
@@ -29,6 +29,7 @@ const
29
29
  useEffect(() => {
30
30
  // Set up debounce fn
31
31
  // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
32
+ debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
32
33
  debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
33
34
  }, [setValue]);
34
35
 
@@ -58,6 +58,7 @@ export function ComboComponent(props) {
58
58
  onGridSave, // to hook into when menu saves (ComboEditor only)
59
59
  onGridDelete, // to hook into when menu deletes (ComboEditor only)
60
60
  newEntityDisplayProperty,
61
+ testID,
61
62
 
62
63
  // withComponent
63
64
  self,
@@ -236,7 +237,7 @@ export function ComboComponent(props) {
236
237
  }
237
238
 
238
239
  if (_.isEmpty(gridSelection)) {
239
- confirm('You have nothing selected in the dropdown menu. Clear value?', doIt, true);
240
+ hideMenu();
240
241
  return;
241
242
  }
242
243
 
@@ -303,7 +304,11 @@ export function ComboComponent(props) {
303
304
  return;
304
305
  }
305
306
  clearGridFilters();
306
- showMenu();
307
+ if (isMenuShown) {
308
+ hideMenu();
309
+ } else {
310
+ showMenu();
311
+ }
307
312
  },
308
313
  onTriggerBlur = (e) => {
309
314
  if (!isMenuShown) {
@@ -361,7 +366,7 @@ export function ComboComponent(props) {
361
366
  menuRef.current === relatedTarget ||
362
367
  menuRef.current?.contains(relatedTarget);
363
368
  },
364
- getFilterName = () => {
369
+ getFilterName = (isId) => {
365
370
  // Only used for remote repositories
366
371
  // Gets the filter name of the query, which becomes the condition sent to server
367
372
  let filterName = FILTER_NAME;
@@ -372,7 +377,10 @@ export function ComboComponent(props) {
372
377
  displayFieldDef = schema.getPropertyDefinition(displayFieldName);
373
378
 
374
379
  // Verify displayField is a real field
375
- if (!displayFieldDef.isVirtual) {
380
+ if (isId) {
381
+ const idFieldName = schema.model.idProperty;
382
+ filterName = idFieldName;
383
+ } else if (!displayFieldDef.isVirtual) {
376
384
  filterName = displayFieldName + ' LIKE';
377
385
  }
378
386
  }
@@ -418,10 +426,13 @@ export function ComboComponent(props) {
418
426
  }
419
427
 
420
428
  // Set filter
421
- const filterName = getFilterName();
429
+ const
430
+ idRegex = /^id:(.*)$/,
431
+ isId = _.isString(value) && !!value.match(idRegex),
432
+ filterName = getFilterName(isId);
422
433
  if (Repository.isRemote) {
423
434
  // remote
424
- const filterValue = _.isEmpty(value) ? null : value + '%';
435
+ const filterValue = _.isEmpty(value) ? null : (isId ? value.match(idRegex)[1] : value + '%');
425
436
  await Repository.filter(filterName, filterValue);
426
437
  if (!Repository.isAutoLoad) {
427
438
  await Repository.reload();
@@ -958,7 +969,7 @@ export function ComboComponent(props) {
958
969
  if (isRendered && additionalButtons?.length && containerWidth < 500) {
959
970
  // be responsive for small screen sizes and bump additionalButtons to the next line
960
971
  assembledComponents =
961
- <Column>
972
+ <Column testID={testID}>
962
973
  <Row {...refProps} justifyContent="center" alignItems="center" flex={1} h="100%">
963
974
  {xButton}
964
975
  {eyeButton}
@@ -971,7 +982,7 @@ export function ComboComponent(props) {
971
982
  </Column>;
972
983
  } else {
973
984
  assembledComponents =
974
- <Row {...refProps} justifyContent="center" alignItems="center" flex={1} h="100%" onLayout={onLayout}>
985
+ <Row testID={testID} {...refProps} justifyContent="center" alignItems="center" flex={1} h="100%" onLayout={onLayout}>
975
986
  {xButton}
976
987
  {eyeButton}
977
988
  {inputAndTrigger}
@@ -50,10 +50,15 @@ export function DateElement(props) {
50
50
  isDisabled = false,
51
51
  tooltipPlacement = 'bottom',
52
52
  placeholder = 'Choose a date.',
53
+ testID,
54
+
55
+ // withComponent
56
+ self,
53
57
 
54
58
  // withValue
55
59
  value,
56
60
  setValue,
61
+ ...propsToPass
57
62
  } = props,
58
63
  styles = UiGlobals.styles,
59
64
  Datetime = getComponentFromType('Datetime'),
@@ -97,9 +102,6 @@ export function DateElement(props) {
97
102
  return value;
98
103
  },
99
104
  showPicker = () => {
100
- if (isPickerShown) {
101
- return;
102
- }
103
105
  if (UiGlobals.mode === UI_MODE_WEB && triggerRef.current?.getBoundingClientRect) {
104
106
  // For web, ensure it's in the proper place
105
107
  const
@@ -124,9 +126,6 @@ export function DateElement(props) {
124
126
  setIsPickerShown(true);
125
127
  },
126
128
  hidePicker = () => {
127
- if (!isPickerShown) {
128
- return;
129
- }
130
129
  setIsPickerShown(false);
131
130
  },
132
131
  togglePicker = () => {
@@ -165,7 +164,7 @@ export function DateElement(props) {
165
164
  }
166
165
  showPicker();
167
166
  },
168
- onInputChangeText = (value) => {
167
+ onInputChangeValue = (value) => {
169
168
  if (disableDirectEntry) {
170
169
  return;
171
170
  }
@@ -174,6 +173,7 @@ export function DateElement(props) {
174
173
  setTextInputValue('');
175
174
  return;
176
175
  }
176
+
177
177
  value = formatByMode(value);
178
178
 
179
179
  if (value !== 'Invalid date') {
@@ -358,11 +358,11 @@ export function DateElement(props) {
358
358
  >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
359
359
  </Pressable> :
360
360
  <Input
361
- {...testProps('input')}
361
+ testID={testID}
362
362
  ref={inputRef}
363
363
  value={textInputValue}
364
364
  // setValue={onInputSetValue}
365
- onChangeValue={onInputChangeText}
365
+ onChangeValue={onInputChangeValue}
366
366
  onKeyPress={onInputKeyPress}
367
367
  onBlur={onInputBlur}
368
368
  onFocus={onInputFocus}
@@ -559,7 +559,7 @@ export function DateElement(props) {
559
559
  value={textInputValue}
560
560
  autoSubmit={true}
561
561
  isDisabled={isDisabled}
562
- onChangeValue={onInputChangeText}
562
+ onChangeValue={onInputChangeValue}
563
563
  onKeyPress={onInputKeyPress}
564
564
  onFocus={onInputFocus}
565
565
  onBlur={onInputBlur}
@@ -619,7 +619,7 @@ export function DateElement(props) {
619
619
  if (tooltipRef) {
620
620
  refProps.ref = tooltipRef;
621
621
  }
622
- assembledComponents = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
622
+ assembledComponents = <Row {...refProps} {...propsToPass} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
623
623
  {xButton}
624
624
  {inputAndTrigger}
625
625
  {additionalButtons}
@@ -20,12 +20,14 @@ function InputElement(props) {
20
20
  onChangeText,
21
21
  tooltip = null,
22
22
  tooltipPlacement = 'bottom',
23
+ self,
23
24
  } = props,
24
25
  styles = UiGlobals.styles,
25
26
  debouncedSetValueRef = useRef(),
26
27
  [localValue, setLocalValue] = useState(value),
27
28
  onKeyPressLocal = (e) => {
28
29
  if (e.key === 'Enter') {
30
+ debouncedSetValueRef.current?.cancel();
29
31
  setValue(localValue);
30
32
  }
31
33
  if (onKeyPress) {
@@ -48,15 +50,20 @@ function InputElement(props) {
48
50
  };
49
51
 
50
52
  useEffect(() => {
53
+
51
54
  // Set up debounce fn
52
55
  // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
56
+ debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
53
57
  debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
58
+
54
59
  }, [setValue]);
55
60
 
56
61
  useEffect(() => {
57
62
 
58
- // Make local value conform to externally changed value
59
- setLocalValue(value);
63
+ if (value !== localValue) {
64
+ // Make local value conform to externally changed value
65
+ setLocalValue(value);
66
+ }
60
67
 
61
68
  }, [value]);
62
69
 
@@ -26,6 +26,8 @@ function NumberElement(props) {
26
26
  autoSubmitDelay = UiGlobals.autoSubmitDelay,
27
27
  tooltip = null,
28
28
  isDisabled = false,
29
+ testID,
30
+ ...propsToPass
29
31
  } = props,
30
32
  styles = UiGlobals.styles,
31
33
  debouncedSetValueRef = useRef(),
@@ -40,6 +42,7 @@ function NumberElement(props) {
40
42
  onIncrement();
41
43
  break;
42
44
  case 'Enter':
45
+ debouncedSetValueRef.current?.cancel();
43
46
  setValue(value);
44
47
  break;
45
48
  case 'ArrowLeft':
@@ -93,6 +96,7 @@ function NumberElement(props) {
93
96
  useEffect(() => {
94
97
  // Set up debounce fn
95
98
  // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
99
+ debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
96
100
  debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
97
101
  }, [setValue]);
98
102
 
@@ -119,7 +123,15 @@ function NumberElement(props) {
119
123
  isIncrementDisabled = typeof maxValue !== 'undefined' && value === maxValue,
120
124
  isDecrementDisabled = typeof minValue !== 'undefined' && (value === minValue || (!value && minValue === 0));
121
125
 
122
- return <Row flex={1} h="100%" p={0} borderWidth={1} borderColor="trueGray.400" borderRadius={6} {...props}>
126
+ return <Row
127
+ flex={1}
128
+ h="100%"
129
+ p={0}
130
+ borderWidth={1}
131
+ borderColor="trueGray.400"
132
+ borderRadius={6}
133
+ {...propsToPass}
134
+ >
123
135
  <IconButton
124
136
  {...testProps('decrementBtn')}
125
137
  icon={<Icon as={Minus} color={(isDecrementDisabled || isDisabled) ? 'disabled' : 'trueGray.500'} />}
@@ -135,7 +147,7 @@ function NumberElement(props) {
135
147
  zIndex={10}
136
148
  />
137
149
  <InputWithTooltip
138
- {...testProps('input')}
150
+ testID={testID}
139
151
  value={inputValue}
140
152
  onChangeText={onChangeText}
141
153
  onKeyPress={onInputKeyPress}
@@ -36,6 +36,7 @@ const
36
36
  useEffect(() => {
37
37
  // Set up debounce fn
38
38
  // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
39
+ debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
39
40
  debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
40
41
  }, [setValue]);
41
42
 
@@ -12,6 +12,7 @@ import {
12
12
  import Inflector from 'inflector-js';
13
13
  import Header from './Header.js';
14
14
  import Mask from './Mask.js';
15
+ import testProps from '../../Functions/testProps.js';
15
16
  import withCollapsible from '../Hoc/withCollapsible.js';
16
17
  import withComponent from '../Hoc/withComponent.js';
17
18
  import emptyFn from '../../Functions/emptyFn.js';
@@ -145,19 +146,20 @@ function Panel(props) {
145
146
  framePropsToUse = frameProps;
146
147
  }
147
148
 
149
+ const self = props.self;
148
150
  if (isCollapsed) {
149
151
  if (collapseDirection === HORIZONTAL) {
150
- return <Column overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} w="33px">
152
+ return <Column {...testProps(self?.reference)} overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} w="33px">
151
153
  {isDisabled && <Mask />}
152
154
  {headerComponent}
153
155
  </Column>;
154
156
  }
155
- return <Column overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} h="33px">
157
+ return <Column {...testProps(self?.reference)} overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} h="33px">
156
158
  {isDisabled && <Mask />}
157
159
  {headerComponent}
158
160
  </Column>;
159
161
  }
160
- return <Column overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} onLayout={onLayout}>
162
+ return <Column {...testProps(self?.reference)} overflow="hidden" {...propsToPass} {...framePropsToUse} {...sizeProps} onLayout={onLayout}>
161
163
  {isDisabled && <Mask />}
162
164
  {headerComponent}
163
165
  {topToolbar}
@@ -6,6 +6,7 @@ import {
6
6
  Pressable,
7
7
  Icon,
8
8
  Row,
9
+ ScrollView,
9
10
  Text,
10
11
  } from 'native-base';
11
12
  import {
@@ -361,7 +362,7 @@ function TreeComponent(props) {
361
362
 
362
363
  const isMultipleHits = found.length > 1;
363
364
  if (!isMultipleHits) {
364
- expandPath(found[0].path); // highlights and selects the last node in the path
365
+ expandPath(found[0].cPath); // highlights and selects the last node in the cPath
365
366
  return;
366
367
  }
367
368
 
@@ -650,23 +651,23 @@ function TreeComponent(props) {
650
651
  }
651
652
  });
652
653
  },
653
- expandPath = async (path) => {
654
+ expandPath = async (cPath) => {
654
655
  // First, close thw whole tree.
655
656
  let newTreeNodeData = _.clone(getTreeNodeData());
656
657
  collapseNodes(newTreeNodeData);
657
658
 
658
659
  // As it navigates down, it will expand the appropriate branches,
659
660
  // and then finally highlight & select the node in question
660
- let pathParts,
661
+ let cPathParts,
661
662
  id,
662
663
  currentLevelData = newTreeNodeData,
663
664
  currentDatum,
664
665
  parentDatum,
665
666
  currentNode;
666
667
 
667
- while(path.length) {
668
- pathParts = path.split('/');
669
- id = parseInt(pathParts[0], 10); // grab the first part of the path
668
+ while(cPath.length) {
669
+ cPathParts = cPath.split('/');
670
+ id = parseInt(cPathParts[0], 10); // grab the first part of the cPath
670
671
 
671
672
  // find match in current level
672
673
  currentDatum = _.find(currentLevelData, (treeNodeDatum) => {
@@ -687,7 +688,7 @@ function TreeComponent(props) {
687
688
  // THE MAGIC!
688
689
  currentDatum.isExpanded = true;
689
690
 
690
- path = pathParts.slice(1).join('/'); // put the rest of it back together
691
+ cPath = cPathParts.slice(1).join('/'); // put the rest of it back together
691
692
  currentLevelData = currentDatum.children;
692
693
  parentDatum = currentDatum;
693
694
  }
@@ -729,7 +730,7 @@ function TreeComponent(props) {
729
730
  // Also, keep in mind that document.getElementById(id).scrollIntoView() might not work as expected in all situations, especially in complex layouts or when using certain CSS properties. Always test your code thoroughly to make sure it works as expected.
730
731
 
731
732
  // ... Not sure how to do this with NativeBase, as I've had trouble assigning IDs
732
- // Maybe I first collapse the tree, then expand just the path?
733
+ // Maybe I first collapse the tree, then expand just the cPath?
733
734
  },
734
735
 
735
736
  // render
@@ -1197,9 +1198,11 @@ function TreeComponent(props) {
1197
1198
  }
1198
1199
  }}
1199
1200
  >
1200
- {!treeNodes?.length ?
1201
- <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} /> :
1202
- treeNodes}
1201
+ <ScrollView flex={1} w="100%">
1202
+ {!treeNodes?.length ?
1203
+ <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} /> :
1204
+ treeNodes}
1205
+ </ScrollView>
1203
1206
  </Column>
1204
1207
 
1205
1208
  {treeFooterComponent}
@@ -1239,8 +1242,8 @@ function TreeComponent(props) {
1239
1242
  treeNode = _.find(searchResults, (item) => {
1240
1243
  return item.id === data.node_id;
1241
1244
  }),
1242
- path = treeNode.path;
1243
- expandPath(path);
1245
+ cPath = treeNode.cPath;
1246
+ expandPath(cPath);
1244
1247
 
1245
1248
  // Close the modal
1246
1249
  setIsModalShown(false);