tree-element 0.1.0
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/LICENSE +201 -0
- package/README.md +221 -0
- package/lib/animation.d.ts +5 -0
- package/lib/animation.d.ts.map +1 -0
- package/lib/animation.js +39 -0
- package/lib/animation.js.map +1 -0
- package/lib/classNames.d.ts +34 -0
- package/lib/classNames.d.ts.map +1 -0
- package/lib/classNames.js +39 -0
- package/lib/classNames.js.map +1 -0
- package/lib/dataLoader.d.ts +25 -0
- package/lib/dataLoader.d.ts.map +1 -0
- package/lib/dataLoader.js +69 -0
- package/lib/dataLoader.js.map +1 -0
- package/lib/dragAndDropHandler/binarySearch.d.ts +3 -0
- package/lib/dragAndDropHandler/binarySearch.d.ts.map +1 -0
- package/lib/dragAndDropHandler/binarySearch.js +23 -0
- package/lib/dragAndDropHandler/binarySearch.js.map +1 -0
- package/lib/dragAndDropHandler/dragElement.d.ts +20 -0
- package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -0
- package/lib/dragAndDropHandler/dragElement.js +39 -0
- package/lib/dragAndDropHandler/dragElement.js.map +1 -0
- package/lib/dragAndDropHandler/generateHitAreas.d.ts +13 -0
- package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -0
- package/lib/dragAndDropHandler/generateHitAreas.js +130 -0
- package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -0
- package/lib/dragAndDropHandler/index.d.ts +76 -0
- package/lib/dragAndDropHandler/index.d.ts.map +1 -0
- package/lib/dragAndDropHandler/index.js +311 -0
- package/lib/dragAndDropHandler/index.js.map +1 -0
- package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts +11 -0
- package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -0
- package/lib/dragAndDropHandler/iterateVisibleNodes.js +54 -0
- package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -0
- package/lib/dragAndDropHandler/types.d.ts +11 -0
- package/lib/dragAndDropHandler/types.d.ts.map +1 -0
- package/lib/elementsRenderer.d.ts +53 -0
- package/lib/elementsRenderer.d.ts.map +1 -0
- package/lib/elementsRenderer.js +251 -0
- package/lib/elementsRenderer.js.map +1 -0
- package/lib/events.d.ts +224 -0
- package/lib/events.d.ts.map +1 -0
- package/lib/index.d.ts +367 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1098 -0
- package/lib/index.js.map +1 -0
- package/lib/keyHandler.d.ts +29 -0
- package/lib/keyHandler.d.ts.map +1 -0
- package/lib/keyHandler.js +107 -0
- package/lib/keyHandler.js.map +1 -0
- package/lib/methodTypes.d.ts +21 -0
- package/lib/methodTypes.d.ts.map +1 -0
- package/lib/mouseHandler.d.ts +60 -0
- package/lib/mouseHandler.d.ts.map +1 -0
- package/lib/mouseHandler.js +283 -0
- package/lib/mouseHandler.js.map +1 -0
- package/lib/mouseUtils.d.ts +9 -0
- package/lib/mouseUtils.d.ts.map +1 -0
- package/lib/mouseUtils.js +15 -0
- package/lib/mouseUtils.js.map +1 -0
- package/lib/node.d.ts +320 -0
- package/lib/node.d.ts.map +1 -0
- package/lib/node.js +737 -0
- package/lib/node.js.map +1 -0
- package/lib/nodeElement/borderDropHint.d.ts +9 -0
- package/lib/nodeElement/borderDropHint.d.ts.map +1 -0
- package/lib/nodeElement/borderDropHint.js +24 -0
- package/lib/nodeElement/borderDropHint.js.map +1 -0
- package/lib/nodeElement/folderElement.d.ts +22 -0
- package/lib/nodeElement/folderElement.d.ts.map +1 -0
- package/lib/nodeElement/folderElement.js +100 -0
- package/lib/nodeElement/folderElement.js.map +1 -0
- package/lib/nodeElement/ghostDropHint.d.ts +18 -0
- package/lib/nodeElement/ghostDropHint.d.ts.map +1 -0
- package/lib/nodeElement/ghostDropHint.js +67 -0
- package/lib/nodeElement/ghostDropHint.js.map +1 -0
- package/lib/nodeElement/index.d.ts +28 -0
- package/lib/nodeElement/index.d.ts.map +1 -0
- package/lib/nodeElement/index.js +66 -0
- package/lib/nodeElement/index.js.map +1 -0
- package/lib/nodeUtils.d.ts +7 -0
- package/lib/nodeUtils.d.ts.map +1 -0
- package/lib/nodeUtils.js +4 -0
- package/lib/nodeUtils.js.map +1 -0
- package/lib/options.d.ts +310 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/package.json +3 -0
- package/lib/positionUtils.d.ts +8 -0
- package/lib/positionUtils.d.ts.map +1 -0
- package/lib/positionUtils.js +14 -0
- package/lib/positionUtils.js.map +1 -0
- package/lib/requestUrl.d.ts +9 -0
- package/lib/requestUrl.d.ts.map +1 -0
- package/lib/requestUrl.js +37 -0
- package/lib/requestUrl.js.map +1 -0
- package/lib/saveStateHandler.d.ts +46 -0
- package/lib/saveStateHandler.d.ts.map +1 -0
- package/lib/saveStateHandler.js +204 -0
- package/lib/saveStateHandler.js.map +1 -0
- package/lib/scrollHandler/containerScrollParent.d.ts +12 -0
- package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -0
- package/lib/scrollHandler/containerScrollParent.js +49 -0
- package/lib/scrollHandler/containerScrollParent.js.map +1 -0
- package/lib/scrollHandler/createScrollParent.d.ts +4 -0
- package/lib/scrollHandler/createScrollParent.d.ts.map +1 -0
- package/lib/scrollHandler/createScrollParent.js +38 -0
- package/lib/scrollHandler/createScrollParent.js.map +1 -0
- package/lib/scrollHandler/documentScrollParent.d.ts +22 -0
- package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -0
- package/lib/scrollHandler/documentScrollParent.js +71 -0
- package/lib/scrollHandler/documentScrollParent.js.map +1 -0
- package/lib/scrollHandler/scrollParent.d.ts +26 -0
- package/lib/scrollHandler/scrollParent.d.ts.map +1 -0
- package/lib/scrollHandler/scrollParent.js +79 -0
- package/lib/scrollHandler/scrollParent.js.map +1 -0
- package/lib/scrollHandler.d.ts +20 -0
- package/lib/scrollHandler.d.ts.map +1 -0
- package/lib/scrollHandler.js +41 -0
- package/lib/scrollHandler.js.map +1 -0
- package/lib/selectNodeHandler.d.ts +38 -0
- package/lib/selectNodeHandler.d.ts.map +1 -0
- package/lib/selectNodeHandler.js +166 -0
- package/lib/selectNodeHandler.js.map +1 -0
- package/lib/setDefaultOptions.d.ts +4 -0
- package/lib/setDefaultOptions.d.ts.map +1 -0
- package/lib/setDefaultOptions.js +86 -0
- package/lib/setDefaultOptions.js.map +1 -0
- package/lib/triggerCustomEvent.d.ts +3 -0
- package/lib/triggerCustomEvent.d.ts.map +1 -0
- package/lib/triggerCustomEvent.js +13 -0
- package/lib/triggerCustomEvent.js.map +1 -0
- package/lib/util.d.ts +3 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +5 -0
- package/lib/util.js.map +1 -0
- package/lib/version.d.ts +3 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +5 -0
- package/lib/version.js.map +1 -0
- package/package.json +110 -0
- package/src/animation.ts +48 -0
- package/src/classNames.ts +70 -0
- package/src/dataLoader.ts +92 -0
- package/src/dragAndDropHandler/binarySearch.ts +27 -0
- package/src/dragAndDropHandler/dragElement.ts +62 -0
- package/src/dragAndDropHandler/generateHitAreas.ts +195 -0
- package/src/dragAndDropHandler/index.ts +442 -0
- package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
- package/src/dragAndDropHandler/types.ts +12 -0
- package/src/elementsRenderer.ts +367 -0
- package/src/events.ts +237 -0
- package/src/header.txt +16 -0
- package/src/index.ts +1290 -0
- package/src/keyHandler.ts +139 -0
- package/src/methodTypes.ts +44 -0
- package/src/mouseHandler.ts +391 -0
- package/src/mouseUtils.ts +23 -0
- package/src/node.ts +831 -0
- package/src/nodeElement/borderDropHint.ts +37 -0
- package/src/nodeElement/folderElement.ts +140 -0
- package/src/nodeElement/ghostDropHint.ts +84 -0
- package/src/nodeElement/index.ts +103 -0
- package/src/nodeUtils.ts +12 -0
- package/src/options.ts +325 -0
- package/src/positionUtils.ts +18 -0
- package/src/requestUrl.ts +41 -0
- package/src/saveStateHandler.ts +279 -0
- package/src/scrollHandler/containerScrollParent.ts +71 -0
- package/src/scrollHandler/createScrollParent.ts +51 -0
- package/src/scrollHandler/documentScrollParent.ts +104 -0
- package/src/scrollHandler/scrollParent.ts +117 -0
- package/src/scrollHandler.ts +55 -0
- package/src/selectNodeHandler.ts +192 -0
- package/src/setDefaultOptions.ts +79 -0
- package/src/triggerCustomEvent.ts +14 -0
- package/src/util.ts +5 -0
- package/src/version.ts +4 -0
- package/tree_element.css +195 -0
- package/tree_element.debug.js +4153 -0
- package/tree_element.debug.js.map +1 -0
- package/tree_element.js +21 -0
- package/tree_element.js.map +1 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Html-tree 0.1.0
|
|
3
|
+
|
|
4
|
+
Copyright 2026 Marco Braak
|
|
5
|
+
|
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
you may not use this file except in compliance with the License.
|
|
8
|
+
You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
See the License for the specific language governing permissions and
|
|
16
|
+
limitations under the License.
|
|
17
|
+
@license
|
|
18
|
+
|
|
19
|
+
*/
|
|
20
|
+
import createClassNames from './classNames.js';
|
|
21
|
+
import DataLoader from './dataLoader.js';
|
|
22
|
+
import { DragAndDropHandler } from './dragAndDropHandler/index.js';
|
|
23
|
+
import ElementsRenderer from './elementsRenderer.js';
|
|
24
|
+
import KeyHandler from './keyHandler.js';
|
|
25
|
+
import MouseHandler from './mouseHandler.js';
|
|
26
|
+
import { Node } from './node.js';
|
|
27
|
+
import NodeElement from './nodeElement/index.js';
|
|
28
|
+
import FolderElement from './nodeElement/folderElement.js';
|
|
29
|
+
import { getOffsetTop } from './positionUtils.js';
|
|
30
|
+
import RequestUrl from './requestUrl.js';
|
|
31
|
+
import SaveStateHandler from './saveStateHandler.js';
|
|
32
|
+
import ScrollHandler from './scrollHandler.js';
|
|
33
|
+
import SelectNodeHandler from './selectNodeHandler.js';
|
|
34
|
+
import setDefaultOptions from './setDefaultOptions.js';
|
|
35
|
+
import triggerCustomEvent from './triggerCustomEvent.js';
|
|
36
|
+
import version from './version.js';
|
|
37
|
+
|
|
38
|
+
// The types that appear in the public api. Consumers cannot import them from
|
|
39
|
+
// the submodules directly, because those are not exposed in package.json.
|
|
40
|
+
// Type only, so that the iife build keeps exposing the TreeElement class itself
|
|
41
|
+
// as its global, instead of an object of named exports.
|
|
42
|
+
|
|
43
|
+
class TreeElement {
|
|
44
|
+
/** @hidden */
|
|
45
|
+
tree;
|
|
46
|
+
_classNames;
|
|
47
|
+
_dataLoader;
|
|
48
|
+
_dndHandler;
|
|
49
|
+
_htmlElement;
|
|
50
|
+
_isInitialized;
|
|
51
|
+
_keyHandler;
|
|
52
|
+
_mouseHandler;
|
|
53
|
+
_nodeMap;
|
|
54
|
+
_options;
|
|
55
|
+
_renderer;
|
|
56
|
+
_saveStateHandler;
|
|
57
|
+
_scrollHandler;
|
|
58
|
+
_selectNodeHandler;
|
|
59
|
+
_triggerEventProvider;
|
|
60
|
+
|
|
61
|
+
/** @hidden */
|
|
62
|
+
constructor({
|
|
63
|
+
htmlElement,
|
|
64
|
+
overrideTriggerEventProvider,
|
|
65
|
+
...options
|
|
66
|
+
}) {
|
|
67
|
+
this._htmlElement = htmlElement;
|
|
68
|
+
this._options = setDefaultOptions(htmlElement, options);
|
|
69
|
+
this._classNames = createClassNames(this._options);
|
|
70
|
+
this._triggerEventProvider = overrideTriggerEventProvider ?? triggerCustomEvent;
|
|
71
|
+
this._isInitialized = false;
|
|
72
|
+
this.tree = new Node({}, true);
|
|
73
|
+
this._nodeMap = new WeakMap();
|
|
74
|
+
const {
|
|
75
|
+
autoEscape,
|
|
76
|
+
buttonLeft,
|
|
77
|
+
closedIcon,
|
|
78
|
+
dataFilter,
|
|
79
|
+
dragAndDrop,
|
|
80
|
+
keyboardSupport,
|
|
81
|
+
onCanMove,
|
|
82
|
+
onCanMoveTo,
|
|
83
|
+
onCreateLi,
|
|
84
|
+
onDragMove,
|
|
85
|
+
onDragStop,
|
|
86
|
+
onGetStateFromStorage,
|
|
87
|
+
onIsMoveHandle,
|
|
88
|
+
onSetStateFromStorage,
|
|
89
|
+
openedIcon,
|
|
90
|
+
openFolderDelay,
|
|
91
|
+
rtl,
|
|
92
|
+
saveState: saveStateOption,
|
|
93
|
+
showEmptyFolder,
|
|
94
|
+
slide,
|
|
95
|
+
tabIndex
|
|
96
|
+
} = this._options;
|
|
97
|
+
const classNames = this._classNames;
|
|
98
|
+
const closeNode = this.closeNode.bind(this);
|
|
99
|
+
const getNodeElement = this._getNodeElement.bind(this);
|
|
100
|
+
const getNodeElementForNode = this._getNodeElementForNode.bind(this);
|
|
101
|
+
const getNodeById = this.getNodeById.bind(this);
|
|
102
|
+
const getSelectedNode = this.getSelectedNode.bind(this);
|
|
103
|
+
const getTree = this.getTree.bind(this);
|
|
104
|
+
const isFocusOnTree = this._isFocusOnTree.bind(this);
|
|
105
|
+
const loadData = this.loadData.bind(this);
|
|
106
|
+
const openNode = this.openNode.bind(this);
|
|
107
|
+
const openParents = this._openParents.bind(this);
|
|
108
|
+
const refreshElements = this._refreshElements.bind(this);
|
|
109
|
+
const refreshHitAreas = this.refreshHitAreas.bind(this);
|
|
110
|
+
const setNodeElement = this._setNodeElement.bind(this);
|
|
111
|
+
const treeElement = this._htmlElement;
|
|
112
|
+
const triggerEvent = this._triggerEvent.bind(this);
|
|
113
|
+
const saveState = () => {
|
|
114
|
+
saveStateHandler.saveState();
|
|
115
|
+
};
|
|
116
|
+
const selectNodeHandler = new SelectNodeHandler({
|
|
117
|
+
getNodeById,
|
|
118
|
+
getNodeElementForNode,
|
|
119
|
+
getOnCanSelectNode: () => this._options.onCanSelectNode,
|
|
120
|
+
getSelectable: () => this._options.selectable,
|
|
121
|
+
openParents,
|
|
122
|
+
saveState,
|
|
123
|
+
triggerEvent
|
|
124
|
+
});
|
|
125
|
+
const addToSelection = selectNodeHandler.addToSelection.bind(selectNodeHandler);
|
|
126
|
+
const getSelectedNodes = selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
|
|
127
|
+
const isNodeSelected = selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
|
|
128
|
+
const removeFromSelection = selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
|
|
129
|
+
const selectNode = selectNodeHandler.selectSingleNode.bind(selectNodeHandler);
|
|
130
|
+
const getMouseDelay = () => this._options.startDndDelay ?? 0;
|
|
131
|
+
const dataLoader = new DataLoader({
|
|
132
|
+
classNames,
|
|
133
|
+
dataFilter,
|
|
134
|
+
loadData,
|
|
135
|
+
treeElement,
|
|
136
|
+
triggerEvent
|
|
137
|
+
});
|
|
138
|
+
const saveStateHandler = new SaveStateHandler({
|
|
139
|
+
addToSelection,
|
|
140
|
+
getNodeById,
|
|
141
|
+
getSelectedNodes,
|
|
142
|
+
getTree,
|
|
143
|
+
onGetStateFromStorage,
|
|
144
|
+
onSetStateFromStorage,
|
|
145
|
+
openNode,
|
|
146
|
+
refreshElements,
|
|
147
|
+
removeFromSelection,
|
|
148
|
+
saveState: saveStateOption
|
|
149
|
+
});
|
|
150
|
+
const scrollHandler = new ScrollHandler({
|
|
151
|
+
refreshHitAreas,
|
|
152
|
+
treeElement
|
|
153
|
+
});
|
|
154
|
+
const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
|
|
155
|
+
const dndHandler = new DragAndDropHandler({
|
|
156
|
+
autoEscape,
|
|
157
|
+
classNames,
|
|
158
|
+
getNodeElement,
|
|
159
|
+
getNodeElementForNode,
|
|
160
|
+
getScrollLeft,
|
|
161
|
+
getTree,
|
|
162
|
+
onCanMove,
|
|
163
|
+
onCanMoveTo,
|
|
164
|
+
onDragMove,
|
|
165
|
+
onDragStop,
|
|
166
|
+
onIsMoveHandle,
|
|
167
|
+
openFolderDelay,
|
|
168
|
+
openNode,
|
|
169
|
+
refreshElements,
|
|
170
|
+
slide,
|
|
171
|
+
treeElement,
|
|
172
|
+
triggerEvent
|
|
173
|
+
});
|
|
174
|
+
const keyHandler = new KeyHandler({
|
|
175
|
+
closeNode,
|
|
176
|
+
getSelectedNode,
|
|
177
|
+
isFocusOnTree,
|
|
178
|
+
keyboardSupport,
|
|
179
|
+
openNode,
|
|
180
|
+
selectNode
|
|
181
|
+
});
|
|
182
|
+
const renderer = new ElementsRenderer({
|
|
183
|
+
autoEscape,
|
|
184
|
+
buttonLeft,
|
|
185
|
+
classNames,
|
|
186
|
+
closedIcon,
|
|
187
|
+
dragAndDrop,
|
|
188
|
+
getTree,
|
|
189
|
+
htmlElement: treeElement,
|
|
190
|
+
isNodeSelected,
|
|
191
|
+
onCreateLi,
|
|
192
|
+
openedIcon,
|
|
193
|
+
rtl,
|
|
194
|
+
setNodeElement,
|
|
195
|
+
showEmptyFolder,
|
|
196
|
+
tabIndex
|
|
197
|
+
});
|
|
198
|
+
const getNode = this.getNode.bind(this);
|
|
199
|
+
const onMouseCapture = this._mouseCapture.bind(this);
|
|
200
|
+
const onMouseDrag = this._mouseDrag.bind(this);
|
|
201
|
+
const onMouseStart = this._mouseStart.bind(this);
|
|
202
|
+
const onMouseStop = this._mouseStop.bind(this);
|
|
203
|
+
const mouseHandler = new MouseHandler({
|
|
204
|
+
classNames,
|
|
205
|
+
element: treeElement,
|
|
206
|
+
getMouseDelay,
|
|
207
|
+
getNode,
|
|
208
|
+
onClickButton: this.toggle.bind(this),
|
|
209
|
+
onClickTitle: selectNode,
|
|
210
|
+
onMouseCapture,
|
|
211
|
+
onMouseDrag,
|
|
212
|
+
onMouseStart,
|
|
213
|
+
onMouseStop,
|
|
214
|
+
triggerEvent,
|
|
215
|
+
useContextMenu: this._options.useContextMenu
|
|
216
|
+
});
|
|
217
|
+
this._dataLoader = dataLoader;
|
|
218
|
+
this._dndHandler = dndHandler;
|
|
219
|
+
this._keyHandler = keyHandler;
|
|
220
|
+
this._mouseHandler = mouseHandler;
|
|
221
|
+
this._renderer = renderer;
|
|
222
|
+
this._saveStateHandler = saveStateHandler;
|
|
223
|
+
this._scrollHandler = scrollHandler;
|
|
224
|
+
this._selectNodeHandler = selectNodeHandler;
|
|
225
|
+
this._initData();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Adds a sibling after a node.
|
|
230
|
+
*
|
|
231
|
+
* @returns The new node, or `null` when the node has no parent.
|
|
232
|
+
* @group Changing the tree
|
|
233
|
+
*/
|
|
234
|
+
addNodeAfter(nodeData, existingNode) {
|
|
235
|
+
const newNode = existingNode.addAfter(nodeData);
|
|
236
|
+
if (newNode) {
|
|
237
|
+
this._refreshElements(existingNode.parent);
|
|
238
|
+
}
|
|
239
|
+
return newNode;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Adds a sibling before a node.
|
|
244
|
+
*
|
|
245
|
+
* @returns The new node, or `null` when the node has no parent.
|
|
246
|
+
* @group Changing the tree
|
|
247
|
+
*/
|
|
248
|
+
addNodeBefore(nodeData, existingNode) {
|
|
249
|
+
const newNode = existingNode.addBefore(nodeData);
|
|
250
|
+
if (newNode) {
|
|
251
|
+
this._refreshElements(existingNode.parent);
|
|
252
|
+
}
|
|
253
|
+
return newNode;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Inserts a new node between a node and its parent, taking the node as its
|
|
258
|
+
* child.
|
|
259
|
+
*
|
|
260
|
+
* @returns The new node, or `null` when the node has no parent.
|
|
261
|
+
* @group Changing the tree
|
|
262
|
+
*/
|
|
263
|
+
addParentNode(nodeData, existingNode) {
|
|
264
|
+
const newNode = existingNode.addParent(nodeData);
|
|
265
|
+
if (newNode) {
|
|
266
|
+
this._refreshElements(newNode.parent);
|
|
267
|
+
}
|
|
268
|
+
return newNode;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Adds a node to the selection instead of replacing it.
|
|
273
|
+
*
|
|
274
|
+
* @param mustSetFocus - Move the focus to the node. Default `true`.
|
|
275
|
+
* @group Selection
|
|
276
|
+
*/
|
|
277
|
+
addToSelection(node, mustSetFocus) {
|
|
278
|
+
this._selectNodeHandler.addToSelection(node);
|
|
279
|
+
this._openParents(node);
|
|
280
|
+
this._getNodeElementForNode(node).select(mustSetFocus ?? true);
|
|
281
|
+
this._saveState();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Adds a node as the last child of a parent.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```js
|
|
289
|
+
* tree.appendNode({ name: "child", id: 5 }, tree.getNodeById(1));
|
|
290
|
+
* ```
|
|
291
|
+
*
|
|
292
|
+
* @returns The new node.
|
|
293
|
+
* @group Changing the tree
|
|
294
|
+
*/
|
|
295
|
+
appendNode(nodeData, parentNode) {
|
|
296
|
+
const node = parentNode.append(nodeData);
|
|
297
|
+
this._refreshElements(parentNode);
|
|
298
|
+
return node;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Closes a folder.
|
|
303
|
+
*
|
|
304
|
+
* @param slide - Override the `slide` option for this call.
|
|
305
|
+
* @group Opening and closing
|
|
306
|
+
*/
|
|
307
|
+
closeNode(node, slide) {
|
|
308
|
+
if (node.isFolder() || node.isEmptyFolder) {
|
|
309
|
+
this._createFolderElement(node).close(slide ?? this._options.slide, this._options.animationSpeed);
|
|
310
|
+
this._saveState();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Empties the element and removes the tree's document-level keyboard
|
|
316
|
+
* listener. Call it when you remove the tree from the page.
|
|
317
|
+
*
|
|
318
|
+
* @group Other
|
|
319
|
+
*/
|
|
320
|
+
deinit() {
|
|
321
|
+
this._htmlElement.textContent = '';
|
|
322
|
+
this._dataLoader.deinit();
|
|
323
|
+
this._keyHandler.deinit();
|
|
324
|
+
this._mouseHandler.deinit();
|
|
325
|
+
this.tree = new Node({}, true);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Returns the node that belongs to a `li` element the tree rendered.
|
|
330
|
+
*
|
|
331
|
+
* @group Finding nodes
|
|
332
|
+
*/
|
|
333
|
+
getNode(element) {
|
|
334
|
+
const liElement = element.closest(`li.${this._classNames.common}`);
|
|
335
|
+
if (liElement) {
|
|
336
|
+
return this._nodeMap.get(liElement) ?? null;
|
|
337
|
+
} else {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Returns the first node for which the callback returns `true`.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```js
|
|
347
|
+
* const node = tree.getNodeByCallback((node) => node.children.length > 3);
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* @group Finding nodes
|
|
351
|
+
*/
|
|
352
|
+
getNodeByCallback(callback) {
|
|
353
|
+
return this.tree.getNodeByCallback(callback);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Returns the node with this id.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```js
|
|
361
|
+
* const node = tree.getNodeById(1);
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* @group Finding nodes
|
|
365
|
+
*/
|
|
366
|
+
getNodeById(id) {
|
|
367
|
+
return this.tree.getNodeById(id);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Returns the first node with this name.
|
|
372
|
+
*
|
|
373
|
+
* @group Finding nodes
|
|
374
|
+
*/
|
|
375
|
+
getNodeByName(name) {
|
|
376
|
+
return this.tree.getNodeByName(name);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Like `getNodeByName`, but throws when there is no such node. Convenient
|
|
381
|
+
* in tests and when a missing node is a bug.
|
|
382
|
+
*
|
|
383
|
+
* @group Finding nodes
|
|
384
|
+
*/
|
|
385
|
+
getNodeByNameMustExist(name) {
|
|
386
|
+
return this.tree.getNodeByNameMustExist(name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Returns all nodes with this property value.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```js
|
|
394
|
+
* tree.getNodesByProperty("color", "green");
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @group Finding nodes
|
|
398
|
+
*/
|
|
399
|
+
getNodesByProperty(key, value) {
|
|
400
|
+
return this.tree.getNodesByProperty(key, value);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Returns the selected node, or `false` when nothing is selected.
|
|
405
|
+
*
|
|
406
|
+
* @group Selection
|
|
407
|
+
*/
|
|
408
|
+
getSelectedNode() {
|
|
409
|
+
return this._selectNodeHandler.getSelectedNode();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Returns the selected nodes.
|
|
414
|
+
*
|
|
415
|
+
* @group Selection
|
|
416
|
+
*/
|
|
417
|
+
getSelectedNodes() {
|
|
418
|
+
return this._selectNodeHandler.getSelectedNodes();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Returns the current state — `open_nodes` and `selected_node` — whether or
|
|
423
|
+
* not `saveState` is enabled.
|
|
424
|
+
*
|
|
425
|
+
* @group State
|
|
426
|
+
*/
|
|
427
|
+
getState() {
|
|
428
|
+
return this._saveStateHandler.getState();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Returns the state as it is stored.
|
|
433
|
+
*
|
|
434
|
+
* @group State
|
|
435
|
+
*/
|
|
436
|
+
getStateFromStorage() {
|
|
437
|
+
return this._saveStateHandler.getStateFromStorage();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Returns the root node. It is not rendered; its `children` are the
|
|
442
|
+
* top-level nodes. Also available as the `tree` property.
|
|
443
|
+
*
|
|
444
|
+
* @group Finding nodes
|
|
445
|
+
*/
|
|
446
|
+
getTree() {
|
|
447
|
+
return this.tree;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Returns the version of tree-element.
|
|
452
|
+
*
|
|
453
|
+
* @group Other
|
|
454
|
+
*/
|
|
455
|
+
getVersion() {
|
|
456
|
+
return version;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Returns whether the user is dragging a node.
|
|
461
|
+
*
|
|
462
|
+
* @group Other
|
|
463
|
+
*/
|
|
464
|
+
isDragging() {
|
|
465
|
+
return this._dndHandler.isDragging;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Returns whether the node is selected.
|
|
470
|
+
*
|
|
471
|
+
* @group Selection
|
|
472
|
+
*/
|
|
473
|
+
isNodeSelected(node) {
|
|
474
|
+
return this._selectNodeHandler.isNodeSelected(node);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Loads data into the tree.
|
|
479
|
+
*
|
|
480
|
+
* @param parentNode - Replace this node's children instead of the whole
|
|
481
|
+
* tree.
|
|
482
|
+
* @group Loading data
|
|
483
|
+
*/
|
|
484
|
+
loadData(data, parentNode) {
|
|
485
|
+
if (data) {
|
|
486
|
+
if (parentNode) {
|
|
487
|
+
this._deselectNodes(parentNode);
|
|
488
|
+
this._loadSubtree(data, parentNode);
|
|
489
|
+
} else {
|
|
490
|
+
this._initTree(data);
|
|
491
|
+
}
|
|
492
|
+
if (this.isDragging()) {
|
|
493
|
+
this._dndHandler.refresh();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
this._triggerEvent("tree.set_data", {
|
|
497
|
+
node: parentNode,
|
|
498
|
+
treeData: data ?? undefined
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Fetches data and loads it into the tree, or into `parentNode`.
|
|
504
|
+
*
|
|
505
|
+
* @param url - Defaults to the `dataUrl` option.
|
|
506
|
+
* @group Loading data
|
|
507
|
+
*/
|
|
508
|
+
async loadDataFromUrl(url, parentNode) {
|
|
509
|
+
const requestUrl = url ? new RequestUrl(url) : this._createRequestUrl(parentNode);
|
|
510
|
+
if (requestUrl) {
|
|
511
|
+
await this._dataLoader.loadFromUrl(requestUrl, parentNode);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Selects the next visible node, like the down arrow key.
|
|
517
|
+
*
|
|
518
|
+
* @group Selection
|
|
519
|
+
*/
|
|
520
|
+
moveDown() {
|
|
521
|
+
const selectedNode = this.getSelectedNode();
|
|
522
|
+
if (selectedNode) {
|
|
523
|
+
this._keyHandler.moveDown(selectedNode);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Moves a node inside the tree.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```js
|
|
532
|
+
* tree.moveNode(tree.getNodeById(2), tree.getNodeById(1), "inside");
|
|
533
|
+
* ```
|
|
534
|
+
*
|
|
535
|
+
* @param position - `"before"`, `"after"` or `"inside"`.
|
|
536
|
+
* @group Changing the tree
|
|
537
|
+
*/
|
|
538
|
+
moveNode(node, targetNode, position) {
|
|
539
|
+
this.tree.moveNode(node, targetNode, position);
|
|
540
|
+
this._refreshElements(null);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Selects the previous visible node, like the up arrow key.
|
|
545
|
+
*
|
|
546
|
+
* @group Selection
|
|
547
|
+
*/
|
|
548
|
+
moveUp() {
|
|
549
|
+
const selectedNode = this.getSelectedNode();
|
|
550
|
+
if (selectedNode) {
|
|
551
|
+
this._keyHandler.moveUp(selectedNode);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Opens a folder, and the folders above it. A node marked `load_on_demand`
|
|
557
|
+
* is fetched first, so await the promise when you need to know it is really
|
|
558
|
+
* open.
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```js
|
|
562
|
+
* await tree.openNode(node);
|
|
563
|
+
* console.log("open");
|
|
564
|
+
* ```
|
|
565
|
+
*
|
|
566
|
+
* @param slide - Override the `slide` option for this call.
|
|
567
|
+
* @group Opening and closing
|
|
568
|
+
*/
|
|
569
|
+
async openNode(node, slide) {
|
|
570
|
+
const mustSlide = slide ?? this._options.slide;
|
|
571
|
+
const doOpenNode = async (openedNode, slideOption) => {
|
|
572
|
+
if (!node.children.length) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const folderElement = this._createFolderElement(openedNode);
|
|
576
|
+
await folderElement.open(slideOption, this._options.animationSpeed);
|
|
577
|
+
};
|
|
578
|
+
if (node.isFolder() || node.isEmptyFolder) {
|
|
579
|
+
if (node.load_on_demand) {
|
|
580
|
+
await this._loadFolderOnDemand(node, mustSlide);
|
|
581
|
+
} else {
|
|
582
|
+
let parent = node.parent;
|
|
583
|
+
while (parent) {
|
|
584
|
+
// nb: do not open root element
|
|
585
|
+
if (parent.parent) {
|
|
586
|
+
await doOpenNode(parent, false);
|
|
587
|
+
}
|
|
588
|
+
parent = parent.parent;
|
|
589
|
+
}
|
|
590
|
+
await doOpenNode(node, mustSlide);
|
|
591
|
+
this._saveState();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Adds a node as the first child of a parent.
|
|
598
|
+
*
|
|
599
|
+
* @returns The new node.
|
|
600
|
+
* @group Changing the tree
|
|
601
|
+
*/
|
|
602
|
+
prependNode(nodeData, parentNode) {
|
|
603
|
+
const node = parentNode.prepend(nodeData);
|
|
604
|
+
this._refreshElements(parentNode);
|
|
605
|
+
return node;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Re-renders the whole tree. Needed after changing node data directly
|
|
610
|
+
* instead of through these methods.
|
|
611
|
+
*
|
|
612
|
+
* @group Changing the tree
|
|
613
|
+
*/
|
|
614
|
+
refresh() {
|
|
615
|
+
this._refreshElements(null);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Recomputes the drop targets. Call this if the layout changes during a
|
|
620
|
+
* drag.
|
|
621
|
+
*
|
|
622
|
+
* @group Other
|
|
623
|
+
*/
|
|
624
|
+
refreshHitAreas() {
|
|
625
|
+
this._dndHandler.refresh();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Removes a node from the selection.
|
|
630
|
+
*
|
|
631
|
+
* @group Selection
|
|
632
|
+
*/
|
|
633
|
+
removeFromSelection(node) {
|
|
634
|
+
this._selectNodeHandler.removeFromSelection(node);
|
|
635
|
+
this._getNodeElementForNode(node).deselect();
|
|
636
|
+
this._saveState();
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Removes a node and its children.
|
|
641
|
+
*
|
|
642
|
+
* @group Changing the tree
|
|
643
|
+
*/
|
|
644
|
+
removeNode(node) {
|
|
645
|
+
this._selectNodeHandler.removeFromSelection(node, true); // including children
|
|
646
|
+
|
|
647
|
+
const parent = node.parent;
|
|
648
|
+
node.remove();
|
|
649
|
+
this._refreshElements(parent);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Scrolls the node into view.
|
|
654
|
+
*
|
|
655
|
+
* @group Selection
|
|
656
|
+
*/
|
|
657
|
+
scrollToNode(node) {
|
|
658
|
+
if (!node.element) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const top = getOffsetTop(node.element) - getOffsetTop(this._htmlElement);
|
|
662
|
+
this._scrollHandler.scrollToY(top);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Replaces the selection, and opens the parents of the node.
|
|
667
|
+
*
|
|
668
|
+
* @param node - `null` clears the selection.
|
|
669
|
+
* @param options - `mustSetFocus`: move focus to the node, default `true`.
|
|
670
|
+
* `mustToggle`: deselect the node if it is already selected, default
|
|
671
|
+
* `false`.
|
|
672
|
+
* @group Selection
|
|
673
|
+
*/
|
|
674
|
+
selectNode(node, options) {
|
|
675
|
+
if (!node) {
|
|
676
|
+
// Called with empty node -> deselect current node
|
|
677
|
+
this._deselectCurrentNode();
|
|
678
|
+
this._saveStateHandler.saveState();
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
this._selectNodeHandler.selectSingleNode(node, options);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Changes an option after construction. See
|
|
686
|
+
* [Options](/reference/options#changing-an-option-later) for the caveats.
|
|
687
|
+
*
|
|
688
|
+
* @group Other
|
|
689
|
+
*/
|
|
690
|
+
setOption(option, value) {
|
|
691
|
+
this._options[option] = value;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Applies a state to the tree.
|
|
696
|
+
*
|
|
697
|
+
* @group State
|
|
698
|
+
*/
|
|
699
|
+
setState(state) {
|
|
700
|
+
this._saveStateHandler.setInitialState(state);
|
|
701
|
+
this._refreshElements(null);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Closes an open node and opens a closed one.
|
|
706
|
+
*
|
|
707
|
+
* @param slide - Override the `slide` option for this call.
|
|
708
|
+
* @group Opening and closing
|
|
709
|
+
*/
|
|
710
|
+
toggle(node, slide = null) {
|
|
711
|
+
const mustSlide = slide ?? this._options.slide;
|
|
712
|
+
if (node.is_open) {
|
|
713
|
+
this.closeNode(node, mustSlide);
|
|
714
|
+
} else {
|
|
715
|
+
void this.openNode(node, mustSlide);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Returns the tree as a json string, including changes made through the
|
|
721
|
+
* api.
|
|
722
|
+
*
|
|
723
|
+
* @group Other
|
|
724
|
+
*/
|
|
725
|
+
toJson() {
|
|
726
|
+
return JSON.stringify(this.tree.getData());
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Updates the data of a node and re-renders it. A string updates just the
|
|
731
|
+
* name.
|
|
732
|
+
*
|
|
733
|
+
* Changing the `id` re-indexes the node. `children` and `parent` are
|
|
734
|
+
* ignored — use `loadData` or `moveNode` for those.
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```js
|
|
738
|
+
* tree.updateNode(node, "new name");
|
|
739
|
+
* tree.updateNode(node, { name: "new name", color: "red" });
|
|
740
|
+
* ```
|
|
741
|
+
*
|
|
742
|
+
* @group Changing the tree
|
|
743
|
+
*/
|
|
744
|
+
updateNode(node, data) {
|
|
745
|
+
const idIsChanged = typeof data === "object" && data.id && data.id !== node.id;
|
|
746
|
+
if (idIsChanged) {
|
|
747
|
+
this.tree.removeNodeFromIndex(node);
|
|
748
|
+
}
|
|
749
|
+
node.setData(data);
|
|
750
|
+
if (idIsChanged) {
|
|
751
|
+
this.tree.addNodeToIndex(node);
|
|
752
|
+
}
|
|
753
|
+
if (typeof data === "object" && data.children && data.children instanceof Array) {
|
|
754
|
+
node.removeChildren();
|
|
755
|
+
if (data.children.length) {
|
|
756
|
+
node.loadFromData(data.children);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
this._refreshElements(node);
|
|
760
|
+
}
|
|
761
|
+
_createFolderElement(node) {
|
|
762
|
+
const classNames = this._classNames;
|
|
763
|
+
const closedIconElement = this._renderer.closedIconElement;
|
|
764
|
+
const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
|
|
765
|
+
const openedIconElement = this._renderer.openedIconElement;
|
|
766
|
+
const tabIndex = this._options.tabIndex;
|
|
767
|
+
const treeElement = this._htmlElement;
|
|
768
|
+
const triggerEvent = this._triggerEvent.bind(this);
|
|
769
|
+
return new FolderElement({
|
|
770
|
+
classNames,
|
|
771
|
+
closedIconElement,
|
|
772
|
+
getScrollLeft,
|
|
773
|
+
node,
|
|
774
|
+
openedIconElement,
|
|
775
|
+
tabIndex,
|
|
776
|
+
treeElement,
|
|
777
|
+
triggerEvent
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
_createNodeElement(node) {
|
|
781
|
+
const classNames = this._classNames;
|
|
782
|
+
const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
|
|
783
|
+
const tabIndex = this._options.tabIndex;
|
|
784
|
+
const treeElement = this._htmlElement;
|
|
785
|
+
return new NodeElement({
|
|
786
|
+
classNames,
|
|
787
|
+
getScrollLeft,
|
|
788
|
+
node,
|
|
789
|
+
tabIndex,
|
|
790
|
+
treeElement
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* Create a RequestUrl based on the url in the options.
|
|
795
|
+
* Add a 'node' query parameter for loading on demand
|
|
796
|
+
* Add a 'selected_node' query parameter if a node is selected.
|
|
797
|
+
*/
|
|
798
|
+
_createRequestUrl(node) {
|
|
799
|
+
const dataUrl = this._options.dataUrl;
|
|
800
|
+
let url;
|
|
801
|
+
if (typeof dataUrl === "function") {
|
|
802
|
+
url = dataUrl(node);
|
|
803
|
+
} else {
|
|
804
|
+
url = dataUrl;
|
|
805
|
+
}
|
|
806
|
+
if (!url) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
const requestUrl = new RequestUrl(url);
|
|
810
|
+
if (node?.id) {
|
|
811
|
+
// Load on demand of a subtree; add node parameter
|
|
812
|
+
requestUrl.setSearchParam('node', node.id.toString());
|
|
813
|
+
} else {
|
|
814
|
+
// Add selected_node parameter
|
|
815
|
+
const selectedNodeId = this._getNodeIdToBeSelected();
|
|
816
|
+
if (selectedNodeId) {
|
|
817
|
+
requestUrl.setSearchParam('selected_node', selectedNodeId.toString());
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return requestUrl;
|
|
821
|
+
}
|
|
822
|
+
_deselectCurrentNode() {
|
|
823
|
+
const node = this.getSelectedNode();
|
|
824
|
+
if (node) {
|
|
825
|
+
this.removeFromSelection(node);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Deselect the children of the node.
|
|
830
|
+
_deselectNodes(parentNode) {
|
|
831
|
+
const selectedNodesUnderParent = this._selectNodeHandler.getSelectedNodesUnder(parentNode);
|
|
832
|
+
for (const n of selectedNodesUnderParent) {
|
|
833
|
+
this._selectNodeHandler.removeFromSelection(n);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Get the maximum level for auto open
|
|
838
|
+
_getAutoOpenMaxLevel() {
|
|
839
|
+
if (this._options.autoOpen === true) {
|
|
840
|
+
return -1;
|
|
841
|
+
} else if (typeof this._options.autoOpen === "number") {
|
|
842
|
+
return this._options.autoOpen;
|
|
843
|
+
} else if (typeof this._options.autoOpen === "string") {
|
|
844
|
+
return parseInt(this._options.autoOpen, 10);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* istanbul ignore next @preserve */
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
_getNodeElement(element) {
|
|
851
|
+
const node = this.getNode(element);
|
|
852
|
+
if (node) {
|
|
853
|
+
return this._getNodeElementForNode(node);
|
|
854
|
+
} else {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
_getNodeElementForNode(node) {
|
|
859
|
+
if (node.isFolder()) {
|
|
860
|
+
return this._createFolderElement(node);
|
|
861
|
+
} else {
|
|
862
|
+
return this._createNodeElement(node);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
_getNodeIdToBeSelected() {
|
|
866
|
+
return this._saveStateHandler.getNodeIdToBeSelected();
|
|
867
|
+
}
|
|
868
|
+
_initData() {
|
|
869
|
+
if (this._options.data) {
|
|
870
|
+
this.loadData(this._options.data);
|
|
871
|
+
} else {
|
|
872
|
+
const dataUrl = this._createRequestUrl();
|
|
873
|
+
if (dataUrl) {
|
|
874
|
+
void this.loadDataFromUrl();
|
|
875
|
+
} else {
|
|
876
|
+
this.loadData([]);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
_initTree(data) {
|
|
881
|
+
const doInit = () => {
|
|
882
|
+
if (!this._isInitialized) {
|
|
883
|
+
this._isInitialized = true;
|
|
884
|
+
this._triggerEvent("tree.init");
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
this.tree = new this._options.nodeClass(null, true, this._options.nodeClass);
|
|
888
|
+
this._selectNodeHandler.clear();
|
|
889
|
+
this.tree.loadFromData(data);
|
|
890
|
+
const mustLoadOnDemand = this._setInitialState();
|
|
891
|
+
this._refreshElements(null);
|
|
892
|
+
if (mustLoadOnDemand) {
|
|
893
|
+
// Load data on demand and then init the tree
|
|
894
|
+
void this._setInitialStateOnDemand().then(doInit);
|
|
895
|
+
} else {
|
|
896
|
+
doInit();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Does an HTML element of the tree have the focus?
|
|
901
|
+
_isFocusOnTree() {
|
|
902
|
+
const activeElement = document.activeElement;
|
|
903
|
+
|
|
904
|
+
/* istanbul ignore if */
|
|
905
|
+
if (!activeElement) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// The keyboard must still work for input elements.
|
|
910
|
+
const tagName = activeElement.tagName;
|
|
911
|
+
if (tagName !== "A" && tagName !== "SPAN") {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
const node = this.getNode(activeElement);
|
|
915
|
+
return node?.tree === this.tree;
|
|
916
|
+
}
|
|
917
|
+
_isSelectedNodeInSubtree(subtree) {
|
|
918
|
+
const selectedNode = this.getSelectedNode();
|
|
919
|
+
if (!selectedNode) {
|
|
920
|
+
return false;
|
|
921
|
+
} else {
|
|
922
|
+
return subtree === selectedNode || subtree.isParentOf(selectedNode);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async _loadFolderOnDemand(node, slide) {
|
|
926
|
+
node.is_loading = true;
|
|
927
|
+
await this.loadDataFromUrl(undefined, node);
|
|
928
|
+
await this.openNode(node, slide);
|
|
929
|
+
}
|
|
930
|
+
_loadSubtree(data, parentNode) {
|
|
931
|
+
parentNode.loadFromData(data);
|
|
932
|
+
parentNode.load_on_demand = false;
|
|
933
|
+
parentNode.is_loading = false;
|
|
934
|
+
this._refreshElements(parentNode);
|
|
935
|
+
}
|
|
936
|
+
_mouseCapture(positionInfo) {
|
|
937
|
+
if (!this._options.dragAndDrop) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
return this._dndHandler.mouseCapture(positionInfo);
|
|
941
|
+
}
|
|
942
|
+
_mouseDrag(positionInfo) {
|
|
943
|
+
/* istanbul ignore if */
|
|
944
|
+
if (!this._options.dragAndDrop) {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
const result = this._dndHandler.mouseDrag(positionInfo);
|
|
948
|
+
this._scrollHandler.checkScrolling(positionInfo);
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
_mouseStart(positionInfo) {
|
|
952
|
+
/* istanbul ignore if */
|
|
953
|
+
if (!this._options.dragAndDrop) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
return this._dndHandler.mouseStart(positionInfo);
|
|
957
|
+
}
|
|
958
|
+
_mouseStop(positionInfo) {
|
|
959
|
+
/* istanbul ignore if */
|
|
960
|
+
if (!this._options.dragAndDrop) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
this._scrollHandler.stopScrolling();
|
|
964
|
+
return this._dndHandler.mouseStop(positionInfo);
|
|
965
|
+
}
|
|
966
|
+
_openParents(node) {
|
|
967
|
+
const parent = node.parent;
|
|
968
|
+
if (parent?.parent && !parent.is_open) {
|
|
969
|
+
void this.openNode(parent, false);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/*
|
|
974
|
+
Redraw the tree or part of the tree.
|
|
975
|
+
from_node: redraw this subtree
|
|
976
|
+
*/
|
|
977
|
+
_refreshElements(fromNode) {
|
|
978
|
+
const mustSetFocus = this._isFocusOnTree();
|
|
979
|
+
const mustSelect = fromNode ? this._isSelectedNodeInSubtree(fromNode) : false;
|
|
980
|
+
this._renderer.render(fromNode);
|
|
981
|
+
if (mustSelect) {
|
|
982
|
+
this._selectCurrentNode(mustSetFocus);
|
|
983
|
+
}
|
|
984
|
+
this._triggerEvent("tree.refresh");
|
|
985
|
+
}
|
|
986
|
+
_saveState() {
|
|
987
|
+
this._saveStateHandler.saveState();
|
|
988
|
+
}
|
|
989
|
+
_selectCurrentNode(mustSetFocus) {
|
|
990
|
+
const node = this.getSelectedNode();
|
|
991
|
+
if (node) {
|
|
992
|
+
const nodeElement = this._getNodeElementForNode(node);
|
|
993
|
+
nodeElement.select(mustSetFocus);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Set initial state, either by restoring the state or auto-opening nodes
|
|
998
|
+
// result: must load nodes on demand?
|
|
999
|
+
_setInitialState() {
|
|
1000
|
+
const restoreState = () => {
|
|
1001
|
+
// result: is state restored, must load on demand?
|
|
1002
|
+
const state = this._saveStateHandler.getStateFromStorage();
|
|
1003
|
+
if (!state) {
|
|
1004
|
+
return [false, false];
|
|
1005
|
+
} else {
|
|
1006
|
+
const mustLoadOnDemand = this._saveStateHandler.setInitialState(state);
|
|
1007
|
+
|
|
1008
|
+
// return true: the state is restored
|
|
1009
|
+
return [true, mustLoadOnDemand];
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const autoOpenNodes = () => {
|
|
1013
|
+
// result: must load on demand?
|
|
1014
|
+
if (this._options.autoOpen === false) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
const maxLevel = this._getAutoOpenMaxLevel();
|
|
1018
|
+
let mustLoadOnDemand = false;
|
|
1019
|
+
this.tree.iterate((node, level) => {
|
|
1020
|
+
if (node.load_on_demand) {
|
|
1021
|
+
mustLoadOnDemand = true;
|
|
1022
|
+
return false;
|
|
1023
|
+
} else if (!node.hasChildren()) {
|
|
1024
|
+
return false;
|
|
1025
|
+
} else {
|
|
1026
|
+
node.is_open = true;
|
|
1027
|
+
return level !== maxLevel;
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
return mustLoadOnDemand;
|
|
1031
|
+
};
|
|
1032
|
+
let [isRestored, mustLoadOnDemand] = restoreState(); // eslint-disable-line prefer-const
|
|
1033
|
+
|
|
1034
|
+
if (!isRestored) {
|
|
1035
|
+
mustLoadOnDemand = autoOpenNodes();
|
|
1036
|
+
}
|
|
1037
|
+
return mustLoadOnDemand;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Set the initial state for nodes that are loaded on demand
|
|
1041
|
+
async _setInitialStateOnDemand() {
|
|
1042
|
+
return new Promise(resolve => {
|
|
1043
|
+
const restoreState = () => {
|
|
1044
|
+
const state = this._saveStateHandler.getStateFromStorage();
|
|
1045
|
+
if (!state) {
|
|
1046
|
+
return false;
|
|
1047
|
+
} else {
|
|
1048
|
+
void this._saveStateHandler.setInitialStateOnDemand(state).then(() => {
|
|
1049
|
+
resolve();
|
|
1050
|
+
});
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
const autoOpenNodes = () => {
|
|
1055
|
+
const maxLevel = this._getAutoOpenMaxLevel();
|
|
1056
|
+
let loadingCount = 0;
|
|
1057
|
+
const loadAndOpenNode = node => {
|
|
1058
|
+
loadingCount += 1;
|
|
1059
|
+
void this.openNode(node, false).then(() => {
|
|
1060
|
+
loadingCount -= 1;
|
|
1061
|
+
openNodes();
|
|
1062
|
+
});
|
|
1063
|
+
};
|
|
1064
|
+
const openNodes = () => {
|
|
1065
|
+
this.tree.iterate((node, level) => {
|
|
1066
|
+
if (node.load_on_demand) {
|
|
1067
|
+
if (!node.is_loading) {
|
|
1068
|
+
loadAndOpenNode(node);
|
|
1069
|
+
}
|
|
1070
|
+
return false;
|
|
1071
|
+
} else {
|
|
1072
|
+
void this.openNode(node, false);
|
|
1073
|
+
return level !== maxLevel;
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
if (loadingCount === 0) {
|
|
1077
|
+
resolve();
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
openNodes();
|
|
1081
|
+
};
|
|
1082
|
+
if (!restoreState()) {
|
|
1083
|
+
autoOpenNodes();
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Set this HTML element to this node in the node map.
|
|
1089
|
+
_setNodeElement(element, node) {
|
|
1090
|
+
this._nodeMap.set(element, node);
|
|
1091
|
+
}
|
|
1092
|
+
_triggerEvent(eventName, values) {
|
|
1093
|
+
return this._triggerEventProvider(this._htmlElement, eventName, values);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export { TreeElement as default };
|
|
1098
|
+
//# sourceMappingURL=index.js.map
|