@keenmate/svelte-treeview 1.0.0-beta.1 → 1.0.0-beta.3
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 +9 -9
- package/dist/Branch.svelte +58 -76
- package/dist/Branch.svelte.d.ts +7 -7
- package/dist/Checkbox.svelte +5 -2
- package/dist/TreeView.svelte +114 -34
- package/dist/TreeView.svelte.d.ts +17 -16
- package/dist/constants.d.ts +1 -2
- package/dist/constants.js +5 -7
- package/dist/helpers/tree-helper.d.ts +10 -10
- package/dist/helpers/tree-helper.js +17 -11
- package/dist/menu/Menu.svelte +0 -1
- package/dist/menu/MenuDivider.svelte +0 -1
- package/dist/menu/MenuOption.svelte +0 -1
- package/dist/providers/drag-drop-provider.d.ts +9 -0
- package/dist/providers/drag-drop-provider.js +33 -186
- package/dist/providers/selection-provider.d.ts +2 -2
- package/dist/providers/selection-provider.js +1 -1
- package/dist/tree-styles.sass +5 -5
- package/dist/types.d.ts +12 -17
- package/dist/types.js +3 -9
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,34 +5,34 @@ The most elaborate treeview for svelte on earth (or even in our galaxy).
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- load new nodes whne expanding
|
|
8
|
-
- automatically expanding to given depth
|
|
9
8
|
- customization of all object properties
|
|
10
9
|
- checkboxes enabled on whole tree or based on property
|
|
11
10
|
- recursive seletion mode, where leafes can be selected
|
|
12
11
|
- build-in support for search
|
|
12
|
+
- drag and drop functionality controlable per node
|
|
13
|
+
- context menu
|
|
13
14
|
|
|
14
15
|
## Instalation
|
|
15
16
|
|
|
16
17
|
install the package `@keenmate/svelte-treeview` using your favourite package manager.
|
|
17
18
|
|
|
18
|
-
Font awesome is required for expand/collapse icons
|
|
19
|
+
**Font awesome is required for expand/collapse icons.**
|
|
19
20
|
|
|
20
21
|
## Minimal usage
|
|
21
22
|
|
|
22
|
-
|
|
23
23
|
```svelte
|
|
24
24
|
<script lang="ts">
|
|
25
|
-
|
|
25
|
+
import { TreeView } from '$lib/index.js';
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
let tree = [
|
|
28
|
+
{ nodePath: 'animals', title: 'Animals', hasChildren: true },
|
|
29
29
|
//...
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
{ nodePath: 'animals.insects.butterflies', title: 'Butterflies' }
|
|
31
|
+
];
|
|
32
32
|
</script>
|
|
33
33
|
|
|
34
34
|
<TreeView {tree} treeId="my-tree" let:node>
|
|
35
|
-
|
|
35
|
+
{node.title}
|
|
36
36
|
</TreeView>
|
|
37
37
|
|
|
38
38
|
```
|
package/dist/Branch.svelte
CHANGED
|
@@ -14,13 +14,11 @@ export let readonly;
|
|
|
14
14
|
export let expandTo;
|
|
15
15
|
export let classes;
|
|
16
16
|
export let helper;
|
|
17
|
-
export let draggedPath;
|
|
18
|
-
export let highlightedNode;
|
|
19
17
|
export let childDepth;
|
|
20
18
|
export let branchRootNode;
|
|
21
|
-
export let
|
|
22
|
-
export let
|
|
23
|
-
export let
|
|
19
|
+
export let draggedNode;
|
|
20
|
+
export let highlightedNode;
|
|
21
|
+
export let insertionType;
|
|
24
22
|
const getNodeId = (node) => `${treeId}-${node.path}`;
|
|
25
23
|
function setExpansion(node, changeTo) {
|
|
26
24
|
dispatch('internal-expand', { node: node, changeTo });
|
|
@@ -42,10 +40,10 @@ function handleDragStart(e, node) {
|
|
|
42
40
|
dispatch('internal-handleDragStart', { node: node, e: e });
|
|
43
41
|
}
|
|
44
42
|
function handleDragDrop(e, node, el) {
|
|
45
|
-
dispatch('internal-
|
|
43
|
+
dispatch('internal-handleDragDrop', { node: node, event: e, element: el });
|
|
46
44
|
}
|
|
47
|
-
function handleDragOver(e, node, el) {
|
|
48
|
-
dispatch('internal-handleDragOver', { node: node, event: e, element: el });
|
|
45
|
+
function handleDragOver(e, node, el, nest) {
|
|
46
|
+
dispatch('internal-handleDragOver', { node: node, event: e, element: el, nest });
|
|
49
47
|
}
|
|
50
48
|
function handleDragEnter(e, node, el) {
|
|
51
49
|
dispatch('internal-handleDragEnter', { node: node, event: e, element: el });
|
|
@@ -56,36 +54,13 @@ function handleDragEnd(e, node) {
|
|
|
56
54
|
function handleDragLeave(e, node, el) {
|
|
57
55
|
dispatch('internal-handleDragLeave', { node: node, event: e, element: el });
|
|
58
56
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* returns true, it should highlight nesting on this node
|
|
67
|
-
* @param node node
|
|
68
|
-
* @param highlitedNode highlited node
|
|
69
|
-
* @param validTarget valid target
|
|
70
|
-
* @param canNest can nest
|
|
71
|
-
*/
|
|
72
|
-
function highlightNesting(node, highlitedNode, validTarget, canNest) {
|
|
73
|
-
if (!highlitedNode)
|
|
74
|
-
return false;
|
|
75
|
-
return (canNest && highlighThisNode(node, highlitedNode, validTarget) && node.nestDisabled !== true);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* returns true, it should highlight nesting on this node
|
|
79
|
-
* @param node node
|
|
80
|
-
* @param highlitedNode highlited node
|
|
81
|
-
* @param validTarget valid target
|
|
82
|
-
* @param canNest can nest
|
|
83
|
-
*/
|
|
84
|
-
function highlightInsert(node, highlitedNode, validTarget, canNest) {
|
|
85
|
-
if (!highlitedNode)
|
|
86
|
-
return false;
|
|
87
|
-
return (!canNest && highlighThisNode(node, highlitedNode, validTarget) && node.nestDisabled !== true);
|
|
57
|
+
function getHighlighMode(node, highlightedNode, insertionType) {
|
|
58
|
+
// return InsertionType.insertAbove;
|
|
59
|
+
if (highlightedNode?.path !== node.path)
|
|
60
|
+
return InsertionType.none;
|
|
61
|
+
return insertionType;
|
|
88
62
|
}
|
|
63
|
+
// TODO maybe this can be removed?
|
|
89
64
|
let liElements = {};
|
|
90
65
|
</script>
|
|
91
66
|
|
|
@@ -95,27 +70,24 @@ let liElements = {};
|
|
|
95
70
|
class={childDepth === 0 ? classes.treeClass : ''}
|
|
96
71
|
>
|
|
97
72
|
{#each helper.getDirectChildren(tree, branchRootNode?.path ?? null) as node (getNodeId(node))}
|
|
98
|
-
{@const nesthighlighed = highlightNesting(node, highlightedNode, validTarget, canNest)}
|
|
99
|
-
{@const insertHighlighted = highlightInsert(node, highlightedNode, validTarget, canNest)}
|
|
100
73
|
{@const expanded = isExpanded(node, childDepth, expandTo)}
|
|
101
|
-
{@const
|
|
102
|
-
{@const
|
|
103
|
-
{@const
|
|
104
|
-
draggedPath == node.path || (draggedPath && node.path?.startsWith(draggedPath))}
|
|
105
|
-
|
|
74
|
+
{@const draggable = !readonly && dragAndDrop && !node.dragDisabled}
|
|
75
|
+
{@const isCurrentlyDragged = draggedNode && node.path.startsWith(draggedNode?.path)}
|
|
76
|
+
{@const effectiveHighlight = getHighlighMode(node, highlightedNode, insertionType)}
|
|
106
77
|
<li
|
|
107
78
|
class:is-child={helper.nodePathIsChild(node.path)}
|
|
108
|
-
class:has-children={hasChildren}
|
|
79
|
+
class:has-children={node.hasChildren}
|
|
109
80
|
on:contextmenu|stopPropagation={(e) => {
|
|
110
81
|
dispatch('open-ctxmenu', { e: e, node: Node });
|
|
111
82
|
}}
|
|
112
83
|
on:drop|stopPropagation={(e) => handleDragDrop(e, node, liElements[getNodeId(node)])}
|
|
113
|
-
on:dragover|stopPropagation={(e) =>
|
|
84
|
+
on:dragover|stopPropagation={(e) =>
|
|
85
|
+
handleDragOver(e, node, liElements[getNodeId(node)], false)}
|
|
114
86
|
on:dragenter|stopPropagation={(e) => handleDragEnter(e, node, liElements[getNodeId(node)])}
|
|
115
87
|
on:dragleave|stopPropagation={(e) => handleDragLeave(e, node, liElements[getNodeId(node)])}
|
|
116
88
|
bind:this={liElements[getNodeId(node)]}
|
|
117
89
|
>
|
|
118
|
-
{#if
|
|
90
|
+
{#if effectiveHighlight == InsertionType.insertAbove}
|
|
119
91
|
<div class="insert-line-wrapper">
|
|
120
92
|
<div class="insert-line {classes.inserLineClass}" />
|
|
121
93
|
</div>
|
|
@@ -124,26 +96,24 @@ let liElements = {};
|
|
|
124
96
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
125
97
|
<div
|
|
126
98
|
class="tree-item
|
|
127
|
-
{
|
|
99
|
+
{effectiveHighlight === InsertionType.nest ? classes.expandClass : ''}
|
|
128
100
|
{classes.nodeClass} {isCurrentlyDragged ? classes.currentlyDraggedClass : ''}"
|
|
129
|
-
class:div-has-children={hasChildren}
|
|
130
|
-
class:hover={
|
|
101
|
+
class:div-has-children={node.hasChildren}
|
|
102
|
+
class:hover={effectiveHighlight !== InsertionType.none}
|
|
131
103
|
{draggable}
|
|
132
104
|
on:dragstart={(e) => handleDragStart(e, node)}
|
|
133
105
|
on:dragend={(e) => handleDragEnd(e, node)}
|
|
134
106
|
>
|
|
135
|
-
{#if hasChildren}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/>
|
|
144
|
-
</span>
|
|
107
|
+
{#if node.hasChildren}
|
|
108
|
+
<button
|
|
109
|
+
class="expansion-button"
|
|
110
|
+
on:click={() => setExpansion(node, !expanded)}
|
|
111
|
+
type="button"
|
|
112
|
+
>
|
|
113
|
+
<i class="fa-fw {expanded ? classes.collapseIcon : classes.expandIcon}" />
|
|
114
|
+
</button>
|
|
145
115
|
{:else}
|
|
146
|
-
<span />
|
|
116
|
+
<span class="fa-fw" />
|
|
147
117
|
{/if}
|
|
148
118
|
|
|
149
119
|
<Checkbox
|
|
@@ -158,16 +128,21 @@ let liElements = {};
|
|
|
158
128
|
<span class:pointer-cursor={draggable}>
|
|
159
129
|
<slot node={node.originalNode} />
|
|
160
130
|
</span>
|
|
161
|
-
</div>
|
|
162
131
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
132
|
+
{#if dragAndDrop && node.nestAllowed}
|
|
133
|
+
<span
|
|
134
|
+
on:dragover|stopPropagation={(e) =>
|
|
135
|
+
handleDragOver(e, node, liElements[getNodeId(node)], true)}
|
|
136
|
+
>
|
|
137
|
+
<i class="fa-fw {classes.nestIcon}" />
|
|
138
|
+
|
|
139
|
+
{#if effectiveHighlight === InsertionType.nest}
|
|
140
|
+
<slot name="nest-highlight" />
|
|
141
|
+
{/if}
|
|
142
|
+
</span>
|
|
143
|
+
{/if}
|
|
144
|
+
</div>
|
|
145
|
+
{#if expanded && node.hasChildren}
|
|
171
146
|
<svelte:self
|
|
172
147
|
branchRootNode={node}
|
|
173
148
|
childDepth={childDepth + 1}
|
|
@@ -181,26 +156,33 @@ let liElements = {};
|
|
|
181
156
|
{onlyLeafCheckboxes}
|
|
182
157
|
{hideDisabledCheckboxes}
|
|
183
158
|
{expandTo}
|
|
184
|
-
{draggedPath}
|
|
185
159
|
{dragAndDrop}
|
|
186
160
|
{verticalLines}
|
|
187
|
-
{
|
|
188
|
-
{insPos}
|
|
189
|
-
{validTarget}
|
|
161
|
+
{draggedNode}
|
|
190
162
|
{highlightedNode}
|
|
163
|
+
{insertionType}
|
|
191
164
|
on:open-ctxmenu
|
|
192
165
|
on:internal-expand
|
|
193
166
|
on:internal-selectionChanged
|
|
167
|
+
on:internal-handleDragStart
|
|
168
|
+
on:internal-handleDragDrop
|
|
169
|
+
on:internal-handleDragOver
|
|
170
|
+
on:internal-handleDragEnter
|
|
171
|
+
on:internal-handleDragEnd
|
|
172
|
+
on:internal-handleDragLeave
|
|
194
173
|
let:node={nodeNested}
|
|
195
174
|
>
|
|
196
175
|
<slot node={nodeNested} />
|
|
176
|
+
<svelte:fragment slot="nest-highlight">
|
|
177
|
+
<slot name="nest-highlight" />
|
|
178
|
+
</svelte:fragment>
|
|
197
179
|
</svelte:self>
|
|
198
180
|
{/if}
|
|
199
|
-
{#if !expanded && hasChildren}
|
|
181
|
+
{#if !expanded && node.hasChildren}
|
|
200
182
|
<ul class:child-menu={childDepth > 0} />
|
|
201
183
|
{/if}
|
|
202
184
|
<!-- Show line if insering -->
|
|
203
|
-
{#if
|
|
185
|
+
{#if effectiveHighlight === InsertionType.insertBelow}
|
|
204
186
|
<div class="insert-line-wrapper">
|
|
205
187
|
<div class="insert-line {classes.inserLineClass}" />
|
|
206
188
|
</div>
|
package/dist/Branch.svelte.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
|
-
import { SelectionModes, InsertionType, type Node } from './types.js';
|
|
3
|
-
import type {
|
|
2
|
+
import { SelectionModes, InsertionType, type Node, type CustomizableClasses } from './types.js';
|
|
3
|
+
import type { TreeHelper } from './helpers/tree-helper.js';
|
|
4
4
|
declare const __propDef: {
|
|
5
5
|
props: {
|
|
6
6
|
tree: Node[];
|
|
@@ -15,19 +15,18 @@ declare const __propDef: {
|
|
|
15
15
|
expandTo: number;
|
|
16
16
|
classes: CustomizableClasses;
|
|
17
17
|
helper: TreeHelper;
|
|
18
|
-
draggedPath: string | null;
|
|
19
|
-
highlightedNode: Node | null;
|
|
20
18
|
childDepth: number;
|
|
21
19
|
branchRootNode: Node | null;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
draggedNode: Node | null;
|
|
21
|
+
highlightedNode: Node | null;
|
|
22
|
+
insertionType: InsertionType;
|
|
25
23
|
};
|
|
26
24
|
events: {
|
|
27
25
|
'open-ctxmenu': CustomEvent<any>;
|
|
28
26
|
'internal-expand': CustomEvent<any>;
|
|
29
27
|
'internal-selectionChanged': CustomEvent<any>;
|
|
30
28
|
'internal-handleDragStart': CustomEvent<any>;
|
|
29
|
+
'internal-handleDragDrop': CustomEvent<any>;
|
|
31
30
|
'internal-handleDragOver': CustomEvent<any>;
|
|
32
31
|
'internal-handleDragEnter': CustomEvent<any>;
|
|
33
32
|
'internal-handleDragEnd': CustomEvent<any>;
|
|
@@ -39,6 +38,7 @@ declare const __propDef: {
|
|
|
39
38
|
default: {
|
|
40
39
|
node: any;
|
|
41
40
|
};
|
|
41
|
+
'nest-highlight': {};
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
export type BranchProps = typeof __propDef.props;
|
package/dist/Checkbox.svelte
CHANGED
|
@@ -8,8 +8,10 @@ export let onlyLeafCheckboxes;
|
|
|
8
8
|
export let hideDisabledCheckboxes;
|
|
9
9
|
export let readonly = false;
|
|
10
10
|
const dispatch = createEventDispatcher();
|
|
11
|
-
function onSelect(node) {
|
|
11
|
+
function onSelect(e, node) {
|
|
12
|
+
// e.preventDefault();
|
|
12
13
|
dispatch('select', { node });
|
|
14
|
+
return false;
|
|
13
15
|
}
|
|
14
16
|
</script>
|
|
15
17
|
|
|
@@ -18,7 +20,8 @@ function onSelect(node) {
|
|
|
18
20
|
{#if !recursive || (recursive && !node.hasChildren) || !onlyLeafCheckboxes}
|
|
19
21
|
<input
|
|
20
22
|
type="checkbox"
|
|
21
|
-
on:
|
|
23
|
+
on:click={(e) => onSelect(e, node)}
|
|
24
|
+
on:keypress={(e) => e.key === 'Enter' && onSelect(e, node)}
|
|
22
25
|
checked={node.visualState === VisualState.selected}
|
|
23
26
|
indeterminate={node.visualState === VisualState.indeterminate}
|
|
24
27
|
disabled={readonly}
|
package/dist/TreeView.svelte
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import { createEventDispatcher
|
|
3
|
-
import { defaultClasses,
|
|
4
|
-
import { SelectionModes as SelectionModes,
|
|
1
|
+
<script>import ContextMenu from './menu/ContextMenu.svelte';
|
|
2
|
+
import { createEventDispatcher } from 'svelte';
|
|
3
|
+
import { defaultClasses, defaultPropNames } from './constants.js';
|
|
4
|
+
import { SelectionModes as SelectionModes, InsertionType } from './types.js';
|
|
5
5
|
import { TreeHelper } from './index.js';
|
|
6
6
|
import Branch from './Branch.svelte';
|
|
7
7
|
import { SelectionProvider } from './providers/selection-provider.js';
|
|
8
|
+
import { DragDropProvider } from './providers/drag-drop-provider.js';
|
|
8
9
|
const dispatch = createEventDispatcher();
|
|
9
10
|
export let treeId;
|
|
10
11
|
/**
|
|
@@ -76,8 +77,7 @@ export let expandTo = 0;
|
|
|
76
77
|
* Classes used in tree. You can override default classes with this prop.
|
|
77
78
|
* It is recommended to use default classes and add aditinal styles in your css
|
|
78
79
|
*/
|
|
79
|
-
export let customClasses =
|
|
80
|
-
// use any so use doesnt have to cast from unknown
|
|
80
|
+
export let customClasses = {};
|
|
81
81
|
/**
|
|
82
82
|
* Function used to filter what nodes should be shown.
|
|
83
83
|
* Tree automatically adds all parents for nodes.
|
|
@@ -91,43 +91,54 @@ export let filter = null;
|
|
|
91
91
|
* Used mostly for debugging
|
|
92
92
|
*/
|
|
93
93
|
export let logger = null;
|
|
94
|
-
//
|
|
94
|
+
// TODO rewrite documentation
|
|
95
|
+
/*
|
|
96
|
+
* Drag and drop mode allows all nodes, that dont have dragDisabled property set to true
|
|
97
|
+
* to be dragged and dropped. By default you can only insert at same level node you are dropping on,
|
|
98
|
+
* but you can allow nesting by setting nestAllowed to true on node. If you want to disable insertion,
|
|
99
|
+
* set dropDisabled to true on node. if both is disabled, you wont be able to drop on node.
|
|
100
|
+
*/
|
|
95
101
|
export let dragAndDrop = false; //bool
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
export let
|
|
102
|
+
/**
|
|
103
|
+
* Callback that will be called when user drags above node.
|
|
104
|
+
* It should return true, if drop is disabled on that node.
|
|
105
|
+
*/
|
|
106
|
+
export let dropDisabledCallback = null;
|
|
101
107
|
let ctxMenu;
|
|
102
108
|
let expandedIds = [];
|
|
103
|
-
|
|
104
|
-
let draggedPath = null;
|
|
109
|
+
let draggedNode = null;
|
|
105
110
|
let highlightedNode = null;
|
|
106
|
-
let
|
|
107
|
-
|
|
108
|
-
let insPos;
|
|
111
|
+
let insertionType = InsertionType.none;
|
|
112
|
+
$: computedClasses = { ...defaultClasses, ...(customClasses ?? {}) };
|
|
109
113
|
$: dragAndDrop && console.warn('Drag and drop is not supported in this version');
|
|
110
114
|
$: helper = new TreeHelper({
|
|
111
115
|
separator
|
|
112
116
|
});
|
|
117
|
+
$: dragAndDropProvider = new DragDropProvider(helper);
|
|
113
118
|
$: selectionProvider = new SelectionProvider(helper, recursiveSelection);
|
|
114
119
|
$: computedTree = computeTree(helper, selectionProvider, tree, filter, props, expandedIds, value);
|
|
115
120
|
$: debugLog('computedTree', computedTree);
|
|
116
121
|
export function changeAllExpansion(changeTo) {
|
|
117
122
|
debugLog('chaning expantion of every node to ', changeTo ? 'expanded' : 'collapsed');
|
|
118
|
-
|
|
123
|
+
if (changeTo) {
|
|
124
|
+
expandedIds = computedTree.map((node) => node.id);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
expandedIds = [];
|
|
128
|
+
}
|
|
119
129
|
}
|
|
120
130
|
function computeTree(helper, selectionProvider, userProvidedTree, filter, props, expandedIds, value) {
|
|
121
131
|
if (!Array.isArray(userProvidedTree) || !Array.isArray(value)) {
|
|
122
132
|
console.error('value and tree must be arrays!!');
|
|
123
133
|
return [];
|
|
124
134
|
}
|
|
125
|
-
|
|
126
|
-
helper.
|
|
135
|
+
const mappedTree = helper.mapTree(userProvidedTree, { ...defaultPropNames, ...props });
|
|
136
|
+
const filteredTree = helper.searchTree(mappedTree, filter);
|
|
137
|
+
helper.markExpanded(filteredTree, expandedIds);
|
|
127
138
|
// TODO here we could save last value and only recompute visual state if value changed
|
|
128
139
|
// or use diff to only update affected nodes
|
|
129
|
-
selectionProvider.markSelected(
|
|
130
|
-
return
|
|
140
|
+
selectionProvider.markSelected(filteredTree, value);
|
|
141
|
+
return filteredTree;
|
|
131
142
|
}
|
|
132
143
|
function onExpand(event) {
|
|
133
144
|
const { node, changeTo } = event.detail;
|
|
@@ -160,6 +171,7 @@ function handleCallback(node) {
|
|
|
160
171
|
}
|
|
161
172
|
debugLog('calling callback for node', node);
|
|
162
173
|
// TODO mark node as loaded and dont call callback again
|
|
174
|
+
// this is now responsibility of user
|
|
163
175
|
loadChildrenAsync(node);
|
|
164
176
|
}
|
|
165
177
|
function onSelectionChanged(event) {
|
|
@@ -187,6 +199,69 @@ function openContextMenu(ce) {
|
|
|
187
199
|
e.preventDefault();
|
|
188
200
|
ctxMenu.onRightClick(e, node);
|
|
189
201
|
}
|
|
202
|
+
function onDragStart(event) {
|
|
203
|
+
const { node, e } = event.detail;
|
|
204
|
+
draggedNode = null;
|
|
205
|
+
if (!dragAndDrop || node.dragDisabled) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
draggedNode = node;
|
|
209
|
+
}
|
|
210
|
+
function onDragEnd({ detail: { node, event, element } }) {
|
|
211
|
+
// fires when you stop dragging element
|
|
212
|
+
draggedNode = null;
|
|
213
|
+
highlightedNode = null;
|
|
214
|
+
}
|
|
215
|
+
function onDragDrop({ detail: { node, event, element } }) {
|
|
216
|
+
// here we asume that highlightType is correctly calculated in handleDragOver
|
|
217
|
+
if (!dragAndDrop || draggedNode === null || insertionType === InsertionType.none) {
|
|
218
|
+
event.preventDefault();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
highlightedNode = null;
|
|
222
|
+
debugLog('DROPPED: ', draggedNode, 'on', node);
|
|
223
|
+
dispatch('moved', {
|
|
224
|
+
node: draggedNode,
|
|
225
|
+
target: node,
|
|
226
|
+
insertType: insertionType
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// handle highlihting
|
|
230
|
+
function onDragEnter({ detail: { node, event, element } }) {
|
|
231
|
+
highlightedNode = null;
|
|
232
|
+
if (!draggedNode || !dragAndDrop) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// static rules
|
|
236
|
+
if (!dragAndDropProvider.isDropAllowed(draggedNode, node)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (typeof dropDisabledCallback === 'function') {
|
|
240
|
+
// possible bug, if the promise is resolved, when user is over another node
|
|
241
|
+
dropDisabledCallback(draggedNode, node).then((dropDisabled) => {
|
|
242
|
+
if (!dropDisabled) {
|
|
243
|
+
highlightedNode = node;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
highlightedNode = node;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function onDragOver({ detail: { node, event, element, nest } }) {
|
|
252
|
+
if (!dragAndDrop || draggedNode === null || node.dropDisabled) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const insertType = dragAndDropProvider.getInsertionPosition(draggedNode, node, event, element, nest);
|
|
256
|
+
if (insertType === InsertionType.none) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
insertionType = insertType;
|
|
261
|
+
}
|
|
262
|
+
function onDragLeave({ detail: { node, event, element } }) {
|
|
263
|
+
insertionType = InsertionType.none;
|
|
264
|
+
}
|
|
190
265
|
function debugLog(...data) {
|
|
191
266
|
if (logger) {
|
|
192
267
|
logger(...data);
|
|
@@ -203,25 +278,31 @@ function debugLog(...data) {
|
|
|
203
278
|
{onlyLeafCheckboxes}
|
|
204
279
|
{hideDisabledCheckboxes}
|
|
205
280
|
{expandTo}
|
|
206
|
-
{draggedPath}
|
|
207
281
|
{dragAndDrop}
|
|
208
|
-
{highlightedNode}
|
|
209
282
|
{readonly}
|
|
210
283
|
{helper}
|
|
211
|
-
classes={
|
|
284
|
+
classes={computedClasses}
|
|
212
285
|
{verticalLines}
|
|
213
286
|
on:open-ctxmenu={openContextMenu}
|
|
214
287
|
on:internal-expand={onExpand}
|
|
215
288
|
on:internal-selectionChanged={onSelectionChanged}
|
|
216
289
|
let:node={nodeInSlot}
|
|
217
290
|
childDepth={0}
|
|
218
|
-
{
|
|
219
|
-
{
|
|
220
|
-
{
|
|
291
|
+
{insertionType}
|
|
292
|
+
{highlightedNode}
|
|
293
|
+
{draggedNode}
|
|
294
|
+
on:internal-handleDragStart={onDragStart}
|
|
295
|
+
on:internal-handleDragDrop={onDragDrop}
|
|
296
|
+
on:internal-handleDragOver={onDragOver}
|
|
297
|
+
on:internal-handleDragEnter={onDragEnter}
|
|
298
|
+
on:internal-handleDragEnd={onDragEnd}
|
|
299
|
+
on:internal-handleDragLeave={onDragLeave}
|
|
221
300
|
>
|
|
222
301
|
<slot node={nodeInSlot} />
|
|
302
|
+
<svelte:fragment slot="nest-highlight">
|
|
303
|
+
<slot name="nest-highlight" />
|
|
304
|
+
</svelte:fragment>
|
|
223
305
|
</Branch>
|
|
224
|
-
|
|
225
306
|
<ContextMenu bind:this={ctxMenu}>
|
|
226
307
|
<svelte:fragment let:node>
|
|
227
308
|
<slot name="context-menu" {node} />
|
|
@@ -285,9 +366,6 @@ function debugLog(...data) {
|
|
|
285
366
|
font-weight: 700;
|
|
286
367
|
position: relative;
|
|
287
368
|
}
|
|
288
|
-
:global(.treeview) :global(li:not(.has-children)) :global(.tree-item) {
|
|
289
|
-
margin-left: 14px;
|
|
290
|
-
}
|
|
291
369
|
:global(.treeview) :global(.tree-item) {
|
|
292
370
|
display: flex;
|
|
293
371
|
column-gap: 0.4em;
|
|
@@ -323,8 +401,7 @@ function debugLog(...data) {
|
|
|
323
401
|
display: block;
|
|
324
402
|
border-radius: 3px;
|
|
325
403
|
margin-left: 28px;
|
|
326
|
-
|
|
327
|
-
margin-top: -2px;
|
|
404
|
+
pointer-events: none;
|
|
328
405
|
}
|
|
329
406
|
:global(.treeview) :global(.insert-line-child) {
|
|
330
407
|
margin-left: calc(28px + 5em);
|
|
@@ -339,4 +416,7 @@ function debugLog(...data) {
|
|
|
339
416
|
}
|
|
340
417
|
:global(.treeview) :global(.pointer-cursor) {
|
|
341
418
|
cursor: grab;
|
|
419
|
+
}
|
|
420
|
+
:global(.treeview) :global(.expansion-button) {
|
|
421
|
+
all: unset;
|
|
342
422
|
}</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
|
-
import { SelectionModes as SelectionModes, type Props, type CustomizableClasses, type DragEnterCallback, type
|
|
3
|
-
declare
|
|
4
|
-
props
|
|
2
|
+
import { SelectionModes as SelectionModes, type Props, type CustomizableClasses, type DragEnterCallback, type ExpandedCallback, type NodeId, type ProvidedTree, type FilterFunction } from './types.js';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
5
|
treeId: string;
|
|
6
6
|
/**
|
|
7
7
|
* Array of nodes that represent tree structure.
|
|
@@ -58,7 +58,7 @@ declare class __sveltets_Render<T> {
|
|
|
58
58
|
/**
|
|
59
59
|
* Classes used in tree. You can override default classes with this prop.
|
|
60
60
|
* It is recommended to use default classes and add aditinal styles in your css
|
|
61
|
-
*/ customClasses?: CustomizableClasses | undefined;
|
|
61
|
+
*/ customClasses?: Partial<CustomizableClasses> | undefined;
|
|
62
62
|
/**
|
|
63
63
|
* Function used to filter what nodes should be shown.
|
|
64
64
|
* Tree automatically adds all parents for nodes.
|
|
@@ -71,14 +71,13 @@ declare class __sveltets_Render<T> {
|
|
|
71
71
|
* Used mostly for debugging
|
|
72
72
|
*/ logger?: ((...data: any[]) => void) | null | undefined;
|
|
73
73
|
dragAndDrop?: boolean | undefined;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
beforeMovedCallback?: BeforeMovedCallback | null | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Callback that will be called when user drags above node.
|
|
76
|
+
* It should return true, if drop is disabled on that node.
|
|
77
|
+
*/ dropDisabledCallback?: DragEnterCallback | null | undefined;
|
|
79
78
|
changeAllExpansion?: ((changeTo: boolean) => void) | undefined;
|
|
80
79
|
};
|
|
81
|
-
events
|
|
80
|
+
events: {
|
|
82
81
|
expansion: CustomEvent<any>;
|
|
83
82
|
expanded: CustomEvent<any>;
|
|
84
83
|
closed: CustomEvent<any>;
|
|
@@ -86,22 +85,24 @@ declare class __sveltets_Render<T> {
|
|
|
86
85
|
selection: CustomEvent<any>;
|
|
87
86
|
selected: CustomEvent<any>;
|
|
88
87
|
unselected: CustomEvent<any>;
|
|
88
|
+
moved: CustomEvent<any>;
|
|
89
89
|
} & {
|
|
90
90
|
[evt: string]: CustomEvent<any>;
|
|
91
91
|
};
|
|
92
|
-
slots
|
|
92
|
+
slots: {
|
|
93
93
|
default: {
|
|
94
94
|
node: any;
|
|
95
95
|
};
|
|
96
|
+
'nest-highlight': {};
|
|
96
97
|
'context-menu': {
|
|
97
98
|
node: any;
|
|
98
99
|
};
|
|
99
100
|
};
|
|
100
|
-
}
|
|
101
|
-
export type TreeViewProps
|
|
102
|
-
export type TreeViewEvents
|
|
103
|
-
export type TreeViewSlots
|
|
104
|
-
export default class TreeView
|
|
101
|
+
};
|
|
102
|
+
export type TreeViewProps = typeof __propDef.props;
|
|
103
|
+
export type TreeViewEvents = typeof __propDef.events;
|
|
104
|
+
export type TreeViewSlots = typeof __propDef.slots;
|
|
105
|
+
export default class TreeView extends SvelteComponent<TreeViewProps, TreeViewEvents, TreeViewSlots> {
|
|
105
106
|
get changeAllExpansion(): (changeTo: boolean) => void;
|
|
106
107
|
}
|
|
107
108
|
export {};
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HelperConfig, CustomizableClasses, Props } from './types.js';
|
|
2
2
|
export declare const defaultPropNames: Props;
|
|
3
|
-
export declare const defaultPixelTreshold = 50;
|
|
4
3
|
export declare const defaultClasses: CustomizableClasses;
|
|
5
4
|
export declare const defaultConfig: HelperConfig;
|
package/dist/constants.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
import { SelectionModes } from './types.js';
|
|
2
1
|
export const defaultPropNames = {
|
|
3
2
|
nodePath: 'nodePath',
|
|
4
3
|
nodeId: 'nodePath',
|
|
5
4
|
hasChildren: 'hasChildren',
|
|
6
5
|
useCallback: '__useCallback',
|
|
7
6
|
priority: 'priority',
|
|
8
|
-
|
|
7
|
+
dragDisabled: 'dragDisabled',
|
|
9
8
|
insertDisabled: 'insertDisabled',
|
|
10
|
-
|
|
9
|
+
nestAllowed: 'nestAllowed',
|
|
11
10
|
checkbox: 'checkbox'
|
|
12
11
|
};
|
|
13
|
-
export const defaultPixelTreshold = 50;
|
|
14
12
|
export const defaultClasses = {
|
|
15
13
|
treeClass: 'treeview',
|
|
16
14
|
expandClass: 'inserting-highlighted',
|
|
17
15
|
currentlyDraggedClass: 'currently-dragged',
|
|
18
16
|
nodeClass: '',
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
expandIcon: 'far fa-fw fa-plus-square',
|
|
18
|
+
collapseIcon: 'far fa-fw fa-minus-square',
|
|
21
19
|
inserLineClass: '',
|
|
22
|
-
|
|
20
|
+
nestIcon: 'fas fa-level-down-alt'
|
|
23
21
|
};
|
|
24
22
|
export const defaultConfig = {
|
|
25
23
|
separator: '.'
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { type Node, type
|
|
1
|
+
import { type Node, type HelperConfig, type Tree, type NodeId, type Props, type FilterFunction } from '../types.js';
|
|
2
2
|
export declare class TreeHelper {
|
|
3
3
|
config: HelperConfig;
|
|
4
4
|
constructor(config?: HelperConfig);
|
|
5
|
-
mapTree(tree: Tree,
|
|
5
|
+
mapTree(tree: Tree, properties: Props): Node[];
|
|
6
6
|
markExpanded(tree: Tree, expandedNodeIds: NodeId[]): void;
|
|
7
|
-
getParentNodePath(nodePath:
|
|
8
|
-
isChildrenOf(parentNodePath:
|
|
9
|
-
hasChildren(tree: Tree, nodePath:
|
|
10
|
-
findNode(tree: Tree, nodePath:
|
|
11
|
-
nodePathIsChild(nodePath:
|
|
12
|
-
getDirectChildren(tree: Tree, parentNodePath:
|
|
13
|
-
allCHildren(tree: Tree, parentNodePath:
|
|
7
|
+
getParentNodePath(nodePath: string): string | null;
|
|
8
|
+
isChildrenOf(parentNodePath: string | null, childrenNodePath: string): boolean;
|
|
9
|
+
hasChildren(tree: Tree, nodePath: string): Node | undefined;
|
|
10
|
+
findNode(tree: Tree, nodePath: string): Node | null;
|
|
11
|
+
nodePathIsChild(nodePath: string): boolean;
|
|
12
|
+
getDirectChildren(tree: Tree, parentNodePath: string | null): Tree;
|
|
13
|
+
allCHildren(tree: Tree, parentNodePath: string | null): Node[];
|
|
14
14
|
getAllLeafNodes(tree: Tree): Node[];
|
|
15
15
|
joinTrees(filteredTree: Tree, tree: Tree): Node[];
|
|
16
16
|
mergeTrees(oldTree: Tree, addedTree: Tree, nodePath?: string): Node[];
|
|
@@ -20,7 +20,7 @@ export declare class TreeHelper {
|
|
|
20
20
|
/** changes expansion of every node that has this.hasChildren set to true if they are abose set level and expansion property isnt set
|
|
21
21
|
*/
|
|
22
22
|
expandToLevel(tree: Tree, level: number): NodeId[];
|
|
23
|
-
getDepthLevel(nodePath:
|
|
23
|
+
getDepthLevel(nodePath: string): number;
|
|
24
24
|
searchTree(tree: Tree, filter: FilterFunction | null): Tree;
|
|
25
25
|
getParents(tree: Tree, targetNode: Node): Node[];
|
|
26
26
|
/** orders nodes by priorityProp
|
|
@@ -7,25 +7,27 @@ export class TreeHelper {
|
|
|
7
7
|
constructor(config = defaultConfig) {
|
|
8
8
|
this.config = config;
|
|
9
9
|
}
|
|
10
|
-
mapTree(tree,
|
|
10
|
+
mapTree(tree, properties) {
|
|
11
11
|
{
|
|
12
|
-
return
|
|
12
|
+
return tree.map((node) => {
|
|
13
13
|
// TODO maybe create class for nodes
|
|
14
14
|
const mappedNode = {
|
|
15
15
|
originalNode: node,
|
|
16
16
|
id: node[properties.nodeId],
|
|
17
17
|
path: node[properties.nodePath],
|
|
18
|
-
hasChildren: node[properties.hasChildren],
|
|
19
|
-
useCallback: node[properties.useCallback],
|
|
18
|
+
hasChildren: node[properties.hasChildren] === true,
|
|
19
|
+
useCallback: node[properties.useCallback] === true,
|
|
20
20
|
priority: node[properties.priority],
|
|
21
|
-
|
|
22
|
-
insertDisabled: node[properties.insertDisabled],
|
|
23
|
-
|
|
24
|
-
checkbox: node[properties.checkbox],
|
|
21
|
+
dragDisabled: node[properties.dragDisabled] === true,
|
|
22
|
+
insertDisabled: node[properties.insertDisabled] === true,
|
|
23
|
+
nestAllowed: node[properties.nestAllowed] === true,
|
|
24
|
+
checkbox: node[properties.checkbox] === true,
|
|
25
25
|
expanded: false,
|
|
26
26
|
selected: false,
|
|
27
|
-
visualState: VisualState.notSelected
|
|
27
|
+
visualState: VisualState.notSelected,
|
|
28
|
+
dropDisabled: false
|
|
28
29
|
};
|
|
30
|
+
mappedNode.dropDisabled = mappedNode.insertDisabled && !mappedNode.nestAllowed;
|
|
29
31
|
return mappedNode;
|
|
30
32
|
});
|
|
31
33
|
}
|
|
@@ -129,10 +131,14 @@ export class TreeHelper {
|
|
|
129
131
|
}
|
|
130
132
|
getParents(tree, targetNode) {
|
|
131
133
|
const parentsPaths = [];
|
|
132
|
-
|
|
134
|
+
// TODO refactor
|
|
135
|
+
let nodePath = this.getParentNodePath(targetNode.path);
|
|
133
136
|
// get all parents
|
|
134
|
-
while (nodePath && nodePath
|
|
137
|
+
while (nodePath !== null && nodePath !== '') {
|
|
135
138
|
nodePath = this.getParentNodePath(nodePath);
|
|
139
|
+
if (nodePath === null) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
136
142
|
parentsPaths.push(nodePath);
|
|
137
143
|
}
|
|
138
144
|
//find nodes for given ids
|
package/dist/menu/Menu.svelte
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TreeHelper } from '../helpers/tree-helper.js';
|
|
2
|
+
import { type Node, InsertionType } from '../types.js';
|
|
3
|
+
export declare class DragDropProvider {
|
|
4
|
+
helper: TreeHelper;
|
|
5
|
+
constructor(treeHelper: TreeHelper);
|
|
6
|
+
getInsertionPosition(draggendNode: Node, draggedOverNode: Node, e: DragEvent, element: HTMLElement, nest: boolean): InsertionType;
|
|
7
|
+
getRelativePosition(element: Element, e: DragEvent): InsertionType;
|
|
8
|
+
isDropAllowed(draggedNode: Node, targeNode: Node): boolean;
|
|
9
|
+
}
|
|
@@ -1,186 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// const isNesting = insType === InsertionType.nest;
|
|
35
|
-
// // if you are not isNestinging, you want to be on same level
|
|
36
|
-
// //so you will have same parent as target node
|
|
37
|
-
// const parentNodePath = isNesting
|
|
38
|
-
// ? targetNodePath
|
|
39
|
-
// : this.helper.getParentNodePath(targetNodePath);
|
|
40
|
-
// //trying to move parent to child
|
|
41
|
-
// if (parentNodePath?.startsWith(movedNodePath)) {
|
|
42
|
-
// return tree;
|
|
43
|
-
// }
|
|
44
|
-
// const changedParent = this.helper.getParentNodePath(movedNodePath) !== parentNodePath;
|
|
45
|
-
// let newNodePath = movedNodePath;
|
|
46
|
-
// //dont create new node if you only moved inside same parent
|
|
47
|
-
// if (changedParent) {
|
|
48
|
-
// newNodePath = this.calculateNewNodePath(
|
|
49
|
-
// tree,
|
|
50
|
-
// parentNodePath,
|
|
51
|
-
// movedNodePath,
|
|
52
|
-
// recalculateNodePath
|
|
53
|
-
// );
|
|
54
|
-
// }
|
|
55
|
-
// //* find target node
|
|
56
|
-
// const targetNode = this.helper.findNode(tree, targetNodePath);
|
|
57
|
-
// if (!targetNode) return tree;
|
|
58
|
-
// let movedNode;
|
|
59
|
-
// //move nodes
|
|
60
|
-
// tree = tree.map((node) => {
|
|
61
|
-
// //make sure that parent's haschild is set to true, so that children are visible
|
|
62
|
-
// if (this.path(node) == parentNodePath) {
|
|
63
|
-
// this.props.setHasChildren(node, true);
|
|
64
|
-
// this.props.setExpanded(node, true);
|
|
65
|
-
// }
|
|
66
|
-
// //move moved nodes to new location ( if location is being changed)
|
|
67
|
-
// if (changedParent && this.path(node)?.startsWith(movedNodePath)) {
|
|
68
|
-
// //replace old parent with new one
|
|
69
|
-
// const newPath = this.path(node)?.replace(movedNodePath, newNodePath) ?? null;
|
|
70
|
-
// this.props.setPath(node, newPath);
|
|
71
|
-
// }
|
|
72
|
-
// //if it is moved node
|
|
73
|
-
// if (this.path(node) === newNodePath) {
|
|
74
|
-
// movedNode = node;
|
|
75
|
-
// //? not sure if this is best
|
|
76
|
-
// this.updatePriority(tree, movedNode, parentNodePath, newNodePath, targetNode, insType);
|
|
77
|
-
// }
|
|
78
|
-
// return node;
|
|
79
|
-
// });
|
|
80
|
-
// if (!movedNode) return tree;
|
|
81
|
-
// //* insert node at right possition of array
|
|
82
|
-
// const oldIndex = tree.findIndex((x) => this.path(x) == newNodePath);
|
|
83
|
-
// tree.splice(oldIndex, 1);
|
|
84
|
-
// const index = tree.findIndex((x) => this.path(x) == this.path(targetNode));
|
|
85
|
-
// tree.splice(index + (insType == InsertionType.above ? 0 : 1), 0, movedNode);
|
|
86
|
-
// //TODO maybe add option to setting this.hasChildren to false when moved last children
|
|
87
|
-
// //hide plus icon if parent of moved node doesnt have any more children
|
|
88
|
-
// const oldParent = this.helper.findNode(tree, this.helper.getParentNodePath(movedNodePath));
|
|
89
|
-
// if (!oldParent) return tree;
|
|
90
|
-
// //moved last node
|
|
91
|
-
// const oldParentHasChildren = this.helper.allCHildren(tree, this.path(oldParent)).length;
|
|
92
|
-
// if (oldParent && !oldParentHasChildren) {
|
|
93
|
-
// this.props.setHasChildren(oldParent, false);
|
|
94
|
-
// }
|
|
95
|
-
// return tree;
|
|
96
|
-
// }
|
|
97
|
-
// calculateNewNodePath(
|
|
98
|
-
// tree: Tree,
|
|
99
|
-
// parentNodePath: NodePath,
|
|
100
|
-
// movedNodePath: NodePath,
|
|
101
|
-
// recalculateNodePath: boolean
|
|
102
|
-
// ) {
|
|
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 = this.helper.getNodeIdFromPath(movedNodePath);
|
|
110
|
-
// }
|
|
111
|
-
// if (parentNodePath === null) return nodeId as string;
|
|
112
|
-
// return `${parentNodePath}${this.separator}${nodeId}`;
|
|
113
|
-
// }
|
|
114
|
-
// updatePriority(
|
|
115
|
-
// tree: Tree,
|
|
116
|
-
// node: Node,
|
|
117
|
-
// parentNodePath: NodePath,
|
|
118
|
-
// newNodePath: NodePath,
|
|
119
|
-
// targetNode: Node,
|
|
120
|
-
// insType: InsertionType
|
|
121
|
-
// ) {
|
|
122
|
-
// const isNesting = insType == InsertionType.nest;
|
|
123
|
-
// if (isNesting || this.props.priority(targetNode) != null) {
|
|
124
|
-
// let newpriority = 0;
|
|
125
|
-
// if (!isNesting) {
|
|
126
|
-
// //calculate next
|
|
127
|
-
// newpriority = this.props.priority(targetNode) ?? 0;
|
|
128
|
-
// if (insType == InsertionType.below) {
|
|
129
|
-
// newpriority += 1;
|
|
130
|
-
// } else {
|
|
131
|
-
// //targetNode[this.props.priority] -= 1;
|
|
132
|
-
// }
|
|
133
|
-
// }
|
|
134
|
-
// this.recalculatesPriorities(tree, parentNodePath, newNodePath, newpriority);
|
|
135
|
-
// this.props.setPriority(targetNode, newpriority);
|
|
136
|
-
// } else {
|
|
137
|
-
// //so old priority doesnt mess up orderring
|
|
138
|
-
// this.props.setPriority(targetNode, undefined);
|
|
139
|
-
// }
|
|
140
|
-
// }
|
|
141
|
-
// /** recomputes all priorities after inserted priority.F
|
|
142
|
-
// * Also changes all priorities to be one apart (1,5,6 => 1,2,3)
|
|
143
|
-
// */
|
|
144
|
-
// //? maybe it will recalculate properly if dont set insertedPriority
|
|
145
|
-
// recalculatesPriorities(
|
|
146
|
-
// tree: Tree,
|
|
147
|
-
// parentNode: NodePath,
|
|
148
|
-
// movedNodePath: NodePath,
|
|
149
|
-
// insertedPriority = -1
|
|
150
|
-
// ) {
|
|
151
|
-
// let nextPriority = insertedPriority + 1;
|
|
152
|
-
// this.helper.orderByPriority(this.helper.allCHildren(tree, parentNode)).forEach((node) => {
|
|
153
|
-
// if (this.props.priority(node) >= insertedPriority && this.path(node) != movedNodePath) {
|
|
154
|
-
// this.props.setPriority(node, nextPriority++);
|
|
155
|
-
// }
|
|
156
|
-
// });
|
|
157
|
-
// }
|
|
158
|
-
// /** return biggest value of nodepath number that children are using +1 */
|
|
159
|
-
// getNextNodeId(tree: Tree, parentPath: NodePath) {
|
|
160
|
-
// let max = 0;
|
|
161
|
-
// //findes biggest nodeNumber for
|
|
162
|
-
// this.helper.allCHildren(tree, parentPath).forEach((node) => {
|
|
163
|
-
// const parent = this.helper.getParentNodePath(this.path(node));
|
|
164
|
-
// if (parent === parentPath) {
|
|
165
|
-
// const num = parseInt(this.path(node)?.substring(parent ? parent.length + 1 : 0) ?? '0');
|
|
166
|
-
// max = Math.max(max, num);
|
|
167
|
-
// }
|
|
168
|
-
// });
|
|
169
|
-
// return (max + 1).toString();
|
|
170
|
-
// }
|
|
171
|
-
// getInsertionPosition(e: DragEvent, element: HTMLElement): InsertionType {
|
|
172
|
-
// const targetCords = element.getBoundingClientRect();
|
|
173
|
-
// const half = targetCords.bottom - targetCords.height / 2;
|
|
174
|
-
// if (e.y < half) {
|
|
175
|
-
// return InsertionType.below;
|
|
176
|
-
// }
|
|
177
|
-
// return InsertionType.above;
|
|
178
|
-
// }
|
|
179
|
-
// getNodeIdFromPath(nodePath: NodePath) {
|
|
180
|
-
// if (nodePath == null) {
|
|
181
|
-
// console.warn('getting node id of null node path');
|
|
182
|
-
// return null;
|
|
183
|
-
// }
|
|
184
|
-
// return nodePath?.split(this.helper.config.separator).slice(-1)[0];
|
|
185
|
-
// }
|
|
186
|
-
// }
|
|
1
|
+
import { InsertionType } from '../types.js';
|
|
2
|
+
export class DragDropProvider {
|
|
3
|
+
helper;
|
|
4
|
+
constructor(treeHelper) {
|
|
5
|
+
this.helper = treeHelper;
|
|
6
|
+
}
|
|
7
|
+
getInsertionPosition(draggendNode, draggedOverNode, e, element, nest) {
|
|
8
|
+
if (nest && draggedOverNode.nestAllowed) {
|
|
9
|
+
return InsertionType.nest;
|
|
10
|
+
}
|
|
11
|
+
if (draggedOverNode.insertDisabled) {
|
|
12
|
+
return InsertionType.none;
|
|
13
|
+
}
|
|
14
|
+
return this.getRelativePosition(element, e);
|
|
15
|
+
}
|
|
16
|
+
getRelativePosition(element, e) {
|
|
17
|
+
const targetCords = element.getBoundingClientRect();
|
|
18
|
+
const half = targetCords.bottom - targetCords.height / 2;
|
|
19
|
+
if (e.y < half) {
|
|
20
|
+
return InsertionType.insertAbove;
|
|
21
|
+
}
|
|
22
|
+
return InsertionType.insertBelow;
|
|
23
|
+
}
|
|
24
|
+
isDropAllowed(draggedNode, targeNode) {
|
|
25
|
+
if (targeNode.dropDisabled) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (targeNode.path.startsWith(draggedNode.path + this.helper.config.separator)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TreeHelper } from '../helpers/tree-helper.js';
|
|
2
|
-
import { SelectionModes, type
|
|
2
|
+
import { SelectionModes, type Tree, type TreeVisualStates, type Node, type NodeId } from '../types.js';
|
|
3
3
|
export declare class SelectionProvider {
|
|
4
4
|
helper: TreeHelper;
|
|
5
5
|
recursiveMode: boolean;
|
|
@@ -8,7 +8,7 @@ export declare class SelectionProvider {
|
|
|
8
8
|
isNodeSelected(node: Node): boolean;
|
|
9
9
|
isSelected(nodeId: string, visualStates: TreeVisualStates, selectedNodeIds: NodeId[]): boolean;
|
|
10
10
|
getSelectableDirectChildren(tree: Tree, parentNodePath: string | null): Node[];
|
|
11
|
-
setSelection(tree: Tree, nodePath:
|
|
11
|
+
setSelection(tree: Tree, nodePath: string | null, changeTo: boolean, oldSelection: NodeId[]): NodeId[];
|
|
12
12
|
/** Computes visual states for all nodes. Used for computing initial visual states when tree changes */
|
|
13
13
|
computeVisualStates(tree: Tree, selectedNodeIds: (string | number)[]): TreeVisualStates;
|
|
14
14
|
private computeVisualState;
|
|
@@ -38,7 +38,7 @@ export class SelectionProvider {
|
|
|
38
38
|
.filter((node) => isSelectable(node, SelectionModes.all));
|
|
39
39
|
}
|
|
40
40
|
setSelection(tree, nodePath, changeTo, oldSelection) {
|
|
41
|
-
const node = this.helper.findNode(tree, nodePath);
|
|
41
|
+
const node = nodePath ? this.helper.findNode(tree, nodePath) : null;
|
|
42
42
|
const nodeHasChildren = node ? node.hasChildren : false;
|
|
43
43
|
// allow selection of root node
|
|
44
44
|
if (nodePath === null || (this.recursiveMode && nodeHasChildren)) {
|
package/dist/tree-styles.sass
CHANGED
|
@@ -52,9 +52,6 @@ $treeview-lines: solid black 1px
|
|
|
52
52
|
color: #555
|
|
53
53
|
font-weight: 700
|
|
54
54
|
position: relative
|
|
55
|
-
&:not(.has-children)
|
|
56
|
-
.tree-item
|
|
57
|
-
margin-left: 14px
|
|
58
55
|
|
|
59
56
|
.tree-item
|
|
60
57
|
display: flex
|
|
@@ -89,8 +86,9 @@ $treeview-lines: solid black 1px
|
|
|
89
86
|
display: block
|
|
90
87
|
border-radius: 3px
|
|
91
88
|
margin-left: 28px
|
|
92
|
-
|
|
93
|
-
margin-
|
|
89
|
+
pointer-events: none //! this is needed to fix flickering issue
|
|
90
|
+
// margin-bottom: -2px
|
|
91
|
+
// margin-top: -2px
|
|
94
92
|
.insert-line-child
|
|
95
93
|
margin-left: calc( 28px + 5em )
|
|
96
94
|
background-color: red
|
|
@@ -103,3 +101,5 @@ $treeview-lines: solid black 1px
|
|
|
103
101
|
color: LightGray
|
|
104
102
|
.pointer-cursor
|
|
105
103
|
cursor: grab
|
|
104
|
+
.expansion-button
|
|
105
|
+
all: unset
|
package/dist/types.d.ts
CHANGED
|
@@ -4,14 +4,14 @@ export type Props = {
|
|
|
4
4
|
hasChildren: string;
|
|
5
5
|
useCallback: string;
|
|
6
6
|
priority: string;
|
|
7
|
-
|
|
7
|
+
dragDisabled: string;
|
|
8
8
|
insertDisabled: string;
|
|
9
|
-
|
|
9
|
+
nestAllowed: string;
|
|
10
10
|
checkbox: string;
|
|
11
11
|
};
|
|
12
12
|
export type MappedNode = {
|
|
13
13
|
id: NodeId;
|
|
14
|
-
path:
|
|
14
|
+
path: string;
|
|
15
15
|
hasChildren: boolean;
|
|
16
16
|
useCallback: boolean;
|
|
17
17
|
priority: number;
|
|
@@ -23,17 +23,18 @@ export type MappedNode = {
|
|
|
23
23
|
export type Node = {
|
|
24
24
|
originalNode: any;
|
|
25
25
|
id: NodeId;
|
|
26
|
-
path:
|
|
26
|
+
path: string;
|
|
27
27
|
hasChildren: boolean;
|
|
28
28
|
useCallback: boolean;
|
|
29
29
|
priority: number;
|
|
30
|
-
|
|
30
|
+
dragDisabled: boolean;
|
|
31
31
|
insertDisabled: boolean;
|
|
32
|
-
|
|
32
|
+
nestAllowed: boolean;
|
|
33
33
|
checkbox: boolean;
|
|
34
34
|
visualState: VisualState;
|
|
35
35
|
expanded: boolean;
|
|
36
36
|
selected: boolean;
|
|
37
|
+
dropDisabled: boolean;
|
|
37
38
|
};
|
|
38
39
|
export declare enum VisualState {
|
|
39
40
|
indeterminate = "indeterminate",
|
|
@@ -46,30 +47,24 @@ export declare enum SelectionModes {
|
|
|
46
47
|
none = "none"
|
|
47
48
|
}
|
|
48
49
|
export type Tree = Node[];
|
|
49
|
-
export declare enum InsertionType {
|
|
50
|
-
above = "above",
|
|
51
|
-
below = "below",
|
|
52
|
-
nest = "nest"
|
|
53
|
-
}
|
|
54
|
-
export type NodePath = string | null;
|
|
55
50
|
export type NodeId = string | number;
|
|
56
51
|
export type CustomizableClasses = {
|
|
57
52
|
treeClass: string;
|
|
58
53
|
nodeClass: string;
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
expandIcon: string;
|
|
55
|
+
collapseIcon: string;
|
|
56
|
+
nestIcon: string;
|
|
61
57
|
expandClass: string;
|
|
62
58
|
inserLineClass: string;
|
|
63
|
-
inserLineNestClass: string;
|
|
64
59
|
currentlyDraggedClass: string;
|
|
65
60
|
};
|
|
66
|
-
export type DragEnterCallback = (draggendNode: Node,
|
|
61
|
+
export type DragEnterCallback = (draggendNode: Node, targetNode: Node) => Promise<boolean>;
|
|
67
62
|
export type BeforeMovedCallback = (draggendNode: Node, oldParent: Node, newParent: Node, insertionType: string) => boolean;
|
|
68
63
|
export type ExpandedCallback = (node: Node) => Promise<void>;
|
|
69
64
|
export type HelperConfig = {
|
|
70
65
|
separator: string;
|
|
71
66
|
};
|
|
72
|
-
export declare enum
|
|
67
|
+
export declare enum InsertionType {
|
|
73
68
|
nest = "nest",
|
|
74
69
|
insertAbove = "insert-above",
|
|
75
70
|
insertBelow = "insert-below",
|
package/dist/types.js
CHANGED
|
@@ -12,14 +12,8 @@ export var SelectionModes;
|
|
|
12
12
|
})(SelectionModes || (SelectionModes = {}));
|
|
13
13
|
export var InsertionType;
|
|
14
14
|
(function (InsertionType) {
|
|
15
|
-
InsertionType["above"] = "above";
|
|
16
|
-
InsertionType["below"] = "below";
|
|
17
15
|
InsertionType["nest"] = "nest";
|
|
16
|
+
InsertionType["insertAbove"] = "insert-above";
|
|
17
|
+
InsertionType["insertBelow"] = "insert-below";
|
|
18
|
+
InsertionType["none"] = "none";
|
|
18
19
|
})(InsertionType || (InsertionType = {}));
|
|
19
|
-
export var HighlighType;
|
|
20
|
-
(function (HighlighType) {
|
|
21
|
-
HighlighType["nest"] = "nest";
|
|
22
|
-
HighlighType["insertAbove"] = "insert-above";
|
|
23
|
-
HighlighType["insertBelow"] = "insert-below";
|
|
24
|
-
HighlighType["none"] = "none";
|
|
25
|
-
})(HighlighType || (HighlighType = {}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keenmate/svelte-treeview",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run package",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"types": "./dist/index.d.ts",
|
|
62
62
|
"type": "module",
|
|
63
63
|
"dependencies": {
|
|
64
|
+
"@keenmate/js-common-helpers": "^1.2.0",
|
|
64
65
|
"@types/lodash.unionby": "^4.8.9",
|
|
65
66
|
"@types/lodash.uniq": "^4.5.9",
|
|
66
67
|
"@types/lodash.uniqby": "^4.7.9",
|