@keenmate/svelte-treeview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -0
- package/dist/Checkbox.svelte +58 -0
- package/dist/Checkbox.svelte.d.ts +41 -0
- package/dist/TreeView.svelte +562 -0
- package/dist/TreeView.svelte.d.ts +65 -0
- package/dist/consts.d.ts +28 -0
- package/dist/consts.js +31 -0
- package/dist/helpers/drag-drop-helpers.d.ts +29 -0
- package/dist/helpers/drag-drop-helpers.js +193 -0
- package/dist/helpers/selection-helpers.d.ts +22 -0
- package/dist/helpers/selection-helpers.js +182 -0
- package/dist/helpers/tree-helper.d.ts +33 -0
- package/dist/helpers/tree-helper.js +169 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/menu/ContextMenu.svelte +33 -0
- package/dist/menu/ContextMenu.svelte.d.ts +32 -0
- package/dist/menu/Menu.svelte +50 -0
- package/dist/menu/Menu.svelte.d.ts +35 -0
- package/dist/menu/MenuDivider.svelte +10 -0
- package/dist/menu/MenuDivider.svelte.d.ts +23 -0
- package/dist/menu/MenuOption.svelte +49 -0
- package/dist/menu/MenuOption.svelte.d.ts +33 -0
- package/dist/menu/menu.d.ts +1 -0
- package/dist/menu/menu.js +3 -0
- package/package.json +63 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class DragAndDropHelper {
|
|
2
|
+
constructor(treeHelper: any);
|
|
3
|
+
helper: any;
|
|
4
|
+
props: any;
|
|
5
|
+
separator: any;
|
|
6
|
+
path(node: any): any;
|
|
7
|
+
/**
|
|
8
|
+
* moves node from one parent to another
|
|
9
|
+
* @param {Object[]} tree - tree
|
|
10
|
+
* @param {nodePath} movedNodePath - nodepath of moved(dragged) node
|
|
11
|
+
* @param {nodePath} targetNodePath - nodepath of node where it should be moved ( either bellow it in priority or as child)
|
|
12
|
+
* @param {int} insType - if true, it will insert moved node as child of target node, if false, it will insert it bellow it in priority
|
|
13
|
+
* @param {boolean} recalculateNodePath - wont recalculare id of moved node, used when last part of nodePath is always unique
|
|
14
|
+
*/
|
|
15
|
+
moveNode(tree: Object[], movedNodePath: nodePath, targetNodePath: nodePath, insType: int, recalculateNodePath: boolean): Object[] | undefined;
|
|
16
|
+
calculateNewNodePath(tree: any, parentNodePath: any, movedNodePath: any, recalculateNodePath: any): any;
|
|
17
|
+
updatePriority(tree: any, node: any, parentNodePath: any, newNodePath: any, targetNode: any, insType: any): void;
|
|
18
|
+
/** recomputes all priorities after inserted priority.F
|
|
19
|
+
* Also changes all priorities to be one apart (1,5,6 => 1,2,3)
|
|
20
|
+
*/
|
|
21
|
+
recalculatesPriorities(tree: any, parentNode: any, movedNodePath: any, insertedPriority?: number): void;
|
|
22
|
+
/** return biggest value of nodepath number that children are using +1 */
|
|
23
|
+
getNextNodeId(tree: any, parentPath: any): number;
|
|
24
|
+
getInsertionPosition(e: any, element: any): 1 | -1;
|
|
25
|
+
huminifyInsType(insType: any): "before" | "inside" | "after" | undefined;
|
|
26
|
+
/** orders nodes by priorityProp
|
|
27
|
+
*/
|
|
28
|
+
OrderByPriority(tree: any): any;
|
|
29
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
export class DragAndDropHelper {
|
|
3
|
+
constructor(treeHelper) {
|
|
4
|
+
this.helper = treeHelper;
|
|
5
|
+
this.props = treeHelper.props;
|
|
6
|
+
|
|
7
|
+
this.separator = this.helper.config.separator ?? '.';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
path(node) {
|
|
11
|
+
return this.helper.path(node);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* moves node from one parent to another
|
|
16
|
+
* @param {Object[]} tree - tree
|
|
17
|
+
* @param {nodePath} movedNodePath - nodepath of moved(dragged) node
|
|
18
|
+
* @param {nodePath} targetNodePath - nodepath of node where it should be moved ( either bellow it in priority or as child)
|
|
19
|
+
* @param {int} insType - if true, it will insert moved node as child of target node, if false, it will insert it bellow it in priority
|
|
20
|
+
* @param {boolean} recalculateNodePath - wont recalculare id of moved node, used when last part of nodePath is always unique
|
|
21
|
+
*/
|
|
22
|
+
moveNode(tree, movedNodePath, targetNodePath, insType, recalculateNodePath) {
|
|
23
|
+
const isNesting = insType == 0;
|
|
24
|
+
console.log({ tree, movedNodePath, targetNodePath, insType });
|
|
25
|
+
// if you are not isNestinging, you want to be on same level
|
|
26
|
+
//so you will have same parent as target node
|
|
27
|
+
const parentNodePath = isNesting
|
|
28
|
+
? targetNodePath
|
|
29
|
+
: this.helper.getParentNodePath(targetNodePath);
|
|
30
|
+
|
|
31
|
+
//trying to move parent to child
|
|
32
|
+
if (parentNodePath.startsWith(movedNodePath)) return;
|
|
33
|
+
|
|
34
|
+
const changedParent = this.helper.getParentNodePath(movedNodePath) !== parentNodePath;
|
|
35
|
+
|
|
36
|
+
let newNodePath = movedNodePath;
|
|
37
|
+
|
|
38
|
+
//dont create new node if you only moved inside same parent
|
|
39
|
+
if (changedParent) {
|
|
40
|
+
newNodePath = this.calculateNewNodePath(
|
|
41
|
+
tree,
|
|
42
|
+
parentNodePath,
|
|
43
|
+
movedNodePath,
|
|
44
|
+
recalculateNodePath
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//* find target node
|
|
49
|
+
|
|
50
|
+
let targetNode = this.helper.findNode(tree, targetNodePath);
|
|
51
|
+
|
|
52
|
+
let movedNode;
|
|
53
|
+
|
|
54
|
+
//move nodes
|
|
55
|
+
tree = tree.map((node) => {
|
|
56
|
+
//make sure that parent's haschild is set to true, so that children are visible
|
|
57
|
+
if (this.path(node) == parentNodePath) {
|
|
58
|
+
node[this.props.hasChildren] = true;
|
|
59
|
+
node[this.props.expanded] = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//move moved nodes to new location ( if location is being changed)
|
|
63
|
+
if (changedParent && this.path(node).startsWith(movedNodePath)) {
|
|
64
|
+
//replace old parent with new one
|
|
65
|
+
const newPath = this.path(node).replace(movedNodePath, newNodePath);
|
|
66
|
+
node[this.props.nodePath] = newPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//if it is moved node
|
|
70
|
+
if (this.path(node) === newNodePath) {
|
|
71
|
+
movedNode = node;
|
|
72
|
+
|
|
73
|
+
//? not sure if this is best
|
|
74
|
+
this.updatePriority(tree, movedNode, parentNodePath, newNodePath, targetNode, insType);
|
|
75
|
+
}
|
|
76
|
+
return node;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
//* insert node at right possition of array
|
|
80
|
+
|
|
81
|
+
let oldIndex = tree.findIndex((x) => this.path(x) == newNodePath);
|
|
82
|
+
tree.splice(oldIndex, 1);
|
|
83
|
+
|
|
84
|
+
let index = tree.findIndex((x) => this.path(x) == this.path(targetNode));
|
|
85
|
+
|
|
86
|
+
tree.splice(index + (insType == 1 ? 0 : 1), 0, movedNode);
|
|
87
|
+
|
|
88
|
+
//TODO maybe add option to setting this.hasChildren to false when moved last children
|
|
89
|
+
|
|
90
|
+
//hide plus icon if parent of moved node doesnt have any more children
|
|
91
|
+
let oldParent = this.helper.findNode(tree, this.helper.getParentNodePath(movedNodePath));
|
|
92
|
+
|
|
93
|
+
//moved last node
|
|
94
|
+
const oldParentHasChildren = this.helper.allCHildren(tree, this.path(oldParent)).length;
|
|
95
|
+
if (oldParent && !oldParentHasChildren) {
|
|
96
|
+
oldParent[this.props.hasChildren] = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return tree;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
calculateNewNodePath(tree, parentNodePath, movedNodePath, recalculateNodePath) {
|
|
103
|
+
//node id is last part of nodePath
|
|
104
|
+
let nodeId;
|
|
105
|
+
if (recalculateNodePath) {
|
|
106
|
+
nodeId = this.getNextNodeId(tree, parentNodePath);
|
|
107
|
+
} else {
|
|
108
|
+
//get last segment of path
|
|
109
|
+
nodeId = movedNodePath.split(this.separator).slice(-1)[0];
|
|
110
|
+
}
|
|
111
|
+
return (parentNodePath ? parentNodePath + this.separator : '') + nodeId;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
updatePriority(tree, node, parentNodePath, newNodePath, targetNode, insType) {
|
|
115
|
+
const isNesting = insType == 0;
|
|
116
|
+
if (isNesting || targetNode[this.props.priority] != null) {
|
|
117
|
+
let newpriority = 0;
|
|
118
|
+
if (!isNesting) {
|
|
119
|
+
//calculate next
|
|
120
|
+
newpriority = targetNode[this.props.priority] ?? 0;
|
|
121
|
+
if (insType == -1) {
|
|
122
|
+
newpriority += 1;
|
|
123
|
+
} else {
|
|
124
|
+
//targetNode[this.props.priority] -= 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.recalculatesPriorities(tree, parentNodePath, newNodePath, newpriority);
|
|
128
|
+
|
|
129
|
+
node[this.props.priority] = newpriority;
|
|
130
|
+
} else {
|
|
131
|
+
//so old priority doesnt mess up orderring
|
|
132
|
+
node[this.props.priority] = undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** recomputes all priorities after inserted priority.F
|
|
137
|
+
* Also changes all priorities to be one apart (1,5,6 => 1,2,3)
|
|
138
|
+
*/
|
|
139
|
+
//? maybe it will recalculate properly if dont set insertedPriority
|
|
140
|
+
recalculatesPriorities(tree, parentNode, movedNodePath, insertedPriority = -1) {
|
|
141
|
+
let nextPriority = insertedPriority + 1;
|
|
142
|
+
this.OrderByPriority(this.helper.allCHildren(tree, parentNode)).forEach((node) => {
|
|
143
|
+
if (node[this.props.priority] >= insertedPriority && this.path(node) != movedNodePath) {
|
|
144
|
+
node[this.props.priority] = nextPriority++;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** return biggest value of nodepath number that children are using +1 */
|
|
150
|
+
getNextNodeId(tree, parentPath) {
|
|
151
|
+
let max = 0;
|
|
152
|
+
//findes biggest nodeNumber for
|
|
153
|
+
this.helper.allCHildren(tree, parentPath).forEach((node) => {
|
|
154
|
+
const parent = this.helper.getParentNodePath(this.path(node));
|
|
155
|
+
|
|
156
|
+
if (parent === parentPath) {
|
|
157
|
+
let num = this.path(node).substring(parent ? parent.length + 1 : 0);
|
|
158
|
+
max = Math.max(max, num);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return parseInt(max) + 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
getInsertionPosition(e, element) {
|
|
165
|
+
let targetCords = element.getBoundingClientRect();
|
|
166
|
+
let half = targetCords.bottom - targetCords.height / 2;
|
|
167
|
+
|
|
168
|
+
if (e.y < half) {
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
huminifyInsType(insType) {
|
|
175
|
+
switch (insType) {
|
|
176
|
+
case 1:
|
|
177
|
+
return 'before';
|
|
178
|
+
case 0:
|
|
179
|
+
return 'inside';
|
|
180
|
+
case -1:
|
|
181
|
+
return 'after';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** orders nodes by priorityProp
|
|
186
|
+
*/
|
|
187
|
+
OrderByPriority(tree) {
|
|
188
|
+
tree.sort((a, b) => {
|
|
189
|
+
!!b[this.props.priority] ? a[this.props.priority] > b[this.props.priority] : true;
|
|
190
|
+
});
|
|
191
|
+
return tree;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class SelectionHelper {
|
|
2
|
+
constructor(treeHelper: any);
|
|
3
|
+
helper: any;
|
|
4
|
+
props: any;
|
|
5
|
+
path(node: any): any;
|
|
6
|
+
selected(node: any): any;
|
|
7
|
+
getChildrenWithCheckboxes(tree: any, parentNodePath: any): any;
|
|
8
|
+
changeSelection(tree: any, nodePath: any, filteredTree: any): any;
|
|
9
|
+
toggleSelected(tree: any, nodePath: any): void;
|
|
10
|
+
changeSelectedForChildren(tree: any, parentNodePath: any, changeTo: any, filteredTree: any): any;
|
|
11
|
+
updateLeafNodeSelection(node: any, changeTo: any): void;
|
|
12
|
+
/**Calculates visual state based on children */
|
|
13
|
+
getVisualState(filteredTree: any, node: any): string;
|
|
14
|
+
/** recursibly recomputes parent visual state until root */
|
|
15
|
+
recomputeAllParentVisualState(tree: any, filteredTree: any, nodePath: any): any;
|
|
16
|
+
/** Computes visual states for all nodes. Used for computing initial visual states when tree changes */
|
|
17
|
+
computeInitialVisualStates(tree: any, filteredTree: any): any;
|
|
18
|
+
/** Recursivly computes visual state for children */
|
|
19
|
+
computeChildrenVisualStates(tree: any, filteredTree: any, node: any): any;
|
|
20
|
+
deleteSelected(tree: any): any;
|
|
21
|
+
isSelectable(node: any, showCheckboxes: any): boolean;
|
|
22
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { checkboxesType, visualStateType } from "../consts";
|
|
3
|
+
|
|
4
|
+
export class SelectionHelper {
|
|
5
|
+
constructor(treeHelper) {
|
|
6
|
+
this.helper = treeHelper;
|
|
7
|
+
this.props = treeHelper.props;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
path(node) {
|
|
11
|
+
return this.helper.path(node);
|
|
12
|
+
}
|
|
13
|
+
selected(node) {
|
|
14
|
+
return node[this.props.selected];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getChildrenWithCheckboxes(tree, parentNodePath) {
|
|
18
|
+
return this.helper
|
|
19
|
+
.getDirectChildren(tree, parentNodePath)
|
|
20
|
+
.filter((node) => this.isSelectable(node, checkboxesType.all));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
changeSelection(tree, nodePath, filteredTree) {
|
|
24
|
+
this.toggleSelected(tree, nodePath);
|
|
25
|
+
|
|
26
|
+
const recursive = this.helper.config?.recursive ?? false;
|
|
27
|
+
|
|
28
|
+
if (recursive) {
|
|
29
|
+
tree = this.recomputeAllParentVisualState(tree, filteredTree, nodePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return tree;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
toggleSelected(tree, nodePath) {
|
|
36
|
+
const node = this.helper.findNode(tree, nodePath);
|
|
37
|
+
|
|
38
|
+
node[this.props.selected] = !node[this.props.selected];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
changeSelectedForChildren(tree, parentNodePath, changeTo, filteredTree) {
|
|
42
|
+
tree = tree.map((node) => {
|
|
43
|
+
//changes itself
|
|
44
|
+
if (parentNodePath == this.path(node)) {
|
|
45
|
+
this.updateLeafNodeSelection(node, changeTo);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!parentNodePath) {
|
|
49
|
+
// TODO i think this isnt working
|
|
50
|
+
// this only updates
|
|
51
|
+
|
|
52
|
+
//top level
|
|
53
|
+
this.updateLeafNodeSelection(node, changeTo);
|
|
54
|
+
} else {
|
|
55
|
+
//if parentNodePath isnt root
|
|
56
|
+
if (
|
|
57
|
+
this.path(node).startsWith(parentNodePath.toString()) &&
|
|
58
|
+
this.path(node) != parentNodePath.toString()
|
|
59
|
+
) {
|
|
60
|
+
this.updateLeafNodeSelection(node, changeTo);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return node;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
tree = this.recomputeAllParentVisualState(
|
|
67
|
+
tree,
|
|
68
|
+
filteredTree,
|
|
69
|
+
parentNodePath
|
|
70
|
+
);
|
|
71
|
+
return tree;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//changes selected or visual state depending on
|
|
75
|
+
updateLeafNodeSelection(node, changeTo) {
|
|
76
|
+
//dont change if not selectable
|
|
77
|
+
if (!this.isSelectable(node, checkboxesType.all)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!node[this.props.hasChildren]) {
|
|
81
|
+
node[this.props.selected] = changeTo;
|
|
82
|
+
} else {
|
|
83
|
+
node.__visual_state = changeTo.toString();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**Calculates visual state based on children */
|
|
88
|
+
getVisualState(filteredTree, node) {
|
|
89
|
+
const children = this.getChildrenWithCheckboxes(
|
|
90
|
+
filteredTree,
|
|
91
|
+
this.path(node)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!children || children?.length == 0) return visualStateType.notSelected;
|
|
95
|
+
|
|
96
|
+
//if every child is selected or vs=true return true
|
|
97
|
+
if (
|
|
98
|
+
children.every(
|
|
99
|
+
(x) =>
|
|
100
|
+
x[this.props.selected] === true ||
|
|
101
|
+
x.__visual_state === visualStateType.selected
|
|
102
|
+
)
|
|
103
|
+
) {
|
|
104
|
+
return visualStateType.selected;
|
|
105
|
+
}
|
|
106
|
+
//at least sone child is selected or indeterminate
|
|
107
|
+
else if (
|
|
108
|
+
children.some((x) => {
|
|
109
|
+
return (
|
|
110
|
+
x[this.props.selected] === true ||
|
|
111
|
+
x.__visual_state === visualStateType.indeterminate ||
|
|
112
|
+
x.__visual_state === visualStateType.selected
|
|
113
|
+
);
|
|
114
|
+
})
|
|
115
|
+
) {
|
|
116
|
+
return visualStateType.indeterminate;
|
|
117
|
+
} else {
|
|
118
|
+
return visualStateType.notSelected;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** recursibly recomputes parent visual state until root */
|
|
123
|
+
recomputeAllParentVisualState(tree, filteredTree, nodePath) {
|
|
124
|
+
const parent = this.helper.getParentNodePath(nodePath);
|
|
125
|
+
|
|
126
|
+
let newstate;
|
|
127
|
+
filteredTree.forEach((x) => {
|
|
128
|
+
if (this.path(x) == parent) {
|
|
129
|
+
newstate = this.getVisualState(filteredTree, x);
|
|
130
|
+
x.__visual_state = newstate;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
if (this.helper.getParentNodePath(parent) != "") {
|
|
134
|
+
tree = this.recomputeAllParentVisualState(tree, filteredTree, parent);
|
|
135
|
+
}
|
|
136
|
+
return tree;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Computes visual states for all nodes. Used for computing initial visual states when tree changes */
|
|
140
|
+
computeInitialVisualStates(tree, filteredTree) {
|
|
141
|
+
let rootELements = this.getChildrenWithCheckboxes(tree, null);
|
|
142
|
+
rootELements.forEach((x) => {
|
|
143
|
+
if (x[this.props.hasChildren] == true) {
|
|
144
|
+
tree = this.computeChildrenVisualStates(tree, filteredTree, x);
|
|
145
|
+
x.__visual_state = this.getVisualState(filteredTree, x);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return tree;
|
|
149
|
+
}
|
|
150
|
+
/** Recursivly computes visual state for children */
|
|
151
|
+
computeChildrenVisualStates(tree, filteredTree, node) {
|
|
152
|
+
let children = this.getChildrenWithCheckboxes(tree, this.path(node));
|
|
153
|
+
//foreaches all children if it has children, it calls itself, then it computes __vs => will compute from childern to parent
|
|
154
|
+
children.forEach((x) => {
|
|
155
|
+
if (x[this.props.hasChildren] === true) {
|
|
156
|
+
tree = this.computeChildrenVisualStates(tree, filteredTree, x);
|
|
157
|
+
x.__visual_state = this.getVisualState(filteredTree, x);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return tree;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
deleteSelected(tree) {
|
|
164
|
+
return tree.map((node) => {
|
|
165
|
+
node[this.props.selected] = false;
|
|
166
|
+
node.__visual_state = undefined;
|
|
167
|
+
return node;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
isSelectable(node, showCheckboxes) {
|
|
172
|
+
if (showCheckboxes == checkboxesType.all) {
|
|
173
|
+
return node[this.props.checkbox] !== false;
|
|
174
|
+
}
|
|
175
|
+
//show only if pop is true
|
|
176
|
+
if (showCheckboxes == checkboxesType.perNode) {
|
|
177
|
+
return node[this.props.checkbox] === true;
|
|
178
|
+
}
|
|
179
|
+
//dont show at all
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class TreeHelper {
|
|
2
|
+
constructor(propNames: any, config?: {});
|
|
3
|
+
props: any;
|
|
4
|
+
config: {};
|
|
5
|
+
selection: SelectionHelper;
|
|
6
|
+
dragDrop: DragAndDropHelper;
|
|
7
|
+
path(node: any): any;
|
|
8
|
+
getParentNodePath(nodePath: any): any;
|
|
9
|
+
hasChildren(tree: any, nodePath: any): any;
|
|
10
|
+
findNode(tree: any, nodePath: any): any;
|
|
11
|
+
nodePathIsChild(nodePath: any): any;
|
|
12
|
+
getDirectChildren(tree: any, parentNodePath: any): any;
|
|
13
|
+
allCHildren(tree: any, parentNodePath: any): any;
|
|
14
|
+
getAllLeafNodes(tree: any): any;
|
|
15
|
+
joinTrees(filteredTree: any, tree: any): any;
|
|
16
|
+
mergeTrees(oldTree: any, addedTree: any, nodePath?: string): any;
|
|
17
|
+
/** toggles expansion on
|
|
18
|
+
*/
|
|
19
|
+
changeExpansion(tree: any, node: any, changeTo: any): void;
|
|
20
|
+
/** changes expansion of every node that has this.hasChildren set to true
|
|
21
|
+
*/
|
|
22
|
+
changeEveryExpansion(tree: any, changeTo: any): any;
|
|
23
|
+
/** changes expansion of every node that has this.hasChildren set to true if they are abose set level and expansion property isnt set
|
|
24
|
+
*/
|
|
25
|
+
expandToLevel(tree: any, level: any): any;
|
|
26
|
+
getDepthLevel(nodePath: any): number;
|
|
27
|
+
searchTree(tree: any, filter: any, leafesOnly: any): any;
|
|
28
|
+
getParents(tree: any, node: any): any;
|
|
29
|
+
}
|
|
30
|
+
declare function _default(...args: any[]): TreeHelper;
|
|
31
|
+
export default _default;
|
|
32
|
+
import { SelectionHelper } from './selection-helpers';
|
|
33
|
+
import { DragAndDropHelper } from './drag-drop-helpers';
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import orderBy from 'lodash.unionby'; // used by tree merge
|
|
3
|
+
import unique from 'lodash.uniq'; // used by tree merge
|
|
4
|
+
import { SelectionHelper } from './selection-helpers';
|
|
5
|
+
import { DragAndDropHelper } from './drag-drop-helpers';
|
|
6
|
+
import { defaultPropNames } from '../consts.js';
|
|
7
|
+
|
|
8
|
+
export class TreeHelper {
|
|
9
|
+
constructor(propNames, config = {}) {
|
|
10
|
+
this.props = { ...defaultPropNames, ...propNames };
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.selection = new SelectionHelper(this);
|
|
13
|
+
this.dragDrop = new DragAndDropHelper(this);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
path(node) {
|
|
17
|
+
return node?.[this.props.nodePath];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#region basic helpres
|
|
21
|
+
|
|
22
|
+
getParentNodePath(nodePath) {
|
|
23
|
+
const separator = this.config.separator ?? '.';
|
|
24
|
+
const parentPath = nodePath?.substring(0, nodePath.lastIndexOf(separator));
|
|
25
|
+
return parentPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
hasChildren(tree, nodePath) {
|
|
29
|
+
return tree?.find((x) => this.getParentNodePath(this.path(x)) === nodePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
findNode(tree, nodePath) {
|
|
33
|
+
return tree.find((node) => this.path(node) === nodePath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
nodePathIsChild(nodePath) {
|
|
37
|
+
const separator = this.config.separator ?? '.';
|
|
38
|
+
|
|
39
|
+
const includesSeparator = nodePath?.includes(separator);
|
|
40
|
+
return includesSeparator;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getDirectChildren(tree, parentNodePath) {
|
|
44
|
+
const children = (tree || []).filter((x) =>
|
|
45
|
+
!parentNodePath
|
|
46
|
+
? !this.nodePathIsChild(this.path(x))
|
|
47
|
+
: this.getParentNodePath(this.path(x)) === parentNodePath
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return children;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
allCHildren(tree, parentNodePath) {
|
|
54
|
+
let children;
|
|
55
|
+
children = tree.filter((x) => {
|
|
56
|
+
if (!parentNodePath) {
|
|
57
|
+
//top level
|
|
58
|
+
return !this.nodePathIsChild(this.path(x));
|
|
59
|
+
} else {
|
|
60
|
+
return this.path(x).startsWith(parentNodePath.toString()) && this.path(x) != parentNodePath;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return children;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getAllLeafNodes(tree) {
|
|
67
|
+
return tree.filter((x) => {
|
|
68
|
+
return x[this.props.hasChildren] == undefined || x[this.props.hasChildren] == false;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
joinTrees(filteredTree, tree) {
|
|
73
|
+
return tree.map((tnode) => this.findNode(filteredTree, this.path(tnode)) || tnode);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mergeTrees(oldTree, addedTree, nodePath = 'nodePath') {
|
|
77
|
+
return orderBy(addedTree, oldTree, nodePath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** toggles expansion on
|
|
81
|
+
*/
|
|
82
|
+
changeExpansion(tree, node, changeTo) {
|
|
83
|
+
let foundNode = this.findNode(tree, this.path(node));
|
|
84
|
+
|
|
85
|
+
foundNode[this.props.expanded] = changeTo;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** changes expansion of every node that has this.hasChildren set to true
|
|
89
|
+
*/
|
|
90
|
+
changeEveryExpansion(tree, changeTo) {
|
|
91
|
+
return tree.map((node) => {
|
|
92
|
+
if (node[this.props.hasChildren] == true) node[this.props.expanded] = changeTo;
|
|
93
|
+
return node;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** changes expansion of every node that has this.hasChildren set to true if they are abose set level and expansion property isnt set
|
|
98
|
+
*/
|
|
99
|
+
expandToLevel(tree, level) {
|
|
100
|
+
return tree.map((n) => {
|
|
101
|
+
if (
|
|
102
|
+
n[this.props.expanded] == undefined &&
|
|
103
|
+
n[this.props.expanded] == null &&
|
|
104
|
+
n[this.props.useCallback] != true &&
|
|
105
|
+
this.getDepthLevel(this.path(n)) <= level
|
|
106
|
+
) {
|
|
107
|
+
n[this.props.expanded] = true;
|
|
108
|
+
}
|
|
109
|
+
return n;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//based on number of dots
|
|
114
|
+
getDepthLevel(nodePath) {
|
|
115
|
+
const separator = this.config.separator ?? '.';
|
|
116
|
+
return nodePath.split(separator).length - 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
|
|
121
|
+
searchTree(tree, filter, leafesOnly) {
|
|
122
|
+
let filteredNodes;
|
|
123
|
+
if (leafesOnly) {
|
|
124
|
+
filteredNodes = this.getAllLeafNodes(tree).filter(filter);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(tree);
|
|
127
|
+
filteredNodes = tree.filter(filter);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const resultNodes = [];
|
|
131
|
+
|
|
132
|
+
//console.log("matching nodes length:" + matchingPathes.length)
|
|
133
|
+
filteredNodes.forEach((node) => {
|
|
134
|
+
resultNodes.push(node);
|
|
135
|
+
|
|
136
|
+
const parentNodes = this.getParents(tree, node);
|
|
137
|
+
resultNodes.push(...parentNodes);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const uniqueNodes = unique(resultNodes, (node) => this.path(node));
|
|
141
|
+
|
|
142
|
+
return uniqueNodes;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getParents(tree, node) {
|
|
146
|
+
const parentsPaths = [];
|
|
147
|
+
|
|
148
|
+
let nodePath = this.path(node);
|
|
149
|
+
|
|
150
|
+
// get all parents
|
|
151
|
+
while (nodePath.length > 0) {
|
|
152
|
+
nodePath = this.getParentNodePath(nodePath);
|
|
153
|
+
parentsPaths.push(nodePath);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//find nodes for given ids
|
|
157
|
+
const parentNodes = tree.filter((n) =>
|
|
158
|
+
parentsPaths.some((parentNodePath) => this.path(n) === parentNodePath)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return parentNodes;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default (...args) => {
|
|
166
|
+
// TODO redundant now, maybe remove
|
|
167
|
+
let helper = new TreeHelper(...args);
|
|
168
|
+
return helper;
|
|
169
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as TreeView } from "./TreeView.svelte";
|
|
2
|
+
export { default as MenuDivider } from "./menu/MenuDivider.svelte";
|
|
3
|
+
export { default as MenuOption } from "./menu/MenuOption.svelte";
|
|
4
|
+
export { TreeHelper } from "./helpers/tree-helper.js";
|
|
5
|
+
export * as Constants from "./consts.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as TreeView } from './TreeView.svelte';
|
|
2
|
+
export { default as MenuDivider } from './menu/MenuDivider.svelte';
|
|
3
|
+
export { default as MenuOption } from './menu/MenuOption.svelte';
|
|
4
|
+
export { TreeHelper } from './helpers/tree-helper.js';
|
|
5
|
+
export * as Constants from './consts.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import Menu from './Menu.svelte';
|
|
5
|
+
|
|
6
|
+
let pos = { x: 0, y: 0 };
|
|
7
|
+
let showMenu = false;
|
|
8
|
+
let node = null;
|
|
9
|
+
|
|
10
|
+
export async function onRightClick(e, n) {
|
|
11
|
+
if (showMenu) {
|
|
12
|
+
showMenu = false;
|
|
13
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
14
|
+
}
|
|
15
|
+
node = n;
|
|
16
|
+
pos = { x: e.clientX, y: e.clientY };
|
|
17
|
+
showMenu = true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function closeMenu() {
|
|
21
|
+
showMenu = false;
|
|
22
|
+
node = null;
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<svelte:window on:click={closeMenu} />
|
|
27
|
+
{#if showMenu}
|
|
28
|
+
<Menu {...pos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
|
29
|
+
<slot {node}>
|
|
30
|
+
<b> context menu openned from: {node?.nodePath}</b>
|
|
31
|
+
</slot>
|
|
32
|
+
</Menu>
|
|
33
|
+
{/if}
|