@onehat/ui 0.3.263 → 0.3.265

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.263",
3
+ "version": "0.3.265",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -975,7 +975,7 @@ function Form(props) {
975
975
  >{editor}</Row>}
976
976
  {editorType !== EDITOR_TYPE__INLINE &&
977
977
  <ScrollView _web={{ minHeight, }} width="100%" pb={1}>
978
- {formheader}
978
+ {formHeader}
979
979
  {formButtons}
980
980
  {editor}
981
981
  </ScrollView>}
@@ -17,7 +17,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
17
17
  return (props) => {
18
18
 
19
19
  if (props.secondaryDisableWithEditor) {
20
- return <WrappedComponent {...props} />;
20
+ return <WrappedComponent {...props} isTree={isTree} />;
21
21
  }
22
22
 
23
23
  let [secondaryEditorMode, secondarySetEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -560,6 +560,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
560
560
  secondaryDisableDuplicate={secondaryDisableDuplicate}
561
561
  secondaryDisableView ={secondaryDisableView}
562
562
  secondarySetSelection={secondarySetSelectionDecorated}
563
+ isTree={isTree}
563
564
  />;
564
565
  };
565
566
  }
@@ -15,7 +15,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
15
15
  return (props) => {
16
16
 
17
17
  if (props.disableWithEditor) {
18
- return <WrappedComponent {...props} />;
18
+ return <WrappedComponent {...props} isTree={isTree} />;
19
19
  }
20
20
 
21
21
  let [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -569,6 +569,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
569
569
  disableDuplicate={disableDuplicate}
570
570
  disableView ={disableView}
571
571
  setSelection={setSelectionDecorated}
572
+ isTree={isTree}
572
573
  />;
573
574
  };
574
575
  }
@@ -51,6 +51,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
51
51
  // for local use
52
52
  isEditor = false,
53
53
  isTree = false,
54
+ canDeleteRootNode = false,
54
55
  isSideEditor = false,
55
56
  canEditorViewOnly = false,
56
57
  disableAdd = !isEditor,
@@ -192,6 +193,12 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
192
193
  if (verifyCanDelete && !verifyCanDelete(selection)) {
193
194
  isDisabled = true;
194
195
  }
196
+ if (isTree) {
197
+ const isRootNode = !!_.find(selection, { isRoot: true, });
198
+ if (isRootNode && !canDeleteRootNode) {
199
+ isDisabled = true;
200
+ }
201
+ }
195
202
  break;
196
203
  case 'view':
197
204
  key = 'viewBtn';
@@ -41,6 +41,7 @@ import nbToRgb from '../../Functions/nbToRgb.js';
41
41
  import TreeNode, { DraggableTreeNode } from './TreeNode.js';
42
42
  import FormPanel from '../Panel/FormPanel.js';
43
43
  import Input from '../Form/Field/Input.js';
44
+ import Xmark from '../Icons/Xmark.js';
44
45
  import Dot from '../Icons/Dot.js';
45
46
  import Collapse from '../Icons/Collapse.js';
46
47
  import FolderClosed from '../Icons/FolderClosed.js';
@@ -53,7 +54,7 @@ import NoRecordsFound from '../Grid/NoRecordsFound.js';
53
54
  import Toolbar from '../Toolbar/Toolbar.js';
54
55
  import _ from 'lodash';
55
56
 
56
- const DEPTH_INDENT_PX = 20;
57
+ const DEPTH_INDENT_PX = 25;
57
58
 
58
59
  function TreeComponent(props) {
59
60
  const {
@@ -96,6 +97,14 @@ function TreeComponent(props) {
96
97
  additionalToolbarButtons = [],
97
98
  reload = null, // Whenever this value changes after initial render, the tree will reload from scratch
98
99
  parentIdIx,
100
+
101
+ // withComponent
102
+ self,
103
+
104
+ // withAlert
105
+ alert,
106
+ confirm,
107
+ showInfo,
99
108
 
100
109
  // withEditor
101
110
  onAdd,
@@ -138,7 +147,7 @@ function TreeComponent(props) {
138
147
  treeNodeData = useRef(),
139
148
  [isReady, setIsReady] = useState(false),
140
149
  [isLoading, setIsLoading] = useState(false),
141
- [isSearchModalShown, setIsSearchModalShown] = useState(false),
150
+ [isModalShown, setIsModalShown] = useState(false),
142
151
  [rowToDatumMap, setRowToDatumMap] = useState({}),
143
152
  [searchResults, setSearchResults] = useState([]),
144
153
  [searchFormData, setSearchFormData] = useState([]),
@@ -222,6 +231,10 @@ function TreeComponent(props) {
222
231
  },
223
232
  onBeforeAdd = async () => {
224
233
  // Load children before adding the new node
234
+ if (_.isEmpty(selection)) {
235
+ alert('Please select a parent node first.')
236
+ return;
237
+ }
225
238
  const
226
239
  parent = selection[0],
227
240
  parentDatum = getNodeData(parent.id);
@@ -229,16 +242,17 @@ function TreeComponent(props) {
229
242
  if (parent.hasChildren && !parent.areChildrenLoaded) {
230
243
  await loadChildren(parentDatum);
231
244
  }
245
+ if (!parentDatum.isExpanded) {
246
+ parentDatum.isExpanded = true;
247
+ }
248
+ forceUpdate();
232
249
  },
233
- onAfterAdd = async (entity) => {
250
+ onAfterAdd = async (entities) => {
234
251
  // Expand the parent before showing the new node
235
252
  const
236
- parent = entity.parent,
237
- parentDatum = getNodeData(parent.id);
253
+ entity = entities[0],
254
+ parentDatum = getNodeData(entity.parentId);
238
255
 
239
- if (!parentDatum.isExpanded) {
240
- parentDatum.isExpanded = true;
241
- }
242
256
 
243
257
  // Add the entity to the tree
244
258
  const entityDatum = buildTreeNodeDatum(entity);
@@ -322,29 +336,43 @@ function TreeComponent(props) {
322
336
  buildRowToDatumMap();
323
337
  return newTreeNodeData;
324
338
  },
325
- onSearchTree = async (value) => {
339
+ onSearchTree = async (q) => {
326
340
  let found = [];
341
+ if (q === '') {
342
+ setHighlitedDatum(null);
343
+ alert('Please enter a search query.');
344
+ return;
345
+ }
346
+
327
347
  if (Repository?.isRemote) {
328
348
  // Search tree on server
329
- found = await Repository.searchNodes(value);
349
+ found = await Repository.searchNodes(q);
330
350
  } else {
331
351
  // Search local tree data
332
- found = findTreeNodesByText(value);
352
+ found = findTreeNodesByText(q);
353
+ }
354
+
355
+ if (_.isEmpty(found)) {
356
+ deselectAll();
357
+ setHighlitedDatum(null);
358
+ alert('No matches found.');
359
+ return;
333
360
  }
334
361
 
335
362
  const isMultipleHits = found.length > 1;
336
363
  if (!isMultipleHits) {
337
- expandPath(found[0].path);
364
+ expandPath(found[0].path); // highlights and selects the last node in the path
338
365
  return;
339
366
  }
340
367
 
368
+ // Show modal so user can select which node to go to
341
369
  const searchFormData = [];
342
370
  _.each(found, (item) => {
343
371
  searchFormData.push([item.id, getNodeText(item)]);
344
372
  });
345
373
  setSearchFormData(searchFormData);
346
374
  setSearchResults(found);
347
- setIsSearchModalShown(true);
375
+ setIsModalShown(true);
348
376
  },
349
377
 
350
378
  // utilities
@@ -677,7 +705,31 @@ function TreeComponent(props) {
677
705
 
678
706
  // TODO: This will probably need different methods in web and mobile
679
707
 
680
-
708
+ // From Github Copliot:
709
+ // In React, if you want to scroll individual DOM nodes into view, you would typically assign a ref to each of them. However, managing a large number of refs can be cumbersome and may lead to performance issues.
710
+ // An alternative approach is to assign a unique id to each DOM node and use the document.getElementById(id).scrollIntoView() method to scroll to a specific node. This way, you don't need to manage a large number of refs.
711
+ // Here's an example:
712
+ // const MyComponent = () => {
713
+ // const scrollTo = (id) => {
714
+ // document.getElementById(id).scrollIntoView();
715
+ // };
716
+ // return (
717
+ // <div>
718
+ // {Array.from({ length: 100 }).map((_, index) => (
719
+ // <div id={`item-${index}`} key={index}>
720
+ // Item {index}
721
+ // </div>
722
+ // ))}
723
+ // <button onClick={() => scrollTo('item-50')}>Scroll to item 50</button>
724
+ // </div>
725
+ // );
726
+ // };
727
+ // In this example, we're creating 100 divs each with a unique id. We also have a button that, when clicked, scrolls to the div with the id 'item-50'.
728
+ // Please note that this approach uses the DOM API directly, which is generally discouraged in React. It's recommended to use refs when you need to interact with DOM nodes directly. However, in cases where you need to manage a large number of DOM nodes, using ids can be a more practical solution.
729
+ // 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
+ // ... 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?
681
733
  },
682
734
 
683
735
  // render
@@ -687,7 +739,7 @@ function TreeComponent(props) {
687
739
  {
688
740
  key: 'searchBtn',
689
741
  text: 'Search tree',
690
- handler: onSearchTree,
742
+ handler: () => onSearchTree(treeSearchValue),
691
743
  icon: MagnifyingGlass,
692
744
  isDisabled: !treeSearchValue.length,
693
745
  },
@@ -704,32 +756,44 @@ function TreeComponent(props) {
704
756
  key: 'reorderBtn',
705
757
  text: (isDragMode ? 'Exit' : 'Enter') + ' reorder mode',
706
758
  handler: () => {
707
- setIsDragMode(!isDragMode)
759
+ setIsDragMode(!isDragMode);
708
760
  },
709
761
  icon: isDragMode ? NoReorderRows : ReorderRows,
710
762
  isDisabled: false,
711
763
  });
712
764
  }
713
- const items = _.map(buttons, getIconButtonFromConfig);
765
+ const items = _.map(buttons, (config, ix) => getIconButtonFromConfig(config, ix, self));
714
766
 
715
767
  items.unshift(<Input // Add text input to beginning of header items
716
768
  key="searchNodes"
717
769
  flex={1}
718
770
  placeholder="Find tree node"
719
771
  onChangeText={(val) => setTreeSearchValue(val)}
720
- onKeyPress={(e, value) => {
772
+ onKeyPress={(e) => {
721
773
  if (e.key === 'Enter') {
722
- onSearchTree(value);
774
+ onSearchTree(treeSearchValue);
723
775
  }
724
776
  }}
725
777
  value={treeSearchValue}
726
778
  autoSubmit={false}
727
779
  />);
728
780
 
781
+ if (treeSearchValue.length) {
782
+ // Add 'X' button to clear search
783
+ items.unshift(getIconButtonFromConfig({
784
+ key: 'xBtn',
785
+ handler: () => {
786
+ setHighlitedDatum(null);
787
+ setTreeSearchValue('');
788
+ },
789
+ icon: Xmark,
790
+ }, 0, self));
791
+ }
792
+
729
793
  return items;
730
794
  },
731
795
  getFooterToolbarItems = () => {
732
- return _.map(additionalToolbarButtons, getIconButtonFromConfig);
796
+ return _.map(additionalToolbarButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
733
797
  },
734
798
  renderTreeNode = (datum) => {
735
799
  if (!datum.isVisible) {
@@ -743,7 +807,6 @@ function TreeComponent(props) {
743
807
 
744
808
  let nodeProps = getNodeProps ? getNodeProps(item) : {},
745
809
  isSelected = isInSelection(item);
746
-
747
810
  return <Pressable
748
811
  // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
749
812
  key={item.hash}
@@ -1115,6 +1178,7 @@ function TreeComponent(props) {
1115
1178
  w="100%"
1116
1179
  >
1117
1180
  {topToolbar}
1181
+
1118
1182
  {headerToolbarItemComponents?.length && <Row>{headerToolbarItemComponents}</Row>}
1119
1183
 
1120
1184
  <Column
@@ -1129,16 +1193,18 @@ function TreeComponent(props) {
1129
1193
  }
1130
1194
  }}
1131
1195
  >
1132
- {!treeNodes?.length ? <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} /> :
1196
+ {!treeNodes?.length ?
1197
+ <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} /> :
1133
1198
  treeNodes}
1134
1199
  </Column>
1135
1200
 
1136
1201
  {treeFooterComponent}
1202
+
1137
1203
  </Column>
1138
1204
 
1139
1205
  <Modal
1140
- isOpen={isSearchModalShown}
1141
- onClose={() => setIsSearchModalShown(false)}
1206
+ isOpen={isModalShown}
1207
+ onClose={() => setIsModalShown(false)}
1142
1208
  >
1143
1209
  <Column bg="#fff" w={300}>
1144
1210
  <FormPanel
@@ -1161,8 +1227,8 @@ function TreeComponent(props) {
1161
1227
  },
1162
1228
  ]}
1163
1229
  onCancel={(e) => {
1164
- // Just close the modal
1165
- setIsSearchModalShown(false);
1230
+ setHighlitedDatum(null);
1231
+ setIsModalShown(false);
1166
1232
  }}
1167
1233
  onSave={(data, e) => {
1168
1234
  const
@@ -1173,7 +1239,7 @@ function TreeComponent(props) {
1173
1239
  expandPath(path);
1174
1240
 
1175
1241
  // Close the modal
1176
- setIsSearchModalShown(false);
1242
+ setIsModalShown(false);
1177
1243
  }}
1178
1244
  />
1179
1245
  </Column>
@@ -116,7 +116,7 @@ const defaults = {
116
116
  TREE_NODE_HOVER_BG: 'hover',
117
117
  TREE_NODE_SELECTED_BG: 'selected',
118
118
  TREE_NODE_SELECTED_HOVER_BG: 'selectedHover',
119
- TREE_NODE_HIGHLIGHTED_BG: '#ffa',
119
+ TREE_NODE_HIGHLIGHTED_BG: 'highlighted',
120
120
  TOOLBAR_ITEMS_COLOR: 'trueGray.800',
121
121
  TOOLBAR_ITEMS_DISABLED_COLOR: 'trueGray.400',
122
122
  TOOLBAR_ITEMS_ICON_SIZE: 'sm',