@elliemae/ds-treeview 2.2.0-next.6 → 2.2.1

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 (61) hide show
  1. package/cjs/TreeView.js +6 -2
  2. package/cjs/TreeViewContext.js +8 -2
  3. package/cjs/config/useTreeview.js +13 -3
  4. package/cjs/index.js +1 -0
  5. package/cjs/parts/TreeItem.js +68 -43
  6. package/cjs/parts/TreeList.js +17 -15
  7. package/cjs/plugins/dnd/TreeDndPlugin.js +37 -0
  8. package/cjs/plugins/dnd/index.js +1 -0
  9. package/cjs/plugins/index.js +1 -0
  10. package/cjs/react-desc-prop-types.js +0 -1
  11. package/cjs/utils/dnd-helpers.js +140 -0
  12. package/cjs/utils/keyboard-helpers.js +5 -6
  13. package/cjs/utils/tree-helpers.js +2 -28
  14. package/cjs/utils/useDnDHelpers.js +220 -0
  15. package/cjs/utils/useTree.js +2 -4
  16. package/esm/TreeView.js +6 -2
  17. package/esm/TreeViewContext.js +8 -2
  18. package/esm/config/useTreeview.js +14 -4
  19. package/esm/index.js +1 -1
  20. package/esm/parts/TreeItem.js +67 -42
  21. package/esm/parts/TreeList.js +17 -16
  22. package/esm/plugins/dnd/TreeDndPlugin.js +33 -1
  23. package/esm/plugins/dnd/index.js +1 -1
  24. package/esm/plugins/index.js +1 -1
  25. package/esm/react-desc-prop-types.js +0 -1
  26. package/esm/utils/dnd-helpers.js +132 -0
  27. package/esm/utils/keyboard-helpers.js +5 -6
  28. package/esm/utils/tree-helpers.js +3 -28
  29. package/esm/utils/useDnDHelpers.js +216 -0
  30. package/esm/utils/useTree.js +3 -5
  31. package/package.json +19 -35
  32. package/types/TreeView.d.ts +1 -1
  33. package/types/parts/TreeItem.d.ts +11 -4
  34. package/types/parts/TreeList.d.ts +4 -0
  35. package/types/plugins/dnd/TreeDndPlugin.d.ts +2 -0
  36. package/types/react-desc-prop-types.d.ts +3 -17
  37. package/types/sharedTypes.d.ts +8 -1
  38. package/types/utils/dnd-helpers.d.ts +39 -0
  39. package/types/utils/keyboard-helpers.d.ts +1 -2
  40. package/types/utils/refs-helpers.d.ts +1 -1
  41. package/types/utils/tree-helpers.d.ts +1 -4
  42. package/types/utils/useDnDHelpers.d.ts +26 -0
  43. package/types/utils/useTree.d.ts +0 -1
  44. package/cjs/hoc/DnDTreeContext.js +0 -18
  45. package/cjs/hoc/SortableItemContext.js +0 -21
  46. package/cjs/hoc/WithConditionalDnDContext.js +0 -138
  47. package/cjs/hoc/WithDnDSortableItemContext.js +0 -70
  48. package/cjs/parts/DnDHandle.js +0 -46
  49. package/cjs/parts/DropIndicator.js +0 -75
  50. package/esm/hoc/DnDTreeContext.js +0 -14
  51. package/esm/hoc/SortableItemContext.js +0 -17
  52. package/esm/hoc/WithConditionalDnDContext.js +0 -128
  53. package/esm/hoc/WithDnDSortableItemContext.js +0 -61
  54. package/esm/parts/DnDHandle.js +0 -38
  55. package/esm/parts/DropIndicator.js +0 -68
  56. package/types/hoc/DnDTreeContext.d.ts +0 -14
  57. package/types/hoc/SortableItemContext.d.ts +0 -19
  58. package/types/hoc/WithConditionalDnDContext.d.ts +0 -4
  59. package/types/hoc/WithDnDSortableItemContext.d.ts +0 -4
  60. package/types/parts/DnDHandle.d.ts +0 -6
  61. package/types/parts/DropIndicator.d.ts +0 -10
@@ -19,13 +19,14 @@ var groupExpandsHelpers = require('./group-expands-helpers.js');
19
19
  // 36 home
20
20
  // 56 number 8
21
21
  // 106 (NumPad) *
22
- const useOnItemKeyDown = (item, draggableProps) => {
22
+ const useOnItemKeyDown = item => {
23
23
  const ctx = React.useContext(TreeViewContext["default"]);
24
24
  const {
25
25
  props: {
26
26
  onItemFocus
27
27
  },
28
28
  triggerTreeRerender,
29
+ isDragging,
29
30
  visibleItems,
30
31
  setFocusedItem,
31
32
  setSelectedItem,
@@ -54,12 +55,10 @@ const useOnItemKeyDown = (item, draggableProps) => {
54
55
  keyCode
55
56
  } = e;
56
57
 
57
- if (!(draggableProps && draggableProps.active)) {
58
+ if (!isDragging) {
58
59
  if (keyCode !== 9) e.preventDefault(); // allow tab/shift+tab
59
60
 
60
- if ((keyCode === 13 || keyCode === 32) && e.target.tagName === 'LI') {
61
- setSelectedItem(item);
62
- }
61
+ if (keyCode === 13 || keyCode === 32) setSelectedItem(item);
63
62
 
64
63
  if (keyCode === 37) {
65
64
  if (isGroup && isExpanded) {
@@ -147,7 +146,7 @@ const useOnItemKeyDown = (item, draggableProps) => {
147
146
  }
148
147
  }
149
148
  }
150
- }, [draggableProps, setSelectedItem, item, isGroup, isExpanded, triggerTreeRerender, updateUserExpandedState, scrollToIndex, setLatestToggledItem, visibleItems, setFocusedItem, onItemFocusCurry]);
149
+ }, [isDragging, setSelectedItem, item, isGroup, isExpanded, triggerTreeRerender, scrollToIndex, setLatestToggledItem, visibleItems, setFocusedItem, onItemFocusCurry, updateUserExpandedState]);
151
150
  return onItemKeyDown;
152
151
  };
153
152
  const useGlobalKeyboardListener = func => {
@@ -132,7 +132,7 @@ const getChildrenMatchesSearchQuery = function (parentNode) {
132
132
  */
133
133
 
134
134
  const enrichNodeModelInPlace = function (node) {
135
- var _node$model$children$, _node$model, _node$model$children;
135
+ var _node$model, _node$model$children;
136
136
 
137
137
  let searchQuery = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
138
138
  let selection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
@@ -148,7 +148,7 @@ const enrichNodeModelInPlace = function (node) {
148
148
  // this is useful to avoid having to always check both the flag and the children.length...
149
149
 
150
150
 
151
- if (typeof node.model.isGroup !== 'boolean' && ((_node$model$children$ = (_node$model = node.model) === null || _node$model === void 0 ? void 0 : (_node$model$children = _node$model.children) === null || _node$model$children === void 0 ? void 0 : _node$model$children.length) !== null && _node$model$children$ !== void 0 ? _node$model$children$ : 0) > 0) {
151
+ if (typeof node.model.isGroup !== 'boolean' && ((_node$model = node.model) === null || _node$model === void 0 ? void 0 : (_node$model$children = _node$model.children) === null || _node$model$children === void 0 ? void 0 : _node$model$children.length) > 0) {
152
152
  node.model.isGroup = true;
153
153
  }
154
154
 
@@ -180,35 +180,9 @@ const focusItem = item => {
180
180
 
181
181
  item === null || item === void 0 ? void 0 : (_item$nodeItemRef = item.nodeItemRef) === null || _item$nodeItemRef === void 0 ? void 0 : (_item$nodeItemRef$cur = _item$nodeItemRef.current) === null || _item$nodeItemRef$cur === void 0 ? void 0 : _item$nodeItemRef$cur.focus();
182
182
  };
183
- const flattenTreeForDnD = function (data) {
184
- let shouldDescend = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : () => true;
185
- const flattenedTree = [];
186
- let index = 0;
187
-
188
- const doit = function (node) {
189
- var _node$children$length, _node$children, _node$children2;
190
-
191
- let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
192
- let parentId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
193
- flattenedTree.push({
194
- uid: node.id.toString(),
195
- depth,
196
- realIndex: index,
197
- childrenCount: (_node$children$length = (_node$children = node.children) === null || _node$children === void 0 ? void 0 : _node$children.length) !== null && _node$children$length !== void 0 ? _node$children$length : 0,
198
- parentId: parentId !== null ? parentId.toString() : null,
199
- original: node
200
- });
201
- index += 1;
202
- if (shouldDescend(node)) (_node$children2 = node.children) === null || _node$children2 === void 0 ? void 0 : _node$children2.forEach(child => doit(child, depth + 1, node.id.toString()));
203
- };
204
-
205
- data.forEach(datum => doit(datum));
206
- return flattenedTree;
207
- };
208
183
 
209
184
  exports.cloneNode = cloneNode;
210
185
  exports.enrichNodeModelInPlace = enrichNodeModelInPlace;
211
- exports.flattenTreeForDnD = flattenTreeForDnD;
212
186
  exports.focusItem = focusItem;
213
187
  exports.getChildrenMatchesSearchQuery = getChildrenMatchesSearchQuery;
214
188
  exports.getItemsParentNode = getItemsParentNode;
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('core-js/modules/esnext.async-iterator.constructor.js');
6
+ require('core-js/modules/esnext.async-iterator.drop.js');
7
+ require('core-js/modules/esnext.iterator.constructor.js');
8
+ require('core-js/modules/esnext.iterator.drop.js');
9
+ require('core-js/modules/web.dom-collections.iterator.js');
10
+ var React = require('react');
11
+ var lodash = require('lodash');
12
+ var TreeViewContext = require('../TreeViewContext.js');
13
+ var treeHelpers = require('./tree-helpers.js');
14
+ var dndHelpers = require('./dnd-helpers.js');
15
+
16
+ const useDnDHelpers = () => {
17
+ const ctx = React.useContext(TreeViewContext["default"]);
18
+ const {
19
+ props: {
20
+ data,
21
+ onOrderChange,
22
+ restrictDragAndDrop
23
+ },
24
+ relativeMouseCord,
25
+ setRelativeMouseCord,
26
+ visibleItems,
27
+ isDragging,
28
+ setIsDragging,
29
+ isKeyboardDragging,
30
+ setIsKeyboardDragging,
31
+ setDraggedItem,
32
+ setFocusedItem,
33
+ triggerTreeRerender,
34
+ tree,
35
+ treeRoot,
36
+ draggedItem
37
+ } = ctx;
38
+ const onSortEnd = React.useCallback(_ref => {
39
+ let {
40
+ oldIndex,
41
+ newIndex,
42
+ isKeySorting
43
+ } = _ref;
44
+ const {
45
+ isBefore,
46
+ isAfter,
47
+ item: overItem,
48
+ isValid
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);
92
+
93
+ if (newParentNode.isRoot()) {
94
+ newItems = [...newParentNode.model.children];
95
+ } else {
96
+ newItems = [...treeRoot.model.children];
97
+ }
98
+ }
99
+
100
+ onOrderChange(newItems, data, draggedItem);
101
+ setIsDragging(false);
102
+ setDraggedItem(null);
103
+ triggerTreeRerender();
104
+ }
105
+ }, [data, draggedItem, relativeMouseCord, visibleItems, tree, treeRoot, onOrderChange, triggerTreeRerender, setIsDragging, setDraggedItem]);
106
+ const onSortStart = React.useCallback(() => {}, // ({ node, index, collection, isKeySorting }, event) => {
107
+ // console.log('node :>> ', node);
108
+ // console.log('isKeySorting :>> ', isKeySorting);
109
+ // console.log('event :>> ', event);
110
+ // const item = collection?.[index] || visibleItems[index];
111
+ // setIsDragging(true);
112
+ // setDraggedItem(item);
113
+ // },
114
+ []);
115
+ const updateBeforeSortStart = React.useCallback(beforeStartArgs => new Promise(resolve => {
116
+ const {
117
+ index,
118
+ isKeySorting
119
+ } = beforeStartArgs;
120
+ const item = visibleItems[index];
121
+ const {
122
+ isExpanded
123
+ } = item.node.model;
124
+
125
+ if (isExpanded) {
126
+ item.node.model.isExpanded = false;
127
+ triggerTreeRerender();
128
+ }
129
+
130
+ setRelativeMouseCord(TreeViewContext.defaultRelativeMouseCoord);
131
+ setIsKeyboardDragging(isKeySorting);
132
+ setIsDragging(true);
133
+ setDraggedItem(item);
134
+ treeHelpers.focusItem(null);
135
+ setFocusedItem(null);
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
139
+ // at the top/center/bottom of an element to choose where to move the element
140
+ // requested in the specs
141
+
142
+ const onMouseDragOverItem = React.useCallback(_ref2 => {
143
+ let {
144
+ event,
145
+ item,
146
+ itemIndex,
147
+ setIsDraggingOverThis,
148
+ openFolderOnHoverTimeout
149
+ } = _ref2;
150
+ const {
151
+ node
152
+ } = item;
153
+ const {
154
+ nodeItemRef
155
+ } = node.model;
156
+
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
+ }
207
+ }
208
+ } else {
209
+ setRelativeMouseCord(TreeViewContext.defaultRelativeMouseCoord);
210
+ }
211
+ }, [isDragging, isKeyboardDragging, draggedItem, restrictDragAndDrop, setRelativeMouseCord, triggerTreeRerender]);
212
+ return {
213
+ updateBeforeSortStart,
214
+ onSortStart,
215
+ onSortEnd,
216
+ onMouseDragOverItem
217
+ };
218
+ };
219
+
220
+ exports.useDnDHelpers = useDnDHelpers;
@@ -76,9 +76,9 @@ const useTree = (data, props, states) => {
76
76
  React.useEffect(() => {
77
77
  const parsedVisibleItems = [];
78
78
  const newExpandedHashMap = {};
79
- const shouldContinueWalking = true; // tree-model ask we return true if we want to continue walking
80
-
81
79
  treeRoot.walk(node => {
80
+ const shouldContinueWalking = true; // tree-model ask we return true if we want to continue walking
81
+
82
82
  treeHelpers.enrichNodeModelInPlace(node, searchQuery, selection, expanded);
83
83
  if (node.model.id && node.model.isGroup && node.model.isExpanded) newExpandedHashMap[node.model.id] = true;
84
84
  return shouldContinueWalking;
@@ -93,10 +93,8 @@ const useTree = (data, props, states) => {
93
93
  setExpandedGroups(newExpandedHashMap); // eslint-disable-next-line react-hooks/exhaustive-deps
94
94
  }, [treeRoot, rerenderItems, searchQuery, highlightOnlyQuery, selection, expanded, setExpandedGroups // onVisibleItemsChange // evaluate potential performance hit?
95
95
  ]);
96
- const flattenedItems = React.useMemo(() => treeHelpers.flattenTreeForDnD(data), [data]);
97
96
  return {
98
97
  visibleItems,
99
- flattenedItems,
100
98
  tree,
101
99
  treeRoot,
102
100
  rerenderItems,
package/esm/TreeView.js CHANGED
@@ -3,17 +3,21 @@ import 'react';
3
3
  import { describe } from 'react-desc';
4
4
  import { TreeViewPropTypes } from './react-desc-prop-types.js';
5
5
  import TreeViewContext from './TreeViewContext.js';
6
+ import { TreeDndPlugin, TreeDnD } from './plugins/dnd/TreeDndPlugin.js';
6
7
  import { useTreeview } from './config/useTreeview.js';
7
8
  import { TreeList } from './parts/TreeList.js';
8
9
 
9
- var _TreeList;
10
+ var _TreeDnD, _TreeList;
10
11
  function TreeView(props) {
12
+ const {
13
+ plugins
14
+ } = props;
11
15
  const {
12
16
  ctx
13
17
  } = useTreeview(props);
14
18
  return /*#__PURE__*/_jsx(TreeViewContext.Provider, {
15
19
  value: ctx
16
- }, void 0, _TreeList || (_TreeList = /*#__PURE__*/_jsx(TreeList, {})));
20
+ }, void 0, plugins !== null && plugins !== void 0 && plugins.includes(TreeDndPlugin) ? _TreeDnD || (_TreeDnD = /*#__PURE__*/_jsx(TreeDnD, {})) : _TreeList || (_TreeList = /*#__PURE__*/_jsx(TreeList, {})));
17
21
  }
18
22
  const TreeViewWithSchema = describe(TreeView);
19
23
  TreeViewWithSchema.propTypes = TreeViewPropTypes;
@@ -14,7 +14,6 @@ const defaultProps = {
14
14
  fluid: false,
15
15
  isMultiSelect: false,
16
16
  isSingleSelect: false,
17
- getIsDropValid: () => true,
18
17
  restrictDragAndDrop: false,
19
18
  showChildrenAmount: false,
20
19
  sortable: false,
@@ -55,10 +54,11 @@ const defaultRelativeMouseCoord = {
55
54
  const defaultContext = {
56
55
  props: defaultProps,
57
56
  visibleItems: [],
58
- flattenedItems: [],
59
57
  withRadioChecks: false,
60
58
  withCheckboxChecks: false,
61
59
  withDragAndDrop: false,
60
+ relativeMouseCord: defaultRelativeMouseCoord,
61
+ setRelativeMouseCord: noop,
62
62
  triggerTreeRerender: noop,
63
63
  tree: undefined,
64
64
  treeRoot: undefined,
@@ -69,6 +69,12 @@ const defaultContext = {
69
69
  scrollToIndex: noop
70
70
  },
71
71
  virtualListRef: undefined,
72
+ isDragging: false,
73
+ setIsDragging: noop,
74
+ isKeyboardDragging: false,
75
+ setIsKeyboardDragging: noop,
76
+ draggedItem: null,
77
+ setDraggedItem: noop,
72
78
  uniqueTreeViewUUID: '',
73
79
  selectedItem: null,
74
80
  setSelectedItem: noop,
@@ -10,7 +10,7 @@ import { useVirtual } from 'react-virtual';
10
10
  import { useMemoMergePropsWithDefault, useValidateTypescriptPropTypes } from '@elliemae/ds-props-helpers';
11
11
  import { v4 } from 'uuid';
12
12
  import { TreeViewPropTypes } from '../react-desc-prop-types.js';
13
- import { defaultProps } from '../TreeViewContext.js';
13
+ import { defaultProps, defaultRelativeMouseCoord } from '../TreeViewContext.js';
14
14
  import { TreeDndPlugin } from '../plugins/dnd/TreeDndPlugin.js';
15
15
  import { useTree } from '../utils/useTree.js';
16
16
  import { toggleItemExpand, useNotifyExpandedChange } from '../utils/group-expands-helpers.js';
@@ -42,7 +42,11 @@ const useTreeview = props => {
42
42
  selection,
43
43
  onInstanceRefInitialized
44
44
  } = propsWithDefault;
45
+ const [isDragging, setIsDragging] = useState(false);
46
+ const [isKeyboardDragging, setIsKeyboardDragging] = useState(false);
47
+ const [relativeMouseCord, setRelativeMouseCord] = useState(defaultRelativeMouseCoord);
45
48
  const [uniqueTreeViewUUID] = useState(v4());
49
+ const [draggedItem, setDraggedItem] = useState(null);
46
50
  const [hoverItem, setHoverItem] = useState(null);
47
51
  const [latestToggledItem, setLatestToggledItem] = useState(null);
48
52
  const [selectedItem, setSelectedItem] = useState(null);
@@ -51,7 +55,6 @@ const useTreeview = props => {
51
55
  const [expandedGroups, setExpandedGroups] = useState({});
52
56
  const {
53
57
  visibleItems,
54
- flattenedItems,
55
58
  tree,
56
59
  treeRoot,
57
60
  triggerTreeRerender,
@@ -73,13 +76,20 @@ const useTreeview = props => {
73
76
  const handleExpandGroup = useCallback(toggleItemExpand, []);
74
77
  const ctx = useMemo(() => ({
75
78
  props: propsWithDefault,
79
+ setRelativeMouseCord,
80
+ relativeMouseCord,
76
81
  virtualListHelpers,
77
82
  virtualListRef,
78
83
  tree,
79
84
  treeRoot,
80
85
  visibleItems,
81
- flattenedItems,
82
86
  triggerTreeRerender,
87
+ isDragging,
88
+ setIsDragging,
89
+ isKeyboardDragging,
90
+ setIsKeyboardDragging,
91
+ draggedItem,
92
+ setDraggedItem,
83
93
  withRadioChecks,
84
94
  withCheckboxChecks,
85
95
  withDragAndDrop,
@@ -98,7 +108,7 @@ const useTreeview = props => {
98
108
  setHoverItem,
99
109
  handleExpandGroup,
100
110
  updateUserExpandedState
101
- }), [propsWithDefault, virtualListHelpers, tree, treeRoot, visibleItems, flattenedItems, triggerTreeRerender, withRadioChecks, withCheckboxChecks, withDragAndDrop, uniqueTreeViewUUID, selectedCheckboxes, selectedItem, expandedGroups, latestToggledItem, focusedItem, hoverItem, handleExpandGroup, updateUserExpandedState]); // this hooks can't use the context because the context provider doesn't wrap this
111
+ }), [draggedItem, expandedGroups, focusedItem, handleExpandGroup, hoverItem, isDragging, isKeyboardDragging, latestToggledItem, propsWithDefault, relativeMouseCord, selectedCheckboxes, selectedItem, tree, treeRoot, triggerTreeRerender, uniqueTreeViewUUID, virtualListHelpers, visibleItems, withCheckboxChecks, withDragAndDrop, withRadioChecks, updateUserExpandedState]); // this hooks can't use the context because the context provider doesn't wrap this
102
112
  // as such we manually pass the second parameter as fit
103
113
 
104
114
  if (instanceRef) instanceRef.current = treeRef.current;
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { TreeView, TreeViewWithSchema } from './TreeView.js';
2
- export { TreeDndPlugin } from './plugins/dnd/TreeDndPlugin.js';
2
+ export { TreeDnD, TreeDndPlugin } from './plugins/dnd/TreeDndPlugin.js';
3
3
  export { TreeRovingPlugin } from './plugins/roving/TreeRovingPlugin.js';
4
4
  export { SelectablePluginTree } from './plugins/selectable/SelectablePluginTree.js';
5
5
  export { TreeToolbarPlugin } from './plugins/toolbar/TreeToolbarPlugin.js';
@@ -1,12 +1,15 @@
1
+ import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
2
+ import _jsx from '@babel/runtime/helpers/esm/jsx';
3
+ import 'core-js/modules/web.dom-collections.iterator.js';
1
4
  import 'core-js/modules/esnext.async-iterator.filter.js';
2
5
  import 'core-js/modules/esnext.iterator.constructor.js';
3
6
  import 'core-js/modules/esnext.iterator.filter.js';
4
7
  import 'core-js/modules/esnext.async-iterator.for-each.js';
5
8
  import 'core-js/modules/esnext.iterator.for-each.js';
6
- import _jsx from '@babel/runtime/helpers/esm/jsx';
7
- import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
8
- import { useContext, useMemo, useCallback } from 'react';
9
+ import { useRef, useState, useContext, useMemo, useCallback } from 'react';
10
+ import { SortableHandle, SortableElement } from '@elliemae/react-sortable-hoc';
9
11
  import { aggregatedClasses } from '@elliemae/ds-classnames';
12
+ import { GripperVertical } from '@elliemae/ds-icons';
10
13
  import RadioSelectable from './RadioSelectable.js';
11
14
  import CheckboxSelectable from './CheckboxSelectable.js';
12
15
  import TreeViewContext from '../TreeViewContext.js';
@@ -17,10 +20,6 @@ import { Icon } from './Icon.js';
17
20
  import { ExpandCaret } from './ExpandCaret.js';
18
21
  import { TreeItemText } from './TreeItemText.js';
19
22
  import { treeItemBlockName, DSTreeViewPrefix } from '../config/cssClassesConstants.js';
20
- import { DnDHandle } from './DnDHandle.js';
21
- import { withDnDSortableItemContext } from '../hoc/WithDnDSortableItemContext.js';
22
- import { SortableItemContext, DropIndicatorPosition } from '../hoc/SortableItemContext.js';
23
- import DropIndicator from './DropIndicator.js';
24
23
  import { jsxs } from 'react/jsx-runtime';
25
24
 
26
25
  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; }
@@ -33,6 +32,23 @@ const TreeListItem = aggregatedClasses('li', {
33
32
  })(treeItemBlockName);
34
33
  const TreeListItemAddonsWrapper = aggregatedClasses('div')(treeItemBlockName, 'addons' // temp fix untill we move to styled components
35
34
  );
35
+ const DnDHandle = SortableHandle(_ref => {
36
+ let {
37
+ id
38
+ } = _ref;
39
+ return /*#__PURE__*/_jsx(GripperVertical, {
40
+ "data-testid": "drag-handle",
41
+ className: "drag-handle",
42
+ size: "s",
43
+ onMouseDown: e => {
44
+ // prevent focusing the drag-handle to avoid borders
45
+ if (e) {
46
+ e.preventDefault();
47
+ }
48
+ },
49
+ tabIndex: "0"
50
+ }, "".concat(id, "-drag-handle"));
51
+ });
36
52
  const TreeItem = props => {
37
53
  const {
38
54
  item,
@@ -40,7 +56,7 @@ const TreeItem = props => {
40
56
  itemWrapperStyle = {},
41
57
  virtualItemRef,
42
58
  spaceForNestingLevel = 1.5,
43
- isDragOverlay = false
59
+ onMouseDragOverItem
44
60
  } = props;
45
61
  const {
46
62
  id,
@@ -49,10 +65,9 @@ const TreeItem = props => {
49
65
  const {
50
66
  nodeItemRef
51
67
  } = node.model;
52
- const {
53
- draggableProps
54
- } = useContext(SortableItemContext);
55
- const onItemKeyDown = useOnItemKeyDown(item, draggableProps);
68
+ const openFolderOnHoverTimeout = useRef(null);
69
+ const [isDraggingOverThis, setIsDraggingOverThis] = useState(false);
70
+ const onItemKeyDown = useOnItemKeyDown(item);
56
71
  const ctx = useContext(TreeViewContext);
57
72
  const {
58
73
  props: {
@@ -63,12 +78,14 @@ const TreeItem = props => {
63
78
  rowSize,
64
79
  labelOverflow
65
80
  },
81
+ relativeMouseCord,
66
82
  withRadioChecks,
67
83
  withCheckboxChecks,
68
84
  withDragAndDrop,
69
85
  selectedItem,
70
86
  selectedCheckboxes,
71
87
  hoverItem,
88
+ isDragging,
72
89
  virtualListHelpers: {
73
90
  scrollToIndex
74
91
  },
@@ -78,27 +95,24 @@ const TreeItem = props => {
78
95
  } = ctx; // Sortable Items needs to update
79
96
  // - the virtual list ref (for react-virtual requirement)
80
97
  // - nodeItemRef which is a prop avaiable in the TreeModel nodes
81
- // - the drag and drop node reference
82
98
 
83
99
  const setItemRefs = ref => {
84
- setMultipleRefs(virtualItemRef, !isDragOverlay && nodeItemRef, !isDragOverlay && draggableProps && draggableProps.setNodeRef)(ref);
100
+ setMultipleRefs(virtualItemRef, nodeItemRef)(ref);
85
101
  };
86
102
 
87
- const dropIndicatorPosition = draggableProps && draggableProps.shouldShowDropIndicatorPosition && draggableProps.dropIndicatorPosition;
88
- const isDropIndicatorPositionInside = dropIndicatorPosition === DropIndicatorPosition.Inside;
89
- const isDragging = draggableProps && draggableProps.isDragging;
90
- const isDropValid = draggableProps && draggableProps.isDropValid;
91
103
  const dropPreviewClassName = useMemo(() => {
92
- if (isDropIndicatorPositionInside) {
93
- const className = "".concat(DSTreeViewPrefix, "-").concat(treeItemBlockName).concat(isDropValid ? '--valid' : '--invalid', "-drop-in");
94
- return className;
95
- }
96
-
97
- return '';
98
- }, [isDropIndicatorPositionInside, isDropValid]);
99
- const dragOverlayClassName = useMemo(() => isDragOverlay ? 'drag-overlay' : '', [isDragOverlay]);
100
- const selectedClassName = useMemo(() => (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id && !isDragOverlay ? 'selected' : '', [isDragOverlay, item.id, selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id]);
101
- const hoverClassName = useMemo(() => !isDragOverlay && ((hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.id) === item.id || isDragging) ? 'hover' : '', [isDragOverlay, hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.id, item.id, isDragging]);
104
+ if (!withDragAndDrop || !relativeMouseCord || !isDraggingOverThis) return '';
105
+ const {
106
+ isBefore,
107
+ isAfter,
108
+ isValid
109
+ } = relativeMouseCord;
110
+ let className = "".concat(DSTreeViewPrefix, "-").concat(treeItemBlockName).concat(isValid ? '--valid' : '--invalid');
111
+ if (isBefore) className += '-drop-before';else if (isAfter) className += '-drop-after';else className += '-drop-in';
112
+ return className;
113
+ }, [withDragAndDrop, relativeMouseCord, isDraggingOverThis]);
114
+ const selectedClassName = useMemo(() => (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id ? 'selected' : '', [item.id, selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id]);
115
+ const hoverClassName = useMemo(() => (hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.id) !== item.id || withDragAndDrop && isDragging ? '' : 'hover', [hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.id, item.id, withDragAndDrop, isDragging]);
102
116
  const fixedHeightClassname = useMemo(() => {
103
117
  if (labelOverflow === 'truncate') {
104
118
  if (rowSize === 'compact') return "".concat(DSTreeViewPrefix, "-").concat(treeItemBlockName, "--fixed-height-compact");
@@ -107,7 +121,7 @@ const TreeItem = props => {
107
121
 
108
122
  return '';
109
123
  }, [labelOverflow, rowSize]);
110
- const finalClassName = useMemo(() => "".concat(dropPreviewClassName, " ").concat(fixedHeightClassname, " ").concat(selectedClassName, " ").concat(hoverClassName, " ").concat(dragOverlayClassName), [dropPreviewClassName, selectedClassName, hoverClassName, fixedHeightClassname, dragOverlayClassName]);
124
+ const finalClassName = useMemo(() => "".concat(dropPreviewClassName, " ").concat(fixedHeightClassname, " ").concat(selectedClassName, " ").concat(hoverClassName), [dropPreviewClassName, selectedClassName, hoverClassName, fixedHeightClassname]);
111
125
  const handleItemClick = useCallback(e => {
112
126
  onItemClick(item, e);
113
127
  setFocusedItem(item);
@@ -126,7 +140,23 @@ const TreeItem = props => {
126
140
  }, [item, scrollToIndex, onItemClick, onItemFocus, setSelectedItem, setFocusedItem]);
127
141
  return /*#__PURE__*/jsxs(TreeListItem, {
128
142
  tabIndex: 0,
143
+ onMouseMove: event => {
144
+ if (withDragAndDrop && onMouseDragOverItem) {
145
+ onMouseDragOverItem({
146
+ event,
147
+ item,
148
+ itemIndex,
149
+ setIsDraggingOverThis,
150
+ openFolderOnHoverTimeout
151
+ });
152
+ }
153
+ },
129
154
  onMouseLeave: () => {
155
+ if (withDragAndDrop) {
156
+ setIsDraggingOverThis(false);
157
+ if (openFolderOnHoverTimeout !== null && openFolderOnHoverTimeout !== void 0 && openFolderOnHoverTimeout.current) clearTimeout(openFolderOnHoverTimeout.current);
158
+ }
159
+
130
160
  if ((hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.id) === item.id) {
131
161
  setHoverItem(null);
132
162
  }
@@ -135,7 +165,6 @@ const TreeItem = props => {
135
165
  setHoverItem(item);
136
166
  },
137
167
  onClick: handleItemClick,
138
- onBlur: () => setFocusedItem(null),
139
168
  ref: setItemRefs,
140
169
  style: _objectSpread({}, itemWrapperStyle),
141
170
  className: finalClassName,
@@ -145,29 +174,25 @@ const TreeItem = props => {
145
174
  "data-index": item.virtualIndex,
146
175
  onKeyDown: onItemKeyDown,
147
176
  children: [/*#__PURE__*/_jsx(TreeListItemAddonsWrapper, {}, void 0, withDragAndDrop ? /*#__PURE__*/_jsx(DnDHandle, {
148
- id: id,
149
- isDragOverlay: isDragOverlay
150
- }) : null, withRadioChecks && !isDragOverlay ? /*#__PURE__*/_jsx(RadioSelectable, {
177
+ id: id
178
+ }) : null, withRadioChecks ? /*#__PURE__*/_jsx(RadioSelectable, {
151
179
  item: item,
152
180
  itemIndex: itemIndex
153
- }) : null, withCheckboxChecks && !isDragOverlay ? /*#__PURE__*/_jsx(CheckboxSelectable, {
181
+ }) : null, withCheckboxChecks ? /*#__PURE__*/_jsx(CheckboxSelectable, {
154
182
  item: item,
155
183
  itemIndex: itemIndex
156
- }) : null, !isDragOverlay && /*#__PURE__*/_jsx(NestingSpace, {
184
+ }) : null, /*#__PURE__*/_jsx(NestingSpace, {
157
185
  item: item,
158
186
  spaceForNestingLevel: spaceForNestingLevel
159
- }), !isDragOverlay && /*#__PURE__*/_jsx(ExpandCaret, {
187
+ }), /*#__PURE__*/_jsx(ExpandCaret, {
160
188
  item: item
161
189
  }), !disableIcons ? /*#__PURE__*/_jsx(Icon, {
162
190
  item: item
163
191
  }) : null), /*#__PURE__*/_jsx(TreeItemText, {
164
192
  item: item
165
- }), rightAddons && /*#__PURE__*/_jsx(TreeListItemAddonsWrapper, {}, void 0, typeof rightAddons === 'function' ? rightAddons(item) : rightAddons), /*#__PURE__*/_jsx(DropIndicator, {
166
- dropIndicatorPosition: dropIndicatorPosition,
167
- isDropValid: isDropValid
168
- })]
193
+ }), rightAddons && /*#__PURE__*/_jsx(TreeListItemAddonsWrapper, {}, void 0, typeof rightAddons === 'function' ? rightAddons(item) : rightAddons)]
169
194
  });
170
195
  };
171
- var TreeItem$1 = withDnDSortableItemContext(TreeItem);
196
+ const DragAndDropTreeItem = SortableElement(TreeItem);
172
197
 
173
- export { TreeItem, TreeListItem, TreeListItemAddonsWrapper, TreeItem$1 as default };
198
+ export { DragAndDropTreeItem, TreeItem, TreeListItem, TreeListItemAddonsWrapper };