jqtree 1.8.0 → 1.8.2

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