@elliemae/ds-treeview 2.0.1 → 2.1.0-rc.3

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 (167) hide show
  1. package/cjs/TreeView.js +5 -103
  2. package/cjs/TreeViewContext.js +44 -47
  3. package/cjs/config/useTreeview.js +137 -0
  4. package/cjs/index.js +1 -7
  5. package/cjs/parts/CheckboxSelectable.js +7 -9
  6. package/cjs/parts/ExpandCaret.js +17 -11
  7. package/cjs/parts/Icon.js +4 -0
  8. package/cjs/parts/NestingSpace.js +15 -22
  9. package/cjs/parts/RadioSelectable.js +7 -9
  10. package/cjs/parts/TreeItem.js +16 -13
  11. package/cjs/parts/TreeList.js +6 -4
  12. package/cjs/plugins/dnd/TreeDndPlugin.js +1 -1
  13. package/cjs/react-desc-prop-types.js +28 -27
  14. package/cjs/related-components/TreeViewSearchBar.js +9 -1
  15. package/cjs/{deprecated/plugins/selectable/helper.js → sharedTypes.js} +0 -0
  16. package/cjs/utils/dnd-helpers.js +2 -4
  17. package/cjs/utils/group-expands-helpers.js +34 -8
  18. package/cjs/utils/keyboard-helpers.js +64 -60
  19. package/cjs/utils/object-helpers.js +25 -7
  20. package/cjs/utils/refs-helpers.js +2 -2
  21. package/cjs/utils/selectable-helper.js +2 -2
  22. package/cjs/utils/tree-helpers.js +42 -27
  23. package/cjs/utils/useDnDHelpers.js +107 -100
  24. package/cjs/utils/useInstanceRefActions.js +3 -2
  25. package/cjs/utils/useTree.js +27 -8
  26. package/esm/TreeView.js +6 -103
  27. package/esm/TreeViewContext.js +44 -47
  28. package/esm/config/useTreeview.js +129 -0
  29. package/esm/index.js +0 -8
  30. package/esm/parts/CheckboxSelectable.js +7 -9
  31. package/esm/parts/ExpandCaret.js +17 -11
  32. package/esm/parts/Icon.js +4 -0
  33. package/esm/parts/NestingSpace.js +15 -21
  34. package/esm/parts/RadioSelectable.js +7 -9
  35. package/esm/parts/TreeItem.js +16 -13
  36. package/esm/parts/TreeList.js +6 -4
  37. package/esm/plugins/dnd/TreeDndPlugin.js +1 -1
  38. package/esm/react-desc-prop-types.js +28 -27
  39. package/esm/related-components/TreeViewSearchBar.js +9 -1
  40. package/esm/{deprecated/plugins/selectable/helper.js → sharedTypes.js} +0 -0
  41. package/esm/utils/dnd-helpers.js +2 -4
  42. package/esm/utils/group-expands-helpers.js +34 -9
  43. package/esm/utils/keyboard-helpers.js +62 -58
  44. package/esm/utils/object-helpers.js +25 -7
  45. package/esm/utils/refs-helpers.js +2 -2
  46. package/esm/utils/selectable-helper.js +2 -2
  47. package/esm/utils/tree-helpers.js +42 -27
  48. package/esm/utils/useDnDHelpers.js +107 -100
  49. package/esm/utils/useInstanceRefActions.js +3 -2
  50. package/esm/utils/useTree.js +27 -8
  51. package/package.json +17 -128
  52. package/types/TreeView.d.ts +4 -148
  53. package/types/TreeViewContext.d.ts +4 -106
  54. package/types/config/useTreeview.d.ts +7 -0
  55. package/types/index.d.ts +0 -1
  56. package/types/parts/CheckboxSelectable.d.ts +5 -6
  57. package/types/parts/ChildrenCountDisplayer.d.ts +6 -6
  58. package/types/parts/ExpandCaret.d.ts +6 -6
  59. package/types/parts/Icon.d.ts +7 -5
  60. package/types/parts/NestingSpace.d.ts +3 -8
  61. package/types/parts/RadioSelectable.d.ts +5 -6
  62. package/types/parts/TreeItem.d.ts +22 -14
  63. package/types/parts/TreeItemText.d.ts +5 -5
  64. package/types/parts/TreeList.d.ts +22 -9
  65. package/types/react-desc-prop-types.d.ts +124 -43
  66. package/types/related-components/TreeViewSearchBar.d.ts +14 -11
  67. package/types/sharedTypes.d.ts +54 -0
  68. package/types/utils/array-helpers.d.ts +2 -2
  69. package/types/utils/dnd-helpers.d.ts +26 -25
  70. package/types/utils/group-expands-helpers.d.ts +13 -10
  71. package/types/utils/keyboard-helpers.d.ts +5 -3
  72. package/types/utils/object-helpers.d.ts +6 -2
  73. package/types/utils/refs-helpers.d.ts +4 -1
  74. package/types/utils/selectable-helper.d.ts +7 -3
  75. package/types/utils/string-helpers.d.ts +1 -1
  76. package/types/utils/tree-helpers.d.ts +25 -11
  77. package/types/utils/useDnDHelpers.d.ts +23 -3
  78. package/types/utils/useInstanceRefActions.d.ts +3 -3
  79. package/types/utils/useTree.d.ts +8 -7
  80. package/cjs/deprecated/TreeView.js +0 -72
  81. package/cjs/deprecated/__testUtils__/utils.js +0 -89
  82. package/cjs/deprecated/index.js +0 -27
  83. package/cjs/deprecated/plugins/dnd/TreeDndPlugin.js +0 -119
  84. package/cjs/deprecated/plugins/dnd/index.js +0 -9
  85. package/cjs/deprecated/plugins/dnd/renderers/DraggableItem.js +0 -67
  86. package/cjs/deprecated/plugins/dnd/renderers/DroppableList.js +0 -181
  87. package/cjs/deprecated/plugins/dnd/utils.js +0 -157
  88. package/cjs/deprecated/plugins/index.js +0 -21
  89. package/cjs/deprecated/plugins/roving/TreeRovingPlugin.js +0 -109
  90. package/cjs/deprecated/plugins/roving/index.js +0 -9
  91. package/cjs/deprecated/plugins/searchable/SearchableTreePlugin.js +0 -109
  92. package/cjs/deprecated/plugins/searchable/index.js +0 -9
  93. package/cjs/deprecated/plugins/searchable/utils.js +0 -7
  94. package/cjs/deprecated/plugins/selectable/SelectablePluginTree.js +0 -290
  95. package/cjs/deprecated/plugins/selectable/index.js +0 -9
  96. package/cjs/deprecated/plugins/selectable/utils.js +0 -19
  97. package/cjs/deprecated/plugins/toolbar/TreeToolbarPlugin.js +0 -128
  98. package/cjs/deprecated/plugins/toolbar/index.js +0 -9
  99. package/cjs/deprecated/plugins/tree/TreeDataPlugin.js +0 -281
  100. package/cjs/deprecated/plugins/tree/index.js +0 -9
  101. package/cjs/deprecated/plugins/tree/useExpandTreeState.js +0 -143
  102. package/cjs/deprecated/plugins/tree/utils.js +0 -43
  103. package/cjs/deprecated/plugins/tree/walkTreeStrategy.js +0 -60
  104. package/cjs/deprecated/plugins/virtualization/TreeVirtualizationPlugin.js +0 -209
  105. package/cjs/deprecated/plugins/virtualization/index.js +0 -9
  106. package/cjs/deprecated/renderers.js +0 -76
  107. package/cjs/prop-types.js +0 -61
  108. package/esm/deprecated/TreeView.js +0 -61
  109. package/esm/deprecated/__testUtils__/utils.js +0 -79
  110. package/esm/deprecated/index.js +0 -14
  111. package/esm/deprecated/plugins/dnd/TreeDndPlugin.js +0 -110
  112. package/esm/deprecated/plugins/dnd/index.js +0 -1
  113. package/esm/deprecated/plugins/dnd/renderers/DraggableItem.js +0 -59
  114. package/esm/deprecated/plugins/dnd/renderers/DroppableList.js +0 -172
  115. package/esm/deprecated/plugins/dnd/utils.js +0 -139
  116. package/esm/deprecated/plugins/index.js +0 -7
  117. package/esm/deprecated/plugins/roving/TreeRovingPlugin.js +0 -96
  118. package/esm/deprecated/plugins/roving/index.js +0 -1
  119. package/esm/deprecated/plugins/searchable/SearchableTreePlugin.js +0 -99
  120. package/esm/deprecated/plugins/searchable/index.js +0 -1
  121. package/esm/deprecated/plugins/searchable/utils.js +0 -3
  122. package/esm/deprecated/plugins/selectable/SelectablePluginTree.js +0 -280
  123. package/esm/deprecated/plugins/selectable/index.js +0 -1
  124. package/esm/deprecated/plugins/selectable/utils.js +0 -15
  125. package/esm/deprecated/plugins/toolbar/TreeToolbarPlugin.js +0 -114
  126. package/esm/deprecated/plugins/toolbar/index.js +0 -1
  127. package/esm/deprecated/plugins/tree/TreeDataPlugin.js +0 -269
  128. package/esm/deprecated/plugins/tree/index.js +0 -1
  129. package/esm/deprecated/plugins/tree/useExpandTreeState.js +0 -135
  130. package/esm/deprecated/plugins/tree/utils.js +0 -28
  131. package/esm/deprecated/plugins/tree/walkTreeStrategy.js +0 -56
  132. package/esm/deprecated/plugins/virtualization/TreeVirtualizationPlugin.js +0 -197
  133. package/esm/deprecated/plugins/virtualization/index.js +0 -1
  134. package/esm/deprecated/renderers.js +0 -59
  135. package/esm/prop-types.js +0 -46
  136. package/types/deprecated/TreeView.d.ts +0 -8
  137. package/types/deprecated/__testUtils__/utils.d.ts +0 -72
  138. package/types/deprecated/index.d.ts +0 -2
  139. package/types/deprecated/plugins/dnd/TreeDndPlugin.d.ts +0 -1
  140. package/types/deprecated/plugins/dnd/index.d.ts +0 -1
  141. package/types/deprecated/plugins/dnd/renderers/DraggableItem.d.ts +0 -3
  142. package/types/deprecated/plugins/dnd/renderers/DroppableList.d.ts +0 -6
  143. package/types/deprecated/plugins/dnd/tests/TreeDndPlugin.test.d.ts +0 -0
  144. package/types/deprecated/plugins/dnd/utils.d.ts +0 -28
  145. package/types/deprecated/plugins/index.d.ts +0 -7
  146. package/types/deprecated/plugins/roving/TreeRovingPlugin.d.ts +0 -1
  147. package/types/deprecated/plugins/roving/index.d.ts +0 -1
  148. package/types/deprecated/plugins/searchable/SearchableTreePlugin.d.ts +0 -1
  149. package/types/deprecated/plugins/searchable/index.d.ts +0 -1
  150. package/types/deprecated/plugins/searchable/utils.d.ts +0 -1
  151. package/types/deprecated/plugins/selectable/SelectablePluginTree.d.ts +0 -1
  152. package/types/deprecated/plugins/selectable/helper.d.ts +0 -0
  153. package/types/deprecated/plugins/selectable/index.d.ts +0 -1
  154. package/types/deprecated/plugins/selectable/tests/SelectablePluginTree.test.d.ts +0 -1
  155. package/types/deprecated/plugins/selectable/utils.d.ts +0 -1
  156. package/types/deprecated/plugins/toolbar/TreeToolbarPlugin.d.ts +0 -1
  157. package/types/deprecated/plugins/toolbar/index.d.ts +0 -1
  158. package/types/deprecated/plugins/tree/TreeDataPlugin.d.ts +0 -1
  159. package/types/deprecated/plugins/tree/index.d.ts +0 -1
  160. package/types/deprecated/plugins/tree/tests/TreeDataPlugin.test.d.ts +0 -1
  161. package/types/deprecated/plugins/tree/useExpandTreeState.d.ts +0 -14
  162. package/types/deprecated/plugins/tree/utils.d.ts +0 -8
  163. package/types/deprecated/plugins/tree/walkTreeStrategy.d.ts +0 -9
  164. package/types/deprecated/plugins/virtualization/TreeVirtualizationPlugin.d.ts +0 -1
  165. package/types/deprecated/plugins/virtualization/index.d.ts +0 -1
  166. package/types/deprecated/renderers.d.ts +0 -19
  167. package/types/prop-types.d.ts +0 -26
@@ -27,9 +27,9 @@ const walkVisibles = function (node, callback) {
27
27
  matchesSearchQuery
28
28
  } = model;
29
29
  const shouldSkipBecauseIsRoot = skipRoot && name === '__root';
30
- const queryRestrictionsPassed = highlightOnlyQuery || childrenMatchesSearchQuery || matchesSearchQuery;
30
+ const queryRestrictionsPassed = !!(highlightOnlyQuery || childrenMatchesSearchQuery || matchesSearchQuery);
31
31
  if (!shouldSkipBecauseIsRoot && queryRestrictionsPassed) callback(node);
32
- const shouldWalkChildren = children.length && isExpanded && queryRestrictionsPassed;
32
+ const shouldWalkChildren = !!(children.length > 0 && isExpanded && queryRestrictionsPassed);
33
33
 
34
34
  if (shouldWalkChildren) {
35
35
  children.forEach(childNode => {
@@ -65,13 +65,10 @@ const getNodeById = (treeRoot, id) => {
65
65
  return nodes[0];
66
66
  };
67
67
  const cloneNode = (tree, node) => tree.parse(node.model);
68
- const getItemsParentNode = function () {
68
+ const itemsShareSameParent = function () {
69
69
  let items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
70
- let skipSameParentCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
71
- if (items.length === 0) throw new Error("Received empty items, can't retrieve parent"); // in case we already know the items compared shares the same parent
72
-
73
- if (!skipSameParentCheck) if (!itemsShareSameParent(items)) // if we don't know (default, we don't) check if they share the parent
74
- throw new Error("Received items don't share the same parent, can't retrieve parent"); // when moving in first position we receive [undefined,...{items}]
70
+ if (items.length === 0) throw new Error('Received empty items for comparison');
71
+ if (items.length === 1) return true; // when moving in first position we receive [undefined,...{items}]
75
72
  // when moving in last position we receive [...{items}, undefined]
76
73
 
77
74
  const firstExistingItem = items.find(item => !!item);
@@ -82,12 +79,21 @@ const getItemsParentNode = function () {
82
79
  // root node is not printed and is always present so minimum path length is 2 (root + currentNode)
83
80
 
84
81
  const directParentNode = firstExistingItemPath[firstExistingItemPath.length - 2];
85
- return directParentNode;
82
+ return items.every(item => {
83
+ // undefined nodes count as having the same parent
84
+ if (!item) return true;
85
+ const itemPath = item.node.getPath();
86
+ const itemDirectParent = itemPath[itemPath.length - 2];
87
+ return directParentNode === itemDirectParent;
88
+ });
86
89
  };
87
- const itemsShareSameParent = function () {
90
+ const getItemsParentNode = function () {
88
91
  let items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
89
- if (items.length === 0) throw new Error('Received empty items for comparison');
90
- if (items.length === 1) return true; // when moving in first position we receive [undefined,...{items}]
92
+ let skipSameParentCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
93
+ if (items.length === 0) throw new Error("Received empty items, can't retrieve parent"); // in case we already know the items compared shares the same parent
94
+
95
+ if (!skipSameParentCheck) if (!itemsShareSameParent(items)) // if we don't know (default, we don't) check if they share the parent
96
+ throw new Error("Received items don't share the same parent, can't retrieve parent"); // when moving in first position we receive [undefined,...{items}]
91
97
  // when moving in last position we receive [...{items}, undefined]
92
98
 
93
99
  const firstExistingItem = items.find(item => !!item);
@@ -98,13 +104,7 @@ const itemsShareSameParent = function () {
98
104
  // root node is not printed and is always present so minimum path length is 2 (root + currentNode)
99
105
 
100
106
  const directParentNode = firstExistingItemPath[firstExistingItemPath.length - 2];
101
- return items.every(item => {
102
- // undefined nodes count as having the same parent
103
- if (!item) return true;
104
- const itemPath = item.node.getPath();
105
- const itemDirectParent = itemPath[itemPath.length - 2];
106
- return directParentNode === itemDirectParent;
107
- });
107
+ return directParentNode;
108
108
  };
109
109
  const getNodeMatchesSearchQuery = function (node) {
110
110
  let searchQuery = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
@@ -120,11 +120,23 @@ const getChildrenMatchesSearchQuery = function (parentNode) {
120
120
  });
121
121
  return anyChildMatch;
122
122
  };
123
+ /**
124
+ * Mutate in place a node from tree-model normalizing object properties like "isGroup" even if user didn't specify it explicitly,
125
+ * after execution the node will have "nodePath" "treeDepth" "nodeItemRef" "isGroup" "isExpanded"
126
+ * (optionally, based on the provided arguments) "matchesSearchQuery" "childrenMatchesSearchQuery" "isChecked"
127
+ *
128
+ * @param {Object} node - node (based on 'tree-model' lib) that you wish to mutate with extra props
129
+ * @param {string} [searchQuery=''] - optional string used to parse "matchesSearchQuery"/"childrenMatchesSearchQuery" properties
130
+ * @param {Object} [selection=null] - optional selection hashmap used to parse "isChecked" properties
131
+ * @param {Object} [expanded=null] - optional expanded hashmap used to parse "isExpanded" properties
132
+ */
133
+
123
134
  const enrichNodeModelInPlace = function (node) {
124
135
  var _node$model, _node$model$children;
125
136
 
126
137
  let searchQuery = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
127
138
  let selection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
139
+ let expanded = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
128
140
  const path = node.getPath();
129
141
  const depth = path.length - 1;
130
142
  node.model.nodePath = path;
@@ -140,24 +152,27 @@ const enrichNodeModelInPlace = function (node) {
140
152
  node.model.isGroup = true;
141
153
  }
142
154
 
143
- if (typeof node.model.isExpanded !== 'boolean') {
144
- node.model.isExpanded = false;
155
+ if (node.model.isGroup && node.model.id !== '__ds_tree_root') {
156
+ if (expanded) node.model.isExpanded = !!expanded[node.model.id];else if (typeof node.model.isExpanded !== 'boolean') {
157
+ node.model.isExpanded = false;
158
+ }
145
159
  }
146
160
 
147
161
  node.model.matchesSearchQuery = true;
148
162
  node.model.childrenMatchesSearchQuery = false;
163
+ const partiallyEnrichedNode = node;
149
164
 
150
165
  if (searchQuery) {
151
- node.model.matchesSearchQuery = getNodeMatchesSearchQuery(node, searchQuery);
166
+ partiallyEnrichedNode.model.matchesSearchQuery = getNodeMatchesSearchQuery(partiallyEnrichedNode, searchQuery);
152
167
 
153
- if (node.model.isGroup === true) {
154
- node.model.childrenMatchesSearchQuery = getChildrenMatchesSearchQuery(node, searchQuery);
155
- if (node.model.childrenMatchesSearchQuery) node.model.isExpanded = true;
168
+ if (partiallyEnrichedNode.model.isGroup === true) {
169
+ partiallyEnrichedNode.model.childrenMatchesSearchQuery = getChildrenMatchesSearchQuery(partiallyEnrichedNode, searchQuery);
170
+ if (partiallyEnrichedNode.model.childrenMatchesSearchQuery) partiallyEnrichedNode.model.isExpanded = true;
156
171
  }
157
172
  }
158
173
 
159
- if (selection && selection[node.model.id]) {
160
- node.model.isChecked = selection[node.model.id];
174
+ if (selection && selection[partiallyEnrichedNode.model.id]) {
175
+ partiallyEnrichedNode.model.isChecked = selection[partiallyEnrichedNode.model.id];
161
176
  }
162
177
  };
163
178
  const focusItem = item => {
@@ -41,63 +41,68 @@ const useDnDHelpers = () => {
41
41
  newIndex,
42
42
  isKeySorting
43
43
  } = _ref;
44
- let newItems = lodash.cloneDeep(data);
45
44
  const {
46
45
  isBefore,
47
46
  isAfter,
48
47
  item: overItem,
49
48
  isValid
50
- } = relativeMouseCord; // if dropping in same position do nothing
51
- // if the drop is not allowed do nothing
49
+ } = relativeMouseCord;
50
+
51
+ if (draggedItem && tree && treeRoot && overItem) {
52
+ let newItems = lodash.cloneDeep(data); // if dropping in same position do nothing
53
+ // if the drop is not allowed do nothing
54
+
55
+ if (oldIndex === newIndex || !isValid) {
56
+ // onOrderChange(newItems, data);
57
+ setIsDragging(false);
58
+ setDraggedItem(null);
59
+ triggerTreeRerender();
60
+ return;
61
+ } // if we are moving from topToBottom the offset changes
62
+
63
+
64
+ const movementDirection = newIndex > oldIndex ? 'topToBottom' : 'bottomToTop';
65
+ let newNestedIndex;
66
+ let newParentNode;
67
+
68
+ if (isKeySorting) {
69
+ const kinship = dndHelpers.inferKeyboardKinshipDrop({
70
+ items: visibleItems,
71
+ newIndex
72
+ });
73
+ newNestedIndex = kinship.newNestedIndex;
74
+ newParentNode = kinship.newParentNode;
75
+ } else {
76
+ const kinship = dndHelpers.inferHoverItemKinshipDrop({
77
+ isBefore,
78
+ isAfter,
79
+ overItem,
80
+ movementDirection,
81
+ draggedItem
82
+ });
83
+ newNestedIndex = kinship.newNestedIndex;
84
+ newParentNode = kinship.newParentNode;
85
+ }
86
+
87
+ const newMovedItemNode = tree.parse(draggedItem.node.model);
88
+ treeHelpers.getNodeById(treeRoot, draggedItem.id).drop();
89
+
90
+ if (newParentNode && typeof newNestedIndex === 'number') {
91
+ newParentNode.addChildAtIndex(newMovedItemNode, newNestedIndex);
52
92
 
53
- if (oldIndex === newIndex || !isValid) {
54
- // onOrderChange(newItems, data);
93
+ if (newParentNode.isRoot()) {
94
+ newItems = [...newParentNode.model.children];
95
+ } else {
96
+ newItems = [...treeRoot.model.children];
97
+ }
98
+ }
99
+
100
+ onOrderChange(newItems, data, draggedItem);
55
101
  setIsDragging(false);
56
102
  setDraggedItem(null);
57
103
  triggerTreeRerender();
58
- return;
59
- } // if we are moving from topToBottom the offset changes
60
-
61
-
62
- const movementDirection = newIndex > oldIndex ? 'topToBottom' : 'bottomToTop';
63
- let newNestedIndex;
64
- let newParentNode;
65
-
66
- if (isKeySorting) {
67
- const kinship = dndHelpers.inferKeyboardKinshipDrop({
68
- items: visibleItems,
69
- newIndex,
70
- movementDirection
71
- });
72
- newNestedIndex = kinship.newNestedIndex;
73
- newParentNode = kinship.newParentNode;
74
- } else {
75
- const kinship = dndHelpers.inferHoverItemKinshipDrop({
76
- isBefore,
77
- isAfter,
78
- overItem,
79
- movementDirection,
80
- draggedItem
81
- });
82
- newNestedIndex = kinship.newNestedIndex;
83
- newParentNode = kinship.newParentNode;
84
104
  }
85
-
86
- const newMovedItemNode = tree.parse(draggedItem.node.model);
87
- treeHelpers.getNodeById(treeRoot, draggedItem.id).drop();
88
- newParentNode.addChildAtIndex(newMovedItemNode, newNestedIndex);
89
-
90
- if (newParentNode.isRoot()) {
91
- newItems = [...newParentNode.model.children];
92
- } else {
93
- newItems = [...treeRoot.model.children];
94
- }
95
-
96
- onOrderChange(newItems, data, draggedItem);
97
- setIsDragging(false);
98
- setDraggedItem(null);
99
- triggerTreeRerender();
100
- }, [data, draggedItem, relativeMouseCord, visibleItems, tree, treeRoot, onOrderChange, triggerTreeRerender, setIsDragging, setDraggedItem, setRelativeMouseCord]);
105
+ }, [data, draggedItem, relativeMouseCord, visibleItems, tree, treeRoot, onOrderChange, triggerTreeRerender, setIsDragging, setDraggedItem]);
101
106
  const onSortStart = React.useCallback(() => {}, // ({ node, index, collection, isKeySorting }, event) => {
102
107
  // console.log('node :>> ', node);
103
108
  // console.log('isKeySorting :>> ', isKeySorting);
@@ -110,10 +115,9 @@ const useDnDHelpers = () => {
110
115
  const updateBeforeSortStart = React.useCallback(beforeStartArgs => new Promise(resolve => {
111
116
  const {
112
117
  index,
113
- collection,
114
118
  isKeySorting
115
119
  } = beforeStartArgs;
116
- const item = (collection === null || collection === void 0 ? void 0 : collection[index]) || visibleItems[index];
120
+ const item = visibleItems[index];
117
121
  const {
118
122
  isExpanded
119
123
  } = item.node.model;
@@ -129,9 +133,9 @@ const useDnDHelpers = () => {
129
133
  setDraggedItem(item);
130
134
  treeHelpers.focusItem(null);
131
135
  setFocusedItem(null);
132
- document.activeElement.blur();
133
- resolve();
134
- }), [visibleItems, isKeyboardDragging, setFocusedItem, setIsKeyboardDragging, setRelativeMouseCord]); // tracks mouse coords relative to an element to check if the drop happens
136
+ if (document.activeElement && typeof document.activeElement.blur === 'function') document.activeElement.blur();
137
+ resolve(true);
138
+ }), [visibleItems, setRelativeMouseCord, setIsKeyboardDragging, setIsDragging, setDraggedItem, setFocusedItem, triggerTreeRerender]); // tracks mouse coords relative to an element to check if the drop happens
135
139
  // at the top/center/bottom of an element to choose where to move the element
136
140
  // requested in the specs
137
141
 
@@ -150,58 +154,61 @@ const useDnDHelpers = () => {
150
154
  nodeItemRef
151
155
  } = node.model;
152
156
 
153
- if (isDragging && !isKeyboardDragging) {
154
- const rect = nodeItemRef.current.getBoundingClientRect();
155
- const x = event.clientX - rect.left; // x position within the element.
156
-
157
- const y = event.clientY - rect.top; // y position within the element.
158
- // by spec we need to track where the drop will happens
159
- // if drop happens on top 25% drop before
160
- // if drop happens on bottom of 25% drop after
161
- // if drop happens in the middle 50% either invalid or drop into children
162
-
163
- const {
164
- height: elementHeight
165
- } = rect;
166
- const dropBeforeMaxY = elementHeight * 0.25;
167
- const dropAfterMinY = elementHeight - elementHeight * 0.25;
168
- const isBefore = y < dropBeforeMaxY;
169
- const isAfter = y > dropAfterMinY;
170
- const isInside = !isBefore && !isAfter;
171
- const isValid = dndHelpers.isDropValid({
172
- isBefore,
173
- isAfter,
174
- draggedItem,
175
- overItem: item,
176
- restrictDragAndDrop
177
- });
178
- setRelativeMouseCord({
179
- isBefore,
180
- isAfter,
181
- isInside,
182
- isValid,
183
- item,
184
- x,
185
- y,
186
- relativeElementDOMRect: rect,
187
- relativeElementIndex: itemIndex
188
- });
189
- setIsDraggingOverThis(true);
190
-
191
- if (!(openFolderOnHoverTimeout !== null && openFolderOnHoverTimeout !== void 0 && openFolderOnHoverTimeout.current) && // only set the interval if it wasn't set already once
192
- (draggedItem === null || draggedItem === void 0 ? void 0 : draggedItem.id) !== (item === null || item === void 0 ? void 0 : item.id) && ( // don't open ghost element
193
- item.isGroup || item.children.length > 0) && // only open groups
194
- !restrictDragAndDrop) {
195
- openFolderOnHoverTimeout.current = setTimeout(() => {
196
- item.node.model.isExpanded = true;
197
- openFolderOnHoverTimeout.current = null;
198
- triggerTreeRerender();
199
- }, 1000);
157
+ if (isDragging && !isKeyboardDragging && draggedItem) {
158
+ var _nodeItemRef$current;
159
+
160
+ const rect = nodeItemRef === null || nodeItemRef === void 0 ? void 0 : (_nodeItemRef$current = nodeItemRef.current) === null || _nodeItemRef$current === void 0 ? void 0 : _nodeItemRef$current.getBoundingClientRect();
161
+
162
+ if (rect) {
163
+ const x = event.clientX - rect.left; // x position within the element.
164
+
165
+ const y = event.clientY - rect.top; // y position within the element.
166
+ // by spec we need to track where the drop will happens
167
+ // if drop happens on top 25% drop before
168
+ // if drop happens on bottom of 25% drop after
169
+ // if drop happens in the middle 50% either invalid or drop into children
170
+
171
+ const {
172
+ height: elementHeight
173
+ } = rect;
174
+ const dropBeforeMaxY = elementHeight * 0.25;
175
+ const dropAfterMinY = elementHeight - elementHeight * 0.25;
176
+ const isBefore = y < dropBeforeMaxY;
177
+ const isAfter = y > dropAfterMinY;
178
+ const isValid = dndHelpers.isDropValid({
179
+ isBefore,
180
+ isAfter,
181
+ draggedItem,
182
+ overItem: item,
183
+ restrictDragAndDrop
184
+ });
185
+ setRelativeMouseCord({
186
+ isBefore,
187
+ isAfter,
188
+ isValid,
189
+ item,
190
+ x,
191
+ y,
192
+ relativeElementDOMRect: rect,
193
+ relativeElementIndex: itemIndex
194
+ });
195
+ setIsDraggingOverThis(true);
196
+
197
+ if (!(openFolderOnHoverTimeout !== null && openFolderOnHoverTimeout !== void 0 && openFolderOnHoverTimeout.current) && // only set the interval if it wasn't set already once
198
+ (draggedItem === null || draggedItem === void 0 ? void 0 : draggedItem.id) !== (item === null || item === void 0 ? void 0 : item.id) && ( // don't open ghost element
199
+ item.isGroup || item.children.length > 0) && // only open groups
200
+ !restrictDragAndDrop) {
201
+ openFolderOnHoverTimeout.current = setTimeout(() => {
202
+ item.node.model.isExpanded = true;
203
+ openFolderOnHoverTimeout.current = null;
204
+ triggerTreeRerender();
205
+ }, 1000);
206
+ }
200
207
  }
201
208
  } else {
202
209
  setRelativeMouseCord(TreeViewContext.defaultRelativeMouseCoord);
203
210
  }
204
- }, [isDragging, setRelativeMouseCord, draggedItem, restrictDragAndDrop, isKeyboardDragging]);
211
+ }, [isDragging, isKeyboardDragging, draggedItem, restrictDragAndDrop, setRelativeMouseCord, triggerTreeRerender]);
205
212
  return {
206
213
  updateBeforeSortStart,
207
214
  onSortStart,
@@ -19,6 +19,7 @@ const useInstanceRefActions = (_ref, ctx) => {
19
19
  treeRoot,
20
20
  visibleItems,
21
21
  triggerTreeRerender,
22
+ updateUserExpandedState,
22
23
  setSelectedItem,
23
24
  setFocusedItem,
24
25
  setLatestToggledItem
@@ -40,11 +41,11 @@ const useInstanceRefActions = (_ref, ctx) => {
40
41
  actions.toggleExpandAll = React.useCallback(isExpand => {
41
42
  const p = new Promise(resolve => {
42
43
  promises.expandResolve = resolve;
43
- groupExpandsHelpers.toggleExpandAllHelper(isExpand, treeRoot, triggerTreeRerender, setLatestToggledItem);
44
+ if (treeRoot) groupExpandsHelpers.toggleExpandAllHelper(isExpand, treeRoot, triggerTreeRerender, setLatestToggledItem, updateUserExpandedState);
44
45
  });
45
46
  setPromise(p);
46
47
  return p;
47
- }, [promises, treeRoot, triggerTreeRerender, setLatestToggledItem]);
48
+ }, [promises, treeRoot, triggerTreeRerender, setLatestToggledItem, updateUserExpandedState]);
48
49
  actions.scrollTo = React.useCallback(function (indx) {
49
50
  let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
50
51
  align: 'start'
@@ -13,29 +13,34 @@ var React = require('react');
13
13
  var TreeModel = require('tree-model');
14
14
  var lodash = require('lodash');
15
15
  var treeHelpers = require('./tree-helpers.js');
16
+ var groupExpandsHelpers = require('./group-expands-helpers.js');
16
17
 
17
18
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
19
 
19
20
  var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty);
20
21
  var TreeModel__default = /*#__PURE__*/_interopDefaultLegacy(TreeModel);
21
22
 
22
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
23
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
23
24
 
24
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty__default["default"](target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
25
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty__default["default"](target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
25
26
  const useTree = (data, props, states) => {
26
27
  const {
27
28
  setExpandedGroups
28
29
  } = states;
29
30
  const unfrozenData = React.useMemo(() => lodash.cloneDeep(data), [data]); // convert data into a TreeModel with extra isExpanded prop
31
+ // root element type is an exception, children model is based on what the dev passed on
32
+ // since root is an excepcion we must typecast
30
33
 
31
34
  const tree = React.useMemo(() => new TreeModel__default["default"](), []);
32
35
  const [treeData, setTreeData] = React.useState({
36
+ id: '__ds_tree_root',
33
37
  name: '__root',
34
38
  isExpanded: true,
35
39
  children: unfrozenData
36
40
  });
37
41
  React.useEffect(() => {
38
42
  setTreeData({
43
+ id: '__ds_tree_root',
39
44
  name: '__root',
40
45
  isExpanded: true,
41
46
  children: unfrozenData
@@ -51,20 +56,32 @@ const useTree = (data, props, states) => {
51
56
  const [treeRoot, setTreeRoot] = React.useState(tree.parse(treeData));
52
57
  React.useEffect(() => {
53
58
  setTreeRoot(tree.parse(treeData));
54
- }, [treeData, rerenderItems]);
59
+ }, [treeData, rerenderItems, tree]);
55
60
  const [visibleItems, setVisibleItems] = React.useState([]);
56
61
  const {
57
62
  searchQuery,
58
63
  highlightOnlyQuery,
59
64
  onVisibleItemsChange,
60
- selection
65
+ selection,
66
+ expanded,
67
+ onExpandChange
61
68
  } = props;
69
+ const updateUserExpandedState = React.useCallback(() => {
70
+ groupExpandsHelpers.updateExpandedState(treeRoot, onExpandChange);
71
+ }, [treeRoot, onExpandChange]); // we add missing properties to the received Items
72
+ // not the best of the patterns, but it works
73
+ // this is where we convert "SimpleItems" to "Items"
74
+ // this was done to avoid breaking changes back in v1
75
+
62
76
  React.useEffect(() => {
63
77
  const parsedVisibleItems = [];
64
78
  const newExpandedHashMap = {};
65
79
  treeRoot.walk(node => {
66
- treeHelpers.enrichNodeModelInPlace(node, searchQuery, selection);
80
+ const shouldContinueWalking = true; // tree-model ask we return true if we want to continue walking
81
+
82
+ treeHelpers.enrichNodeModelInPlace(node, searchQuery, selection, expanded);
67
83
  if (node.model.id && node.model.isGroup && node.model.isExpanded) newExpandedHashMap[node.model.id] = true;
84
+ return shouldContinueWalking;
68
85
  });
69
86
  treeHelpers.walkVisibles(treeRoot, node => {
70
87
  parsedVisibleItems.push(_objectSpread(_objectSpread({}, node.model), {}, {
@@ -73,14 +90,16 @@ const useTree = (data, props, states) => {
73
90
  }, true, highlightOnlyQuery);
74
91
  setVisibleItems(parsedVisibleItems);
75
92
  onVisibleItemsChange(parsedVisibleItems);
76
- setExpandedGroups(newExpandedHashMap);
77
- }, [treeRoot, rerenderItems, searchQuery]);
93
+ setExpandedGroups(newExpandedHashMap); // eslint-disable-next-line react-hooks/exhaustive-deps
94
+ }, [treeRoot, rerenderItems, searchQuery, highlightOnlyQuery, selection, expanded, setExpandedGroups // onVisibleItemsChange // evaluate potential performance hit?
95
+ ]);
78
96
  return {
79
97
  visibleItems,
80
98
  tree,
81
99
  treeRoot,
82
100
  rerenderItems,
83
- triggerTreeRerender
101
+ triggerTreeRerender,
102
+ updateUserExpandedState
84
103
  };
85
104
  };
86
105
 
package/esm/TreeView.js CHANGED
@@ -1,121 +1,24 @@
1
- import 'core-js/modules/esnext.async-iterator.filter.js';
2
- import 'core-js/modules/esnext.iterator.constructor.js';
3
- import 'core-js/modules/esnext.iterator.filter.js';
4
- import 'core-js/modules/esnext.async-iterator.for-each.js';
5
- import 'core-js/modules/esnext.iterator.for-each.js';
6
1
  import _jsx from '@babel/runtime/helpers/esm/jsx';
7
- import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
8
- import 'core-js/modules/web.dom-collections.iterator.js';
9
- import { useRef, useState, useEffect } from 'react';
10
- import { useVirtual } from 'react-virtual';
11
- import { v4 } from 'uuid';
2
+ import 'react';
12
3
  import { describe } from 'react-desc';
13
4
  import { TreeViewPropTypes } from './react-desc-prop-types.js';
14
- import TreeViewContext, { defaultRelativeMouseCoord, defaultProps } from './TreeViewContext.js';
5
+ import TreeViewContext from './TreeViewContext.js';
15
6
  import { TreeDndPlugin, TreeDnD } from './plugins/dnd/TreeDndPlugin.js';
16
- import { useTree } from './utils/useTree.js';
7
+ import { useTreeview } from './config/useTreeview.js';
17
8
  import { TreeList } from './parts/TreeList.js';
18
- import { useNotifyExpandedChange } from './utils/group-expands-helpers.js';
19
- import { useInstanceRefActions } from './utils/useInstanceRefActions.js';
20
- import { useGlobalToggleAllExpandShortcut } from './utils/keyboard-helpers.js';
21
9
 
22
10
  var _TreeDnD, _TreeList;
23
-
24
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
25
-
26
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
27
11
  function TreeView(props) {
28
- const treeRef = useRef({
29
- actions: {}
30
- });
31
12
  const {
32
- data,
33
- plugins,
34
- onOrderChange,
35
- isMultiSelect,
36
- isSingleSelect,
37
- instanceRef,
38
- selection
13
+ plugins
39
14
  } = props;
40
- const [isDragging, setIsDragging] = useState(false);
41
- const [isKeyboardDragging, setIsKeyboardDragging] = useState(false);
42
- const [draggedItem, setDraggedItem] = useState(null);
43
- const [hoverItem, setHoverItem] = useState(null);
44
- const [relativeMouseCord, setRelativeMouseCord] = useState(defaultRelativeMouseCoord);
45
- const [uniqueTreeViewUUID] = useState(v4());
46
- const [latestToggledItem, setLatestToggledItem] = useState(null);
47
- const [selectedItem, setSelectedItem] = useState(null);
48
- const [focusedItem, setFocusedItem] = useState(null);
49
- const [selectedCheckboxes, setSelectedCheckboxes] = useState(_objectSpread({}, selection));
50
- const [expandedGroups, setExpandedGroups] = useState({});
51
15
  const {
52
- visibleItems,
53
- tree,
54
- treeRoot,
55
- triggerTreeRerender
56
- } = useTree(data, props, {
57
- setExpandedGroups
58
- });
59
- const virtualListRef = useRef(); // estimateSize should not be really required given what was stated on
60
- // https://github.com/tannerlinsley/react-virtual/issues/23
61
-
62
- const virtualListHelpers = useVirtual({
63
- size: visibleItems.length,
64
- parentRef: virtualListRef,
65
- overscan: 15
66
- });
67
- const withDragAndDrop = (plugins === null || plugins === void 0 ? void 0 : plugins.includes(TreeDndPlugin)) && onOrderChange;
68
- const withRadioChecks = isSingleSelect && !isMultiSelect;
69
- const withCheckboxChecks = !isSingleSelect && isMultiSelect;
70
- const ctx = {
71
- props,
72
- setRelativeMouseCord,
73
- relativeMouseCord,
74
- virtualListHelpers,
75
- virtualListRef,
76
- tree,
77
- treeRoot,
78
- visibleItems,
79
- triggerTreeRerender,
80
- isDragging,
81
- setIsDragging,
82
- isKeyboardDragging,
83
- setIsKeyboardDragging,
84
- draggedItem,
85
- setDraggedItem,
86
- withRadioChecks,
87
- withCheckboxChecks,
88
- withDragAndDrop,
89
- uniqueTreeViewUUID,
90
- selectedCheckboxes,
91
- setSelectedCheckboxes,
92
- selectedItem,
93
- setSelectedItem,
94
- expandedGroups,
95
- setExpandedGroups,
96
- latestToggledItem,
97
- setLatestToggledItem,
98
- focusedItem,
99
- setFocusedItem,
100
- hoverItem,
101
- setHoverItem
102
- }; // this hooks can't use the context because the context provider doesn't wrap this
103
- // as such we manually pass the second parameter as fit
104
-
105
- if (instanceRef) instanceRef.current = treeRef.current;
106
- useInstanceRefActions(treeRef.current, ctx);
107
- useGlobalToggleAllExpandShortcut(treeRoot, triggerTreeRerender, setLatestToggledItem);
108
- useNotifyExpandedChange(props, ctx);
109
- useEffect(() => {
110
- setTimeout(() => {
111
- ctx.props.onInstanceRefInitialized(treeRef);
112
- }, 100);
113
- }, []);
16
+ ctx
17
+ } = useTreeview(props);
114
18
  return /*#__PURE__*/_jsx(TreeViewContext.Provider, {
115
19
  value: ctx
116
20
  }, void 0, plugins !== null && plugins !== void 0 && plugins.includes(TreeDndPlugin) ? _TreeDnD || (_TreeDnD = /*#__PURE__*/_jsx(TreeDnD, {})) : _TreeList || (_TreeList = /*#__PURE__*/_jsx(TreeList, {})));
117
21
  }
118
- TreeView.defaultProps = defaultProps;
119
22
  const TreeViewWithSchema = describe(TreeView);
120
23
  TreeViewWithSchema.propTypes = TreeViewPropTypes;
121
24