jqtree 1.8.0 → 1.8.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 (60) hide show
  1. package/.eslintrc +13 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/codeql-analysis.yml +4 -4
  4. package/.github/workflows/size.yml +3 -3
  5. package/.github/workflows/static.yml +1 -1
  6. package/bower.json +1 -1
  7. package/config/jest.config.js +4 -0
  8. package/config/jest.polyfills.js +14 -0
  9. package/devserver/test_index.html +9 -0
  10. package/docs/.ruby-version +1 -1
  11. package/docs/_config.yml +1 -1
  12. package/docs/_entries/general/changelog.md +4 -0
  13. package/docs/_entries/multiple_selection/get-selected-nodes.md +1 -1
  14. package/docs/_entries/node/getnextnode.md +3 -6
  15. package/docs/_entries/node/getnextsibling.md +1 -1
  16. package/docs/_entries/node/getnextvisiblenode.md +8 -5
  17. package/docs/_entries/node/getpreviousnode.md +12 -0
  18. package/docs/_entries/node/getprevioussibling.md +1 -1
  19. package/docs/_entries/node/getpreviousvisiblenode.md +6 -5
  20. package/package.json +32 -30
  21. package/src/dataLoader.ts +19 -21
  22. package/src/dragAndDropHandler/dragElement.ts +37 -25
  23. package/src/dragAndDropHandler/generateHitAreas.ts +176 -0
  24. package/src/dragAndDropHandler/index.ts +32 -48
  25. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  26. package/src/dragAndDropHandler/types.ts +2 -1
  27. package/src/mouseHandler.ts +385 -0
  28. package/src/mouseUtils.ts +23 -0
  29. package/src/node.ts +1 -29
  30. package/src/nodeElement/folderElement.ts +1 -1
  31. package/src/nodeElement/ghostDropHint.ts +2 -1
  32. package/src/nodeElement/index.ts +2 -1
  33. package/src/playwright/coverage.ts +3 -3
  34. package/src/playwright/playwright.test.ts +150 -49
  35. package/src/playwright/testUtils.ts +28 -5
  36. package/src/position.ts +28 -0
  37. package/src/scrollHandler/containerScrollParent.ts +13 -23
  38. package/src/scrollHandler/createScrollParent.ts +22 -22
  39. package/src/scrollHandler/documentScrollParent.ts +16 -13
  40. package/src/scrollHandler.ts +6 -14
  41. package/src/test/jqTree/events.test.ts +97 -30
  42. package/src/test/jqTree/loadOnDemand.test.ts +22 -15
  43. package/src/test/jqTree/methods.test.ts +8 -11
  44. package/src/test/jqTree/mouse.test.ts +82 -0
  45. package/src/test/jqTree/options.test.ts +9 -8
  46. package/src/test/node.test.ts +2 -1
  47. package/src/test/{nodeUtil.test.ts → position.test.ts} +1 -1
  48. package/src/tree.jquery.ts +108 -184
  49. package/src/util.ts +10 -1
  50. package/src/version.ts +1 -1
  51. package/tree.jquery.debug.js +2158 -2135
  52. package/tree.jquery.debug.js.map +1 -1
  53. package/tree.jquery.js +3 -3
  54. package/tree.jquery.js.map +1 -1
  55. package/tsconfig.json +5 -3
  56. package/docs/_entries/functions/get-selected-nodes.md +0 -10
  57. package/src/dragAndDropHandler/hitAreasGenerator.ts +0 -175
  58. package/src/dragAndDropHandler/visibleNodeIterator.ts +0 -97
  59. package/src/mouse.widget.ts +0 -266
  60. package/src/mouseWidgetTypes.ts +0 -6
@@ -1,7 +1,7 @@
1
1
  /*
2
- JqTree 1.8.0
2
+ JqTree 1.8.1
3
3
 
4
- Copyright 2023 Marco Braak
4
+ Copyright 2024 Marco Braak
5
5
 
6
6
  Licensed under the Apache License, Version 2.0 (the "License");
7
7
  you may not use this file except in compliance with the License.
@@ -20,9 +20,7 @@ limitations under the License.
20
20
  var jqtree = (function (exports) {
21
21
  'use strict';
22
22
 
23
- const version = "1.8.0";
24
-
25
- const isNodeRecordWithChildren = data => typeof data === "object" && "children" in data && data["children"] instanceof Array;
23
+ const version = "1.8.1";
26
24
 
27
25
  let Position = /*#__PURE__*/function (Position) {
28
26
  Position[Position["Before"] = 1] = "Before";
@@ -48,2360 +46,2442 @@ var jqtree = (function (exports) {
48
46
  return "";
49
47
  };
50
48
  const getPosition = name => positionNames[name];
51
- class Node {
52
- constructor() {
53
- let nodeData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
54
- let isRoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
55
- let nodeClass = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Node;
56
- this.name = "";
57
- this.load_on_demand = false;
58
- this.isEmptyFolder = nodeData != null && isNodeRecordWithChildren(nodeData) && nodeData.children.length === 0;
59
- this.setData(nodeData);
60
- this.children = [];
61
- this.parent = null;
62
- if (isRoot) {
63
- this.idMapping = new Map();
64
- this.tree = this;
65
- this.nodeClass = nodeClass;
49
+
50
+ class DragElement {
51
+ constructor(_ref) {
52
+ let {
53
+ autoEscape,
54
+ nodeName,
55
+ offsetX,
56
+ offsetY,
57
+ treeElement
58
+ } = _ref;
59
+ this.offsetX = offsetX;
60
+ this.offsetY = offsetY;
61
+ this.element = this.createElement(nodeName, autoEscape);
62
+ treeElement.appendChild(this.element);
63
+ }
64
+ move(pageX, pageY) {
65
+ this.element.style.left = `${pageX - this.offsetX}px`;
66
+ this.element.style.top = `${pageY - this.offsetY}px`;
67
+ }
68
+ remove() {
69
+ this.element.remove();
70
+ }
71
+ createElement(nodeName, autoEscape) {
72
+ const element = document.createElement("span");
73
+ element.classList.add("jqtree-title", "jqtree-dragging");
74
+ if (autoEscape) {
75
+ element.textContent = nodeName;
76
+ } else {
77
+ element.innerHTML = nodeName;
66
78
  }
79
+ element.style.position = "absolute";
80
+ return element;
67
81
  }
82
+ }
68
83
 
69
- /*
70
- Set the data of this node.
71
- setData(string): set the name of the node
72
- setData(object): set attributes of the node
73
- Examples:
74
- setData('node1')
75
- setData({ name: 'node1', id: 1});
76
- setData({ name: 'node2', id: 2, color: 'green'});
77
- * This is an internal function; it is not in the docs
78
- * Does not remove existing node values
79
- */
80
- setData(o) {
81
- if (!o) {
82
- return;
83
- } else if (typeof o === "string") {
84
- this.name = o;
85
- } else if (typeof o === "object") {
86
- for (const key in o) {
87
- if (Object.prototype.hasOwnProperty.call(o, key)) {
88
- const value = o[key];
89
- if (key === "label" || key === "name") {
90
- // You can use the 'label' key instead of 'name'; this is a legacy feature
91
- if (typeof value === "string") {
92
- this.name = value;
84
+ const isInt = n => typeof n === "number" && n % 1 === 0;
85
+ const isFunction = v => typeof v === "function";
86
+ const getBoolString = value => value ? "true" : "false";
87
+ const getOffsetTop = element => getElementPosition(element).top;
88
+ const getElementPosition = element => {
89
+ const rect = element.getBoundingClientRect();
90
+ return {
91
+ left: rect.x + window.scrollX,
92
+ top: rect.y + window.scrollY
93
+ };
94
+ };
95
+
96
+ const iterateVisibleNodes = (tree, _ref) => {
97
+ let {
98
+ handleAfterOpenFolder,
99
+ handleClosedFolder,
100
+ handleFirstNode,
101
+ handleNode,
102
+ handleOpenFolder
103
+ } = _ref;
104
+ let isFirstNode = true;
105
+ const iterate = (node, nextNode) => {
106
+ let mustIterateInside = (node.is_open || !node.element) && node.hasChildren();
107
+ let element = null;
108
+
109
+ // Is the element visible?
110
+ if (node.element?.offsetParent) {
111
+ element = node.element;
112
+ if (isFirstNode) {
113
+ handleFirstNode(node);
114
+ isFirstNode = false;
115
+ }
116
+ if (!node.hasChildren()) {
117
+ handleNode(node, nextNode, node.element);
118
+ } else if (node.is_open) {
119
+ if (!handleOpenFolder(node, node.element)) {
120
+ mustIterateInside = false;
121
+ }
122
+ } else {
123
+ handleClosedFolder(node, nextNode, element);
124
+ }
125
+ }
126
+ if (mustIterateInside) {
127
+ const childrenLength = node.children.length;
128
+ node.children.forEach((_, i) => {
129
+ const child = node.children[i];
130
+ if (child) {
131
+ if (i === childrenLength - 1) {
132
+ iterate(child, null);
133
+ } else {
134
+ const nextChild = node.children[i + 1];
135
+ if (nextChild) {
136
+ iterate(child, nextChild);
93
137
  }
94
- } else if (key !== "children" && key !== "parent") {
95
- // You can't update the children or the parent using this function
96
- this[key] = value;
97
138
  }
98
139
  }
140
+ });
141
+ if (node.is_open && element) {
142
+ handleAfterOpenFolder(node, nextNode);
99
143
  }
100
144
  }
101
- }
145
+ };
146
+ iterate(tree, null);
147
+ };
102
148
 
103
- /*
104
- Create tree from data.
105
- Structure of data is:
106
- [
107
- {
108
- name: 'node1',
109
- children: [
110
- { name: 'child1' },
111
- { name: 'child2' }
112
- ]
113
- },
114
- {
115
- name: 'node2'
116
- }
117
- ]
118
- */
119
- loadFromData(data) {
120
- this.removeChildren();
121
- for (const childData of data) {
122
- const node = this.createNode(childData);
123
- this.addChild(node);
124
- if (isNodeRecordWithChildren(childData)) {
125
- node.loadFromData(childData.children);
126
- }
149
+ const generatePositions = (tree, currentNode) => {
150
+ const positions = [];
151
+ let lastTop = 0;
152
+ const addPosition = (node, position, top) => {
153
+ const area = {
154
+ top,
155
+ bottom: 0,
156
+ node,
157
+ position
158
+ };
159
+ positions.push(area);
160
+ lastTop = top;
161
+ };
162
+ const handleAfterOpenFolder = (node, nextNode) => {
163
+ if (node === currentNode || nextNode === currentNode) {
164
+ // Cannot move before or after current item
165
+ addPosition(node, Position.None, lastTop);
166
+ } else {
167
+ addPosition(node, Position.After, lastTop);
127
168
  }
128
- return this;
129
- }
130
-
131
- /*
132
- Add child.
133
- tree.addChild(
134
- new Node('child1')
135
- );
136
- */
137
- addChild(node) {
138
- this.children.push(node);
139
- node.setParent(this);
140
- }
169
+ };
170
+ const handleClosedFolder = (node, nextNode, element) => {
171
+ const top = getOffsetTop(element);
172
+ if (node === currentNode) {
173
+ // Cannot move after current item
174
+ addPosition(node, Position.None, top);
175
+ } else {
176
+ addPosition(node, Position.Inside, top);
141
177
 
142
- /*
143
- Add child at position. Index starts at 0.
144
- tree.addChildAtPosition(
145
- new Node('abc'),
146
- 1
147
- );
148
- */
149
- addChildAtPosition(node, index) {
150
- this.children.splice(index, 0, node);
151
- node.setParent(this);
152
- }
178
+ // Cannot move before current item
179
+ if (nextNode !== currentNode) {
180
+ addPosition(node, Position.After, top);
181
+ }
182
+ }
183
+ };
184
+ const handleFirstNode = node => {
185
+ if (node !== currentNode) {
186
+ addPosition(node, Position.Before, getOffsetTop(node.element));
187
+ }
188
+ };
189
+ const handleNode = (node, nextNode, element) => {
190
+ const top = getOffsetTop(element);
191
+ if (node === currentNode) {
192
+ // Cannot move inside current item
193
+ addPosition(node, Position.None, top);
194
+ } else {
195
+ addPosition(node, Position.Inside, top);
196
+ }
197
+ if (nextNode === currentNode || node === currentNode) {
198
+ // Cannot move before or after current item
199
+ addPosition(node, Position.None, top);
200
+ } else {
201
+ addPosition(node, Position.After, top);
202
+ }
203
+ };
204
+ const handleOpenFolder = (node, element) => {
205
+ if (node === currentNode) {
206
+ // Cannot move inside current item
207
+ // Stop iterating
208
+ return false;
209
+ }
153
210
 
154
- /*
155
- Remove child. This also removes the children of the node.
156
- tree.removeChild(tree.children[0]);
157
- */
158
- removeChild(node) {
159
- // remove children from the index
160
- node.removeChildren();
161
- this.doRemoveChild(node);
162
- }
211
+ // Cannot move before current item
212
+ if (node.children[0] !== currentNode) {
213
+ addPosition(node, Position.Inside, getOffsetTop(element));
214
+ }
163
215
 
164
- /*
165
- Get child index.
166
- var index = getChildIndex(node);
167
- */
168
- getChildIndex(node) {
169
- return this.children.indexOf(node);
216
+ // Continue iterating
217
+ return true;
218
+ };
219
+ iterateVisibleNodes(tree, {
220
+ handleAfterOpenFolder,
221
+ handleClosedFolder,
222
+ handleFirstNode,
223
+ handleNode,
224
+ handleOpenFolder
225
+ });
226
+ return positions;
227
+ };
228
+ const generateHitAreasForGroup = (hitAreas, positionsInGroup, top, bottom) => {
229
+ // limit positions in group
230
+ const positionCount = Math.min(positionsInGroup.length, 4);
231
+ const areaHeight = Math.round((bottom - top) / positionCount);
232
+ let areaTop = top;
233
+ let i = 0;
234
+ while (i < positionCount) {
235
+ const position = positionsInGroup[i];
236
+ if (position) {
237
+ hitAreas.push({
238
+ top: areaTop,
239
+ bottom: areaTop + areaHeight,
240
+ node: position.node,
241
+ position: position.position
242
+ });
243
+ }
244
+ areaTop += areaHeight;
245
+ i += 1;
170
246
  }
247
+ };
248
+ const generateHitAreasFromPositions = (positions, treeBottom) => {
249
+ let previousTop = positions[0]?.top ?? 0;
250
+ let group = [];
251
+ const hitAreas = [];
252
+ for (const position of positions) {
253
+ if (position.top !== previousTop && group.length) {
254
+ generateHitAreasForGroup(hitAreas, group, previousTop, position.top);
255
+ previousTop = position.top;
256
+ group = [];
257
+ }
258
+ group.push(position);
259
+ }
260
+ generateHitAreasForGroup(hitAreas, group, previousTop, treeBottom);
261
+ return hitAreas;
262
+ };
263
+ const generateHitAreas = (tree, currentNode, treeBottom) => {
264
+ const positions = generatePositions(tree, currentNode);
265
+ return generateHitAreasFromPositions(positions, treeBottom);
266
+ };
171
267
 
172
- /*
173
- Does the tree have children?
174
- if (tree.hasChildren()) {
175
- //
268
+ class DragAndDropHandler {
269
+ constructor(_ref) {
270
+ let {
271
+ autoEscape,
272
+ getNodeElement,
273
+ getNodeElementForNode,
274
+ getScrollLeft,
275
+ getTree,
276
+ onCanMove,
277
+ onCanMoveTo,
278
+ onDragMove,
279
+ onDragStop,
280
+ onIsMoveHandle,
281
+ openNode,
282
+ refreshElements,
283
+ slide,
284
+ treeElement,
285
+ triggerEvent
286
+ } = _ref;
287
+ this.autoEscape = autoEscape;
288
+ this.getNodeElement = getNodeElement;
289
+ this.getNodeElementForNode = getNodeElementForNode;
290
+ this.getScrollLeft = getScrollLeft;
291
+ this.getTree = getTree;
292
+ this.onCanMove = onCanMove;
293
+ this.onCanMoveTo = onCanMoveTo;
294
+ this.onDragMove = onDragMove;
295
+ this.onDragStop = onDragStop;
296
+ this.onIsMoveHandle = onIsMoveHandle;
297
+ this.openNode = openNode;
298
+ this.refreshElements = refreshElements;
299
+ this.slide = slide;
300
+ this.treeElement = treeElement;
301
+ this.triggerEvent = triggerEvent;
302
+ this.hoveredArea = null;
303
+ this.hitAreas = [];
304
+ this.isDragging = false;
305
+ this.currentItem = null;
176
306
  }
177
- */
178
- hasChildren() {
179
- return this.children.length !== 0;
307
+ mouseCapture(positionInfo) {
308
+ const element = positionInfo.target;
309
+ if (!this.mustCaptureElement(element)) {
310
+ return null;
311
+ }
312
+ if (this.onIsMoveHandle && !this.onIsMoveHandle(jQuery(element))) {
313
+ return null;
314
+ }
315
+ let nodeElement = this.getNodeElement(element);
316
+ if (nodeElement && this.onCanMove) {
317
+ if (!this.onCanMove(nodeElement.node)) {
318
+ nodeElement = null;
319
+ }
320
+ }
321
+ this.currentItem = nodeElement;
322
+ return this.currentItem != null;
180
323
  }
181
- isFolder() {
182
- return this.hasChildren() || this.load_on_demand;
324
+ mouseStart(positionInfo) {
325
+ if (!this.currentItem) {
326
+ return false;
327
+ }
328
+ this.refresh();
329
+ const {
330
+ left,
331
+ top
332
+ } = getElementPosition(positionInfo.target);
333
+ const node = this.currentItem.node;
334
+ this.dragElement = new DragElement({
335
+ autoEscape: this.autoEscape ?? true,
336
+ nodeName: node.name,
337
+ offsetX: positionInfo.pageX - left,
338
+ offsetY: positionInfo.pageY - top,
339
+ treeElement: this.treeElement
340
+ });
341
+ this.isDragging = true;
342
+ this.currentItem.element.classList.add("jqtree-moving");
343
+ return true;
183
344
  }
184
-
185
- /*
186
- Iterate over all the nodes in the tree.
187
- Calls callback with (node, level).
188
- The callback must return true to continue the iteration on current node.
189
- tree.iterate(
190
- function(node, level) {
191
- console.log(node.name);
192
- // stop iteration after level 2
193
- return (level <= 2);
345
+ mouseDrag(positionInfo) {
346
+ if (!this.currentItem || !this.dragElement) {
347
+ return false;
348
+ }
349
+ this.dragElement.move(positionInfo.pageX, positionInfo.pageY);
350
+ const area = this.findHoveredArea(positionInfo.pageX, positionInfo.pageY);
351
+ if (area && this.canMoveToArea(area)) {
352
+ if (!area.node.isFolder()) {
353
+ this.stopOpenFolderTimer();
194
354
  }
195
- );
196
- */
197
- iterate(callback) {
198
- const _iterate = (node, level) => {
199
- if (node.children) {
200
- for (const child of node.children) {
201
- const result = callback(child, level);
202
- if (result && child.hasChildren()) {
203
- _iterate(child, level + 1);
204
- }
355
+ if (this.hoveredArea !== area) {
356
+ this.hoveredArea = area;
357
+
358
+ // If this is a closed folder, start timer to open it
359
+ if (this.mustOpenFolderTimer(area)) {
360
+ this.startOpenFolderTimer(area.node);
361
+ } else {
362
+ this.stopOpenFolderTimer();
205
363
  }
364
+ this.updateDropHint();
206
365
  }
207
- };
208
- _iterate(this, 0);
209
- }
210
-
211
- /*
212
- Move node relative to another node.
213
- Argument position: Position.BEFORE, Position.AFTER or Position.Inside
214
- // move node1 after node2
215
- tree.moveNode(node1, node2, Position.AFTER);
216
- */
217
- moveNode(movedNode, targetNode, position) {
218
- if (!movedNode.parent || movedNode.isParentOf(targetNode)) {
219
- // - Node is parent of target node
220
- // - Or, parent is empty
221
- return false;
222
366
  } else {
223
- movedNode.parent.doRemoveChild(movedNode);
224
- switch (position) {
225
- case Position.After:
226
- {
227
- if (targetNode.parent) {
228
- targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode) + 1);
229
- return true;
230
- }
231
- return false;
232
- }
233
- case Position.Before:
234
- {
235
- if (targetNode.parent) {
236
- targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode));
237
- return true;
238
- }
239
- return false;
240
- }
241
- case Position.Inside:
242
- {
243
- // move inside as first child
244
- targetNode.addChildAtPosition(movedNode, 0);
245
- return true;
246
- }
247
- default:
248
- return false;
367
+ this.removeDropHint();
368
+ this.stopOpenFolderTimer();
369
+ this.hoveredArea = area;
370
+ }
371
+ if (!area) {
372
+ if (this.onDragMove) {
373
+ this.onDragMove(this.currentItem.node, positionInfo.originalEvent);
249
374
  }
250
375
  }
376
+ return true;
251
377
  }
252
-
253
- /*
254
- Get the tree as data.
255
- */
256
- getData() {
257
- let includeParent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
258
- const getDataFromNodes = nodes => {
259
- return nodes.map(node => {
260
- const tmpNode = {};
261
- for (const k in node) {
262
- if (["parent", "children", "element", "idMapping", "load_on_demand", "nodeClass", "tree", "isEmptyFolder"].indexOf(k) === -1 && Object.prototype.hasOwnProperty.call(node, k)) {
263
- const v = node[k];
264
- tmpNode[k] = v;
265
- }
266
- }
267
- if (node.hasChildren()) {
268
- tmpNode["children"] = getDataFromNodes(node.children);
269
- }
270
- return tmpNode;
271
- });
272
- };
273
- if (includeParent) {
274
- return getDataFromNodes([this]);
275
- } else {
276
- return getDataFromNodes(this.children);
378
+ mouseStop(positionInfo) {
379
+ this.moveItem(positionInfo);
380
+ this.clear();
381
+ this.removeHover();
382
+ this.removeDropHint();
383
+ this.removeHitAreas();
384
+ const currentItem = this.currentItem;
385
+ if (this.currentItem) {
386
+ this.currentItem.element.classList.remove("jqtree-moving");
387
+ this.currentItem = null;
277
388
  }
278
- }
279
- getNodeByName(name) {
280
- return this.getNodeByCallback(node => node.name === name);
281
- }
282
- getNodeByNameMustExist(name) {
283
- const node = this.getNodeByCallback(n => n.name === name);
284
- if (!node) {
285
- throw `Node with name ${name} not found`;
389
+ this.isDragging = false;
390
+ if (!this.hoveredArea && currentItem) {
391
+ if (this.onDragStop) {
392
+ this.onDragStop(currentItem.node, positionInfo.originalEvent);
393
+ }
286
394
  }
287
- return node;
395
+ return false;
288
396
  }
289
- getNodeByCallback(callback) {
290
- let result = null;
291
- this.iterate(node => {
292
- if (result) {
293
- return false;
294
- } else if (callback(node)) {
295
- result = node;
296
- return false;
297
- } else {
298
- return true;
397
+ refresh() {
398
+ this.removeHitAreas();
399
+ if (this.currentItem) {
400
+ this.generateHitAreas();
401
+ this.currentItem = this.getNodeElementForNode(this.currentItem.node);
402
+ if (this.isDragging) {
403
+ this.currentItem.element.classList.add("jqtree-moving");
299
404
  }
300
- });
301
- return result;
302
- }
303
- addAfter(nodeInfo) {
304
- if (!this.parent) {
305
- return null;
306
- } else {
307
- const node = this.createNode(nodeInfo);
308
- const childIndex = this.parent.getChildIndex(this);
309
- this.parent.addChildAtPosition(node, childIndex + 1);
310
- node.loadChildrenFromData(nodeInfo);
311
- return node;
312
405
  }
313
406
  }
314
- addBefore(nodeInfo) {
315
- if (!this.parent) {
316
- return null;
407
+ generateHitAreas() {
408
+ const tree = this.getTree();
409
+ if (!this.currentItem || !tree) {
410
+ this.hitAreas = [];
317
411
  } else {
318
- const node = this.createNode(nodeInfo);
319
- const childIndex = this.parent.getChildIndex(this);
320
- this.parent.addChildAtPosition(node, childIndex);
321
- node.loadChildrenFromData(nodeInfo);
322
- return node;
412
+ this.hitAreas = generateHitAreas(tree, this.currentItem.node, this.getTreeDimensions().bottom);
323
413
  }
324
414
  }
325
- addParent(nodeInfo) {
326
- if (!this.parent) {
327
- return null;
328
- } else {
329
- const newParent = this.createNode(nodeInfo);
330
- if (this.tree) {
331
- newParent.setParent(this.tree);
332
- }
333
- const originalParent = this.parent;
334
- for (const child of originalParent.children) {
335
- newParent.addChild(child);
336
- }
337
- originalParent.children = [];
338
- originalParent.addChild(newParent);
339
- return newParent;
340
- }
415
+ mustCaptureElement(element) {
416
+ const nodeName = element.nodeName;
417
+ return nodeName !== "INPUT" && nodeName !== "SELECT" && nodeName !== "TEXTAREA";
341
418
  }
342
- remove() {
343
- if (this.parent) {
344
- this.parent.removeChild(this);
345
- this.parent = null;
419
+ canMoveToArea(area) {
420
+ if (!this.onCanMoveTo) {
421
+ return true;
346
422
  }
423
+ if (!this.currentItem) {
424
+ return false;
425
+ }
426
+ const positionName = getPositionName(area.position);
427
+ return this.onCanMoveTo(this.currentItem.node, area.node, positionName);
347
428
  }
348
- append(nodeInfo) {
349
- const node = this.createNode(nodeInfo);
350
- this.addChild(node);
351
- node.loadChildrenFromData(nodeInfo);
352
- return node;
353
- }
354
- prepend(nodeInfo) {
355
- const node = this.createNode(nodeInfo);
356
- this.addChildAtPosition(node, 0);
357
- node.loadChildrenFromData(nodeInfo);
358
- return node;
429
+ removeHitAreas() {
430
+ this.hitAreas = [];
359
431
  }
360
- isParentOf(node) {
361
- let parent = node.parent;
362
- while (parent) {
363
- if (parent === this) {
364
- return true;
365
- }
366
- parent = parent.parent;
432
+ clear() {
433
+ if (this.dragElement) {
434
+ this.dragElement.remove();
435
+ this.dragElement = null;
367
436
  }
368
- return false;
369
437
  }
370
- getLevel() {
371
- let level = 0;
372
- let node = this; // eslint-disable-line @typescript-eslint/no-this-alias
373
-
374
- while (node.parent) {
375
- level += 1;
376
- node = node.parent;
438
+ removeDropHint() {
439
+ if (this.previousGhost) {
440
+ this.previousGhost.remove();
377
441
  }
378
- return level;
379
442
  }
380
- getNodeById(nodeId) {
381
- return this.idMapping.get(nodeId) || null;
382
- }
383
- addNodeToIndex(node) {
384
- if (node.id != null) {
385
- this.idMapping.set(node.id, node);
386
- }
443
+ removeHover() {
444
+ this.hoveredArea = null;
387
445
  }
388
- removeNodeFromIndex(node) {
389
- if (node.id != null) {
390
- this.idMapping.delete(node.id);
446
+ findHoveredArea(x, y) {
447
+ const dimensions = this.getTreeDimensions();
448
+ if (x < dimensions.left || y < dimensions.top || x > dimensions.right || y > dimensions.bottom) {
449
+ return null;
391
450
  }
392
- }
393
- removeChildren() {
394
- this.iterate(child => {
395
- this.tree?.removeNodeFromIndex(child);
396
- return true;
397
- });
398
- this.children = [];
399
- }
400
- getPreviousSibling() {
401
- if (!this.parent) {
402
- return null;
403
- } else {
404
- const previousIndex = this.parent.getChildIndex(this) - 1;
405
- if (previousIndex >= 0) {
406
- return this.parent.children[previousIndex] || null;
407
- } else {
451
+ let low = 0;
452
+ let high = this.hitAreas.length;
453
+ while (low < high) {
454
+ const mid = low + high >> 1;
455
+ const area = this.hitAreas[mid];
456
+ if (!area) {
408
457
  return null;
409
458
  }
410
- }
411
- }
412
- getNextSibling() {
413
- if (!this.parent) {
414
- return null;
415
- } else {
416
- const nextIndex = this.parent.getChildIndex(this) + 1;
417
- if (nextIndex < this.parent.children.length) {
418
- return this.parent.children[nextIndex] || null;
459
+ if (y < area.top) {
460
+ high = mid;
461
+ } else if (y > area.bottom) {
462
+ low = mid + 1;
419
463
  } else {
420
- return null;
464
+ return area;
421
465
  }
422
466
  }
467
+ return null;
423
468
  }
424
- getNodesByProperty(key, value) {
425
- return this.filter(node => node[key] === value);
469
+ mustOpenFolderTimer(area) {
470
+ const node = area.node;
471
+ return node.isFolder() && !node.is_open && area.position === Position.Inside;
426
472
  }
427
- filter(f) {
428
- const result = [];
429
- this.iterate(node => {
430
- if (f(node)) {
431
- result.push(node);
432
- }
433
- return true;
434
- });
435
- return result;
473
+ updateDropHint() {
474
+ if (!this.hoveredArea) {
475
+ return;
476
+ }
477
+
478
+ // remove previous drop hint
479
+ this.removeDropHint();
480
+
481
+ // add new drop hint
482
+ const nodeElement = this.getNodeElementForNode(this.hoveredArea.node);
483
+ this.previousGhost = nodeElement.addDropHint(this.hoveredArea.position);
436
484
  }
437
- getNextNode() {
438
- let includeChildren = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
439
- if (includeChildren && this.hasChildren()) {
440
- return this.children[0] || null;
441
- } else if (!this.parent) {
442
- return null;
443
- } else {
444
- const nextSibling = this.getNextSibling();
445
- if (nextSibling) {
446
- return nextSibling;
447
- } else {
448
- return this.parent.getNextNode(false);
449
- }
485
+ startOpenFolderTimer(folder) {
486
+ const openFolder = () => {
487
+ this.openNode(folder, this.slide, () => {
488
+ this.refresh();
489
+ this.updateDropHint();
490
+ });
491
+ };
492
+ this.stopOpenFolderTimer();
493
+ const openFolderDelay = this.openFolderDelay;
494
+ if (openFolderDelay !== false) {
495
+ this.openFolderTimer = window.setTimeout(openFolder, openFolderDelay);
450
496
  }
451
497
  }
452
- getNextVisibleNode() {
453
- if (this.hasChildren() && this.is_open) {
454
- // First child
455
- return this.children[0] || null;
456
- } else {
457
- if (!this.parent) {
458
- return null;
459
- } else {
460
- const nextSibling = this.getNextSibling();
461
- if (nextSibling) {
462
- // Next sibling
463
- return nextSibling;
464
- } else {
465
- // Next node of parent
466
- return this.parent.getNextNode(false);
498
+ stopOpenFolderTimer() {
499
+ if (this.openFolderTimer) {
500
+ clearTimeout(this.openFolderTimer);
501
+ this.openFolderTimer = null;
502
+ }
503
+ }
504
+ moveItem(positionInfo) {
505
+ if (this.currentItem && this.hoveredArea && this.hoveredArea.position !== Position.None && this.canMoveToArea(this.hoveredArea)) {
506
+ const movedNode = this.currentItem.node;
507
+ const targetNode = this.hoveredArea.node;
508
+ const position = this.hoveredArea.position;
509
+ const previousParent = movedNode.parent;
510
+ if (position === Position.Inside) {
511
+ this.hoveredArea.node.is_open = true;
512
+ }
513
+ const doMove = () => {
514
+ const tree = this.getTree();
515
+ if (tree) {
516
+ tree.moveNode(movedNode, targetNode, position);
517
+ this.treeElement.textContent = "";
518
+ this.refreshElements(null);
519
+ }
520
+ };
521
+ const event = this.triggerEvent("tree.move", {
522
+ move_info: {
523
+ moved_node: movedNode,
524
+ target_node: targetNode,
525
+ position: getPositionName(position),
526
+ previous_parent: previousParent,
527
+ do_move: doMove,
528
+ original_event: positionInfo.originalEvent
467
529
  }
530
+ });
531
+ if (!event.isDefaultPrevented()) {
532
+ doMove();
468
533
  }
469
534
  }
470
535
  }
471
- getPreviousNode() {
472
- if (!this.parent) {
473
- return null;
536
+ getTreeDimensions() {
537
+ // Return the dimensions of the tree. Add a margin to the bottom to allow
538
+ // to drag-and-drop after the last element.
539
+ const treePosition = getElementPosition(this.treeElement);
540
+ const left = treePosition.left + this.getScrollLeft();
541
+ const top = treePosition.top;
542
+ return {
543
+ left,
544
+ top,
545
+ right: left + this.treeElement.clientWidth,
546
+ bottom: top + this.treeElement.clientHeight + 16
547
+ };
548
+ }
549
+ }
550
+
551
+ class ElementsRenderer {
552
+ constructor(_ref) {
553
+ let {
554
+ autoEscape,
555
+ buttonLeft,
556
+ closedIcon,
557
+ onCreateLi,
558
+ dragAndDrop,
559
+ $element,
560
+ getTree,
561
+ isNodeSelected,
562
+ openedIcon,
563
+ rtl,
564
+ showEmptyFolder,
565
+ tabIndex
566
+ } = _ref;
567
+ this.autoEscape = autoEscape;
568
+ this.buttonLeft = buttonLeft;
569
+ this.dragAndDrop = dragAndDrop;
570
+ this.$element = $element;
571
+ this.getTree = getTree;
572
+ this.isNodeSelected = isNodeSelected;
573
+ this.onCreateLi = onCreateLi;
574
+ this.rtl = rtl;
575
+ this.showEmptyFolder = showEmptyFolder;
576
+ this.tabIndex = tabIndex;
577
+ this.openedIconElement = this.createButtonElement(openedIcon || "+");
578
+ this.closedIconElement = this.createButtonElement(closedIcon || "-");
579
+ }
580
+ render(fromNode) {
581
+ if (fromNode && fromNode.parent) {
582
+ this.renderFromNode(fromNode);
474
583
  } else {
475
- const previousSibling = this.getPreviousSibling();
476
- if (!previousSibling) {
477
- return this.getParent();
478
- } else if (previousSibling.hasChildren()) {
479
- return previousSibling.getLastChild();
480
- } else {
481
- return previousSibling;
482
- }
584
+ this.renderFromRoot();
483
585
  }
484
586
  }
485
- getPreviousVisibleNode() {
486
- if (!this.parent) {
487
- return null;
488
- } else {
489
- const previousSibling = this.getPreviousSibling();
490
- if (!previousSibling) {
491
- return this.getParent();
492
- } else if (!previousSibling.hasChildren() || !previousSibling.is_open) {
493
- // Previous sibling
494
- return previousSibling;
495
- } else {
496
- // Last child of previous sibling
497
- return previousSibling.getLastChild();
498
- }
587
+ renderFromRoot() {
588
+ this.$element.empty();
589
+ const tree = this.getTree();
590
+ if (this.$element[0] && tree) {
591
+ this.createDomElements(this.$element[0], tree.children, true, 1);
499
592
  }
500
593
  }
501
- getParent() {
502
- // Return parent except if it is the root node
503
- if (!this.parent) {
504
- return null;
505
- } else if (!this.parent.parent) {
506
- // Root node -> null
507
- return null;
508
- } else {
509
- return this.parent;
594
+ renderFromNode(node) {
595
+ // remember current li
596
+ const $previousLi = jQuery(node.element);
597
+
598
+ // create element
599
+ const li = this.createLi(node, node.getLevel());
600
+ this.attachNodeData(node, li);
601
+
602
+ // add element to dom
603
+ $previousLi.after(li);
604
+
605
+ // remove previous li
606
+ $previousLi.remove();
607
+
608
+ // create children
609
+ if (node.children) {
610
+ this.createDomElements(li, node.children, false, node.getLevel() + 1);
510
611
  }
511
612
  }
512
- getLastChild() {
513
- if (!this.hasChildren()) {
514
- return null;
515
- } else {
516
- const lastChild = this.children[this.children.length - 1];
517
- if (!lastChild) {
518
- return null;
519
- }
520
- if (!(lastChild.hasChildren() && lastChild.is_open)) {
521
- return lastChild;
522
- } else {
523
- return lastChild?.getLastChild();
613
+ createDomElements(element, children, isRootNode, level) {
614
+ const ul = this.createUl(isRootNode);
615
+ element.appendChild(ul);
616
+ for (const child of children) {
617
+ const li = this.createLi(child, level);
618
+ ul.appendChild(li);
619
+ this.attachNodeData(child, li);
620
+ if (child.hasChildren()) {
621
+ this.createDomElements(li, child.children, false, level + 1);
524
622
  }
525
623
  }
526
624
  }
527
-
528
- // Init Node from data without making it the root of the tree
529
- initFromData(data) {
530
- const addNode = nodeData => {
531
- this.setData(nodeData);
532
- if (isNodeRecordWithChildren(nodeData) && nodeData.children.length) {
533
- addChildren(nodeData.children);
534
- }
535
- };
536
- const addChildren = childrenData => {
537
- for (const child of childrenData) {
538
- const node = this.createNode();
539
- node.initFromData(child);
540
- this.addChild(node);
541
- }
542
- };
543
- addNode(data);
544
- }
545
- setParent(parent) {
546
- this.parent = parent;
547
- this.tree = parent.tree;
548
- this.tree?.addNodeToIndex(this);
549
- }
550
- doRemoveChild(node) {
551
- this.children.splice(this.getChildIndex(node), 1);
552
- this.tree?.removeNodeFromIndex(node);
553
- }
554
- getNodeClass() {
555
- return this.nodeClass || this?.tree?.nodeClass || Node;
556
- }
557
- createNode(nodeData) {
558
- const nodeClass = this.getNodeClass();
559
- return new nodeClass(nodeData);
560
- }
561
-
562
- // Load children data from nodeInfo if it has children
563
- loadChildrenFromData(nodeInfo) {
564
- if (isNodeRecordWithChildren(nodeInfo) && nodeInfo.children.length) {
565
- this.loadFromData(nodeInfo.children);
566
- }
625
+ attachNodeData(node, li) {
626
+ node.element = li;
627
+ jQuery(li).data("node", node);
567
628
  }
568
- }
569
-
570
- class DragElement {
571
- constructor(nodeName, offsetX, offsetY, $tree, autoEscape) {
572
- this.offsetX = offsetX;
573
- this.offsetY = offsetY;
574
- this.$element = jQuery("<span>").addClass("jqtree-title jqtree-dragging");
575
- if (autoEscape) {
576
- this.$element.text(nodeName);
629
+ createUl(isRootNode) {
630
+ let classString;
631
+ let role;
632
+ if (!isRootNode) {
633
+ classString = "";
634
+ role = "group";
577
635
  } else {
578
- this.$element.html(nodeName);
636
+ classString = "jqtree-tree";
637
+ role = "tree";
638
+ if (this.rtl) {
639
+ classString += " jqtree-rtl";
640
+ }
641
+ }
642
+ if (this.dragAndDrop) {
643
+ classString += " jqtree-dnd";
579
644
  }
580
- this.$element.css("position", "absolute");
581
- $tree.append(this.$element);
645
+ const ul = document.createElement("ul");
646
+ ul.className = `jqtree_common ${classString}`;
647
+ ul.setAttribute("role", role);
648
+ return ul;
582
649
  }
583
- move(pageX, pageY) {
584
- this.$element.offset({
585
- left: pageX - this.offsetX,
586
- top: pageY - this.offsetY
587
- });
650
+ createLi(node, level) {
651
+ const isSelected = Boolean(this.isNodeSelected(node));
652
+ const mustShowFolder = node.isFolder() || node.isEmptyFolder && this.showEmptyFolder;
653
+ const li = mustShowFolder ? this.createFolderLi(node, level, isSelected) : this.createNodeLi(node, level, isSelected);
654
+ if (this.onCreateLi) {
655
+ this.onCreateLi(node, jQuery(li), isSelected);
656
+ }
657
+ return li;
588
658
  }
589
- remove() {
590
- this.$element.remove();
659
+ setTreeItemAriaAttributes(element, name, level, isSelected) {
660
+ element.setAttribute("aria-label", name);
661
+ element.setAttribute("aria-level", `${level}`);
662
+ element.setAttribute("aria-selected", getBoolString(isSelected));
663
+ element.setAttribute("role", "treeitem");
591
664
  }
592
- }
665
+ createFolderLi(node, level, isSelected) {
666
+ const buttonClasses = this.getButtonClasses(node);
667
+ const folderClasses = this.getFolderClasses(node, isSelected);
668
+ const iconElement = node.is_open ? this.openedIconElement : this.closedIconElement;
593
669
 
594
- const isInt = n => typeof n === "number" && n % 1 === 0;
595
- const isFunction = v => typeof v === "function";
596
- const getBoolString = value => value ? "true" : "false";
597
- const getOffsetTop = element => element.getBoundingClientRect().y + window.scrollY;
598
-
599
- class VisibleNodeIterator {
600
- constructor(tree) {
601
- this.tree = tree;
602
- }
603
- iterate() {
604
- let isFirstNode = true;
605
- const _iterateNode = (node, nextNode) => {
606
- let mustIterateInside = (node.is_open || !node.element) && node.hasChildren();
607
- let element = null;
608
-
609
- // Is the element visible?
610
- if (node.element?.offsetParent) {
611
- element = node.element;
612
- if (isFirstNode) {
613
- this.handleFirstNode(node);
614
- isFirstNode = false;
615
- }
616
- if (!node.hasChildren()) {
617
- this.handleNode(node, nextNode, node.element);
618
- } else if (node.is_open) {
619
- if (!this.handleOpenFolder(node, node.element)) {
620
- mustIterateInside = false;
621
- }
622
- } else {
623
- this.handleClosedFolder(node, nextNode, element);
624
- }
625
- }
626
- if (mustIterateInside) {
627
- const childrenLength = node.children.length;
628
- node.children.forEach((_, i) => {
629
- const child = node.children[i];
630
- if (child) {
631
- if (i === childrenLength - 1) {
632
- _iterateNode(child, null);
633
- } else {
634
- const nextChild = node.children[i + 1];
635
- if (nextChild) {
636
- _iterateNode(child, nextChild);
637
- }
638
- }
639
- }
640
- });
641
- if (node.is_open && element) {
642
- this.handleAfterOpenFolder(node, nextNode);
643
- }
644
- }
645
- };
646
- _iterateNode(this.tree, null);
647
- }
670
+ // li
671
+ const li = document.createElement("li");
672
+ li.className = `jqtree_common ${folderClasses}`;
673
+ li.setAttribute("role", "none");
648
674
 
649
- /*
650
- override
651
- return
652
- - true: continue iterating
653
- - false: stop iterating
654
- */
655
- }
675
+ // div
676
+ const div = document.createElement("div");
677
+ div.className = "jqtree-element jqtree_common";
678
+ div.setAttribute("role", "none");
679
+ li.appendChild(div);
656
680
 
657
- class HitAreasGenerator extends VisibleNodeIterator {
658
- constructor(tree, currentNode, treeBottom) {
659
- super(tree);
660
- this.currentNode = currentNode;
661
- this.treeBottom = treeBottom;
662
- }
663
- generate() {
664
- this.positions = [];
665
- this.lastTop = 0;
666
- this.iterate();
667
- return this.generateHitAreas(this.positions);
668
- }
669
- generateHitAreas(positions) {
670
- let previousTop = positions[0]?.top ?? 0;
671
- let group = [];
672
- const hitAreas = [];
673
- for (const position of positions) {
674
- if (position.top !== previousTop && group.length) {
675
- this.generateHitAreasForGroup(hitAreas, group, previousTop, position.top);
676
- previousTop = position.top;
677
- group = [];
678
- }
679
- group.push(position);
680
- }
681
- this.generateHitAreasForGroup(hitAreas, group, previousTop, this.treeBottom);
682
- return hitAreas;
683
- }
684
- handleOpenFolder(node, element) {
685
- if (node === this.currentNode) {
686
- // Cannot move inside current item
687
- // Stop iterating
688
- return false;
681
+ // button link
682
+ const buttonLink = document.createElement("a");
683
+ buttonLink.className = buttonClasses;
684
+ if (iconElement) {
685
+ buttonLink.appendChild(iconElement.cloneNode(true));
689
686
  }
690
-
691
- // Cannot move before current item
692
- if (node.children[0] !== this.currentNode) {
693
- this.addPosition(node, Position.Inside, getOffsetTop(element));
687
+ if (this.buttonLeft) {
688
+ div.appendChild(buttonLink);
694
689
  }
695
690
 
696
- // Continue iterating
697
- return true;
691
+ // title span
692
+ const titleSpan = this.createTitleSpan(node.name, isSelected, true, level);
693
+ titleSpan.setAttribute("aria-expanded", getBoolString(node.is_open));
694
+ div.appendChild(titleSpan);
695
+ if (!this.buttonLeft) {
696
+ div.appendChild(buttonLink);
697
+ }
698
+ return li;
698
699
  }
699
- handleClosedFolder(node, nextNode, element) {
700
- const top = getOffsetTop(element);
701
- if (node === this.currentNode) {
702
- // Cannot move after current item
703
- this.addPosition(node, Position.None, top);
704
- } else {
705
- this.addPosition(node, Position.Inside, top);
700
+ createNodeLi(node, level, isSelected) {
701
+ const liClasses = ["jqtree_common"];
702
+ if (isSelected) {
703
+ liClasses.push("jqtree-selected");
704
+ }
705
+ const classString = liClasses.join(" ");
706
706
 
707
- // Cannot move before current item
708
- if (nextNode !== this.currentNode) {
709
- this.addPosition(node, Position.After, top);
707
+ // li
708
+ const li = document.createElement("li");
709
+ li.className = classString;
710
+ li.setAttribute("role", "none");
711
+
712
+ // div
713
+ const div = document.createElement("div");
714
+ div.className = "jqtree-element jqtree_common";
715
+ div.setAttribute("role", "none");
716
+ li.appendChild(div);
717
+
718
+ // title span
719
+ const titleSpan = this.createTitleSpan(node.name, isSelected, false, level);
720
+ div.appendChild(titleSpan);
721
+ return li;
722
+ }
723
+ createTitleSpan(nodeName, isSelected, isFolder, level) {
724
+ const titleSpan = document.createElement("span");
725
+ let classes = "jqtree-title jqtree_common";
726
+ if (isFolder) {
727
+ classes += " jqtree-title-folder";
728
+ }
729
+ classes += ` jqtree-title-button-${this.buttonLeft ? "left" : "right"}`;
730
+ titleSpan.className = classes;
731
+ if (isSelected) {
732
+ const tabIndex = this.tabIndex;
733
+ if (tabIndex !== undefined) {
734
+ titleSpan.setAttribute("tabindex", `${tabIndex}`);
710
735
  }
711
736
  }
712
- }
713
- handleFirstNode(node) {
714
- if (node !== this.currentNode) {
715
- this.addPosition(node, Position.Before, getOffsetTop(node.element));
737
+ this.setTreeItemAriaAttributes(titleSpan, nodeName, level, isSelected);
738
+ if (this.autoEscape) {
739
+ titleSpan.textContent = nodeName;
740
+ } else {
741
+ titleSpan.innerHTML = nodeName;
716
742
  }
743
+ return titleSpan;
717
744
  }
718
- handleAfterOpenFolder(node, nextNode) {
719
- if (node === this.currentNode || nextNode === this.currentNode) {
720
- // Cannot move before or after current item
721
- this.addPosition(node, Position.None, this.lastTop);
745
+ getButtonClasses(node) {
746
+ const classes = ["jqtree-toggler", "jqtree_common"];
747
+ if (!node.is_open) {
748
+ classes.push("jqtree-closed");
749
+ }
750
+ if (this.buttonLeft) {
751
+ classes.push("jqtree-toggler-left");
722
752
  } else {
723
- this.addPosition(node, Position.After, this.lastTop);
753
+ classes.push("jqtree-toggler-right");
724
754
  }
755
+ return classes.join(" ");
725
756
  }
726
- handleNode(node, nextNode, element) {
727
- const top = getOffsetTop(element);
728
- if (node === this.currentNode) {
729
- // Cannot move inside current item
730
- this.addPosition(node, Position.None, top);
731
- } else {
732
- this.addPosition(node, Position.Inside, top);
757
+ getFolderClasses(node, isSelected) {
758
+ const classes = ["jqtree-folder"];
759
+ if (!node.is_open) {
760
+ classes.push("jqtree-closed");
733
761
  }
734
- if (nextNode === this.currentNode || node === this.currentNode) {
735
- // Cannot move before or after current item
736
- this.addPosition(node, Position.None, top);
737
- } else {
738
- this.addPosition(node, Position.After, top);
762
+ if (isSelected) {
763
+ classes.push("jqtree-selected");
764
+ }
765
+ if (node.is_loading) {
766
+ classes.push("jqtree-loading");
739
767
  }
768
+ return classes.join(" ");
740
769
  }
741
- addPosition(node, position, top) {
742
- const area = {
743
- top,
744
- bottom: 0,
745
- node,
746
- position
747
- };
748
- this.positions.push(area);
749
- this.lastTop = top;
750
- }
751
- generateHitAreasForGroup(hitAreas, positionsInGroup, top, bottom) {
752
- // limit positions in group
753
- const positionCount = Math.min(positionsInGroup.length, 4);
754
- const areaHeight = Math.round((bottom - top) / positionCount);
755
- let areaTop = top;
756
- let i = 0;
757
- while (i < positionCount) {
758
- const position = positionsInGroup[i];
759
- if (position) {
760
- hitAreas.push({
761
- top: areaTop,
762
- bottom: areaTop + areaHeight,
763
- node: position.node,
764
- position: position.position
765
- });
766
- }
767
- areaTop += areaHeight;
768
- i += 1;
770
+ createButtonElement(value) {
771
+ if (typeof value === "string") {
772
+ // convert value to html
773
+ const div = document.createElement("div");
774
+ div.innerHTML = value;
775
+ return document.createTextNode(div.innerHTML);
776
+ } else if (value == null) {
777
+ return undefined;
778
+ } else if (value.nodeType) {
779
+ return value;
780
+ } else {
781
+ return jQuery(value)[0];
769
782
  }
770
783
  }
771
784
  }
772
785
 
773
- class DragAndDropHandler {
786
+ class DataLoader {
774
787
  constructor(_ref) {
775
788
  let {
776
- autoEscape,
777
- getNodeElement,
778
- getNodeElementForNode,
779
- getScrollLeft,
780
- getTree,
781
- onCanMove,
782
- onCanMoveTo,
783
- onDragMove,
784
- onDragStop,
785
- onIsMoveHandle,
786
- openNode,
787
- refreshElements,
788
- slide,
789
- $treeElement,
789
+ dataFilter,
790
+ loadData,
791
+ onLoadFailed,
792
+ onLoading,
793
+ treeElement,
790
794
  triggerEvent
791
795
  } = _ref;
792
- this.autoEscape = autoEscape;
793
- this.getNodeElement = getNodeElement;
794
- this.getNodeElementForNode = getNodeElementForNode;
795
- this.getScrollLeft = getScrollLeft;
796
- this.getTree = getTree;
797
- this.onCanMove = onCanMove;
798
- this.onCanMoveTo = onCanMoveTo;
799
- this.onDragMove = onDragMove;
800
- this.onDragStop = onDragStop;
801
- this.onIsMoveHandle = onIsMoveHandle;
802
- this.openNode = openNode;
803
- this.refreshElements = refreshElements;
804
- this.slide = slide;
805
- this.$treeElement = $treeElement;
796
+ this.dataFilter = dataFilter;
797
+ this.loadData = loadData;
798
+ this.onLoadFailed = onLoadFailed;
799
+ this.onLoading = onLoading;
800
+ this.treeElement = treeElement;
806
801
  this.triggerEvent = triggerEvent;
807
- this.hoveredArea = null;
808
- this.hitAreas = [];
809
- this.isDragging = false;
810
- this.currentItem = null;
811
802
  }
812
- mouseCapture(positionInfo) {
813
- const element = positionInfo.target;
814
- if (!this.mustCaptureElement(element)) {
815
- return null;
816
- }
817
- if (this.onIsMoveHandle && !this.onIsMoveHandle(jQuery(element))) {
818
- return null;
803
+ loadFromUrl(urlInfo, parentNode, onFinished) {
804
+ if (!urlInfo) {
805
+ return;
819
806
  }
820
- let nodeElement = this.getNodeElement(element);
821
- if (nodeElement && this.onCanMove) {
822
- if (!this.onCanMove(nodeElement.node)) {
823
- nodeElement = null;
807
+ const element = this.getDomElement(parentNode);
808
+ this.addLoadingClass(element);
809
+ this.notifyLoading(true, parentNode, element);
810
+ const stopLoading = () => {
811
+ this.removeLoadingClass(element);
812
+ this.notifyLoading(false, parentNode, element);
813
+ };
814
+ const handleSuccess = data => {
815
+ stopLoading();
816
+ this.loadData(this.parseData(data), parentNode);
817
+ if (onFinished && typeof onFinished === "function") {
818
+ onFinished();
824
819
  }
825
- }
826
- this.currentItem = nodeElement;
827
- return this.currentItem != null;
820
+ };
821
+ const handleError = jqXHR => {
822
+ stopLoading();
823
+ if (this.onLoadFailed) {
824
+ this.onLoadFailed(jqXHR);
825
+ }
826
+ };
827
+ this.submitRequest(urlInfo, handleSuccess, handleError);
828
828
  }
829
- mouseStart(positionInfo) {
830
- if (!this.currentItem || positionInfo.pageX === undefined || positionInfo.pageY === undefined) {
831
- return false;
829
+ addLoadingClass(element) {
830
+ element.classList.add("jqtree-loading");
831
+ }
832
+ removeLoadingClass(element) {
833
+ element.classList.remove("jqtree-loading");
834
+ }
835
+ getDomElement(parentNode) {
836
+ if (parentNode) {
837
+ return parentNode.element;
838
+ } else {
839
+ return this.treeElement;
832
840
  }
833
- this.refresh();
834
- const offset = jQuery(positionInfo.target).offset();
835
- const left = offset ? offset.left : 0;
836
- const top = offset ? offset.top : 0;
837
- const node = this.currentItem.node;
838
- this.dragElement = new DragElement(node.name, positionInfo.pageX - left, positionInfo.pageY - top, this.$treeElement, this.autoEscape ?? true);
839
- this.isDragging = true;
840
- this.currentItem.element.classList.add("jqtree-moving");
841
- return true;
842
841
  }
843
- mouseDrag(positionInfo) {
844
- if (!this.currentItem || !this.dragElement || positionInfo.pageX === undefined || positionInfo.pageY === undefined) {
845
- return false;
842
+ notifyLoading(isLoading, node, element) {
843
+ const $el = jQuery(element);
844
+ if (this.onLoading) {
845
+ this.onLoading(isLoading, node, $el);
846
846
  }
847
- this.dragElement.move(positionInfo.pageX, positionInfo.pageY);
848
- const area = this.findHoveredArea(positionInfo.pageX, positionInfo.pageY);
849
- if (area && this.canMoveToArea(area)) {
850
- if (!area.node.isFolder()) {
851
- this.stopOpenFolderTimer();
852
- }
853
- if (this.hoveredArea !== area) {
854
- this.hoveredArea = area;
855
-
856
- // If this is a closed folder, start timer to open it
857
- if (this.mustOpenFolderTimer(area)) {
858
- this.startOpenFolderTimer(area.node);
859
- } else {
860
- this.stopOpenFolderTimer();
861
- }
862
- this.updateDropHint();
847
+ this.triggerEvent("tree.loading_data", {
848
+ isLoading,
849
+ node,
850
+ $el
851
+ });
852
+ }
853
+ submitRequest(urlInfoInput, handleSuccess, handleError) {
854
+ const urlInfo = typeof urlInfoInput === "string" ? {
855
+ url: urlInfoInput
856
+ } : urlInfoInput;
857
+ const ajaxSettings = {
858
+ method: "GET",
859
+ cache: false,
860
+ dataType: "json",
861
+ success: handleSuccess,
862
+ error: handleError,
863
+ ...urlInfo
864
+ };
865
+ ajaxSettings.method = ajaxSettings.method?.toUpperCase() || "GET";
866
+ void jQuery.ajax(ajaxSettings);
867
+ }
868
+ parseData(data) {
869
+ const getParsedData = () => {
870
+ if (typeof data === "string") {
871
+ return JSON.parse(data);
872
+ } else {
873
+ return data;
863
874
  }
875
+ };
876
+ const parsedData = getParsedData();
877
+ if (this.dataFilter) {
878
+ return this.dataFilter(parsedData);
864
879
  } else {
865
- this.removeDropHint();
866
- this.stopOpenFolderTimer();
867
- this.hoveredArea = area;
868
- }
869
- if (!area) {
870
- if (this.onDragMove) {
871
- this.onDragMove(this.currentItem.node, positionInfo.originalEvent);
872
- }
880
+ return parsedData;
873
881
  }
874
- return true;
875
882
  }
876
- mouseStop(positionInfo) {
877
- this.moveItem(positionInfo);
878
- this.clear();
879
- this.removeHover();
880
- this.removeDropHint();
881
- this.removeHitAreas();
882
- const currentItem = this.currentItem;
883
- if (this.currentItem) {
884
- this.currentItem.element.classList.remove("jqtree-moving");
885
- this.currentItem = null;
883
+ }
884
+
885
+ class KeyHandler {
886
+ constructor(_ref) {
887
+ let {
888
+ closeNode,
889
+ getSelectedNode,
890
+ isFocusOnTree,
891
+ keyboardSupport,
892
+ openNode,
893
+ selectNode
894
+ } = _ref;
895
+ this.closeNode = closeNode;
896
+ this.getSelectedNode = getSelectedNode;
897
+ this.isFocusOnTree = isFocusOnTree;
898
+ this.keyboardSupport = keyboardSupport;
899
+ this.openNode = openNode;
900
+ this.originalSelectNode = selectNode;
901
+ if (keyboardSupport) {
902
+ this.handleKeyDownHandler = this.handleKeyDown.bind(this);
903
+ document.addEventListener("keydown", this.handleKeyDownHandler);
886
904
  }
887
- this.isDragging = false;
888
- if (!this.hoveredArea && currentItem) {
889
- if (this.onDragStop) {
890
- this.onDragStop(currentItem.node, positionInfo.originalEvent);
891
- }
905
+ }
906
+ deinit() {
907
+ if (this.handleKeyDownHandler) {
908
+ document.removeEventListener("keydown", this.handleKeyDownHandler);
892
909
  }
893
- return false;
894
910
  }
895
- refresh() {
896
- this.removeHitAreas();
897
- if (this.currentItem) {
898
- this.generateHitAreas();
899
- this.currentItem = this.getNodeElementForNode(this.currentItem.node);
900
- if (this.isDragging) {
901
- this.currentItem.element.classList.add("jqtree-moving");
911
+ moveDown(selectedNode) {
912
+ return this.selectNode(selectedNode.getNextVisibleNode());
913
+ }
914
+ moveUp(selectedNode) {
915
+ return this.selectNode(selectedNode.getPreviousVisibleNode());
916
+ }
917
+ moveRight(selectedNode) {
918
+ if (!selectedNode.isFolder()) {
919
+ return true;
920
+ } else {
921
+ // folder node
922
+ if (selectedNode.is_open) {
923
+ // Right moves to the first child of an open node
924
+ return this.selectNode(selectedNode.getNextVisibleNode());
925
+ } else {
926
+ // Right expands a closed node
927
+ this.openNode(selectedNode);
928
+ return false;
902
929
  }
903
930
  }
904
931
  }
905
- generateHitAreas() {
906
- const tree = this.getTree();
907
- if (!this.currentItem || !tree) {
908
- this.hitAreas = [];
932
+ moveLeft(selectedNode) {
933
+ if (selectedNode.isFolder() && selectedNode.is_open) {
934
+ // Left on an open node closes the node
935
+ this.closeNode(selectedNode);
936
+ return false;
909
937
  } else {
910
- const hitAreasGenerator = new HitAreasGenerator(tree, this.currentItem.node, this.getTreeDimensions().bottom);
911
- this.hitAreas = hitAreasGenerator.generate();
938
+ // Left on a closed or end node moves focus to the node's parent
939
+ return this.selectNode(selectedNode.getParent());
912
940
  }
913
941
  }
914
- mustCaptureElement(element) {
915
- const nodeName = element.nodeName;
916
- return nodeName !== "INPUT" && nodeName !== "SELECT" && nodeName !== "TEXTAREA";
917
- }
918
- canMoveToArea(area) {
919
- if (!this.onCanMoveTo) {
942
+ selectNode(node) {
943
+ if (!node) {
920
944
  return true;
921
- }
922
- if (!this.currentItem) {
945
+ } else {
946
+ this.originalSelectNode(node);
923
947
  return false;
924
948
  }
925
- const positionName = getPositionName(area.position);
926
- return this.onCanMoveTo(this.currentItem.node, area.node, positionName);
927
949
  }
928
- removeHitAreas() {
929
- this.hitAreas = [];
930
- }
931
- clear() {
932
- if (this.dragElement) {
933
- this.dragElement.remove();
934
- this.dragElement = null;
950
+ handleKeyDown = e => {
951
+ if (!this.canHandleKeyboard()) {
952
+ return true;
935
953
  }
936
- }
937
- removeDropHint() {
938
- if (this.previousGhost) {
939
- this.previousGhost.remove();
954
+ const selectedNode = this.getSelectedNode();
955
+ if (!selectedNode) {
956
+ return true;
940
957
  }
941
- }
942
- removeHover() {
943
- this.hoveredArea = null;
944
- }
945
- findHoveredArea(x, y) {
946
- const dimensions = this.getTreeDimensions();
947
- if (x < dimensions.left || y < dimensions.top || x > dimensions.right || y > dimensions.bottom) {
948
- return null;
958
+ switch (e.key) {
959
+ case "ArrowDown":
960
+ return this.moveDown(selectedNode);
961
+ case "ArrowUp":
962
+ return this.moveUp(selectedNode);
963
+ case "ArrowRight":
964
+ return this.moveRight(selectedNode);
965
+ case "ArrowLeft":
966
+ return this.moveLeft(selectedNode);
967
+ default:
968
+ return true;
949
969
  }
950
- let low = 0;
951
- let high = this.hitAreas.length;
952
- while (low < high) {
953
- const mid = low + high >> 1;
954
- const area = this.hitAreas[mid];
955
- if (!area) {
956
- return null;
957
- }
958
- if (y < area.top) {
959
- high = mid;
960
- } else if (y > area.bottom) {
961
- low = mid + 1;
962
- } else {
963
- return area;
964
- }
970
+ };
971
+ canHandleKeyboard() {
972
+ return this.keyboardSupport && this.isFocusOnTree();
973
+ }
974
+ }
975
+
976
+ const getPositionInfoFromMouseEvent = e => ({
977
+ originalEvent: e,
978
+ pageX: e.pageX,
979
+ pageY: e.pageY,
980
+ target: e.target
981
+ });
982
+ const getPositionInfoFromTouch = (touch, e) => ({
983
+ originalEvent: e,
984
+ pageX: touch.pageX,
985
+ pageY: touch.pageY,
986
+ target: touch.target
987
+ });
988
+
989
+ class MouseHandler {
990
+ constructor(_ref) {
991
+ let {
992
+ element,
993
+ getMouseDelay,
994
+ getNode,
995
+ onClickButton,
996
+ onClickTitle,
997
+ onMouseCapture,
998
+ onMouseDrag,
999
+ onMouseStart,
1000
+ onMouseStop,
1001
+ triggerEvent,
1002
+ useContextMenu
1003
+ } = _ref;
1004
+ this.element = element;
1005
+ this.getMouseDelay = getMouseDelay;
1006
+ this.getNode = getNode;
1007
+ this.onClickButton = onClickButton;
1008
+ this.onClickTitle = onClickTitle;
1009
+ this.onMouseCapture = onMouseCapture;
1010
+ this.onMouseDrag = onMouseDrag;
1011
+ this.onMouseStart = onMouseStart;
1012
+ this.onMouseStop = onMouseStop;
1013
+ this.triggerEvent = triggerEvent;
1014
+ this.useContextMenu = useContextMenu;
1015
+ element.addEventListener("click", this.handleClick);
1016
+ element.addEventListener("dblclick", this.handleDblclick);
1017
+ element.addEventListener("mousedown", this.mouseDown, {
1018
+ passive: false
1019
+ });
1020
+ element.addEventListener("touchstart", this.touchStart, {
1021
+ passive: false
1022
+ });
1023
+ if (useContextMenu) {
1024
+ element.addEventListener("contextmenu", this.handleContextmenu);
965
1025
  }
966
- return null;
1026
+ this.isMouseStarted = false;
1027
+ this.mouseDelayTimer = null;
1028
+ this.isMouseDelayMet = false;
1029
+ this.mouseDownInfo = null;
967
1030
  }
968
- mustOpenFolderTimer(area) {
969
- const node = area.node;
970
- return node.isFolder() && !node.is_open && area.position === Position.Inside;
1031
+ deinit() {
1032
+ this.element.removeEventListener("click", this.handleClick);
1033
+ this.element.removeEventListener("dblclick", this.handleDblclick);
1034
+ if (this.useContextMenu) {
1035
+ this.element.removeEventListener("contextmenu", this.handleContextmenu);
1036
+ }
1037
+ this.element.removeEventListener("mousedown", this.mouseDown);
1038
+ this.element.removeEventListener("touchstart", this.touchStart);
1039
+ this.removeMouseMoveEventListeners();
971
1040
  }
972
- updateDropHint() {
973
- if (!this.hoveredArea) {
1041
+ mouseDown = e => {
1042
+ // Left mouse button?
1043
+ if (e.button !== 0) {
974
1044
  return;
975
1045
  }
976
-
977
- // remove previous drop hint
978
- this.removeDropHint();
979
-
980
- // add new drop hint
981
- const nodeElement = this.getNodeElementForNode(this.hoveredArea.node);
982
- this.previousGhost = nodeElement.addDropHint(this.hoveredArea.position);
1046
+ const result = this.handleMouseDown(getPositionInfoFromMouseEvent(e));
1047
+ if (result && e.cancelable) {
1048
+ e.preventDefault();
1049
+ }
1050
+ };
1051
+ handleMouseDown(positionInfo) {
1052
+ // We may have missed mouseup (out of window)
1053
+ if (this.isMouseStarted) {
1054
+ this.handleMouseUp(positionInfo);
1055
+ }
1056
+ this.mouseDownInfo = positionInfo;
1057
+ if (!this.onMouseCapture(positionInfo)) {
1058
+ return false;
1059
+ }
1060
+ this.handleStartMouse();
1061
+ return true;
983
1062
  }
984
- startOpenFolderTimer(folder) {
985
- const openFolder = () => {
986
- this.openNode(folder, this.slide, () => {
987
- this.refresh();
988
- this.updateDropHint();
989
- });
990
- };
991
- this.stopOpenFolderTimer();
992
- const openFolderDelay = this.openFolderDelay;
993
- if (openFolderDelay !== false) {
994
- this.openFolderTimer = window.setTimeout(openFolder, openFolderDelay);
1063
+ handleStartMouse() {
1064
+ document.addEventListener("mousemove", this.mouseMove, {
1065
+ passive: false
1066
+ });
1067
+ document.addEventListener("touchmove", this.touchMove, {
1068
+ passive: false
1069
+ });
1070
+ document.addEventListener("mouseup", this.mouseUp, {
1071
+ passive: false
1072
+ });
1073
+ document.addEventListener("touchend", this.touchEnd, {
1074
+ passive: false
1075
+ });
1076
+ const mouseDelay = this.getMouseDelay();
1077
+ if (mouseDelay) {
1078
+ this.startMouseDelayTimer(mouseDelay);
1079
+ } else {
1080
+ this.isMouseDelayMet = true;
995
1081
  }
996
1082
  }
997
- stopOpenFolderTimer() {
998
- if (this.openFolderTimer) {
999
- clearTimeout(this.openFolderTimer);
1000
- this.openFolderTimer = null;
1083
+ startMouseDelayTimer(mouseDelay) {
1084
+ if (this.mouseDelayTimer) {
1085
+ clearTimeout(this.mouseDelayTimer);
1001
1086
  }
1087
+ this.mouseDelayTimer = window.setTimeout(() => {
1088
+ if (this.mouseDownInfo) {
1089
+ this.isMouseDelayMet = true;
1090
+ }
1091
+ }, mouseDelay);
1092
+ this.isMouseDelayMet = false;
1002
1093
  }
1003
- moveItem(positionInfo) {
1004
- if (this.currentItem && this.hoveredArea && this.hoveredArea.position !== Position.None && this.canMoveToArea(this.hoveredArea)) {
1005
- const movedNode = this.currentItem.node;
1006
- const targetNode = this.hoveredArea.node;
1007
- const position = this.hoveredArea.position;
1008
- const previousParent = movedNode.parent;
1009
- if (position === Position.Inside) {
1010
- this.hoveredArea.node.is_open = true;
1094
+ mouseMove = e => {
1095
+ this.handleMouseMove(e, getPositionInfoFromMouseEvent(e));
1096
+ };
1097
+ handleMouseMove(e, positionInfo) {
1098
+ if (this.isMouseStarted) {
1099
+ this.onMouseDrag(positionInfo);
1100
+ if (e.cancelable) {
1101
+ e.preventDefault();
1011
1102
  }
1012
- const doMove = () => {
1013
- const tree = this.getTree();
1014
- if (tree) {
1015
- tree.moveNode(movedNode, targetNode, position);
1016
- this.$treeElement.empty();
1017
- this.refreshElements(null);
1018
- }
1019
- };
1020
- const event = this.triggerEvent("tree.move", {
1021
- move_info: {
1022
- moved_node: movedNode,
1023
- target_node: targetNode,
1024
- position: getPositionName(position),
1025
- previous_parent: previousParent,
1026
- do_move: doMove,
1027
- original_event: positionInfo.originalEvent
1028
- }
1029
- });
1030
- if (!event.isDefaultPrevented()) {
1031
- doMove();
1103
+ return;
1104
+ }
1105
+ if (!this.isMouseDelayMet) {
1106
+ return;
1107
+ }
1108
+ if (this.mouseDownInfo) {
1109
+ this.isMouseStarted = this.onMouseStart(this.mouseDownInfo) !== false;
1110
+ }
1111
+ if (this.isMouseStarted) {
1112
+ this.onMouseDrag(positionInfo);
1113
+ if (e.cancelable) {
1114
+ e.preventDefault();
1032
1115
  }
1116
+ } else {
1117
+ this.handleMouseUp(positionInfo);
1033
1118
  }
1034
1119
  }
1035
- getTreeDimensions() {
1036
- // Return the dimensions of the tree. Add a margin to the bottom to allow
1037
- // to drag-and-drop after the last element.
1038
- const offset = this.$treeElement.offset();
1039
- if (!offset) {
1040
- return {
1041
- left: 0,
1042
- top: 0,
1043
- right: 0,
1044
- bottom: 0
1045
- };
1046
- } else {
1047
- const el = this.$treeElement;
1048
- const width = el.width() || 0;
1049
- const height = el.height() || 0;
1050
- const left = offset.left + this.getScrollLeft();
1051
- return {
1052
- left,
1053
- top: offset.top,
1054
- right: left + width,
1055
- bottom: offset.top + height + 16
1056
- };
1120
+ mouseUp = e => {
1121
+ this.handleMouseUp(getPositionInfoFromMouseEvent(e));
1122
+ };
1123
+ handleMouseUp(positionInfo) {
1124
+ this.removeMouseMoveEventListeners();
1125
+ this.isMouseDelayMet = false;
1126
+ this.mouseDownInfo = null;
1127
+ if (this.isMouseStarted) {
1128
+ this.isMouseStarted = false;
1129
+ this.onMouseStop(positionInfo);
1057
1130
  }
1058
1131
  }
1059
- }
1060
-
1061
- class ElementsRenderer {
1062
- constructor(_ref) {
1063
- let {
1064
- autoEscape,
1065
- buttonLeft,
1066
- closedIcon,
1067
- onCreateLi,
1068
- dragAndDrop,
1069
- $element,
1070
- getTree,
1071
- isNodeSelected,
1072
- openedIcon,
1073
- rtl,
1074
- showEmptyFolder,
1075
- tabIndex
1076
- } = _ref;
1077
- this.autoEscape = autoEscape;
1078
- this.buttonLeft = buttonLeft;
1079
- this.dragAndDrop = dragAndDrop;
1080
- this.$element = $element;
1081
- this.getTree = getTree;
1082
- this.isNodeSelected = isNodeSelected;
1083
- this.onCreateLi = onCreateLi;
1084
- this.rtl = rtl;
1085
- this.showEmptyFolder = showEmptyFolder;
1086
- this.tabIndex = tabIndex;
1087
- this.openedIconElement = this.createButtonElement(openedIcon || "+");
1088
- this.closedIconElement = this.createButtonElement(closedIcon || "-");
1132
+ removeMouseMoveEventListeners() {
1133
+ document.removeEventListener("mousemove", this.mouseMove);
1134
+ document.removeEventListener("touchmove", this.touchMove);
1135
+ document.removeEventListener("mouseup", this.mouseUp);
1136
+ document.removeEventListener("touchend", this.touchEnd);
1089
1137
  }
1090
- render(fromNode) {
1091
- if (fromNode && fromNode.parent) {
1092
- this.renderFromNode(fromNode);
1093
- } else {
1094
- this.renderFromRoot();
1138
+ touchStart = e => {
1139
+ if (!e) {
1140
+ return;
1095
1141
  }
1096
- }
1097
- renderFromRoot() {
1098
- this.$element.empty();
1099
- const tree = this.getTree();
1100
- if (this.$element[0] && tree) {
1101
- this.createDomElements(this.$element[0], tree.children, true, 1);
1142
+ if (e.touches.length > 1) {
1143
+ return;
1102
1144
  }
1103
- }
1104
- renderFromNode(node) {
1105
- // remember current li
1106
- const $previousLi = jQuery(node.element);
1107
-
1108
- // create element
1109
- const li = this.createLi(node, node.getLevel());
1110
- this.attachNodeData(node, li);
1111
-
1112
- // add element to dom
1113
- $previousLi.after(li);
1114
-
1115
- // remove previous li
1116
- $previousLi.remove();
1117
-
1118
- // create children
1119
- if (node.children) {
1120
- this.createDomElements(li, node.children, false, node.getLevel() + 1);
1145
+ const touch = e.touches[0];
1146
+ if (!touch) {
1147
+ return;
1121
1148
  }
1122
- }
1123
- createDomElements(element, children, isRootNode, level) {
1124
- const ul = this.createUl(isRootNode);
1125
- element.appendChild(ul);
1126
- for (const child of children) {
1127
- const li = this.createLi(child, level);
1128
- ul.appendChild(li);
1129
- this.attachNodeData(child, li);
1130
- if (child.hasChildren()) {
1131
- this.createDomElements(li, child.children, false, level + 1);
1132
- }
1149
+ this.handleMouseDown(getPositionInfoFromTouch(touch, e));
1150
+ };
1151
+ touchMove = e => {
1152
+ if (!e) {
1153
+ return;
1133
1154
  }
1134
- }
1135
- attachNodeData(node, li) {
1136
- node.element = li;
1137
- jQuery(li).data("node", node);
1138
- }
1139
- createUl(isRootNode) {
1140
- let classString;
1141
- let role;
1142
- if (!isRootNode) {
1143
- classString = "";
1144
- role = "group";
1145
- } else {
1146
- classString = "jqtree-tree";
1147
- role = "tree";
1148
- if (this.rtl) {
1149
- classString += " jqtree-rtl";
1150
- }
1151
- }
1152
- if (this.dragAndDrop) {
1153
- classString += " jqtree-dnd";
1155
+ if (e.touches.length > 1) {
1156
+ return;
1154
1157
  }
1155
- const ul = document.createElement("ul");
1156
- ul.className = `jqtree_common ${classString}`;
1157
- ul.setAttribute("role", role);
1158
- return ul;
1159
- }
1160
- createLi(node, level) {
1161
- const isSelected = Boolean(this.isNodeSelected(node));
1162
- const mustShowFolder = node.isFolder() || node.isEmptyFolder && this.showEmptyFolder;
1163
- const li = mustShowFolder ? this.createFolderLi(node, level, isSelected) : this.createNodeLi(node, level, isSelected);
1164
- if (this.onCreateLi) {
1165
- this.onCreateLi(node, jQuery(li), isSelected);
1158
+ const touch = e.touches[0];
1159
+ if (!touch) {
1160
+ return;
1166
1161
  }
1167
- return li;
1168
- }
1169
- setTreeItemAriaAttributes(element, name, level, isSelected) {
1170
- element.setAttribute("aria-label", name);
1171
- element.setAttribute("aria-level", `${level}`);
1172
- element.setAttribute("aria-selected", getBoolString(isSelected));
1173
- element.setAttribute("role", "treeitem");
1174
- }
1175
- createFolderLi(node, level, isSelected) {
1176
- const buttonClasses = this.getButtonClasses(node);
1177
- const folderClasses = this.getFolderClasses(node, isSelected);
1178
- const iconElement = node.is_open ? this.openedIconElement : this.closedIconElement;
1179
-
1180
- // li
1181
- const li = document.createElement("li");
1182
- li.className = `jqtree_common ${folderClasses}`;
1183
- li.setAttribute("role", "none");
1184
-
1185
- // div
1186
- const div = document.createElement("div");
1187
- div.className = "jqtree-element jqtree_common";
1188
- div.setAttribute("role", "none");
1189
- li.appendChild(div);
1190
-
1191
- // button link
1192
- const buttonLink = document.createElement("a");
1193
- buttonLink.className = buttonClasses;
1194
- if (iconElement) {
1195
- buttonLink.appendChild(iconElement.cloneNode(true));
1162
+ this.handleMouseMove(e, getPositionInfoFromTouch(touch, e));
1163
+ };
1164
+ touchEnd = e => {
1165
+ if (!e) {
1166
+ return;
1196
1167
  }
1197
- if (this.buttonLeft) {
1198
- div.appendChild(buttonLink);
1168
+ if (e.touches.length > 1) {
1169
+ return;
1199
1170
  }
1200
-
1201
- // title span
1202
- const titleSpan = this.createTitleSpan(node.name, isSelected, true, level);
1203
- titleSpan.setAttribute("aria-expanded", getBoolString(node.is_open));
1204
- div.appendChild(titleSpan);
1205
- if (!this.buttonLeft) {
1206
- div.appendChild(buttonLink);
1171
+ const touch = e.touches[0];
1172
+ if (!touch) {
1173
+ return;
1207
1174
  }
1208
- return li;
1209
- }
1210
- createNodeLi(node, level, isSelected) {
1211
- const liClasses = ["jqtree_common"];
1212
- if (isSelected) {
1213
- liClasses.push("jqtree-selected");
1175
+ this.handleMouseUp(getPositionInfoFromTouch(touch, e));
1176
+ };
1177
+ handleClick = e => {
1178
+ if (!e.target) {
1179
+ return;
1214
1180
  }
1215
- const classString = liClasses.join(" ");
1216
-
1217
- // li
1218
- const li = document.createElement("li");
1219
- li.className = classString;
1220
- li.setAttribute("role", "none");
1221
-
1222
- // div
1223
- const div = document.createElement("div");
1224
- div.className = "jqtree-element jqtree_common";
1225
- div.setAttribute("role", "none");
1226
- li.appendChild(div);
1227
-
1228
- // title span
1229
- const titleSpan = this.createTitleSpan(node.name, isSelected, false, level);
1230
- div.appendChild(titleSpan);
1231
- return li;
1232
- }
1233
- createTitleSpan(nodeName, isSelected, isFolder, level) {
1234
- const titleSpan = document.createElement("span");
1235
- let classes = "jqtree-title jqtree_common";
1236
- if (isFolder) {
1237
- classes += " jqtree-title-folder";
1181
+ const clickTarget = this.getClickTarget(e.target);
1182
+ if (!clickTarget) {
1183
+ return;
1238
1184
  }
1239
- classes += ` jqtree-title-button-${this.buttonLeft ? "left" : "right"}`;
1240
- titleSpan.className = classes;
1241
- if (isSelected) {
1242
- const tabIndex = this.tabIndex;
1243
- if (tabIndex !== undefined) {
1244
- titleSpan.setAttribute("tabindex", `${tabIndex}`);
1185
+ if (clickTarget.type === "button") {
1186
+ this.onClickButton(clickTarget.node);
1187
+ e.preventDefault();
1188
+ e.stopPropagation();
1189
+ } else if (clickTarget.type === "label") {
1190
+ const event = this.triggerEvent("tree.click", {
1191
+ node: clickTarget.node,
1192
+ click_event: e
1193
+ });
1194
+ if (!event.isDefaultPrevented()) {
1195
+ this.onClickTitle(clickTarget.node);
1245
1196
  }
1246
1197
  }
1247
- this.setTreeItemAriaAttributes(titleSpan, nodeName, level, isSelected);
1248
- if (this.autoEscape) {
1249
- titleSpan.textContent = nodeName;
1250
- } else {
1251
- titleSpan.innerHTML = nodeName;
1252
- }
1253
- return titleSpan;
1254
- }
1255
- getButtonClasses(node) {
1256
- const classes = ["jqtree-toggler", "jqtree_common"];
1257
- if (!node.is_open) {
1258
- classes.push("jqtree-closed");
1259
- }
1260
- if (this.buttonLeft) {
1261
- classes.push("jqtree-toggler-left");
1262
- } else {
1263
- classes.push("jqtree-toggler-right");
1198
+ };
1199
+ handleDblclick = e => {
1200
+ if (!e.target) {
1201
+ return;
1264
1202
  }
1265
- return classes.join(" ");
1266
- }
1267
- getFolderClasses(node, isSelected) {
1268
- const classes = ["jqtree-folder"];
1269
- if (!node.is_open) {
1270
- classes.push("jqtree-closed");
1203
+ const clickTarget = this.getClickTarget(e.target);
1204
+ if (clickTarget?.type === "label") {
1205
+ this.triggerEvent("tree.dblclick", {
1206
+ node: clickTarget.node,
1207
+ click_event: e
1208
+ });
1271
1209
  }
1272
- if (isSelected) {
1273
- classes.push("jqtree-selected");
1210
+ };
1211
+ handleContextmenu = e => {
1212
+ if (!e.target) {
1213
+ return;
1274
1214
  }
1275
- if (node.is_loading) {
1276
- classes.push("jqtree-loading");
1215
+ const div = e.target.closest("ul.jqtree-tree .jqtree-element");
1216
+ if (div) {
1217
+ const node = this.getNode(div);
1218
+ if (node) {
1219
+ e.preventDefault();
1220
+ e.stopPropagation();
1221
+ this.triggerEvent("tree.contextmenu", {
1222
+ node,
1223
+ click_event: e
1224
+ });
1225
+ return false;
1226
+ }
1277
1227
  }
1278
- return classes.join(" ");
1279
- }
1280
- createButtonElement(value) {
1281
- if (typeof value === "string") {
1282
- // convert value to html
1283
- const div = document.createElement("div");
1284
- div.innerHTML = value;
1285
- return document.createTextNode(div.innerHTML);
1286
- } else if (value == null) {
1287
- return undefined;
1288
- } else if (value.nodeType) {
1289
- return value;
1228
+ return null;
1229
+ };
1230
+ getClickTarget(element) {
1231
+ const button = element.closest(".jqtree-toggler");
1232
+ if (button) {
1233
+ const node = this.getNode(button);
1234
+ if (node) {
1235
+ return {
1236
+ type: "button",
1237
+ node
1238
+ };
1239
+ }
1290
1240
  } else {
1291
- return jQuery(value)[0];
1241
+ const jqTreeElement = element.closest(".jqtree-element");
1242
+ if (jqTreeElement) {
1243
+ const node = this.getNode(jqTreeElement);
1244
+ if (node) {
1245
+ return {
1246
+ type: "label",
1247
+ node
1248
+ };
1249
+ }
1250
+ }
1292
1251
  }
1252
+ return null;
1293
1253
  }
1294
1254
  }
1295
1255
 
1296
- class DataLoader {
1256
+ class SaveStateHandler {
1297
1257
  constructor(_ref) {
1298
1258
  let {
1299
- dataFilter,
1300
- loadData,
1301
- onLoadFailed,
1302
- onLoading,
1303
- $treeElement,
1304
- triggerEvent
1259
+ addToSelection,
1260
+ getNodeById,
1261
+ getSelectedNodes,
1262
+ getTree,
1263
+ onGetStateFromStorage,
1264
+ onSetStateFromStorage,
1265
+ openNode,
1266
+ refreshElements,
1267
+ removeFromSelection,
1268
+ saveState
1305
1269
  } = _ref;
1306
- this.dataFilter = dataFilter;
1307
- this.loadData = loadData;
1308
- this.onLoadFailed = onLoadFailed;
1309
- this.onLoading = onLoading;
1310
- this.$treeElement = $treeElement;
1311
- this.triggerEvent = triggerEvent;
1270
+ this.addToSelection = addToSelection;
1271
+ this.getNodeById = getNodeById;
1272
+ this.getSelectedNodes = getSelectedNodes;
1273
+ this.getTree = getTree;
1274
+ this.onGetStateFromStorage = onGetStateFromStorage;
1275
+ this.onSetStateFromStorage = onSetStateFromStorage;
1276
+ this.openNode = openNode;
1277
+ this.refreshElements = refreshElements;
1278
+ this.removeFromSelection = removeFromSelection;
1279
+ this.saveStateOption = saveState;
1312
1280
  }
1313
- loadFromUrl(urlInfo, parentNode, onFinished) {
1314
- if (!urlInfo) {
1315
- return;
1281
+ saveState() {
1282
+ const state = JSON.stringify(this.getState());
1283
+ if (this.onSetStateFromStorage) {
1284
+ this.onSetStateFromStorage(state);
1285
+ } else if (this.supportsLocalStorage()) {
1286
+ localStorage.setItem(this.getKeyName(), state);
1316
1287
  }
1317
- const $el = this.getDomElement(parentNode);
1318
- this.addLoadingClass($el);
1319
- this.notifyLoading(true, parentNode, $el);
1320
- const stopLoading = () => {
1321
- this.removeLoadingClass($el);
1322
- this.notifyLoading(false, parentNode, $el);
1323
- };
1324
- const handleSuccess = data => {
1325
- stopLoading();
1326
- this.loadData(this.parseData(data), parentNode);
1327
- if (onFinished && typeof onFinished === "function") {
1328
- onFinished();
1329
- }
1288
+ }
1289
+ getStateFromStorage() {
1290
+ const jsonData = this.loadFromStorage();
1291
+ if (jsonData) {
1292
+ return this.parseState(jsonData);
1293
+ } else {
1294
+ return null;
1295
+ }
1296
+ }
1297
+ getState() {
1298
+ const getOpenNodeIds = () => {
1299
+ const openNodes = [];
1300
+ this.getTree()?.iterate(node => {
1301
+ if (node.is_open && node.id && node.hasChildren()) {
1302
+ openNodes.push(node.id);
1303
+ }
1304
+ return true;
1305
+ });
1306
+ return openNodes;
1330
1307
  };
1331
- const handleError = jqXHR => {
1332
- stopLoading();
1333
- if (this.onLoadFailed) {
1334
- this.onLoadFailed(jqXHR);
1335
- }
1308
+ const getSelectedNodeIds = () => {
1309
+ const selectedNodeIds = [];
1310
+ this.getSelectedNodes().forEach(node => {
1311
+ if (node.id != null) {
1312
+ selectedNodeIds.push(node.id);
1313
+ }
1314
+ });
1315
+ return selectedNodeIds;
1336
1316
  };
1337
- this.submitRequest(urlInfo, handleSuccess, handleError);
1317
+ return {
1318
+ open_nodes: getOpenNodeIds(),
1319
+ selected_node: getSelectedNodeIds()
1320
+ };
1321
+ }
1322
+
1323
+ /*
1324
+ Set initial state
1325
+ Don't handle nodes that are loaded on demand
1326
+ result: must load on demand
1327
+ */
1328
+ setInitialState(state) {
1329
+ if (!state) {
1330
+ return false;
1331
+ } else {
1332
+ let mustLoadOnDemand = false;
1333
+ if (state.open_nodes) {
1334
+ mustLoadOnDemand = this.openInitialNodes(state.open_nodes);
1335
+ }
1336
+ if (state.selected_node) {
1337
+ this.resetSelection();
1338
+ this.selectInitialNodes(state.selected_node);
1339
+ }
1340
+ return mustLoadOnDemand;
1341
+ }
1342
+ }
1343
+ setInitialStateOnDemand(state, cbFinished) {
1344
+ if (state) {
1345
+ this.doSetInitialStateOnDemand(state.open_nodes, state.selected_node, cbFinished);
1346
+ } else {
1347
+ cbFinished();
1348
+ }
1338
1349
  }
1339
- addLoadingClass($el) {
1340
- if ($el) {
1341
- $el.addClass("jqtree-loading");
1350
+ getNodeIdToBeSelected() {
1351
+ const state = this.getStateFromStorage();
1352
+ if (state && state.selected_node) {
1353
+ return state.selected_node[0] || null;
1354
+ } else {
1355
+ return null;
1342
1356
  }
1343
1357
  }
1344
- removeLoadingClass($el) {
1345
- if ($el) {
1346
- $el.removeClass("jqtree-loading");
1358
+ parseState(jsonData) {
1359
+ const state = JSON.parse(jsonData);
1360
+
1361
+ // Check if selected_node is an int (instead of an array)
1362
+ if (state && state.selected_node && isInt(state.selected_node)) {
1363
+ // Convert to array
1364
+ state.selected_node = [state.selected_node];
1347
1365
  }
1366
+ return state;
1348
1367
  }
1349
- getDomElement(parentNode) {
1350
- if (parentNode) {
1351
- return jQuery(parentNode.element);
1368
+ loadFromStorage() {
1369
+ if (this.onGetStateFromStorage) {
1370
+ return this.onGetStateFromStorage();
1371
+ } else if (this.supportsLocalStorage()) {
1372
+ return localStorage.getItem(this.getKeyName());
1352
1373
  } else {
1353
- return this.$treeElement;
1374
+ return null;
1354
1375
  }
1355
1376
  }
1356
- notifyLoading(isLoading, node, $el) {
1357
- if (this.onLoading) {
1358
- this.onLoading(isLoading, node, $el);
1377
+ openInitialNodes(nodeIds) {
1378
+ let mustLoadOnDemand = false;
1379
+ for (const nodeId of nodeIds) {
1380
+ const node = this.getNodeById(nodeId);
1381
+ if (node) {
1382
+ if (!node.load_on_demand) {
1383
+ node.is_open = true;
1384
+ } else {
1385
+ mustLoadOnDemand = true;
1386
+ }
1387
+ }
1359
1388
  }
1360
- this.triggerEvent("tree.loading_data", {
1361
- isLoading,
1362
- node,
1363
- $el
1389
+ return mustLoadOnDemand;
1390
+ }
1391
+ selectInitialNodes(nodeIds) {
1392
+ let selectCount = 0;
1393
+ for (const nodeId of nodeIds) {
1394
+ const node = this.getNodeById(nodeId);
1395
+ if (node) {
1396
+ selectCount += 1;
1397
+ this.addToSelection(node);
1398
+ }
1399
+ }
1400
+ return selectCount !== 0;
1401
+ }
1402
+ resetSelection() {
1403
+ const selectedNodes = this.getSelectedNodes();
1404
+ selectedNodes.forEach(node => {
1405
+ this.removeFromSelection(node);
1364
1406
  });
1365
1407
  }
1366
- submitRequest(urlInfoInput, handleSuccess, handleError) {
1367
- const urlInfo = typeof urlInfoInput === "string" ? {
1368
- url: urlInfoInput
1369
- } : urlInfoInput;
1370
- const ajaxSettings = {
1371
- method: "GET",
1372
- cache: false,
1373
- dataType: "json",
1374
- success: handleSuccess,
1375
- error: handleError,
1376
- ...urlInfo
1408
+ doSetInitialStateOnDemand(nodeIdsParam, selectedNodes, cbFinished) {
1409
+ let loadingCount = 0;
1410
+ let nodeIds = nodeIdsParam;
1411
+ const openNodes = () => {
1412
+ const newNodesIds = [];
1413
+ for (const nodeId of nodeIds) {
1414
+ const node = this.getNodeById(nodeId);
1415
+ if (!node) {
1416
+ newNodesIds.push(nodeId);
1417
+ } else {
1418
+ if (!node.is_loading) {
1419
+ if (node.load_on_demand) {
1420
+ loadAndOpenNode(node);
1421
+ } else {
1422
+ this.openNode(node, false);
1423
+ }
1424
+ }
1425
+ }
1426
+ }
1427
+ nodeIds = newNodesIds;
1428
+ if (this.selectInitialNodes(selectedNodes)) {
1429
+ this.refreshElements(null);
1430
+ }
1431
+ if (loadingCount === 0) {
1432
+ cbFinished();
1433
+ }
1377
1434
  };
1378
- ajaxSettings.method = ajaxSettings.method?.toUpperCase() || "GET";
1379
- void jQuery.ajax(ajaxSettings);
1435
+ const loadAndOpenNode = node => {
1436
+ loadingCount += 1;
1437
+ this.openNode(node, false, () => {
1438
+ loadingCount -= 1;
1439
+ openNodes();
1440
+ });
1441
+ };
1442
+ openNodes();
1380
1443
  }
1381
- parseData(data) {
1382
- const getParsedData = () => {
1383
- if (typeof data === "string") {
1384
- return JSON.parse(data);
1444
+ getKeyName() {
1445
+ if (typeof this.saveStateOption === "string") {
1446
+ return this.saveStateOption;
1447
+ } else {
1448
+ return "tree";
1449
+ }
1450
+ }
1451
+ supportsLocalStorage() {
1452
+ const testSupport = () => {
1453
+ // Is local storage supported?
1454
+ if (localStorage == null) {
1455
+ return false;
1385
1456
  } else {
1386
- return data;
1457
+ // Check if it's possible to store an item. Safari does not allow this in private browsing mode.
1458
+ try {
1459
+ const key = "_storage_test";
1460
+ sessionStorage.setItem(key, "value");
1461
+ sessionStorage.removeItem(key);
1462
+ } catch (error) {
1463
+ return false;
1464
+ }
1465
+ return true;
1387
1466
  }
1388
1467
  };
1389
- const parsedData = getParsedData();
1390
- if (this.dataFilter) {
1391
- return this.dataFilter(parsedData);
1392
- } else {
1393
- return parsedData;
1468
+ if (this._supportsLocalStorage == null) {
1469
+ this._supportsLocalStorage = testSupport();
1394
1470
  }
1471
+ return this._supportsLocalStorage;
1395
1472
  }
1396
1473
  }
1397
1474
 
1398
- class KeyHandler {
1475
+ class ContainerScrollParent {
1399
1476
  constructor(_ref) {
1400
1477
  let {
1401
- closeNode,
1402
- getSelectedNode,
1403
- isFocusOnTree,
1404
- keyboardSupport,
1405
- openNode,
1406
- selectNode
1478
+ container,
1479
+ refreshHitAreas
1407
1480
  } = _ref;
1408
- this.closeNode = closeNode;
1409
- this.getSelectedNode = getSelectedNode;
1410
- this.isFocusOnTree = isFocusOnTree;
1411
- this.keyboardSupport = keyboardSupport;
1412
- this.openNode = openNode;
1413
- this.originalSelectNode = selectNode;
1414
- if (keyboardSupport) {
1415
- this.handleKeyDownHandler = this.handleKeyDown.bind(this);
1416
- document.addEventListener("keydown", this.handleKeyDownHandler);
1481
+ this.container = container;
1482
+ this.refreshHitAreas = refreshHitAreas;
1483
+ }
1484
+ checkHorizontalScrolling(pageX) {
1485
+ const newHorizontalScrollDirection = this.getNewHorizontalScrollDirection(pageX);
1486
+ if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
1487
+ this.horizontalScrollDirection = newHorizontalScrollDirection;
1488
+ if (this.horizontalScrollTimeout != null) {
1489
+ window.clearTimeout(this.verticalScrollTimeout);
1490
+ }
1491
+ if (newHorizontalScrollDirection) {
1492
+ this.horizontalScrollTimeout = window.setTimeout(this.scrollHorizontally.bind(this), 40);
1493
+ }
1417
1494
  }
1418
1495
  }
1419
- deinit() {
1420
- if (this.handleKeyDownHandler) {
1421
- document.removeEventListener("keydown", this.handleKeyDownHandler);
1496
+ checkVerticalScrolling(pageY) {
1497
+ const newVerticalScrollDirection = this.getNewVerticalScrollDirection(pageY);
1498
+ if (this.verticalScrollDirection !== newVerticalScrollDirection) {
1499
+ this.verticalScrollDirection = newVerticalScrollDirection;
1500
+ if (this.verticalScrollTimeout != null) {
1501
+ window.clearTimeout(this.verticalScrollTimeout);
1502
+ this.verticalScrollTimeout = undefined;
1503
+ }
1504
+ if (newVerticalScrollDirection) {
1505
+ this.verticalScrollTimeout = window.setTimeout(this.scrollVertically.bind(this), 40);
1506
+ }
1422
1507
  }
1423
1508
  }
1424
- moveDown(selectedNode) {
1425
- return this.selectNode(selectedNode.getNextVisibleNode());
1509
+ getScrollLeft() {
1510
+ return this.container.scrollLeft;
1426
1511
  }
1427
- moveUp(selectedNode) {
1428
- return this.selectNode(selectedNode.getPreviousVisibleNode());
1512
+ scrollToY(top) {
1513
+ this.container.scrollTop = top;
1429
1514
  }
1430
- moveRight(selectedNode) {
1431
- if (!selectedNode.isFolder()) {
1432
- return true;
1433
- } else {
1434
- // folder node
1435
- if (selectedNode.is_open) {
1436
- // Right moves to the first child of an open node
1437
- return this.selectNode(selectedNode.getNextVisibleNode());
1438
- } else {
1439
- // Right expands a closed node
1440
- this.openNode(selectedNode);
1441
- return false;
1442
- }
1515
+ stopScrolling() {
1516
+ this.horizontalScrollDirection = undefined;
1517
+ this.verticalScrollDirection = undefined;
1518
+ this.scrollParentTop = undefined;
1519
+ this.scrollParentBottom = undefined;
1520
+ }
1521
+ getNewHorizontalScrollDirection(pageX) {
1522
+ const scrollParentOffset = getElementPosition(this.container);
1523
+ const rightEdge = scrollParentOffset.left + this.container.clientWidth;
1524
+ const leftEdge = scrollParentOffset.left;
1525
+ const isNearRightEdge = pageX > rightEdge - 20;
1526
+ const isNearLeftEdge = pageX < leftEdge + 20;
1527
+ if (isNearRightEdge) {
1528
+ return "right";
1529
+ } else if (isNearLeftEdge) {
1530
+ return "left";
1443
1531
  }
1532
+ return undefined;
1444
1533
  }
1445
- moveLeft(selectedNode) {
1446
- if (selectedNode.isFolder() && selectedNode.is_open) {
1447
- // Left on an open node closes the node
1448
- this.closeNode(selectedNode);
1449
- return false;
1450
- } else {
1451
- // Left on a closed or end node moves focus to the node's parent
1452
- return this.selectNode(selectedNode.getParent());
1534
+ getNewVerticalScrollDirection(pageY) {
1535
+ if (pageY < this.getScrollParentTop()) {
1536
+ return "top";
1537
+ }
1538
+ if (pageY > this.getScrollParentBottom()) {
1539
+ return "bottom";
1453
1540
  }
1541
+ return undefined;
1454
1542
  }
1455
- selectNode(node) {
1456
- if (!node) {
1457
- return true;
1458
- } else {
1459
- this.originalSelectNode(node);
1460
- return false;
1543
+ scrollHorizontally() {
1544
+ if (!this.horizontalScrollDirection) {
1545
+ return;
1461
1546
  }
1547
+ const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
1548
+ this.container.scrollBy({
1549
+ left: distance,
1550
+ top: 0,
1551
+ behavior: "instant"
1552
+ });
1553
+ this.refreshHitAreas();
1554
+ setTimeout(this.scrollHorizontally.bind(this), 40);
1462
1555
  }
1463
- handleKeyDown = e => {
1464
- if (!this.canHandleKeyboard()) {
1465
- return true;
1556
+ scrollVertically() {
1557
+ if (!this.verticalScrollDirection) {
1558
+ return;
1466
1559
  }
1467
- const selectedNode = this.getSelectedNode();
1468
- if (!selectedNode) {
1469
- return true;
1560
+ const distance = this.verticalScrollDirection === "top" ? -20 : 20;
1561
+ this.container.scrollBy({
1562
+ left: 0,
1563
+ top: distance,
1564
+ behavior: "instant"
1565
+ });
1566
+ this.refreshHitAreas();
1567
+ setTimeout(this.scrollVertically.bind(this), 40);
1568
+ }
1569
+ getScrollParentTop() {
1570
+ if (this.scrollParentTop == null) {
1571
+ this.scrollParentTop = getOffsetTop(this.container);
1470
1572
  }
1471
- switch (e.key) {
1472
- case "ArrowDown":
1473
- return this.moveDown(selectedNode);
1474
- case "ArrowUp":
1475
- return this.moveUp(selectedNode);
1476
- case "ArrowRight":
1477
- return this.moveRight(selectedNode);
1478
- case "ArrowLeft":
1479
- return this.moveLeft(selectedNode);
1480
- default:
1481
- return true;
1573
+ return this.scrollParentTop;
1574
+ }
1575
+ getScrollParentBottom() {
1576
+ if (this.scrollParentBottom == null) {
1577
+ this.scrollParentBottom = this.getScrollParentTop() + this.container.clientHeight;
1482
1578
  }
1483
- };
1484
- canHandleKeyboard() {
1485
- return this.keyboardSupport && this.isFocusOnTree();
1579
+ return this.scrollParentBottom;
1486
1580
  }
1487
1581
  }
1488
1582
 
1489
- const register = (widgetClass, widgetName) => {
1490
- const getDataKey = () => `simple_widget_${widgetName}`;
1491
- const getWidgetData = (el, dataKey) => {
1492
- const widget = jQuery.data(el, dataKey);
1493
- if (widget && widget instanceof SimpleWidget) {
1494
- return widget;
1495
- } else {
1496
- return null;
1497
- }
1498
- };
1499
- const createWidget = ($el, options) => {
1500
- const dataKey = getDataKey();
1501
- for (const el of $el.get()) {
1502
- const existingWidget = getWidgetData(el, dataKey);
1503
- if (!existingWidget) {
1504
- const simpleWidgetClass = widgetClass;
1505
- const widget = new simpleWidgetClass(el, options);
1506
- if (!jQuery.data(el, dataKey)) {
1507
- jQuery.data(el, dataKey, widget);
1508
- }
1509
-
1510
- // Call init after setting data, so we can call methods
1511
- widget.init();
1583
+ class DocumentScrollParent {
1584
+ constructor(_ref) {
1585
+ let {
1586
+ refreshHitAreas,
1587
+ treeElement
1588
+ } = _ref;
1589
+ this.refreshHitAreas = refreshHitAreas;
1590
+ this.treeElement = treeElement;
1591
+ }
1592
+ checkHorizontalScrolling(pageX) {
1593
+ const newHorizontalScrollDirection = this.getNewHorizontalScrollDirection(pageX);
1594
+ if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
1595
+ this.horizontalScrollDirection = newHorizontalScrollDirection;
1596
+ if (this.horizontalScrollTimeout != null) {
1597
+ window.clearTimeout(this.horizontalScrollTimeout);
1512
1598
  }
1513
- }
1514
- return $el;
1515
- };
1516
- const destroyWidget = $el => {
1517
- const dataKey = getDataKey();
1518
- for (const el of $el.get()) {
1519
- const widget = getWidgetData(el, dataKey);
1520
- if (widget) {
1521
- widget.destroy();
1599
+ if (newHorizontalScrollDirection) {
1600
+ this.horizontalScrollTimeout = window.setTimeout(this.scrollHorizontally.bind(this), 40);
1522
1601
  }
1523
- jQuery.removeData(el, dataKey);
1524
1602
  }
1525
- };
1526
- const callFunction = ($el, functionName, args) => {
1527
- let result = null;
1528
- for (const el of $el.get()) {
1529
- const widget = jQuery.data(el, getDataKey());
1530
- if (widget && widget instanceof SimpleWidget) {
1531
- const simpleWidget = widget;
1532
- const widgetFunction = simpleWidget[functionName];
1533
- if (widgetFunction && typeof widgetFunction === "function") {
1534
- result = widgetFunction.apply(widget, args);
1535
- }
1603
+ }
1604
+ checkVerticalScrolling(pageY) {
1605
+ const newVerticalScrollDirection = this.getNewVerticalScrollDirection(pageY);
1606
+ if (this.verticalScrollDirection !== newVerticalScrollDirection) {
1607
+ this.verticalScrollDirection = newVerticalScrollDirection;
1608
+ if (this.verticalScrollTimeout != null) {
1609
+ window.clearTimeout(this.verticalScrollTimeout);
1610
+ this.verticalScrollTimeout = undefined;
1536
1611
  }
1537
- }
1538
- return result;
1539
- };
1540
-
1541
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1542
- jQuery.fn[widgetName] = function (argument1) {
1543
- if (!argument1) {
1544
- return createWidget(this, null);
1545
- } else if (typeof argument1 === "object") {
1546
- const options = argument1;
1547
- return createWidget(this, options);
1548
- } else if (typeof argument1 === "string" && argument1[0] !== "_") {
1549
- const functionName = argument1;
1550
- if (functionName === "destroy") {
1551
- return destroyWidget(this);
1552
- } else if (functionName === "get_widget_class") {
1553
- return widgetClass;
1554
- } else {
1555
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1556
- args[_key - 1] = arguments[_key];
1557
- }
1558
- return callFunction(this, functionName, args);
1612
+ if (newVerticalScrollDirection) {
1613
+ this.verticalScrollTimeout = window.setTimeout(this.scrollVertically.bind(this), 40);
1559
1614
  }
1560
- } else {
1561
- return undefined;
1562
1615
  }
1563
- };
1564
- };
1565
- class SimpleWidget {
1566
- static register(widgetClass, widgetName) {
1567
- register(widgetClass, widgetName);
1568
- }
1569
- static defaults = {};
1570
- constructor(el, options) {
1571
- this.$el = jQuery(el);
1572
-
1573
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1574
- const defaults = this.constructor["defaults"];
1575
- this.options = {
1576
- ...defaults,
1577
- ...options
1578
- };
1579
1616
  }
1580
- destroy() {
1581
- this.deinit();
1617
+ getScrollLeft() {
1618
+ return document.documentElement.scrollLeft;
1582
1619
  }
1583
- init() {
1584
- //
1620
+ scrollToY(top) {
1621
+ const treeTop = getOffsetTop(this.treeElement);
1622
+ document.documentElement.scrollTop = top + treeTop;
1585
1623
  }
1586
- deinit() {
1587
- //
1624
+ stopScrolling() {
1625
+ this.horizontalScrollDirection = undefined;
1626
+ this.verticalScrollDirection = undefined;
1627
+ this.documentScrollHeight = undefined;
1628
+ this.documentScrollWidth = undefined;
1588
1629
  }
1589
- }
1590
-
1591
- /*
1592
- This widget does the same a the mouse widget in jqueryui.
1593
- */
1594
- const getPositionInfoFromMouseEvent = e => ({
1595
- pageX: e.pageX,
1596
- pageY: e.pageY,
1597
- target: e.target,
1598
- originalEvent: e
1599
- });
1600
- const getPositionInfoFromTouch = (touch, e) => ({
1601
- pageX: touch.pageX,
1602
- pageY: touch.pageY,
1603
- target: touch.target,
1604
- originalEvent: e
1605
- });
1606
- class MouseWidget extends SimpleWidget {
1607
- init() {
1608
- const element = this.$el.get(0);
1609
- if (element) {
1610
- element.addEventListener("mousedown", this.mouseDown, {
1611
- passive: false
1612
- });
1613
- element.addEventListener("touchstart", this.touchStart, {
1614
- passive: false
1615
- });
1630
+ getNewHorizontalScrollDirection(pageX) {
1631
+ const scrollLeft = document.documentElement.scrollLeft;
1632
+ const windowWidth = window.innerWidth;
1633
+ const isNearRightEdge = pageX > windowWidth - 20;
1634
+ const isNearLeftEdge = pageX - scrollLeft < 20;
1635
+ if (isNearRightEdge && this.canScrollRight()) {
1636
+ return "right";
1616
1637
  }
1617
- this.isMouseStarted = false;
1618
- this.mouseDelayTimer = null;
1619
- this.isMouseDelayMet = false;
1620
- this.mouseDownInfo = null;
1621
- }
1622
- deinit() {
1623
- const el = this.$el.get(0);
1624
- if (el) {
1625
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1626
- el.removeEventListener("mousedown", this.mouseDown, {
1627
- passive: false
1628
- });
1629
-
1630
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1631
- el.removeEventListener("touchstart", this.touchStart, {
1632
- passive: false
1633
- });
1638
+ if (isNearLeftEdge) {
1639
+ return "left";
1634
1640
  }
1635
- this.removeMouseMoveEventListeners();
1641
+ return undefined;
1636
1642
  }
1637
- mouseDown = e => {
1638
- // Left mouse button?
1639
- if (e.button !== 0) {
1640
- return;
1641
- }
1642
- const result = this.handleMouseDown(getPositionInfoFromMouseEvent(e));
1643
- if (result && e.cancelable) {
1644
- e.preventDefault();
1645
- }
1646
- };
1647
- handleMouseDown(positionInfo) {
1648
- // We may have missed mouseup (out of window)
1649
- if (this.isMouseStarted) {
1650
- this.handleMouseUp(positionInfo);
1651
- }
1652
- this.mouseDownInfo = positionInfo;
1653
- if (!this.mouseCapture(positionInfo)) {
1654
- return false;
1643
+ canScrollRight() {
1644
+ const documentElement = document.documentElement;
1645
+ return documentElement.scrollLeft + documentElement.clientWidth < this.getDocumentScrollWidth();
1646
+ }
1647
+ canScrollDown() {
1648
+ const documentElement = document.documentElement;
1649
+ return documentElement.scrollTop + documentElement.clientHeight < this.getDocumentScrollHeight();
1650
+ }
1651
+ getDocumentScrollHeight() {
1652
+ // Store the original scroll height because the scroll height can increase when the drag element is moved beyond the scroll height.
1653
+ if (this.documentScrollHeight == null) {
1654
+ this.documentScrollHeight = document.documentElement.scrollHeight;
1655
1655
  }
1656
- this.handleStartMouse();
1657
- return true;
1656
+ return this.documentScrollHeight;
1658
1657
  }
1659
- handleStartMouse() {
1660
- document.addEventListener("mousemove", this.mouseMove, {
1661
- passive: false
1662
- });
1663
- document.addEventListener("touchmove", this.touchMove, {
1664
- passive: false
1665
- });
1666
- document.addEventListener("mouseup", this.mouseUp, {
1667
- passive: false
1668
- });
1669
- document.addEventListener("touchend", this.touchEnd, {
1670
- passive: false
1671
- });
1672
- const mouseDelay = this.getMouseDelay();
1673
- if (mouseDelay) {
1674
- this.startMouseDelayTimer(mouseDelay);
1675
- } else {
1676
- this.isMouseDelayMet = true;
1658
+ getDocumentScrollWidth() {
1659
+ // Store the original scroll width because the scroll width can increase when the drag element is moved beyond the scroll width.
1660
+ if (this.documentScrollWidth == null) {
1661
+ this.documentScrollWidth = document.documentElement.scrollWidth;
1677
1662
  }
1663
+ return this.documentScrollWidth;
1678
1664
  }
1679
- startMouseDelayTimer(mouseDelay) {
1680
- if (this.mouseDelayTimer) {
1681
- clearTimeout(this.mouseDelayTimer);
1665
+ getNewVerticalScrollDirection(pageY) {
1666
+ const scrollTop = jQuery(document).scrollTop() || 0;
1667
+ const distanceTop = pageY - scrollTop;
1668
+ if (distanceTop < 20) {
1669
+ return "top";
1682
1670
  }
1683
- this.mouseDelayTimer = window.setTimeout(() => {
1684
- if (this.mouseDownInfo) {
1685
- this.isMouseDelayMet = true;
1686
- }
1687
- }, mouseDelay);
1688
- this.isMouseDelayMet = false;
1671
+ const windowHeight = window.innerHeight;
1672
+ if (windowHeight - (pageY - scrollTop) < 20 && this.canScrollDown()) {
1673
+ return "bottom";
1674
+ }
1675
+ return undefined;
1689
1676
  }
1690
- mouseMove = e => {
1691
- this.handleMouseMove(e, getPositionInfoFromMouseEvent(e));
1692
- };
1693
- handleMouseMove(e, positionInfo) {
1694
- if (this.isMouseStarted) {
1695
- this.mouseDrag(positionInfo);
1696
- if (e.cancelable) {
1697
- e.preventDefault();
1698
- }
1677
+ scrollHorizontally() {
1678
+ if (!this.horizontalScrollDirection) {
1699
1679
  return;
1700
1680
  }
1701
- if (!this.isMouseDelayMet) {
1681
+ const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
1682
+ window.scrollBy({
1683
+ left: distance,
1684
+ top: 0,
1685
+ behavior: "instant"
1686
+ });
1687
+ this.refreshHitAreas();
1688
+ setTimeout(this.scrollHorizontally.bind(this), 40);
1689
+ }
1690
+ scrollVertically() {
1691
+ if (!this.verticalScrollDirection) {
1702
1692
  return;
1703
1693
  }
1704
- if (this.mouseDownInfo) {
1705
- this.isMouseStarted = this.mouseStart(this.mouseDownInfo) !== false;
1706
- }
1707
- if (this.isMouseStarted) {
1708
- this.mouseDrag(positionInfo);
1709
- if (e.cancelable) {
1710
- e.preventDefault();
1711
- }
1712
- } else {
1713
- this.handleMouseUp(positionInfo);
1714
- }
1694
+ const distance = this.verticalScrollDirection === "top" ? -20 : 20;
1695
+ window.scrollBy({
1696
+ left: 0,
1697
+ top: distance,
1698
+ behavior: "instant"
1699
+ });
1700
+ this.refreshHitAreas();
1701
+ setTimeout(this.scrollVertically.bind(this), 40);
1715
1702
  }
1716
- mouseUp = e => {
1717
- this.handleMouseUp(getPositionInfoFromMouseEvent(e));
1718
- };
1719
- handleMouseUp(positionInfo) {
1720
- this.removeMouseMoveEventListeners();
1721
- this.isMouseDelayMet = false;
1722
- this.mouseDownInfo = null;
1723
- if (this.isMouseStarted) {
1724
- this.isMouseStarted = false;
1725
- this.mouseStop(positionInfo);
1703
+ }
1704
+
1705
+ const isOverflow = overflowValue => overflowValue === "auto" || overflowValue === "scroll";
1706
+ const hasOverFlow = element => {
1707
+ const style = getComputedStyle(element);
1708
+ return isOverflow(style.overflowX) || isOverflow(style.overflowY);
1709
+ };
1710
+ const getParentWithOverflow = treeElement => {
1711
+ if (hasOverFlow(treeElement)) {
1712
+ return treeElement;
1713
+ }
1714
+ let parent = treeElement.parentElement;
1715
+ while (parent) {
1716
+ if (hasOverFlow(parent)) {
1717
+ return parent;
1726
1718
  }
1719
+ parent = parent.parentElement;
1727
1720
  }
1728
- removeMouseMoveEventListeners() {
1729
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1730
- document.removeEventListener("mousemove", this.mouseMove, {
1731
- passive: false
1732
- });
1733
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1734
- document.removeEventListener("touchmove", this.touchMove, {
1735
- passive: false
1736
- });
1737
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1738
- document.removeEventListener("mouseup", this.mouseUp, {
1739
- passive: false
1721
+ return null;
1722
+ };
1723
+ const createScrollParent = (treeElement, refreshHitAreas) => {
1724
+ const container = getParentWithOverflow(treeElement);
1725
+ if (container && container.tagName !== "HTML") {
1726
+ return new ContainerScrollParent({
1727
+ container,
1728
+ refreshHitAreas
1740
1729
  });
1741
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1742
- document.removeEventListener("touchend", this.touchEnd, {
1743
- passive: false
1730
+ } else {
1731
+ return new DocumentScrollParent({
1732
+ refreshHitAreas,
1733
+ treeElement
1744
1734
  });
1745
1735
  }
1746
- touchStart = e => {
1747
- if (!e) {
1748
- return;
1749
- }
1750
- if (e.touches.length > 1) {
1751
- return;
1752
- }
1753
- const touch = e.changedTouches[0];
1754
- if (!touch) {
1755
- return;
1756
- }
1757
- this.handleMouseDown(getPositionInfoFromTouch(touch, e));
1758
- };
1759
- touchMove = e => {
1760
- if (!e) {
1761
- return;
1762
- }
1763
- if (e.touches.length > 1) {
1764
- return;
1765
- }
1766
- const touch = e.changedTouches[0];
1767
- if (!touch) {
1768
- return;
1769
- }
1770
- this.handleMouseMove(e, getPositionInfoFromTouch(touch, e));
1771
- };
1772
- touchEnd = e => {
1773
- if (!e) {
1774
- return;
1775
- }
1776
- if (e.touches.length > 1) {
1777
- return;
1778
- }
1779
- const touch = e.changedTouches[0];
1780
- if (!touch) {
1781
- return;
1736
+ };
1737
+
1738
+ class ScrollHandler {
1739
+ constructor(_ref) {
1740
+ let {
1741
+ refreshHitAreas,
1742
+ treeElement
1743
+ } = _ref;
1744
+ this.refreshHitAreas = refreshHitAreas;
1745
+ this.scrollParent = undefined;
1746
+ this.treeElement = treeElement;
1747
+ }
1748
+ checkScrolling(positionInfo) {
1749
+ this.checkVerticalScrolling(positionInfo);
1750
+ this.checkHorizontalScrolling(positionInfo);
1751
+ }
1752
+ stopScrolling() {
1753
+ this.getScrollParent().stopScrolling();
1754
+ }
1755
+ scrollToY(top) {
1756
+ this.getScrollParent().scrollToY(top);
1757
+ }
1758
+ getScrollLeft() {
1759
+ return this.getScrollParent().getScrollLeft();
1760
+ }
1761
+ checkVerticalScrolling(positionInfo) {
1762
+ this.getScrollParent().checkVerticalScrolling(positionInfo.pageY);
1763
+ }
1764
+ checkHorizontalScrolling(positionInfo) {
1765
+ this.getScrollParent().checkHorizontalScrolling(positionInfo.pageX);
1766
+ }
1767
+ getScrollParent() {
1768
+ if (!this.scrollParent) {
1769
+ this.scrollParent = createScrollParent(this.treeElement, this.refreshHitAreas);
1782
1770
  }
1783
- this.handleMouseUp(getPositionInfoFromTouch(touch, e));
1784
- };
1771
+ return this.scrollParent;
1772
+ }
1785
1773
  }
1786
1774
 
1787
- class SaveStateHandler {
1775
+ class SelectNodeHandler {
1788
1776
  constructor(_ref) {
1789
1777
  let {
1790
- addToSelection,
1791
- getNodeById,
1792
- getSelectedNodes,
1793
- getTree,
1794
- onGetStateFromStorage,
1795
- onSetStateFromStorage,
1796
- openNode,
1797
- refreshElements,
1798
- removeFromSelection,
1799
- saveState
1778
+ getNodeById
1800
1779
  } = _ref;
1801
- this.addToSelection = addToSelection;
1802
1780
  this.getNodeById = getNodeById;
1803
- this.getSelectedNodes = getSelectedNodes;
1804
- this.getTree = getTree;
1805
- this.onGetStateFromStorage = onGetStateFromStorage;
1806
- this.onSetStateFromStorage = onSetStateFromStorage;
1807
- this.openNode = openNode;
1808
- this.refreshElements = refreshElements;
1809
- this.removeFromSelection = removeFromSelection;
1810
- this.saveStateOption = saveState;
1781
+ this.selectedNodes = new Set();
1782
+ this.clear();
1811
1783
  }
1812
- saveState() {
1813
- const state = JSON.stringify(this.getState());
1814
- if (this.onSetStateFromStorage) {
1815
- this.onSetStateFromStorage(state);
1816
- } else if (this.supportsLocalStorage()) {
1817
- localStorage.setItem(this.getKeyName(), state);
1784
+ getSelectedNode() {
1785
+ const selectedNodes = this.getSelectedNodes();
1786
+ if (selectedNodes.length) {
1787
+ return selectedNodes[0] || false;
1788
+ } else {
1789
+ return false;
1818
1790
  }
1819
1791
  }
1820
- getStateFromStorage() {
1821
- const jsonData = this.loadFromStorage();
1822
- if (jsonData) {
1823
- return this.parseState(jsonData);
1792
+ getSelectedNodes() {
1793
+ if (this.selectedSingleNode) {
1794
+ return [this.selectedSingleNode];
1824
1795
  } else {
1825
- return null;
1796
+ const selectedNodes = [];
1797
+ this.selectedNodes.forEach(id => {
1798
+ const node = this.getNodeById(id);
1799
+ if (node) {
1800
+ selectedNodes.push(node);
1801
+ }
1802
+ });
1803
+ return selectedNodes;
1826
1804
  }
1827
1805
  }
1828
- getState() {
1829
- const getOpenNodeIds = () => {
1830
- const openNodes = [];
1831
- this.getTree()?.iterate(node => {
1832
- if (node.is_open && node.id && node.hasChildren()) {
1833
- openNodes.push(node.id);
1834
- }
1835
- return true;
1836
- });
1837
- return openNodes;
1838
- };
1839
- const getSelectedNodeIds = () => {
1840
- const selectedNodeIds = [];
1841
- this.getSelectedNodes().forEach(node => {
1842
- if (node.id != null) {
1843
- selectedNodeIds.push(node.id);
1844
- }
1845
- });
1846
- return selectedNodeIds;
1847
- };
1848
- return {
1849
- open_nodes: getOpenNodeIds(),
1850
- selected_node: getSelectedNodeIds()
1851
- };
1852
- }
1853
-
1854
- /*
1855
- Set initial state
1856
- Don't handle nodes that are loaded on demand
1857
- result: must load on demand
1858
- */
1859
- setInitialState(state) {
1860
- if (!state) {
1861
- return false;
1862
- } else {
1863
- let mustLoadOnDemand = false;
1864
- if (state.open_nodes) {
1865
- mustLoadOnDemand = this.openInitialNodes(state.open_nodes);
1806
+ getSelectedNodesUnder(parent) {
1807
+ if (this.selectedSingleNode) {
1808
+ if (parent.isParentOf(this.selectedSingleNode)) {
1809
+ return [this.selectedSingleNode];
1810
+ } else {
1811
+ return [];
1866
1812
  }
1867
- if (state.selected_node) {
1868
- this.resetSelection();
1869
- this.selectInitialNodes(state.selected_node);
1813
+ } else {
1814
+ const selectedNodes = [];
1815
+ for (const id in this.selectedNodes) {
1816
+ if (Object.prototype.hasOwnProperty.call(this.selectedNodes, id)) {
1817
+ const node = this.getNodeById(id);
1818
+ if (node && parent.isParentOf(node)) {
1819
+ selectedNodes.push(node);
1820
+ }
1821
+ }
1870
1822
  }
1871
- return mustLoadOnDemand;
1823
+ return selectedNodes;
1872
1824
  }
1873
1825
  }
1874
- setInitialStateOnDemand(state, cbFinished) {
1875
- if (state) {
1876
- this.doSetInitialStateOnDemand(state.open_nodes, state.selected_node, cbFinished);
1826
+ isNodeSelected(node) {
1827
+ if (node.id != null) {
1828
+ return this.selectedNodes.has(node.id);
1829
+ } else if (this.selectedSingleNode) {
1830
+ return this.selectedSingleNode.element === node.element;
1877
1831
  } else {
1878
- cbFinished();
1832
+ return false;
1879
1833
  }
1880
1834
  }
1881
- getNodeIdToBeSelected() {
1882
- const state = this.getStateFromStorage();
1883
- if (state && state.selected_node) {
1884
- return state.selected_node[0] || null;
1835
+ clear() {
1836
+ this.selectedNodes.clear();
1837
+ this.selectedSingleNode = null;
1838
+ }
1839
+ removeFromSelection(node) {
1840
+ let includeChildren = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1841
+ if (node.id == null) {
1842
+ if (this.selectedSingleNode && node.element === this.selectedSingleNode.element) {
1843
+ this.selectedSingleNode = null;
1844
+ }
1885
1845
  } else {
1886
- return null;
1846
+ this.selectedNodes.delete(node.id);
1847
+ if (includeChildren) {
1848
+ node.iterate(() => {
1849
+ if (node.id != null) {
1850
+ this.selectedNodes.delete(node.id);
1851
+ }
1852
+ return true;
1853
+ });
1854
+ }
1887
1855
  }
1888
1856
  }
1889
- parseState(jsonData) {
1890
- const state = JSON.parse(jsonData);
1891
-
1892
- // Check if selected_node is an int (instead of an array)
1893
- if (state && state.selected_node && isInt(state.selected_node)) {
1894
- // Convert to array
1895
- state.selected_node = [state.selected_node];
1857
+ addToSelection(node) {
1858
+ if (node.id != null) {
1859
+ this.selectedNodes.add(node.id);
1860
+ } else {
1861
+ this.selectedSingleNode = node;
1896
1862
  }
1897
- return state;
1898
1863
  }
1899
- loadFromStorage() {
1900
- if (this.onGetStateFromStorage) {
1901
- return this.onGetStateFromStorage();
1902
- } else if (this.supportsLocalStorage()) {
1903
- return localStorage.getItem(this.getKeyName());
1864
+ }
1865
+
1866
+ const register = (widgetClass, widgetName) => {
1867
+ const getDataKey = () => `simple_widget_${widgetName}`;
1868
+ const getWidgetData = (el, dataKey) => {
1869
+ const widget = jQuery.data(el, dataKey);
1870
+ if (widget && widget instanceof SimpleWidget) {
1871
+ return widget;
1904
1872
  } else {
1905
1873
  return null;
1906
1874
  }
1907
- }
1908
- openInitialNodes(nodeIds) {
1909
- let mustLoadOnDemand = false;
1910
- for (const nodeId of nodeIds) {
1911
- const node = this.getNodeById(nodeId);
1912
- if (node) {
1913
- if (!node.load_on_demand) {
1914
- node.is_open = true;
1915
- } else {
1916
- mustLoadOnDemand = true;
1875
+ };
1876
+ const createWidget = ($el, options) => {
1877
+ const dataKey = getDataKey();
1878
+ for (const el of $el.get()) {
1879
+ const existingWidget = getWidgetData(el, dataKey);
1880
+ if (!existingWidget) {
1881
+ const simpleWidgetClass = widgetClass;
1882
+ const widget = new simpleWidgetClass(el, options);
1883
+ if (!jQuery.data(el, dataKey)) {
1884
+ jQuery.data(el, dataKey, widget);
1917
1885
  }
1886
+
1887
+ // Call init after setting data, so we can call methods
1888
+ widget.init();
1918
1889
  }
1919
1890
  }
1920
- return mustLoadOnDemand;
1921
- }
1922
- selectInitialNodes(nodeIds) {
1923
- let selectCount = 0;
1924
- for (const nodeId of nodeIds) {
1925
- const node = this.getNodeById(nodeId);
1926
- if (node) {
1927
- selectCount += 1;
1928
- this.addToSelection(node);
1891
+ return $el;
1892
+ };
1893
+ const destroyWidget = $el => {
1894
+ const dataKey = getDataKey();
1895
+ for (const el of $el.get()) {
1896
+ const widget = getWidgetData(el, dataKey);
1897
+ if (widget) {
1898
+ widget.destroy();
1929
1899
  }
1900
+ jQuery.removeData(el, dataKey);
1930
1901
  }
1931
- return selectCount !== 0;
1932
- }
1933
- resetSelection() {
1934
- const selectedNodes = this.getSelectedNodes();
1935
- selectedNodes.forEach(node => {
1936
- this.removeFromSelection(node);
1937
- });
1938
- }
1939
- doSetInitialStateOnDemand(nodeIdsParam, selectedNodes, cbFinished) {
1940
- let loadingCount = 0;
1941
- let nodeIds = nodeIdsParam;
1942
- const openNodes = () => {
1943
- const newNodesIds = [];
1944
- for (const nodeId of nodeIds) {
1945
- const node = this.getNodeById(nodeId);
1946
- if (!node) {
1947
- newNodesIds.push(nodeId);
1948
- } else {
1949
- if (!node.is_loading) {
1950
- if (node.load_on_demand) {
1951
- loadAndOpenNode(node);
1952
- } else {
1953
- this.openNode(node, false);
1954
- }
1955
- }
1902
+ };
1903
+ const callFunction = ($el, functionName, args) => {
1904
+ let result = null;
1905
+ for (const el of $el.get()) {
1906
+ const widget = jQuery.data(el, getDataKey());
1907
+ if (widget && widget instanceof SimpleWidget) {
1908
+ const simpleWidget = widget;
1909
+ const widgetFunction = simpleWidget[functionName];
1910
+ if (widgetFunction && typeof widgetFunction === "function") {
1911
+ result = widgetFunction.apply(widget, args);
1956
1912
  }
1957
1913
  }
1958
- nodeIds = newNodesIds;
1959
- if (this.selectInitialNodes(selectedNodes)) {
1960
- this.refreshElements(null);
1961
- }
1962
- if (loadingCount === 0) {
1963
- cbFinished();
1964
- }
1965
- };
1966
- const loadAndOpenNode = node => {
1967
- loadingCount += 1;
1968
- this.openNode(node, false, () => {
1969
- loadingCount -= 1;
1970
- openNodes();
1971
- });
1972
- };
1973
- openNodes();
1974
- }
1975
- getKeyName() {
1976
- if (typeof this.saveStateOption === "string") {
1977
- return this.saveStateOption;
1978
- } else {
1979
- return "tree";
1980
1914
  }
1981
- }
1982
- supportsLocalStorage() {
1983
- const testSupport = () => {
1984
- // Is local storage supported?
1985
- if (localStorage == null) {
1986
- return false;
1915
+ return result;
1916
+ };
1917
+
1918
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1919
+ jQuery.fn[widgetName] = function (argument1) {
1920
+ if (!argument1) {
1921
+ return createWidget(this, null);
1922
+ } else if (typeof argument1 === "object") {
1923
+ const options = argument1;
1924
+ return createWidget(this, options);
1925
+ } else if (typeof argument1 === "string" && argument1[0] !== "_") {
1926
+ const functionName = argument1;
1927
+ if (functionName === "destroy") {
1928
+ return destroyWidget(this);
1929
+ } else if (functionName === "get_widget_class") {
1930
+ return widgetClass;
1987
1931
  } else {
1988
- // Check if it's possible to store an item. Safari does not allow this in private browsing mode.
1989
- try {
1990
- const key = "_storage_test";
1991
- sessionStorage.setItem(key, "value");
1992
- sessionStorage.removeItem(key);
1993
- } catch (error) {
1994
- return false;
1932
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1933
+ args[_key - 1] = arguments[_key];
1995
1934
  }
1996
- return true;
1935
+ return callFunction(this, functionName, args);
1997
1936
  }
1998
- };
1999
- if (this._supportsLocalStorage == null) {
2000
- this._supportsLocalStorage = testSupport();
1937
+ } else {
1938
+ return undefined;
2001
1939
  }
2002
- return this._supportsLocalStorage;
1940
+ };
1941
+ };
1942
+ class SimpleWidget {
1943
+ static register(widgetClass, widgetName) {
1944
+ register(widgetClass, widgetName);
1945
+ }
1946
+ static defaults = {};
1947
+ constructor(el, options) {
1948
+ this.$el = jQuery(el);
1949
+
1950
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1951
+ const defaults = this.constructor["defaults"];
1952
+ this.options = {
1953
+ ...defaults,
1954
+ ...options
1955
+ };
1956
+ }
1957
+ destroy() {
1958
+ this.deinit();
1959
+ }
1960
+ init() {
1961
+ //
1962
+ }
1963
+ deinit() {
1964
+ //
2003
1965
  }
2004
1966
  }
2005
1967
 
2006
- class ContainerScrollParent {
2007
- constructor(_ref) {
2008
- let {
2009
- $container,
2010
- refreshHitAreas
2011
- } = _ref;
2012
- this.$container = $container;
2013
- this.refreshHitAreas = refreshHitAreas;
1968
+ const isNodeRecordWithChildren = data => typeof data === "object" && "children" in data && data["children"] instanceof Array;
1969
+
1970
+ class Node {
1971
+ constructor() {
1972
+ let nodeData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1973
+ let isRoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1974
+ let nodeClass = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Node;
1975
+ this.name = "";
1976
+ this.load_on_demand = false;
1977
+ this.isEmptyFolder = nodeData != null && isNodeRecordWithChildren(nodeData) && nodeData.children.length === 0;
1978
+ this.setData(nodeData);
1979
+ this.children = [];
1980
+ this.parent = null;
1981
+ if (isRoot) {
1982
+ this.idMapping = new Map();
1983
+ this.tree = this;
1984
+ this.nodeClass = nodeClass;
1985
+ }
2014
1986
  }
2015
- checkHorizontalScrolling(pageX) {
2016
- const newHorizontalScrollDirection = this.getNewHorizontalScrollDirection(pageX);
2017
- if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
2018
- this.horizontalScrollDirection = newHorizontalScrollDirection;
2019
- if (this.horizontalScrollTimeout != null) {
2020
- window.clearTimeout(this.verticalScrollTimeout);
2021
- }
2022
- if (newHorizontalScrollDirection) {
2023
- this.horizontalScrollTimeout = window.setTimeout(this.scrollHorizontally.bind(this), 40);
1987
+
1988
+ /*
1989
+ Set the data of this node.
1990
+ setData(string): set the name of the node
1991
+ setData(object): set attributes of the node
1992
+ Examples:
1993
+ setData('node1')
1994
+ setData({ name: 'node1', id: 1});
1995
+ setData({ name: 'node2', id: 2, color: 'green'});
1996
+ * This is an internal function; it is not in the docs
1997
+ * Does not remove existing node values
1998
+ */
1999
+ setData(o) {
2000
+ if (!o) {
2001
+ return;
2002
+ } else if (typeof o === "string") {
2003
+ this.name = o;
2004
+ } else if (typeof o === "object") {
2005
+ for (const key in o) {
2006
+ if (Object.prototype.hasOwnProperty.call(o, key)) {
2007
+ const value = o[key];
2008
+ if (key === "label" || key === "name") {
2009
+ // You can use the 'label' key instead of 'name'; this is a legacy feature
2010
+ if (typeof value === "string") {
2011
+ this.name = value;
2012
+ }
2013
+ } else if (key !== "children" && key !== "parent") {
2014
+ // You can't update the children or the parent using this function
2015
+ this[key] = value;
2016
+ }
2017
+ }
2024
2018
  }
2025
2019
  }
2026
2020
  }
2027
- checkVerticalScrolling(pageY) {
2028
- const newVerticalScrollDirection = this.getNewVerticalScrollDirection(pageY);
2029
- if (this.verticalScrollDirection !== newVerticalScrollDirection) {
2030
- this.verticalScrollDirection = newVerticalScrollDirection;
2031
- if (this.verticalScrollTimeout != null) {
2032
- window.clearTimeout(this.verticalScrollTimeout);
2033
- this.verticalScrollTimeout = undefined;
2021
+
2022
+ /*
2023
+ Create tree from data.
2024
+ Structure of data is:
2025
+ [
2026
+ {
2027
+ name: 'node1',
2028
+ children: [
2029
+ { name: 'child1' },
2030
+ { name: 'child2' }
2031
+ ]
2032
+ },
2033
+ {
2034
+ name: 'node2'
2034
2035
  }
2035
- if (newVerticalScrollDirection) {
2036
- this.verticalScrollTimeout = window.setTimeout(this.scrollVertically.bind(this), 40);
2036
+ ]
2037
+ */
2038
+ loadFromData(data) {
2039
+ this.removeChildren();
2040
+ for (const childData of data) {
2041
+ const node = this.createNode(childData);
2042
+ this.addChild(node);
2043
+ if (isNodeRecordWithChildren(childData)) {
2044
+ node.loadFromData(childData.children);
2037
2045
  }
2038
2046
  }
2047
+ return this;
2039
2048
  }
2040
- getScrollLeft() {
2041
- return this.$container.scrollLeft() || 0;
2049
+
2050
+ /*
2051
+ Add child.
2052
+ tree.addChild(
2053
+ new Node('child1')
2054
+ );
2055
+ */
2056
+ addChild(node) {
2057
+ this.children.push(node);
2058
+ node.setParent(this);
2042
2059
  }
2043
- scrollToY(top) {
2044
- const container = this.$container.get(0);
2045
- container.scrollTop = top;
2060
+
2061
+ /*
2062
+ Add child at position. Index starts at 0.
2063
+ tree.addChildAtPosition(
2064
+ new Node('abc'),
2065
+ 1
2066
+ );
2067
+ */
2068
+ addChildAtPosition(node, index) {
2069
+ this.children.splice(index, 0, node);
2070
+ node.setParent(this);
2046
2071
  }
2047
- stopScrolling() {
2048
- this.horizontalScrollDirection = undefined;
2049
- this.verticalScrollDirection = undefined;
2050
- this.scrollParentTop = undefined;
2051
- this.scrollParentBottom = undefined;
2072
+
2073
+ /*
2074
+ Remove child. This also removes the children of the node.
2075
+ tree.removeChild(tree.children[0]);
2076
+ */
2077
+ removeChild(node) {
2078
+ // remove children from the index
2079
+ node.removeChildren();
2080
+ this.doRemoveChild(node);
2052
2081
  }
2053
- getNewHorizontalScrollDirection(pageX) {
2054
- const scrollParentOffset = this.$container.offset();
2055
- if (!scrollParentOffset) {
2056
- return undefined;
2057
- }
2058
- const container = this.$container.get(0);
2059
- const rightEdge = scrollParentOffset.left + container.clientWidth;
2060
- const leftEdge = scrollParentOffset.left;
2061
- const isNearRightEdge = pageX > rightEdge - 20;
2062
- const isNearLeftEdge = pageX < leftEdge + 20;
2063
- if (isNearRightEdge) {
2064
- return "right";
2065
- } else if (isNearLeftEdge) {
2066
- return "left";
2067
- }
2068
- return undefined;
2082
+
2083
+ /*
2084
+ Get child index.
2085
+ var index = getChildIndex(node);
2086
+ */
2087
+ getChildIndex(node) {
2088
+ return this.children.indexOf(node);
2069
2089
  }
2070
- getNewVerticalScrollDirection(pageY) {
2071
- if (pageY < this.getScrollParentTop()) {
2072
- return "top";
2073
- }
2074
- if (pageY > this.getScrollParentBottom()) {
2075
- return "bottom";
2076
- }
2077
- return undefined;
2090
+
2091
+ /*
2092
+ Does the tree have children?
2093
+ if (tree.hasChildren()) {
2094
+ //
2078
2095
  }
2079
- scrollHorizontally() {
2080
- if (!this.horizontalScrollDirection) {
2081
- return;
2096
+ */
2097
+ hasChildren() {
2098
+ return this.children.length !== 0;
2099
+ }
2100
+ isFolder() {
2101
+ return this.hasChildren() || this.load_on_demand;
2102
+ }
2103
+
2104
+ /*
2105
+ Iterate over all the nodes in the tree.
2106
+ Calls callback with (node, level).
2107
+ The callback must return true to continue the iteration on current node.
2108
+ tree.iterate(
2109
+ function(node, level) {
2110
+ console.log(node.name);
2111
+ // stop iteration after level 2
2112
+ return (level <= 2);
2113
+ }
2114
+ );
2115
+ */
2116
+ iterate(callback) {
2117
+ const _iterate = (node, level) => {
2118
+ if (node.children) {
2119
+ for (const child of node.children) {
2120
+ const result = callback(child, level);
2121
+ if (result && child.hasChildren()) {
2122
+ _iterate(child, level + 1);
2123
+ }
2124
+ }
2125
+ }
2126
+ };
2127
+ _iterate(this, 0);
2128
+ }
2129
+
2130
+ /*
2131
+ Move node relative to another node.
2132
+ Argument position: Position.BEFORE, Position.AFTER or Position.Inside
2133
+ // move node1 after node2
2134
+ tree.moveNode(node1, node2, Position.AFTER);
2135
+ */
2136
+ moveNode(movedNode, targetNode, position) {
2137
+ if (!movedNode.parent || movedNode.isParentOf(targetNode)) {
2138
+ // - Node is parent of target node
2139
+ // - Or, parent is empty
2140
+ return false;
2141
+ } else {
2142
+ movedNode.parent.doRemoveChild(movedNode);
2143
+ switch (position) {
2144
+ case Position.After:
2145
+ {
2146
+ if (targetNode.parent) {
2147
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode) + 1);
2148
+ return true;
2149
+ }
2150
+ return false;
2151
+ }
2152
+ case Position.Before:
2153
+ {
2154
+ if (targetNode.parent) {
2155
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode));
2156
+ return true;
2157
+ }
2158
+ return false;
2159
+ }
2160
+ case Position.Inside:
2161
+ {
2162
+ // move inside as first child
2163
+ targetNode.addChildAtPosition(movedNode, 0);
2164
+ return true;
2165
+ }
2166
+ default:
2167
+ return false;
2168
+ }
2082
2169
  }
2083
- const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
2084
- const container = this.$container.get(0);
2085
- container.scrollBy({
2086
- left: distance,
2087
- top: 0,
2088
- behavior: "instant"
2089
- });
2090
- this.refreshHitAreas();
2091
- setTimeout(this.scrollHorizontally.bind(this), 40);
2092
2170
  }
2093
- scrollVertically() {
2094
- if (!this.verticalScrollDirection) {
2095
- return;
2171
+
2172
+ /*
2173
+ Get the tree as data.
2174
+ */
2175
+ getData() {
2176
+ let includeParent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
2177
+ const getDataFromNodes = nodes => {
2178
+ return nodes.map(node => {
2179
+ const tmpNode = {};
2180
+ for (const k in node) {
2181
+ if (["parent", "children", "element", "idMapping", "load_on_demand", "nodeClass", "tree", "isEmptyFolder"].indexOf(k) === -1 && Object.prototype.hasOwnProperty.call(node, k)) {
2182
+ const v = node[k];
2183
+ tmpNode[k] = v;
2184
+ }
2185
+ }
2186
+ if (node.hasChildren()) {
2187
+ tmpNode["children"] = getDataFromNodes(node.children);
2188
+ }
2189
+ return tmpNode;
2190
+ });
2191
+ };
2192
+ if (includeParent) {
2193
+ return getDataFromNodes([this]);
2194
+ } else {
2195
+ return getDataFromNodes(this.children);
2096
2196
  }
2097
- const distance = this.verticalScrollDirection === "top" ? -20 : 20;
2098
- const container = this.$container.get(0);
2099
- container.scrollBy({
2100
- left: 0,
2101
- top: distance,
2102
- behavior: "instant"
2103
- });
2104
- this.refreshHitAreas();
2105
- setTimeout(this.scrollVertically.bind(this), 40);
2106
2197
  }
2107
- getScrollParentTop() {
2108
- if (this.scrollParentTop == null) {
2109
- this.scrollParentTop = this.$container.offset()?.top || 0;
2198
+ getNodeByName(name) {
2199
+ return this.getNodeByCallback(node => node.name === name);
2200
+ }
2201
+ getNodeByNameMustExist(name) {
2202
+ const node = this.getNodeByCallback(n => n.name === name);
2203
+ if (!node) {
2204
+ throw `Node with name ${name} not found`;
2110
2205
  }
2111
- return this.scrollParentTop;
2206
+ return node;
2207
+ }
2208
+ getNodeByCallback(callback) {
2209
+ let result = null;
2210
+ this.iterate(node => {
2211
+ if (result) {
2212
+ return false;
2213
+ } else if (callback(node)) {
2214
+ result = node;
2215
+ return false;
2216
+ } else {
2217
+ return true;
2218
+ }
2219
+ });
2220
+ return result;
2112
2221
  }
2113
- getScrollParentBottom() {
2114
- if (this.scrollParentBottom == null) {
2115
- this.scrollParentBottom = this.getScrollParentTop() + (this.$container.innerHeight() ?? 0);
2222
+ addAfter(nodeInfo) {
2223
+ if (!this.parent) {
2224
+ return null;
2225
+ } else {
2226
+ const node = this.createNode(nodeInfo);
2227
+ const childIndex = this.parent.getChildIndex(this);
2228
+ this.parent.addChildAtPosition(node, childIndex + 1);
2229
+ node.loadChildrenFromData(nodeInfo);
2230
+ return node;
2116
2231
  }
2117
- return this.scrollParentBottom;
2118
- }
2119
- }
2120
-
2121
- class DocumentScrollParent {
2122
- constructor($element, refreshHitAreas) {
2123
- this.$element = $element;
2124
- this.refreshHitAreas = refreshHitAreas;
2125
2232
  }
2126
- checkHorizontalScrolling(pageX) {
2127
- const newHorizontalScrollDirection = this.getNewHorizontalScrollDirection(pageX);
2128
- if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
2129
- this.horizontalScrollDirection = newHorizontalScrollDirection;
2130
- if (this.horizontalScrollTimeout != null) {
2131
- window.clearTimeout(this.horizontalScrollTimeout);
2132
- }
2133
- if (newHorizontalScrollDirection) {
2134
- this.horizontalScrollTimeout = window.setTimeout(this.scrollHorizontally.bind(this), 40);
2135
- }
2233
+ addBefore(nodeInfo) {
2234
+ if (!this.parent) {
2235
+ return null;
2236
+ } else {
2237
+ const node = this.createNode(nodeInfo);
2238
+ const childIndex = this.parent.getChildIndex(this);
2239
+ this.parent.addChildAtPosition(node, childIndex);
2240
+ node.loadChildrenFromData(nodeInfo);
2241
+ return node;
2136
2242
  }
2137
2243
  }
2138
- checkVerticalScrolling(pageY) {
2139
- const newVerticalScrollDirection = this.getNewVerticalScrollDirection(pageY);
2140
- if (this.verticalScrollDirection !== newVerticalScrollDirection) {
2141
- this.verticalScrollDirection = newVerticalScrollDirection;
2142
- if (this.verticalScrollTimeout != null) {
2143
- window.clearTimeout(this.verticalScrollTimeout);
2144
- this.verticalScrollTimeout = undefined;
2244
+ addParent(nodeInfo) {
2245
+ if (!this.parent) {
2246
+ return null;
2247
+ } else {
2248
+ const newParent = this.createNode(nodeInfo);
2249
+ if (this.tree) {
2250
+ newParent.setParent(this.tree);
2145
2251
  }
2146
- if (newVerticalScrollDirection) {
2147
- this.verticalScrollTimeout = window.setTimeout(this.scrollVertically.bind(this), 40);
2252
+ const originalParent = this.parent;
2253
+ for (const child of originalParent.children) {
2254
+ newParent.addChild(child);
2148
2255
  }
2256
+ originalParent.children = [];
2257
+ originalParent.addChild(newParent);
2258
+ return newParent;
2149
2259
  }
2150
2260
  }
2151
- getScrollLeft() {
2152
- return document.documentElement.scrollLeft;
2153
- }
2154
- scrollToY(top) {
2155
- const offset = this.$element.offset();
2156
- const treeTop = offset ? offset.top : 0;
2157
- jQuery(document).scrollTop(top + treeTop);
2158
- }
2159
- stopScrolling() {
2160
- this.horizontalScrollDirection = undefined;
2161
- this.verticalScrollDirection = undefined;
2162
- this.documentScrollHeight = undefined;
2163
- this.documentScrollWidth = undefined;
2164
- }
2165
- getNewHorizontalScrollDirection(pageX) {
2166
- const $document = jQuery(document);
2167
- const scrollLeft = $document.scrollLeft() || 0;
2168
- const windowWidth = jQuery(window).width() || 0;
2169
- const isNearRightEdge = pageX > windowWidth - 20;
2170
- const isNearLeftEdge = pageX - scrollLeft < 20;
2171
- if (isNearRightEdge && this.canScrollRight()) {
2172
- return "right";
2173
- }
2174
- if (isNearLeftEdge) {
2175
- return "left";
2261
+ remove() {
2262
+ if (this.parent) {
2263
+ this.parent.removeChild(this);
2264
+ this.parent = null;
2176
2265
  }
2177
- return undefined;
2178
2266
  }
2179
- canScrollRight() {
2180
- const documentElement = document.documentElement;
2181
- return documentElement.scrollLeft + documentElement.clientWidth < this.getDocumentScrollWidth();
2267
+ append(nodeInfo) {
2268
+ const node = this.createNode(nodeInfo);
2269
+ this.addChild(node);
2270
+ node.loadChildrenFromData(nodeInfo);
2271
+ return node;
2182
2272
  }
2183
- canScrollDown() {
2184
- const documentElement = document.documentElement;
2185
- return documentElement.scrollTop + documentElement.clientHeight < this.getDocumentScrollHeight();
2273
+ prepend(nodeInfo) {
2274
+ const node = this.createNode(nodeInfo);
2275
+ this.addChildAtPosition(node, 0);
2276
+ node.loadChildrenFromData(nodeInfo);
2277
+ return node;
2186
2278
  }
2187
- getDocumentScrollHeight() {
2188
- // Store the original scroll height because the scroll height can increase when the drag element is moved beyond the scroll height.
2189
- if (this.documentScrollHeight == null) {
2190
- this.documentScrollHeight = document.documentElement.scrollHeight;
2279
+ isParentOf(node) {
2280
+ let parent = node.parent;
2281
+ while (parent) {
2282
+ if (parent === this) {
2283
+ return true;
2284
+ }
2285
+ parent = parent.parent;
2191
2286
  }
2192
- return this.documentScrollHeight;
2287
+ return false;
2193
2288
  }
2194
- getDocumentScrollWidth() {
2195
- // Store the original scroll width because the scroll width can increase when the drag element is moved beyond the scroll width.
2196
- if (this.documentScrollWidth == null) {
2197
- this.documentScrollWidth = document.documentElement.scrollWidth;
2289
+ getLevel() {
2290
+ let level = 0;
2291
+ let node = this; // eslint-disable-line @typescript-eslint/no-this-alias
2292
+
2293
+ while (node.parent) {
2294
+ level += 1;
2295
+ node = node.parent;
2198
2296
  }
2199
- return this.documentScrollWidth;
2297
+ return level;
2200
2298
  }
2201
- getNewVerticalScrollDirection(pageY) {
2202
- const scrollTop = jQuery(document).scrollTop() || 0;
2203
- const distanceTop = pageY - scrollTop;
2204
- if (distanceTop < 20) {
2205
- return "top";
2206
- }
2207
- const windowHeight = jQuery(window).height() || 0;
2208
- if (windowHeight - (pageY - scrollTop) < 20 && this.canScrollDown()) {
2209
- return "bottom";
2210
- }
2211
- return undefined;
2299
+ getNodeById(nodeId) {
2300
+ return this.idMapping.get(nodeId) || null;
2212
2301
  }
2213
- scrollHorizontally() {
2214
- if (!this.horizontalScrollDirection) {
2215
- return;
2302
+ addNodeToIndex(node) {
2303
+ if (node.id != null) {
2304
+ this.idMapping.set(node.id, node);
2216
2305
  }
2217
- const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
2218
- window.scrollBy({
2219
- left: distance,
2220
- top: 0,
2221
- behavior: "instant"
2222
- });
2223
- this.refreshHitAreas();
2224
- setTimeout(this.scrollHorizontally.bind(this), 40);
2225
2306
  }
2226
- scrollVertically() {
2227
- if (!this.verticalScrollDirection) {
2228
- return;
2307
+ removeNodeFromIndex(node) {
2308
+ if (node.id != null) {
2309
+ this.idMapping.delete(node.id);
2229
2310
  }
2230
- const distance = this.verticalScrollDirection === "top" ? -20 : 20;
2231
- window.scrollBy({
2232
- left: 0,
2233
- top: distance,
2234
- behavior: "instant"
2235
- });
2236
- this.refreshHitAreas();
2237
- setTimeout(this.scrollVertically.bind(this), 40);
2238
2311
  }
2239
- }
2240
-
2241
- const hasOverFlow = $element => {
2242
- for (const attr of ["overflow", "overflow-y"]) {
2243
- const overflowValue = $element.css(attr);
2244
- if (overflowValue === "auto" || overflowValue === "scroll") {
2312
+ removeChildren() {
2313
+ this.iterate(child => {
2314
+ this.tree?.removeNodeFromIndex(child);
2245
2315
  return true;
2246
- }
2247
- }
2248
- return false;
2249
- };
2250
- const getParentWithOverflow = $treeElement => {
2251
- if (hasOverFlow($treeElement)) {
2252
- return $treeElement;
2253
- }
2254
- for (const element of $treeElement.parents().get()) {
2255
- const $element = jQuery(element);
2256
- if (hasOverFlow($element)) {
2257
- return $element;
2258
- }
2259
- }
2260
- return null;
2261
- };
2262
- const createScrollParent = ($treeElement, refreshHitAreas) => {
2263
- const $container = getParentWithOverflow($treeElement);
2264
- if ($container?.length && $container[0]?.tagName !== "HTML") {
2265
- return new ContainerScrollParent({
2266
- $container,
2267
- refreshHitAreas,
2268
- $treeElement
2269
2316
  });
2270
- } else {
2271
- return new DocumentScrollParent($treeElement, refreshHitAreas);
2272
- }
2273
- };
2274
-
2275
- class ScrollHandler {
2276
- constructor(_ref) {
2277
- let {
2278
- refreshHitAreas,
2279
- $treeElement
2280
- } = _ref;
2281
- this.refreshHitAreas = refreshHitAreas;
2282
- this.scrollParent = undefined;
2283
- this.$treeElement = $treeElement;
2284
- }
2285
- checkScrolling(positionInfo) {
2286
- this.checkVerticalScrolling(positionInfo);
2287
- this.checkHorizontalScrolling(positionInfo);
2288
- }
2289
- stopScrolling() {
2290
- this.getScrollParent().stopScrolling();
2291
- }
2292
- scrollToY(top) {
2293
- this.getScrollParent().scrollToY(top);
2294
- }
2295
- getScrollLeft() {
2296
- return this.getScrollParent().getScrollLeft();
2317
+ this.children = [];
2297
2318
  }
2298
- checkVerticalScrolling(positionInfo) {
2299
- if (positionInfo.pageY == null) {
2300
- return;
2319
+ getPreviousSibling() {
2320
+ if (!this.parent) {
2321
+ return null;
2322
+ } else {
2323
+ const previousIndex = this.parent.getChildIndex(this) - 1;
2324
+ if (previousIndex >= 0) {
2325
+ return this.parent.children[previousIndex] || null;
2326
+ } else {
2327
+ return null;
2328
+ }
2301
2329
  }
2302
- this.getScrollParent().checkVerticalScrolling(positionInfo.pageY);
2303
2330
  }
2304
- checkHorizontalScrolling(positionInfo) {
2305
- if (positionInfo.pageX == null) {
2306
- return;
2331
+ getNextSibling() {
2332
+ if (!this.parent) {
2333
+ return null;
2334
+ } else {
2335
+ const nextIndex = this.parent.getChildIndex(this) + 1;
2336
+ if (nextIndex < this.parent.children.length) {
2337
+ return this.parent.children[nextIndex] || null;
2338
+ } else {
2339
+ return null;
2340
+ }
2307
2341
  }
2308
- this.getScrollParent().checkHorizontalScrolling(positionInfo.pageX);
2309
2342
  }
2310
- getScrollParent() {
2311
- if (!this.scrollParent) {
2312
- this.scrollParent = createScrollParent(this.$treeElement, this.refreshHitAreas);
2313
- }
2314
- return this.scrollParent;
2343
+ getNodesByProperty(key, value) {
2344
+ return this.filter(node => node[key] === value);
2315
2345
  }
2316
- }
2317
-
2318
- class SelectNodeHandler {
2319
- constructor(_ref) {
2320
- let {
2321
- getNodeById
2322
- } = _ref;
2323
- this.getNodeById = getNodeById;
2324
- this.selectedNodes = new Set();
2325
- this.clear();
2346
+ filter(f) {
2347
+ const result = [];
2348
+ this.iterate(node => {
2349
+ if (f(node)) {
2350
+ result.push(node);
2351
+ }
2352
+ return true;
2353
+ });
2354
+ return result;
2326
2355
  }
2327
- getSelectedNode() {
2328
- const selectedNodes = this.getSelectedNodes();
2329
- if (selectedNodes.length) {
2330
- return selectedNodes[0] || false;
2356
+ getNextNode() {
2357
+ let includeChildren = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2358
+ if (includeChildren && this.hasChildren()) {
2359
+ return this.children[0] || null;
2360
+ } else if (!this.parent) {
2361
+ return null;
2331
2362
  } else {
2332
- return false;
2363
+ const nextSibling = this.getNextSibling();
2364
+ if (nextSibling) {
2365
+ return nextSibling;
2366
+ } else {
2367
+ return this.parent.getNextNode(false);
2368
+ }
2333
2369
  }
2334
2370
  }
2335
- getSelectedNodes() {
2336
- if (this.selectedSingleNode) {
2337
- return [this.selectedSingleNode];
2371
+ getNextVisibleNode() {
2372
+ if (this.hasChildren() && this.is_open) {
2373
+ // First child
2374
+ return this.children[0] || null;
2338
2375
  } else {
2339
- const selectedNodes = [];
2340
- this.selectedNodes.forEach(id => {
2341
- const node = this.getNodeById(id);
2342
- if (node) {
2343
- selectedNodes.push(node);
2376
+ if (!this.parent) {
2377
+ return null;
2378
+ } else {
2379
+ const nextSibling = this.getNextSibling();
2380
+ if (nextSibling) {
2381
+ // Next sibling
2382
+ return nextSibling;
2383
+ } else {
2384
+ // Next node of parent
2385
+ return this.parent.getNextNode(false);
2344
2386
  }
2345
- });
2346
- return selectedNodes;
2387
+ }
2347
2388
  }
2348
2389
  }
2349
- getSelectedNodesUnder(parent) {
2350
- if (this.selectedSingleNode) {
2351
- if (parent.isParentOf(this.selectedSingleNode)) {
2352
- return [this.selectedSingleNode];
2390
+ getPreviousNode() {
2391
+ if (!this.parent) {
2392
+ return null;
2393
+ } else {
2394
+ const previousSibling = this.getPreviousSibling();
2395
+ if (!previousSibling) {
2396
+ return this.getParent();
2397
+ } else if (previousSibling.hasChildren()) {
2398
+ return previousSibling.getLastChild();
2353
2399
  } else {
2354
- return [];
2400
+ return previousSibling;
2355
2401
  }
2402
+ }
2403
+ }
2404
+ getPreviousVisibleNode() {
2405
+ if (!this.parent) {
2406
+ return null;
2356
2407
  } else {
2357
- const selectedNodes = [];
2358
- for (const id in this.selectedNodes) {
2359
- if (Object.prototype.hasOwnProperty.call(this.selectedNodes, id)) {
2360
- const node = this.getNodeById(id);
2361
- if (node && parent.isParentOf(node)) {
2362
- selectedNodes.push(node);
2363
- }
2364
- }
2408
+ const previousSibling = this.getPreviousSibling();
2409
+ if (!previousSibling) {
2410
+ return this.getParent();
2411
+ } else if (!previousSibling.hasChildren() || !previousSibling.is_open) {
2412
+ // Previous sibling
2413
+ return previousSibling;
2414
+ } else {
2415
+ // Last child of previous sibling
2416
+ return previousSibling.getLastChild();
2365
2417
  }
2366
- return selectedNodes;
2367
2418
  }
2368
2419
  }
2369
- isNodeSelected(node) {
2370
- if (node.id != null) {
2371
- return this.selectedNodes.has(node.id);
2372
- } else if (this.selectedSingleNode) {
2373
- return this.selectedSingleNode.element === node.element;
2420
+ getParent() {
2421
+ // Return parent except if it is the root node
2422
+ if (!this.parent) {
2423
+ return null;
2424
+ } else if (!this.parent.parent) {
2425
+ // Root node -> null
2426
+ return null;
2374
2427
  } else {
2375
- return false;
2428
+ return this.parent;
2376
2429
  }
2377
2430
  }
2378
- clear() {
2379
- this.selectedNodes.clear();
2380
- this.selectedSingleNode = null;
2381
- }
2382
- removeFromSelection(node) {
2383
- let includeChildren = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2384
- if (node.id == null) {
2385
- if (this.selectedSingleNode && node.element === this.selectedSingleNode.element) {
2386
- this.selectedSingleNode = null;
2387
- }
2431
+ getLastChild() {
2432
+ if (!this.hasChildren()) {
2433
+ return null;
2388
2434
  } else {
2389
- this.selectedNodes.delete(node.id);
2390
- if (includeChildren) {
2391
- node.iterate(() => {
2392
- if (node.id != null) {
2393
- this.selectedNodes.delete(node.id);
2394
- }
2395
- return true;
2396
- });
2435
+ const lastChild = this.children[this.children.length - 1];
2436
+ if (!lastChild) {
2437
+ return null;
2438
+ }
2439
+ if (!(lastChild.hasChildren() && lastChild.is_open)) {
2440
+ return lastChild;
2441
+ } else {
2442
+ return lastChild?.getLastChild();
2397
2443
  }
2398
2444
  }
2399
2445
  }
2400
- addToSelection(node) {
2401
- if (node.id != null) {
2402
- this.selectedNodes.add(node.id);
2403
- } else {
2404
- this.selectedSingleNode = node;
2446
+
2447
+ // Init Node from data without making it the root of the tree
2448
+ initFromData(data) {
2449
+ const addNode = nodeData => {
2450
+ this.setData(nodeData);
2451
+ if (isNodeRecordWithChildren(nodeData) && nodeData.children.length) {
2452
+ addChildren(nodeData.children);
2453
+ }
2454
+ };
2455
+ const addChildren = childrenData => {
2456
+ for (const child of childrenData) {
2457
+ const node = this.createNode();
2458
+ node.initFromData(child);
2459
+ this.addChild(node);
2460
+ }
2461
+ };
2462
+ addNode(data);
2463
+ }
2464
+ setParent(parent) {
2465
+ this.parent = parent;
2466
+ this.tree = parent.tree;
2467
+ this.tree?.addNodeToIndex(this);
2468
+ }
2469
+ doRemoveChild(node) {
2470
+ this.children.splice(this.getChildIndex(node), 1);
2471
+ this.tree?.removeNodeFromIndex(node);
2472
+ }
2473
+ getNodeClass() {
2474
+ return this.nodeClass || this?.tree?.nodeClass || Node;
2475
+ }
2476
+ createNode(nodeData) {
2477
+ const nodeClass = this.getNodeClass();
2478
+ return new nodeClass(nodeData);
2479
+ }
2480
+
2481
+ // Load children data from nodeInfo if it has children
2482
+ loadChildrenFromData(nodeInfo) {
2483
+ if (isNodeRecordWithChildren(nodeInfo) && nodeInfo.children.length) {
2484
+ this.loadFromData(nodeInfo.children);
2405
2485
  }
2406
2486
  }
2407
2487
  }
@@ -2634,7 +2714,7 @@ var jqtree = (function (exports) {
2634
2714
 
2635
2715
  const NODE_PARAM_IS_EMPTY = "Node parameter is empty";
2636
2716
  const PARAM_IS_EMPTY = "Parameter is empty: ";
2637
- class JqTreeWidget extends MouseWidget {
2717
+ class JqTreeWidget extends SimpleWidget {
2638
2718
  static defaults = {
2639
2719
  animationSpeed: "fast",
2640
2720
  autoEscape: true,
@@ -2740,7 +2820,7 @@ var jqtree = (function (exports) {
2740
2820
  return this.element;
2741
2821
  }
2742
2822
  refresh() {
2743
- this._refreshElements(null);
2823
+ this.refreshElements(null);
2744
2824
  return this.element;
2745
2825
  }
2746
2826
  getNodeById(nodeId) {
@@ -2785,7 +2865,7 @@ var jqtree = (function (exports) {
2785
2865
  return [slide, onFinished];
2786
2866
  };
2787
2867
  const [slide, onFinished] = parseParams();
2788
- this._openNode(node, slide, onFinished);
2868
+ this.openNodeInternal(node, slide, onFinished);
2789
2869
  return this.element;
2790
2870
  }
2791
2871
  closeNode(node, slideParam) {
@@ -2809,7 +2889,7 @@ var jqtree = (function (exports) {
2809
2889
  addNodeAfter(newNodeInfo, existingNode) {
2810
2890
  const newNode = existingNode.addAfter(newNodeInfo);
2811
2891
  if (newNode) {
2812
- this._refreshElements(existingNode.parent);
2892
+ this.refreshElements(existingNode.parent);
2813
2893
  }
2814
2894
  return newNode;
2815
2895
  }
@@ -2819,7 +2899,7 @@ var jqtree = (function (exports) {
2819
2899
  }
2820
2900
  const newNode = existingNode.addBefore(newNodeInfo);
2821
2901
  if (newNode) {
2822
- this._refreshElements(existingNode.parent);
2902
+ this.refreshElements(existingNode.parent);
2823
2903
  }
2824
2904
  return newNode;
2825
2905
  }
@@ -2829,7 +2909,7 @@ var jqtree = (function (exports) {
2829
2909
  }
2830
2910
  const newNode = existingNode.addParent(newNodeInfo);
2831
2911
  if (newNode) {
2832
- this._refreshElements(newNode.parent);
2912
+ this.refreshElements(newNode.parent);
2833
2913
  }
2834
2914
  return newNode;
2835
2915
  }
@@ -2844,19 +2924,19 @@ var jqtree = (function (exports) {
2844
2924
 
2845
2925
  const parent = node.parent;
2846
2926
  node.remove();
2847
- this._refreshElements(parent);
2927
+ this.refreshElements(parent);
2848
2928
  return this.element;
2849
2929
  }
2850
2930
  appendNode(newNodeInfo, parentNodeParam) {
2851
2931
  const parentNode = parentNodeParam || this.tree;
2852
2932
  const node = parentNode.append(newNodeInfo);
2853
- this._refreshElements(parentNode);
2933
+ this.refreshElements(parentNode);
2854
2934
  return node;
2855
2935
  }
2856
2936
  prependNode(newNodeInfo, parentNodeParam) {
2857
2937
  const parentNode = parentNodeParam ?? this.tree;
2858
2938
  const node = parentNode.prepend(newNodeInfo);
2859
- this._refreshElements(parentNode);
2939
+ this.refreshElements(parentNode);
2860
2940
  return node;
2861
2941
  }
2862
2942
  updateNode(node, data) {
@@ -2877,7 +2957,7 @@ var jqtree = (function (exports) {
2877
2957
  node.loadFromData(data.children);
2878
2958
  }
2879
2959
  }
2880
- this._refreshElements(node);
2960
+ this.refreshElements(node);
2881
2961
  return this.element;
2882
2962
  }
2883
2963
  isSelectedNodeInSubtree(subtree) {
@@ -2898,7 +2978,7 @@ var jqtree = (function (exports) {
2898
2978
  const positionIndex = getPosition(position);
2899
2979
  if (positionIndex !== undefined) {
2900
2980
  this.tree.moveNode(node, targetNode, positionIndex);
2901
- this._refreshElements(null);
2981
+ this.refreshElements(null);
2902
2982
  }
2903
2983
  return this.element;
2904
2984
  }
@@ -2911,7 +2991,7 @@ var jqtree = (function (exports) {
2911
2991
  }
2912
2992
  this.selectNodeHandler.addToSelection(node);
2913
2993
  this.openParents(node);
2914
- this._getNodeElementForNode(node).select(mustSetFocus === undefined ? true : mustSetFocus);
2994
+ this.getNodeElementForNode(node).select(mustSetFocus === undefined ? true : mustSetFocus);
2915
2995
  this.saveState();
2916
2996
  return this.element;
2917
2997
  }
@@ -2929,7 +3009,7 @@ var jqtree = (function (exports) {
2929
3009
  throw Error(NODE_PARAM_IS_EMPTY);
2930
3010
  }
2931
3011
  this.selectNodeHandler.removeFromSelection(node);
2932
- this._getNodeElementForNode(node).deselect();
3012
+ this.getNodeElementForNode(node).deselect();
2933
3013
  this.saveState();
2934
3014
  return this.element;
2935
3015
  }
@@ -2937,9 +3017,7 @@ var jqtree = (function (exports) {
2937
3017
  if (!node) {
2938
3018
  throw Error(NODE_PARAM_IS_EMPTY);
2939
3019
  }
2940
- const nodeTop = jQuery(node.element).offset()?.top ?? 0;
2941
- const treeTop = this.$el.offset()?.top ?? 0;
2942
- const top = nodeTop - treeTop;
3020
+ const top = getOffsetTop(node.element) - getOffsetTop(this.$el.get(0));
2943
3021
  this.scrollHandler.scrollToY(top);
2944
3022
  return this.element;
2945
3023
  }
@@ -2948,7 +3026,7 @@ var jqtree = (function (exports) {
2948
3026
  }
2949
3027
  setState(state) {
2950
3028
  this.saveStateHandler.setInitialState(state);
2951
- this._refreshElements(null);
3029
+ this.refreshElements(null);
2952
3030
  return this.element;
2953
3031
  }
2954
3032
  setOption(option, value) {
@@ -2972,12 +3050,7 @@ var jqtree = (function (exports) {
2972
3050
  getVersion() {
2973
3051
  return version;
2974
3052
  }
2975
- _triggerEvent(eventName, values) {
2976
- const event = jQuery.Event(eventName, values);
2977
- this.element.trigger(event);
2978
- return event;
2979
- }
2980
- _openNode(node) {
3053
+ openNodeInternal(node) {
2981
3054
  let slide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
2982
3055
  let onFinished = arguments.length > 2 ? arguments[2] : undefined;
2983
3056
  const doOpenNode = (_node, _slide, _onFinished) => {
@@ -3006,33 +3079,30 @@ var jqtree = (function (exports) {
3006
3079
  Redraw the tree or part of the tree.
3007
3080
  from_node: redraw this subtree
3008
3081
  */
3009
- _refreshElements(fromNode) {
3082
+ refreshElements(fromNode) {
3010
3083
  const mustSetFocus = this.isFocusOnTree();
3011
3084
  const mustSelect = fromNode ? this.isSelectedNodeInSubtree(fromNode) : false;
3012
3085
  this.renderer.render(fromNode);
3013
3086
  if (mustSelect) {
3014
3087
  this.selectCurrentNode(mustSetFocus);
3015
3088
  }
3016
- this._triggerEvent("tree.refresh");
3089
+ this.triggerEvent("tree.refresh");
3017
3090
  }
3018
- _getNodeElementForNode(node) {
3091
+ getNodeElementForNode(node) {
3019
3092
  if (node.isFolder()) {
3020
3093
  return this.createFolderElement(node);
3021
3094
  } else {
3022
3095
  return this.createNodeElement(node);
3023
3096
  }
3024
3097
  }
3025
- _getNodeElement(element) {
3098
+ getNodeElement(element) {
3026
3099
  const node = this.getNode(element);
3027
3100
  if (node) {
3028
- return this._getNodeElementForNode(node);
3101
+ return this.getNodeElementForNode(node);
3029
3102
  } else {
3030
3103
  return null;
3031
3104
  }
3032
3105
  }
3033
- _getScrollLeft() {
3034
- return this.scrollHandler.getScrollLeft();
3035
- }
3036
3106
  init() {
3037
3107
  super.init();
3038
3108
  this.element = this.$el;
@@ -3043,19 +3113,20 @@ var jqtree = (function (exports) {
3043
3113
  }
3044
3114
  this.connectHandlers();
3045
3115
  this.initData();
3046
- this.element.on("click", this.handleClick);
3047
- this.element.on("dblclick", this.handleDblclick);
3048
- if (this.options.useContextMenu) {
3049
- this.element.on("contextmenu", this.handleContextmenu);
3050
- }
3051
3116
  }
3052
3117
  deinit() {
3053
3118
  this.element.empty();
3054
3119
  this.element.off();
3055
3120
  this.keyHandler.deinit();
3121
+ this.mouseHandler.deinit();
3056
3122
  this.tree = new Node({}, true);
3057
3123
  super.deinit();
3058
3124
  }
3125
+ triggerEvent(eventName, values) {
3126
+ const event = jQuery.Event(eventName, values);
3127
+ this.element.trigger(event);
3128
+ return event;
3129
+ }
3059
3130
  mouseCapture(positionInfo) {
3060
3131
  if (this.options.dragAndDrop) {
3061
3132
  return this.dndHandler.mouseCapture(positionInfo);
@@ -3087,9 +3158,6 @@ var jqtree = (function (exports) {
3087
3158
  return false;
3088
3159
  }
3089
3160
  }
3090
- getMouseDelay() {
3091
- return this.options.startDndDelay ?? 0;
3092
- }
3093
3161
  initData() {
3094
3162
  if (this.options.data) {
3095
3163
  this.doLoadData(this.options.data, null);
@@ -3151,7 +3219,7 @@ var jqtree = (function (exports) {
3151
3219
  const doInit = () => {
3152
3220
  if (!this.isInitialized) {
3153
3221
  this.isInitialized = true;
3154
- this._triggerEvent("tree.init");
3222
+ this.triggerEvent("tree.init");
3155
3223
  }
3156
3224
  };
3157
3225
  if (!this.options.nodeClass) {
@@ -3161,7 +3229,7 @@ var jqtree = (function (exports) {
3161
3229
  this.selectNodeHandler.clear();
3162
3230
  this.tree.loadFromData(data);
3163
3231
  const mustLoadOnDemand = this.setInitialState();
3164
- this._refreshElements(null);
3232
+ this.refreshElements(null);
3165
3233
  if (!mustLoadOnDemand) {
3166
3234
  doInit();
3167
3235
  } else {
@@ -3238,7 +3306,7 @@ var jqtree = (function (exports) {
3238
3306
  let loadingCount = 0;
3239
3307
  const loadAndOpenNode = node => {
3240
3308
  loadingCount += 1;
3241
- this._openNode(node, false, () => {
3309
+ this.openNodeInternal(node, false, () => {
3242
3310
  loadingCount -= 1;
3243
3311
  openNodes();
3244
3312
  });
@@ -3251,7 +3319,7 @@ var jqtree = (function (exports) {
3251
3319
  }
3252
3320
  return false;
3253
3321
  } else {
3254
- this._openNode(node, false);
3322
+ this.openNodeInternal(node, false);
3255
3323
  return level !== maxLevel;
3256
3324
  }
3257
3325
  });
@@ -3276,58 +3344,6 @@ var jqtree = (function (exports) {
3276
3344
  return 0;
3277
3345
  }
3278
3346
  }
3279
- handleClick = e => {
3280
- const clickTarget = this.getClickTarget(e.target);
3281
- if (clickTarget) {
3282
- if (clickTarget.type === "button") {
3283
- this.toggle(clickTarget.node, this.options.slide);
3284
- e.preventDefault();
3285
- e.stopPropagation();
3286
- } else if (clickTarget.type === "label") {
3287
- const node = clickTarget.node;
3288
- const event = this._triggerEvent("tree.click", {
3289
- node,
3290
- click_event: e
3291
- });
3292
- if (!event.isDefaultPrevented()) {
3293
- this.doSelectNode(node);
3294
- }
3295
- }
3296
- }
3297
- };
3298
- handleDblclick = e => {
3299
- const clickTarget = this.getClickTarget(e.target);
3300
- if (clickTarget?.type === "label") {
3301
- this._triggerEvent("tree.dblclick", {
3302
- node: clickTarget.node,
3303
- click_event: e
3304
- });
3305
- }
3306
- };
3307
- getClickTarget(element) {
3308
- const button = element.closest(".jqtree-toggler");
3309
- if (button) {
3310
- const node = this.getNode(button);
3311
- if (node) {
3312
- return {
3313
- type: "button",
3314
- node
3315
- };
3316
- }
3317
- } else {
3318
- const jqTreeElement = element.closest(".jqtree-element");
3319
- if (jqTreeElement) {
3320
- const node = this.getNode(jqTreeElement);
3321
- if (node) {
3322
- return {
3323
- type: "label",
3324
- node
3325
- };
3326
- }
3327
- }
3328
- }
3329
- return null;
3330
- }
3331
3347
  getNode(element) {
3332
3348
  const liElement = element.closest("li.jqtree_common");
3333
3349
  if (liElement) {
@@ -3336,22 +3352,6 @@ var jqtree = (function (exports) {
3336
3352
  return null;
3337
3353
  }
3338
3354
  }
3339
- handleContextmenu = e => {
3340
- const div = e.target.closest("ul.jqtree-tree .jqtree-element");
3341
- if (div) {
3342
- const node = this.getNode(div);
3343
- if (node) {
3344
- e.preventDefault();
3345
- e.stopPropagation();
3346
- this._triggerEvent("tree.contextmenu", {
3347
- node,
3348
- click_event: e
3349
- });
3350
- return false;
3351
- }
3352
- }
3353
- return null;
3354
- };
3355
3355
  saveState() {
3356
3356
  if (this.options.saveState) {
3357
3357
  this.saveStateHandler.saveState();
@@ -3360,7 +3360,7 @@ var jqtree = (function (exports) {
3360
3360
  selectCurrentNode(mustSetFocus) {
3361
3361
  const node = this.getSelectedNode();
3362
3362
  if (node) {
3363
- const nodeElement = this._getNodeElementForNode(node);
3363
+ const nodeElement = this.getNodeElementForNode(node);
3364
3364
  if (nodeElement) {
3365
3365
  nodeElement.select(mustSetFocus);
3366
3366
  }
@@ -3426,7 +3426,7 @@ var jqtree = (function (exports) {
3426
3426
  if (this.selectNodeHandler.isNodeSelected(node)) {
3427
3427
  if (selectOptions.mustToggle) {
3428
3428
  this.deselectCurrentNode();
3429
- this._triggerEvent("tree.select", {
3429
+ this.triggerEvent("tree.select", {
3430
3430
  node: null,
3431
3431
  previous_node: node
3432
3432
  });
@@ -3435,7 +3435,7 @@ var jqtree = (function (exports) {
3435
3435
  const deselectedNode = this.getSelectedNode() || null;
3436
3436
  this.deselectCurrentNode();
3437
3437
  this.addToSelection(node, selectOptions.mustSetFocus);
3438
- this._triggerEvent("tree.select", {
3438
+ this.triggerEvent("tree.select", {
3439
3439
  node,
3440
3440
  deselected_node: deselectedNode
3441
3441
  });
@@ -3455,7 +3455,7 @@ var jqtree = (function (exports) {
3455
3455
  this.dndHandler.refresh();
3456
3456
  }
3457
3457
  }
3458
- this._triggerEvent("tree.load_data", {
3458
+ this.triggerEvent("tree.load_data", {
3459
3459
  tree_data: data,
3460
3460
  parent_node: parentNode
3461
3461
  });
@@ -3470,7 +3470,7 @@ var jqtree = (function (exports) {
3470
3470
  parentNode.loadFromData(data);
3471
3471
  parentNode.load_on_demand = false;
3472
3472
  parentNode.is_loading = false;
3473
- this._refreshElements(parentNode);
3473
+ this.refreshElements(parentNode);
3474
3474
  }
3475
3475
  doLoadDataFromUrl(urlInfoParam, parentNode, onFinished) {
3476
3476
  const urlInfo = urlInfoParam || this.getDataUrlInfo(parentNode);
@@ -3481,7 +3481,7 @@ var jqtree = (function (exports) {
3481
3481
  let onFinished = arguments.length > 2 ? arguments[2] : undefined;
3482
3482
  node.is_loading = true;
3483
3483
  this.doLoadDataFromUrl(null, node, () => {
3484
- this._openNode(node, slide, onFinished);
3484
+ this.openNodeInternal(node, slide, onFinished);
3485
3485
  });
3486
3486
  }
3487
3487
  containsElement(element) {
@@ -3501,6 +3501,7 @@ var jqtree = (function (exports) {
3501
3501
  dragAndDrop,
3502
3502
  keyboardSupport,
3503
3503
  onCanMove,
3504
+ onCanMoveTo,
3504
3505
  onCreateLi,
3505
3506
  onDragMove,
3506
3507
  onDragStop,
@@ -3518,20 +3519,20 @@ var jqtree = (function (exports) {
3518
3519
  tabIndex
3519
3520
  } = this.options;
3520
3521
  const closeNode = this.closeNode.bind(this);
3521
- const getNodeElement = this._getNodeElement.bind(this);
3522
- const getNodeElementForNode = this._getNodeElementForNode.bind(this);
3522
+ const getNodeElement = this.getNodeElement.bind(this);
3523
+ const getNodeElementForNode = this.getNodeElementForNode.bind(this);
3523
3524
  const getNodeById = this.getNodeById.bind(this);
3524
- const getScrollLeft = this._getScrollLeft.bind(this);
3525
3525
  const getSelectedNode = this.getSelectedNode.bind(this);
3526
3526
  const getTree = this.getTree.bind(this);
3527
3527
  const isFocusOnTree = this.isFocusOnTree.bind(this);
3528
3528
  const loadData = this.loadData.bind(this);
3529
- const openNode = this._openNode.bind(this);
3530
- const refreshElements = this._refreshElements.bind(this);
3529
+ const openNode = this.openNodeInternal.bind(this);
3530
+ const refreshElements = this.refreshElements.bind(this);
3531
3531
  const refreshHitAreas = this.refreshHitAreas.bind(this);
3532
3532
  const selectNode = this.selectNode.bind(this);
3533
3533
  const $treeElement = this.element;
3534
- const triggerEvent = this._triggerEvent.bind(this);
3534
+ const treeElement = this.element.get(0);
3535
+ const triggerEvent = this.triggerEvent.bind(this);
3535
3536
  const selectNodeHandler = new SelectNodeHandler({
3536
3537
  getNodeById
3537
3538
  });
@@ -3539,12 +3540,13 @@ var jqtree = (function (exports) {
3539
3540
  const getSelectedNodes = selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
3540
3541
  const isNodeSelected = selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
3541
3542
  const removeFromSelection = selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
3543
+ const getMouseDelay = () => this.options.startDndDelay ?? 0;
3542
3544
  const dataLoader = new DataLoader({
3543
3545
  dataFilter,
3544
3546
  loadData,
3545
3547
  onLoadFailed,
3546
3548
  onLoading,
3547
- $treeElement,
3549
+ treeElement,
3548
3550
  triggerEvent
3549
3551
  });
3550
3552
  const saveStateHandler = new SaveStateHandler({
@@ -3559,6 +3561,11 @@ var jqtree = (function (exports) {
3559
3561
  removeFromSelection,
3560
3562
  saveState
3561
3563
  });
3564
+ const scrollHandler = new ScrollHandler({
3565
+ refreshHitAreas,
3566
+ treeElement
3567
+ });
3568
+ const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
3562
3569
  const dndHandler = new DragAndDropHandler({
3563
3570
  autoEscape,
3564
3571
  getNodeElement,
@@ -3566,6 +3573,7 @@ var jqtree = (function (exports) {
3566
3573
  getScrollLeft,
3567
3574
  getTree,
3568
3575
  onCanMove,
3576
+ onCanMoveTo,
3569
3577
  onDragMove,
3570
3578
  onDragStop,
3571
3579
  onIsMoveHandle,
@@ -3573,13 +3581,9 @@ var jqtree = (function (exports) {
3573
3581
  openNode,
3574
3582
  refreshElements,
3575
3583
  slide,
3576
- $treeElement,
3584
+ treeElement,
3577
3585
  triggerEvent
3578
3586
  });
3579
- const scrollHandler = new ScrollHandler({
3580
- refreshHitAreas,
3581
- $treeElement
3582
- });
3583
3587
  const keyHandler = new KeyHandler({
3584
3588
  closeNode,
3585
3589
  getSelectedNode,
@@ -3602,9 +3606,28 @@ var jqtree = (function (exports) {
3602
3606
  showEmptyFolder,
3603
3607
  tabIndex
3604
3608
  });
3609
+ const getNode = this.getNode.bind(this);
3610
+ const onMouseCapture = this.mouseCapture.bind(this);
3611
+ const onMouseDrag = this.mouseDrag.bind(this);
3612
+ const onMouseStart = this.mouseStart.bind(this);
3613
+ const onMouseStop = this.mouseStop.bind(this);
3614
+ const mouseHandler = new MouseHandler({
3615
+ element: treeElement,
3616
+ getMouseDelay,
3617
+ getNode,
3618
+ onClickButton: this.toggle.bind(this),
3619
+ onClickTitle: this.doSelectNode.bind(this),
3620
+ onMouseCapture,
3621
+ onMouseDrag,
3622
+ onMouseStart,
3623
+ onMouseStop,
3624
+ triggerEvent,
3625
+ useContextMenu: this.options.useContextMenu
3626
+ });
3605
3627
  this.dataLoader = dataLoader;
3606
3628
  this.dndHandler = dndHandler;
3607
3629
  this.keyHandler = keyHandler;
3630
+ this.mouseHandler = mouseHandler;
3608
3631
  this.renderer = renderer;
3609
3632
  this.saveStateHandler = saveStateHandler;
3610
3633
  this.scrollHandler = scrollHandler;
@@ -3612,11 +3635,11 @@ var jqtree = (function (exports) {
3612
3635
  }
3613
3636
  createFolderElement(node) {
3614
3637
  const closedIconElement = this.renderer.closedIconElement;
3615
- const getScrollLeft = this._getScrollLeft.bind(this);
3638
+ const getScrollLeft = this.scrollHandler.getScrollLeft.bind(this.scrollHandler);
3616
3639
  const openedIconElement = this.renderer.openedIconElement;
3617
3640
  const tabIndex = this.options.tabIndex;
3618
3641
  const $treeElement = this.element;
3619
- const triggerEvent = this._triggerEvent.bind(this);
3642
+ const triggerEvent = this.triggerEvent.bind(this);
3620
3643
  return new FolderElement({
3621
3644
  closedIconElement,
3622
3645
  getScrollLeft,
@@ -3628,7 +3651,7 @@ var jqtree = (function (exports) {
3628
3651
  });
3629
3652
  }
3630
3653
  createNodeElement(node) {
3631
- const getScrollLeft = this._getScrollLeft.bind(this);
3654
+ const getScrollLeft = this.scrollHandler.getScrollLeft.bind(this.scrollHandler);
3632
3655
  const tabIndex = this.options.tabIndex;
3633
3656
  const $treeElement = this.element;
3634
3657
  return new NodeElement({