@keenmate/svelte-treeview 1.2.11 → 2.0.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.
@@ -1,7 +1,7 @@
1
- <script>import { createEventDispatcher } from 'svelte';
2
- import Checkbox from './Checkbox.svelte';
3
- import { SelectionModes, InsertionType } from './types.js';
4
- import { capturedKeys } from './constants.js';
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import Checkbox from "./Checkbox.svelte";
3
+ import { InsertionType, SelectionModes } from "./types.js";
4
+ import { capturedKeys } from "./constants.js";
5
5
  const dispatch = createEventDispatcher();
6
6
  export let tree;
7
7
  export let treeId;
@@ -29,7 +29,7 @@ $: if (focusedNode && liElements[getNodeId(focusedNode)]) {
29
29
  liElements[getNodeId(focusedNode)].focus();
30
30
  }
31
31
  function setExpansion(node, changeTo) {
32
- dispatch('internal-expand', { node: node, changeTo });
32
+ dispatch("internal-expand", { node: node, changeTo });
33
33
  }
34
34
  function isExpanded(node, depth, expandToDepth) {
35
35
  const nodeExpanded = node.expanded;
@@ -41,26 +41,26 @@ function isExpanded(node, depth, expandToDepth) {
41
41
  }
42
42
  //checkboxes
43
43
  function selectionChanged(node) {
44
- dispatch('internal-selectionChanged', { node: node });
44
+ dispatch("internal-selectionChanged", { node: node });
45
45
  }
46
46
  // drag and drop
47
47
  function handleDragStart(e, node) {
48
- dispatch('internal-handleDragStart', { node: node, e: e });
48
+ dispatch("internal-handleDragStart", { node: node, e: e });
49
49
  }
50
50
  function handleDragDrop(e, node, el) {
51
- dispatch('internal-handleDragDrop', { node: node, event: e, element: el });
51
+ dispatch("internal-handleDragDrop", { node: node, event: e, element: el });
52
52
  }
53
53
  function handleDragOver(e, node, el, nest) {
54
- dispatch('internal-handleDragOver', { node: node, event: e, element: el, nest });
54
+ dispatch("internal-handleDragOver", { node: node, event: e, element: el, nest });
55
55
  }
56
56
  function handleDragEnter(e, node, el) {
57
- dispatch('internal-handleDragEnter', { node: node, event: e, element: el });
57
+ dispatch("internal-handleDragEnter", { node: node, event: e, element: el });
58
58
  }
59
59
  function handleDragEnd(e, node) {
60
- dispatch('internal-handleDragEnd', { node: node, event: e });
60
+ dispatch("internal-handleDragEnd", { node: node, event: e });
61
61
  }
62
62
  function handleDragLeave(e, node, el) {
63
- dispatch('internal-handleDragLeave', { node: node, event: e, element: el });
63
+ dispatch("internal-handleDragLeave", { node: node, event: e, element: el });
64
64
  }
65
65
  function handleKeyPress(e, node) {
66
66
  if (!capturedKeys.includes(e.key)) {
@@ -68,12 +68,13 @@ function handleKeyPress(e, node) {
68
68
  }
69
69
  e.preventDefault();
70
70
  e.stopPropagation();
71
- dispatch('internal-keypress', { event: e, node });
71
+ dispatch("internal-keypress", { event: e, node });
72
72
  }
73
73
  function getHighlighMode(node, highlightedNode, insertionType) {
74
74
  // return InsertionType.insertAbove;
75
- if (highlightedNode?.path !== node.path)
75
+ if (highlightedNode?.path !== node.path) {
76
76
  return InsertionType.none;
77
+ }
77
78
  return insertionType;
78
79
  }
79
80
  </script>
@@ -1,6 +1,6 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { SelectionModes, InsertionType, type Node, type CustomizableClasses } from './types.js';
3
- import type { TreeHelper } from './helpers/tree-helper.js';
2
+ import { type CustomizableClasses, InsertionType, type Node, SelectionModes } from "./types.js";
3
+ import type { TreeHelper } from "./helpers/tree-helper.js";
4
4
  declare const __propDef: {
5
5
  props: {
6
6
  tree: Node[];
@@ -1,6 +1,6 @@
1
- <script>import { createEventDispatcher } from 'svelte';
2
- import { SelectionModes, VisualState } from './types.js';
3
- import { isSelectable } from './providers/selection-provider.js';
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import { SelectionModes, VisualState } from "./types.js";
3
+ import { isSelectable } from "./providers/selection-provider.js";
4
4
  export let checkboxes;
5
5
  export let recursive;
6
6
  export let node;
@@ -10,7 +10,7 @@ export let readonly = false;
10
10
  const dispatch = createEventDispatcher();
11
11
  function onSelect(e, node) {
12
12
  // e.preventDefault();
13
- dispatch('select', { node });
13
+ dispatch("select", { node });
14
14
  return false;
15
15
  }
16
16
  </script>
@@ -35,6 +35,8 @@ function onSelect(e, node) {
35
35
  type="checkbox"
36
36
  on:click={null}
37
37
  disabled={true}
38
+ checked={node.visualState === VisualState.selected}
39
+ indeterminate={node.visualState === VisualState.indeterminate}
38
40
  class:invisible={hideDisabledCheckboxes}
39
41
  />
40
42
  {/if}
@@ -1,5 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { SelectionModes, type Node } from './types.js';
2
+ import { type Node, SelectionModes } from "./types.js";
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  checkboxes: SelectionModes;
@@ -1,13 +1,13 @@
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
- import { TreeHelper } from './index.js';
6
- import Branch from './Branch.svelte';
7
- import { SelectionProvider } from './providers/selection-provider.js';
8
- import { DragDropProvider } from './providers/drag-drop-provider.js';
9
- import uniq from 'lodash.uniq';
10
- import { calculateNewFocusedNode, parseMovementDirection } from './providers/movement-provider.js';
1
+ <script>import ContextMenu from "./menu/ContextMenu.svelte";
2
+ import { createEventDispatcher } from "svelte";
3
+ import { defaultClasses, defaultPropNames } from "./constants.js";
4
+ import { InsertionType, SelectionModes as SelectionModes } from "./types.js";
5
+ import { TreeHelper } from "./index.js";
6
+ import Branch from "./Branch.svelte";
7
+ import { SelectionProvider } from "./providers/selection-provider.js";
8
+ import { DragDropProvider } from "./providers/drag-drop-provider.js";
9
+ import uniq from "lodash.uniq";
10
+ import { calculateNewFocusedNode, parseMovementDirection } from "./providers/movement-provider.js";
11
11
  const dispatch = createEventDispatcher();
12
12
  export let treeId;
13
13
  /**
@@ -36,7 +36,7 @@ export let readonly = false;
36
36
  * Separator used to parse paths. It is used for getting node depth and getting parent node.
37
37
  * Default is '.'.
38
38
  */
39
- export let separator = '.';
39
+ export let separator = ".";
40
40
  /**
41
41
  * only leaf nodes can be selected, non-leaf nodes only select/deselect its children.
42
42
  * Their visual state is calculated based on their children
@@ -99,6 +99,7 @@ export let filter = null;
99
99
  * Used mostly for debugging
100
100
  */
101
101
  export let logger = null;
102
+ export let nodeSorter = null;
102
103
  /*
103
104
  * Drag and drop mode allows all nodes, that don't have dragDisabled property set to true
104
105
  * to be dragged and dropped. By default you can only insert at same level node you are dropping on,
@@ -122,15 +123,16 @@ let highlightedNode = null;
122
123
  let insertionType = InsertionType.none;
123
124
  let focusedNode = null;
124
125
  $: computedClasses = { ...defaultClasses, ...(customClasses ?? {}) };
125
- $: dragAndDrop && console.warn('Drag and drop is not supported in this version');
126
+ $: dragAndDrop && console.warn("Drag and drop is not supported in this version");
126
127
  $: helper = new TreeHelper({
127
- separator
128
+ separator,
129
+ nodeSorter
128
130
  });
129
131
  $: dragAndDropProvider = new DragDropProvider(helper);
130
132
  $: selectionProvider = new SelectionProvider(helper, recursiveSelection);
131
133
  $: computedTree = computeTree(helper, selectionProvider, tree, filter, props, expandedPaths, value);
132
134
  export function changeAllExpansion(changeTo) {
133
- debugLog('changing expansion of every node to ', changeTo ? 'expanded' : 'collapsed');
135
+ debugLog("changing expansion of every node to ", changeTo ? "expanded" : "collapsed");
134
136
  if (changeTo) {
135
137
  expandedPaths = computedTree.map((node) => node.path);
136
138
  }
@@ -140,11 +142,11 @@ export function changeAllExpansion(changeTo) {
140
142
  }
141
143
  export function expandToNode(targetNodePath) {
142
144
  if (!targetNodePath) {
143
- console.warn('Cannot expand to node with null path');
145
+ console.warn("Cannot expand to node with null path");
144
146
  return;
145
147
  }
146
148
  const parentPaths = helper.getParentsPaths(targetNodePath);
147
- debugLog("expanding to node '" + targetNodePath + "'" + ' parents', parentPaths);
149
+ debugLog("expanding to node '" + targetNodePath + "'" + " parents", parentPaths);
148
150
  expandedPaths = uniq([...expandedPaths, ...parentPaths]);
149
151
  }
150
152
  /**
@@ -153,7 +155,7 @@ export function expandToNode(targetNodePath) {
153
155
  */
154
156
  export function setNodeExpansion(nodePath, changeTo) {
155
157
  if (!nodePath) {
156
- console.warn('Cannot expand node with null path');
158
+ console.warn("Cannot expand node with null path");
157
159
  return;
158
160
  }
159
161
  if (changeTo === null) {
@@ -163,7 +165,7 @@ export function setNodeExpansion(nodePath, changeTo) {
163
165
  }
164
166
  export function setExpansions(expansions) {
165
167
  if (!Array.isArray(expansions)) {
166
- console.error('expansions must be an array');
168
+ console.error("expansions must be an array");
167
169
  return;
168
170
  }
169
171
  expandedPaths = expansions;
@@ -183,12 +185,12 @@ export function focusFirstNode() {
183
185
  return null;
184
186
  }
185
187
  focusedNode = rootChildren[0];
186
- dispatch('focus', focusedNode);
188
+ dispatch("focus", focusedNode);
187
189
  return focusedNode;
188
190
  }
189
191
  function computeTree(helper, selectionProvider, userProvidedTree, filter, props, expandedPaths, value) {
190
192
  if (!Array.isArray(userProvidedTree) || !Array.isArray(value)) {
191
- console.error('value and tree must be arrays!!');
193
+ console.error("value and tree must be arrays!!");
192
194
  return [];
193
195
  }
194
196
  const start = Date.now();
@@ -215,15 +217,15 @@ function onExpand(detail) {
215
217
  handleCallback(node);
216
218
  }
217
219
  //expansion events
218
- dispatch('expansion', {
220
+ dispatch("expansion", {
219
221
  node: node,
220
222
  value: changeTo
221
223
  });
222
224
  if (changeTo) {
223
- dispatch('expanded', node);
225
+ dispatch("expanded", node);
224
226
  }
225
227
  else {
226
- dispatch('closed', node);
228
+ dispatch("closed", node);
227
229
  }
228
230
  }
229
231
  function handleCallback(node) {
@@ -235,10 +237,10 @@ function handleCallback(node) {
235
237
  return;
236
238
  }
237
239
  if (loadChildrenAsync == null) {
238
- console.warn('loadChildrenAsync is not set, but useCallback is set to true on node with path', node.path);
240
+ console.warn("loadChildrenAsync is not set, but useCallback is set to true on node with path", node.path);
239
241
  return;
240
242
  }
241
- debugLog('calling callback for node', node);
243
+ debugLog("calling callback for node", node);
242
244
  // TODO mark node as loaded and don't call callback again
243
245
  // this is now responsibility of user
244
246
  loadChildrenAsync(node);
@@ -248,23 +250,24 @@ function onSelectionChanged(detail) {
248
250
  const nodePath = node.path;
249
251
  const changeTo = !selectionProvider.isNodeSelected(node);
250
252
  const newValue = selectionProvider.setSelection(computedTree, nodePath, changeTo, value);
251
- debugLog("changing selection of node '", nodePath, "' to ", changeTo, ' returning value ', newValue);
252
- dispatch('change', newValue);
253
- dispatch('selection', {
253
+ debugLog("changing selection of node '", nodePath, "' to ", changeTo, " returning value ", newValue);
254
+ dispatch("change", newValue);
255
+ dispatch("selection", {
254
256
  node: node,
255
257
  value: changeTo
256
258
  });
257
259
  if (changeTo) {
258
- dispatch('selected', { node, value: newValue });
260
+ dispatch("selected", { node, value: newValue });
259
261
  }
260
262
  else {
261
- dispatch('unselected', { node, value: newValue });
263
+ dispatch("unselected", { node, value: newValue });
262
264
  }
263
265
  }
264
266
  function openContextMenu(ce) {
265
267
  const { e, node } = ce.detail;
266
- if (!showContextMenu)
268
+ if (!showContextMenu) {
267
269
  return;
270
+ }
268
271
  e.preventDefault();
269
272
  ctxMenu.onRightClick(e, node);
270
273
  }
@@ -288,8 +291,8 @@ function onDragDrop({ detail: { node, event, element } }) {
288
291
  return;
289
292
  }
290
293
  highlightedNode = null;
291
- debugLog('DROPPED: ', draggedNode, 'on', node);
292
- dispatch('moved', {
294
+ debugLog("DROPPED: ", draggedNode, "on", node);
295
+ dispatch("moved", {
293
296
  node: draggedNode,
294
297
  target: node,
295
298
  insertType: insertionType
@@ -305,7 +308,7 @@ function onDragEnter({ detail: { node, event, element } }) {
305
308
  if (!dragAndDropProvider.isDropAllowed(draggedNode, node)) {
306
309
  return;
307
310
  }
308
- if (typeof dropDisabledCallback === 'function') {
311
+ if (typeof dropDisabledCallback === "function") {
309
312
  // possible bug, if the promise is resolved, when user is over another node
310
313
  dropDisabledCallback(draggedNode, node).then((dropDisabled) => {
311
314
  if (!dropDisabled) {
@@ -343,19 +346,19 @@ function onKeyPress(detail) {
343
346
  if (setExpansion !== null) {
344
347
  onExpand({ node: node, changeTo: setExpansion });
345
348
  }
346
- dispatch('focus', node);
349
+ dispatch("focus", node);
347
350
  return;
348
351
  }
349
- if (event.key === 'Enter' || event.key === ' ') {
352
+ if (event.key === "Enter" || event.key === " ") {
350
353
  onSelectionChanged({ node: targetNode });
351
354
  return;
352
355
  }
353
- if (event.key === 'Escape') {
356
+ if (event.key === "Escape") {
354
357
  focusedNode = null;
355
358
  if (document.activeElement instanceof HTMLElement) {
356
359
  document.activeElement.blur();
357
360
  }
358
- dispatch('focus-leave');
361
+ dispatch("focus-leave");
359
362
  return;
360
363
  }
361
364
  }
@@ -1,5 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { SelectionModes as SelectionModes, type Node, type Props, type CustomizableClasses, type DragEnterCallback, type ExpandedCallback, type NodeId, type ProvidedTree, type FilterFunction } from './types.js';
2
+ import { type CustomizableClasses, type DragEnterCallback, type ExpandedCallback, type FilterFunction, type Node, type NodeId, type NodeSorter, type Props, type ProvidedTree, SelectionModes as SelectionModes } from "./types.js";
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  treeId: string;
@@ -74,6 +74,7 @@ declare const __propDef: {
74
74
  * Log function that will be called when something happens in tree.
75
75
  * Used mostly for debugging
76
76
  */ logger?: ((...data: any[]) => void) | null | undefined;
77
+ nodeSorter?: NodeSorter | null | undefined;
77
78
  dragAndDrop?: boolean | undefined;
78
79
  /**
79
80
  * Callback that will be called when user drags above node.
@@ -1,4 +1,4 @@
1
- import type { HelperConfig, CustomizableClasses, Props } from './types.js';
1
+ import type { CustomizableClasses, HelperConfig, Props } from "./types.js";
2
2
  export declare const defaultPropNames: Props;
3
3
  export declare const defaultClasses: CustomizableClasses;
4
4
  export declare const defaultConfig: HelperConfig;
package/dist/constants.js CHANGED
@@ -1,33 +1,33 @@
1
1
  export const defaultPropNames = {
2
- nodePath: 'nodePath',
3
- nodeId: 'nodePath',
4
- hasChildren: 'hasChildren',
5
- useCallback: '__useCallback',
6
- priority: 'priority',
7
- dragDisabled: 'dragDisabled',
8
- insertDisabled: 'insertDisabled',
9
- nestAllowed: 'nestAllowed',
10
- checkbox: 'checkbox'
2
+ nodePath: "nodePath",
3
+ nodeId: "nodePath",
4
+ hasChildren: "hasChildren",
5
+ useCallback: "__useCallback",
6
+ priority: "priority",
7
+ dragDisabled: "dragDisabled",
8
+ insertDisabled: "insertDisabled",
9
+ nestAllowed: "nestAllowed",
10
+ checkbox: "checkbox"
11
11
  };
12
12
  export const defaultClasses = {
13
- treeClass: 'treeview',
14
- expandClass: 'inserting-highlighted',
15
- currentlyDraggedClass: 'currently-dragged',
16
- nodeClass: '',
17
- expandIcon: 'far fa-fw fa-plus-square',
18
- collapseIcon: 'far fa-fw fa-minus-square',
19
- insertLineClass: '',
20
- nestIcon: 'fas fa-level-down-alt'
13
+ treeClass: "treeview",
14
+ expandClass: "inserting-highlighted",
15
+ currentlyDraggedClass: "currently-dragged",
16
+ nodeClass: "",
17
+ expandIcon: "far fa-fw fa-plus-square",
18
+ collapseIcon: "far fa-fw fa-minus-square",
19
+ insertLineClass: "",
20
+ nestIcon: "fas fa-level-down-alt"
21
21
  };
22
22
  export const defaultConfig = {
23
- separator: '.'
23
+ separator: "."
24
24
  };
25
25
  export const capturedKeys = [
26
- 'ArrowUp',
27
- 'ArrowDown',
28
- 'ArrowLeft',
29
- 'ArrowRight',
30
- 'Enter',
31
- ' ',
32
- 'Escape'
26
+ "ArrowUp",
27
+ "ArrowDown",
28
+ "ArrowLeft",
29
+ "ArrowRight",
30
+ "Enter",
31
+ " ",
32
+ "Escape"
33
33
  ];
@@ -1,4 +1,4 @@
1
- import { type Node, type HelperConfig, type Tree, type Props, type FilterFunction } from '../types.js';
1
+ import { type FilterFunction, type HelperConfig, type Node, type Props, type Tree } from "../types.js";
2
2
  export declare class TreeHelper {
3
3
  config: HelperConfig;
4
4
  constructor(config?: HelperConfig);
@@ -10,10 +10,7 @@ export declare class TreeHelper {
10
10
  findNode(tree: Tree, nodePath: string): Node | null;
11
11
  nodePathIsChild(nodePath: string): boolean;
12
12
  getDirectChildren(tree: Tree, parentNodePath: string | null): Tree;
13
- allCHildren(tree: Tree, parentNodePath: string | null): Node[];
14
- getAllLeafNodes(tree: Tree): Node[];
15
- joinTrees(filteredTree: Tree, tree: Tree): Node[];
16
- mergeTrees(oldTree: Tree, addedTree: Tree, nodePath?: string): Node[];
13
+ allChildren(tree: Tree, parentNodePath: string | null): Node[];
17
14
  /** toggles expansion on
18
15
  */
19
16
  changeExpansion(targetNodePath: string, changeTo: boolean, previousExpandedPaths: string[]): string[];
@@ -28,7 +25,8 @@ export declare class TreeHelper {
28
25
  };
29
26
  getParentsPaths(targetNodePath: string): string[];
30
27
  getParents(tree: Tree, targetNode: Node): Node[];
31
- /** orders nodes by priorityProp
28
+ /**
29
+ * Sorts nodes if sorter override is specified
32
30
  */
33
- orderByPriority(tree: Tree): Tree;
31
+ sortNodes(tree: Tree): Tree;
34
32
  }
@@ -1,7 +1,6 @@
1
- import orderBy from 'lodash.unionby'; // used by tree merge
2
- import uniqueBy from 'lodash.uniqby'; // used by tree merge
3
- import { VisualState } from '../types.js';
4
- import { defaultConfig } from '../constants.js';
1
+ import uniqueBy from "lodash.uniqby"; // used by tree merge
2
+ import { VisualState } from "../types.js";
3
+ import { defaultConfig } from "../constants.js";
5
4
  export class TreeHelper {
6
5
  config;
7
6
  constructor(config = defaultConfig) {
@@ -35,24 +34,27 @@ export class TreeHelper {
35
34
  markExpanded(tree, expandedPaths) {
36
35
  {
37
36
  tree.forEach((node) => {
38
- node.expanded = expandedPaths.includes(node.path ?? '');
37
+ node.expanded = expandedPaths.includes(node.path ?? "");
39
38
  });
40
39
  }
41
40
  }
42
41
  //#region basic helpers
43
42
  getParentNodePath(nodePath) {
44
- if (nodePath == null)
45
- throw new Error('cannot get parent of root');
43
+ if (nodePath == null) {
44
+ throw new Error("cannot get parent of root");
45
+ }
46
46
  const separator = this.config.separator;
47
47
  const parentPath = nodePath?.substring(0, nodePath.lastIndexOf(separator));
48
- if (parentPath === '')
48
+ if (parentPath === "") {
49
49
  return null;
50
+ }
50
51
  return parentPath ?? null;
51
52
  }
52
53
  isChildrenOf(parentNodePath, childrenNodePath) {
53
- if (parentNodePath === childrenNodePath)
54
+ if (parentNodePath === childrenNodePath) {
54
55
  return false;
55
- return childrenNodePath?.startsWith(parentNodePath ?? '');
56
+ }
57
+ return childrenNodePath?.startsWith(parentNodePath ?? "");
56
58
  }
57
59
  hasChildren(tree, nodePath) {
58
60
  return tree?.find((x) => this.getParentNodePath(x.path) === nodePath);
@@ -62,31 +64,30 @@ export class TreeHelper {
62
64
  }
63
65
  nodePathIsChild(nodePath) {
64
66
  const separator = this.config.separator;
65
- const includesSeparator = nodePath?.includes(separator);
66
- return includesSeparator;
67
+ return nodePath?.includes(separator);
67
68
  }
68
69
  getDirectChildren(tree, parentNodePath) {
69
70
  const children = tree.filter((node) => !parentNodePath
70
71
  ? !this.nodePathIsChild(node.path)
71
72
  : this.getParentNodePath(node.path) === parentNodePath);
72
- const ordered = this.orderByPriority(children);
73
- return ordered;
74
- }
75
- allCHildren(tree, parentNodePath) {
76
- const children = tree.filter((node) => this.isChildrenOf(parentNodePath, node.path));
77
- return children;
78
- }
79
- getAllLeafNodes(tree) {
80
- return tree.filter((node) => {
81
- return node.hasChildren !== true;
82
- });
83
- }
84
- joinTrees(filteredTree, tree) {
85
- return tree.map((node) => this.findNode(filteredTree, node.path) || node);
86
- }
87
- mergeTrees(oldTree, addedTree, nodePath = 'nodePath') {
88
- return orderBy(addedTree, oldTree, nodePath);
89
- }
73
+ return this.sortNodes(children);
74
+ }
75
+ allChildren(tree, parentNodePath) {
76
+ return tree.filter((node) => this.isChildrenOf(parentNodePath, node.path));
77
+ }
78
+ // getAllLeafNodes(tree: Tree) {
79
+ // return tree.filter((node) => {
80
+ // return node.hasChildren !== true;
81
+ // });
82
+ // }
83
+ //
84
+ // joinTrees(filteredTree: Tree, tree: Tree) {
85
+ // return tree.map((node) => this.findNode(filteredTree, node.path) || node);
86
+ // }
87
+ //
88
+ // mergeTrees(oldTree: Tree, addedTree: Tree, nodePath = 'nodePath') {
89
+ // return orderBy(addedTree, oldTree, nodePath);
90
+ // }
90
91
  /** toggles expansion on
91
92
  */
92
93
  changeExpansion(targetNodePath, changeTo, previousExpandedPaths) {
@@ -107,15 +108,17 @@ export class TreeHelper {
107
108
  }
108
109
  //based on number of dots
109
110
  getDepthLevel(nodePath) {
110
- if (nodePath == null)
111
+ if (nodePath == null) {
111
112
  return 0;
113
+ }
112
114
  const separator = this.config.separator;
113
115
  return nodePath.split(separator).length - 1;
114
116
  }
115
117
  //#endregion
116
118
  searchTree(tree, filter) {
117
- if (!filter)
119
+ if (!filter) {
118
120
  return { count: tree.length, tree };
121
+ }
119
122
  const filteredNodes = tree.filter(filter);
120
123
  const resultNodes = [];
121
124
  // add all parents from each node
@@ -133,7 +136,7 @@ export class TreeHelper {
133
136
  // TODO refactor
134
137
  let nodePath = targetNodePath;
135
138
  // get all parents
136
- while (nodePath !== null && nodePath !== '') {
139
+ while (nodePath !== null && nodePath !== "") {
137
140
  nodePath = this.getParentNodePath(nodePath);
138
141
  if (nodePath === null) {
139
142
  break;
@@ -148,14 +151,15 @@ export class TreeHelper {
148
151
  }
149
152
  const parentsPaths = this.getParentsPaths(targetNode.path);
150
153
  //find nodes for given ids
151
- const parentNodes = tree.filter((node) => parentsPaths.some((parentNodePath) => node.path === parentNodePath));
152
- return parentNodes;
154
+ return tree.filter((node) => parentsPaths.some((parentNodePath) => node.path === parentNodePath));
153
155
  }
154
- /** orders nodes by priorityProp
156
+ /**
157
+ * Sorts nodes if sorter override is specified
155
158
  */
156
- orderByPriority(tree) {
157
- // TODO investigate that it really works
158
- tree.sort((a, b) => (b.priority ? a.priority - b.priority : 1));
159
- return tree;
159
+ sortNodes(tree) {
160
+ if (!this.config.nodeSorter) {
161
+ return tree;
162
+ }
163
+ return tree.slice().sort(this.config.nodeSorter);
160
164
  }
161
165
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from './constants.js';
2
- export { TreeHelper } from './helpers/tree-helper.js';
3
- export { default as TreeView } from './TreeView.svelte';
4
- export { default as MenuDivider } from './menu/MenuDivider.svelte';
5
- export { default as MenuOption } from './menu/MenuOption.svelte';
6
- export * from './types.js';
1
+ export * from "./constants.js";
2
+ export { TreeHelper } from "./helpers/tree-helper.js";
3
+ export { default as TreeView } from "./TreeView.svelte";
4
+ export { default as MenuDivider } from "./menu/MenuDivider.svelte";
5
+ export { default as MenuOption } from "./menu/MenuOption.svelte";
6
+ export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export * from './constants.js';
2
- export { TreeHelper } from './helpers/tree-helper.js';
3
- export { default as TreeView } from './TreeView.svelte';
4
- export { default as MenuDivider } from './menu/MenuDivider.svelte';
5
- export { default as MenuOption } from './menu/MenuOption.svelte';
6
- export * from './types.js';
1
+ export * from "./constants.js";
2
+ export { TreeHelper } from "./helpers/tree-helper.js";
3
+ export { default as TreeView } from "./TreeView.svelte";
4
+ export { default as MenuDivider } from "./menu/MenuDivider.svelte";
5
+ export { default as MenuOption } from "./menu/MenuOption.svelte";
6
+ export * from "./types.js";
@@ -1,4 +1,4 @@
1
- <script>import Menu from './Menu.svelte';
1
+ <script>import Menu from "./Menu.svelte";
2
2
  let pos = { x: 0, y: 0 };
3
3
  let showMenu = false;
4
4
  let clickedNode = null;
@@ -1,5 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import type { Node } from '../types.js';
2
+ import type { Node } from "../types.js";
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  onRightClick?: ((event: MouseEvent, node: Node) => Promise<void>) | undefined;
@@ -3,32 +3,39 @@
3
3
  <script>
4
4
  // @ts-nocheck
5
5
 
6
- import { onMount, setContext, createEventDispatcher } from 'svelte';
7
- import { fade } from 'svelte/transition';
8
- import { key } from './menu.js';
6
+ import {createEventDispatcher, setContext} from "svelte"
7
+ import {fade} from "svelte/transition"
8
+ import {key} from "./menu.js"
9
9
 
10
- export let x;
11
- export let y;
10
+ export let x
11
+ export let y
12
12
 
13
13
  // whenever x and y is changed, restrict box to be within bounds
14
14
  $: (() => {
15
- if (!menuEl) return;
15
+ if (!menuEl) {
16
+ return
17
+ }
16
18
 
17
- const rect = menuEl.getBoundingClientRect();
18
- x = Math.min(window.innerWidth - rect.width, x);
19
- if (y > window.innerHeight - rect.height) y -= rect.height;
20
- })(x, y);
19
+ const rect = menuEl.getBoundingClientRect()
20
+ x = Math.min(window.innerWidth - rect.width, x)
21
+ if (y > window.innerHeight - rect.height) {
22
+ y -= rect.height
23
+ }
24
+ })(x, y)
21
25
 
22
- const dispatch = createEventDispatcher();
26
+ const dispatch = createEventDispatcher()
23
27
 
24
28
  setContext(key, {
25
- dispatchClick: () => dispatch('click')
26
- });
29
+ dispatchClick: () => dispatch("click")
30
+ })
31
+
32
+ let menuEl
27
33
 
28
- let menuEl;
29
34
  function onPageClick(e) {
30
- if (e.target === menuEl || menuEl.contains(e.target)) return;
31
- dispatch('clickoutside');
35
+ if (e.target === menuEl || menuEl.contains(e.target)) {
36
+ return
37
+ }
38
+ dispatch("clickoutside")
32
39
  }
33
40
  </script>
34
41
 
@@ -1,23 +1,24 @@
1
1
  <script>
2
2
  // @ts-nocheck
3
3
 
4
- import { onMount, getContext } from 'svelte';
5
- import { key } from './menu.js';
4
+ import {createEventDispatcher, getContext} from "svelte"
5
+ import {key} from "./menu.js"
6
6
 
7
- export let isDisabled = false;
8
- export let text = '';
7
+ export let isDisabled = false
8
+ export let text = ""
9
9
 
10
- import { createEventDispatcher } from 'svelte';
11
- const dispatch = createEventDispatcher();
10
+ const dispatch = createEventDispatcher()
12
11
 
13
- const { dispatchClick } = getContext(key);
12
+ const {dispatchClick} = getContext(key)
14
13
 
15
14
  const handleClick = (e) => {
16
- if (isDisabled) return;
15
+ if (isDisabled) {
16
+ return
17
+ }
17
18
 
18
- dispatch('click');
19
- dispatchClick();
20
- };
19
+ dispatch("click")
20
+ dispatchClick()
21
+ }
21
22
  </script>
22
23
 
23
24
  <div class:disabled={isDisabled} on:click={handleClick} on:keydown={handleClick} role="button" tabindex="">
@@ -37,12 +38,15 @@
37
38
  align-items: center;
38
39
  grid-gap: 5px;
39
40
  }
41
+
40
42
  div:hover {
41
43
  background: #0002;
42
44
  }
45
+
43
46
  div.disabled {
44
47
  color: #0006;
45
48
  }
49
+
46
50
  div.disabled:hover {
47
51
  background: white;
48
52
  }
package/dist/menu/menu.js CHANGED
@@ -1,3 +1,3 @@
1
- const key = {};
1
+ const key = {}
2
2
 
3
- export { key };
3
+ export {key}
@@ -1,5 +1,5 @@
1
- import type { TreeHelper } from '../helpers/tree-helper.js';
2
- import { type Node, InsertionType } from '../types.js';
1
+ import type { TreeHelper } from "../helpers/tree-helper.js";
2
+ import { InsertionType, type Node } from "../types.js";
3
3
  export declare class DragDropProvider {
4
4
  helper: TreeHelper;
5
5
  constructor(treeHelper: TreeHelper);
@@ -1,4 +1,4 @@
1
- import { InsertionType } from '../types.js';
1
+ import { InsertionType } from "../types.js";
2
2
  export class DragDropProvider {
3
3
  helper;
4
4
  constructor(treeHelper) {
@@ -1,5 +1,5 @@
1
- import type { TreeHelper } from '../helpers/tree-helper.js';
2
- import { KeyboardMovement as MovementDirection, type Node } from '../types.js';
1
+ import type { TreeHelper } from "../helpers/tree-helper.js";
2
+ import { KeyboardMovement as MovementDirection, type Node } from "../types.js";
3
3
  export declare function calculateNewFocusedNode(helper: TreeHelper, tree: Node[], targetNode: Node, movementDirection: MovementDirection): {
4
4
  node: Node;
5
5
  setExpansion: boolean | null;
@@ -1,4 +1,4 @@
1
- import { KeyboardMovement as MovementDirection } from '../types.js';
1
+ import { KeyboardMovement as MovementDirection } from "../types.js";
2
2
  export function calculateNewFocusedNode(helper, tree, targetNode, movementDirection) {
3
3
  // TODO this could use some refactoring
4
4
  const parentNodePath = helper.getParentNodePath(targetNode.path);
@@ -66,7 +66,7 @@ export function calculateNewFocusedNode(helper, tree, targetNode, movementDirect
66
66
  const parentNode = helper.findNode(tree, parentNodePath);
67
67
  // assertion
68
68
  if (!parentNode) {
69
- console.warn('Parent node not found, this should never happen');
69
+ console.warn("Parent node not found, this should never happen");
70
70
  return wrapReturn(targetNode);
71
71
  }
72
72
  return wrapReturn(parentNode);
@@ -75,13 +75,13 @@ export function calculateNewFocusedNode(helper, tree, targetNode, movementDirect
75
75
  }
76
76
  export function parseMovementDirection(key) {
77
77
  switch (key) {
78
- case 'ArrowRight':
78
+ case "ArrowRight":
79
79
  return MovementDirection.Right;
80
- case 'ArrowLeft':
80
+ case "ArrowLeft":
81
81
  return MovementDirection.Left;
82
- case 'ArrowDown':
82
+ case "ArrowDown":
83
83
  return MovementDirection.Down;
84
- case 'ArrowUp':
84
+ case "ArrowUp":
85
85
  return MovementDirection.Up;
86
86
  default:
87
87
  return null;
@@ -95,7 +95,8 @@ function getRelativeSibling(helper, tree, node, relativeIndex) {
95
95
  const parentDirectChildren = helper.getDirectChildren(tree, parentNodePath);
96
96
  const nodeIndex = parentDirectChildren.findIndex((x) => x.path === node.path);
97
97
  const siblingIndex = nodeIndex + relativeIndex;
98
- if (siblingIndex < 0 || siblingIndex >= parentDirectChildren.length)
98
+ if (siblingIndex < 0 || siblingIndex >= parentDirectChildren.length) {
99
99
  return null;
100
+ }
100
101
  return parentDirectChildren[siblingIndex];
101
102
  }
@@ -1,5 +1,5 @@
1
- import type { TreeHelper } from '../helpers/tree-helper.js';
2
- import { SelectionModes, type Tree, type TreeVisualStates, type Node, type NodeId } from '../types.js';
1
+ import type { TreeHelper } from "../helpers/tree-helper.js";
2
+ import { type Node, type NodeId, SelectionModes, type Tree, type TreeVisualStates } from "../types.js";
3
3
  export declare class SelectionProvider {
4
4
  helper: TreeHelper;
5
5
  recursiveMode: boolean;
@@ -1,4 +1,4 @@
1
- import { SelectionModes, VisualState } from '../types.js';
1
+ import { SelectionModes, VisualState } from "../types.js";
2
2
  export class SelectionProvider {
3
3
  helper;
4
4
  recursiveMode;
@@ -9,13 +9,13 @@ export class SelectionProvider {
9
9
  markSelected(tree, selectedNodeIds) {
10
10
  const visualStates = this.computeVisualStates(tree, selectedNodeIds);
11
11
  tree.forEach((node) => {
12
- if (selectedNodeIds.includes(node.id ?? '')) {
12
+ if (selectedNodeIds.includes(node.id ?? "")) {
13
13
  node.selected = true;
14
14
  node.visualState = VisualState.selected;
15
15
  return;
16
16
  }
17
17
  node.selected = false;
18
- const visualState = visualStates[node.id ?? ''];
18
+ const visualState = visualStates[node.id ?? ""];
19
19
  if (!visualState) {
20
20
  node.visualState = VisualState.notSelected;
21
21
  }
@@ -27,8 +27,9 @@ export class SelectionProvider {
27
27
  }
28
28
  isSelected(nodeId, visualStates, selectedNodeIds) {
29
29
  const selected = selectedNodeIds.includes(nodeId);
30
- if (selected)
30
+ if (selected) {
31
31
  return true;
32
+ }
32
33
  const visualState = visualStates[nodeId];
33
34
  return visualState === VisualState.selected;
34
35
  }
@@ -47,10 +48,10 @@ export class SelectionProvider {
47
48
  else {
48
49
  if (!node) {
49
50
  // throw new Error('Node not found ' + nodePath);
50
- console.warn('Node %s doesnt exits', nodePath);
51
+ console.warn("Node %s doesnt exits", nodePath);
51
52
  return oldSelection;
52
53
  }
53
- const nodeId = node.id ?? '';
54
+ const nodeId = node.id ?? "";
54
55
  // prevent double selection
55
56
  const filteredSelection = oldSelection.filter((x) => x !== nodeId);
56
57
  if (changeTo === false) {
@@ -68,14 +69,15 @@ export class SelectionProvider {
68
69
  rootELements.forEach((node) => {
69
70
  if (node.hasChildren == true) {
70
71
  const result = this.computeVisualStateRecursively(tree, node, selectedNodeIds, visualStates);
71
- visualStates[node.id ?? ''] = result.state;
72
+ visualStates[node.id ?? ""] = result.state;
72
73
  }
73
74
  });
74
75
  return visualStates;
75
76
  }
76
77
  computeVisualState(directChildrenVisualStates) {
77
- if (!directChildrenVisualStates || directChildrenVisualStates?.length == 0)
78
+ if (!directChildrenVisualStates || directChildrenVisualStates?.length == 0) {
78
79
  return VisualState.selected;
80
+ }
79
81
  //if every child is selected or vs=true return true
80
82
  if (directChildrenVisualStates.every((state) => state === VisualState.selected)) {
81
83
  return VisualState.selected;
@@ -101,14 +103,14 @@ export class SelectionProvider {
101
103
  // using recustion compute from leaft nodes to root
102
104
  directChildren.forEach((child) => {
103
105
  if (!child.hasChildren) {
104
- const childState = selectedNodeIds.includes(child.id ?? '')
106
+ const childState = selectedNodeIds.includes(child.id ?? "")
105
107
  ? VisualState.selected
106
108
  : VisualState.notSelected;
107
109
  directChildrenStates.push(childState);
108
110
  return;
109
111
  }
110
112
  const result = this.computeVisualStateRecursively(tree, child, selectedNodeIds, visualStates);
111
- visualStates[child.id ?? ''] = result.state;
113
+ visualStates[child.id ?? ""] = result.state;
112
114
  if (!result.ignore) {
113
115
  directChildrenStates.push(result.state);
114
116
  }
@@ -121,7 +123,7 @@ export class SelectionProvider {
121
123
  let newSelection = [...oldSelection];
122
124
  tree.forEach((node) => {
123
125
  // match itself and all children
124
- if (node.path?.startsWith(parentNodePath ? parentNodePath + this.helper.config.separator : '')) {
126
+ if (node.path?.startsWith(parentNodePath ? parentNodePath + this.helper.config.separator : "")) {
125
127
  //don't change if not selectable
126
128
  if (!isSelectable(node, SelectionModes.all)) {
127
129
  return;
@@ -131,9 +133,9 @@ export class SelectionProvider {
131
133
  return;
132
134
  }
133
135
  // prevent double selection
134
- newSelection = newSelection.filter((x) => x !== node.id ?? '');
136
+ newSelection = newSelection.filter((x) => x !== (node.id ?? ""));
135
137
  if (changeTo === true) {
136
- newSelection.push(node.id ?? '');
138
+ newSelection.push(node.id ?? "");
137
139
  }
138
140
  }
139
141
  });
@@ -1,110 +1,121 @@
1
-
2
1
  $treeview-lines: solid black 1px
3
2
  .treeview
4
- padding: 0
5
- //will show lines if you set show-lines to root element
6
- &.show-lines
7
- ul
8
- &:before
9
- border-left: $treeview-lines
10
- li:before
11
- border-top: $treeview-lines
12
- margin: 0
13
- padding: 0
14
- list-style: none
15
- ul, li
16
- margin: 0
17
- padding: 0
18
- list-style: none
19
- ul
20
- margin-left: 0.4em
21
- position: relative
22
- margin-left: .3em
23
- &:before
24
- content: ""
25
- display: block
26
- width: 0
27
- position: absolute
28
- top: 0
29
- bottom: 0
30
- left: 0
31
- li:before
32
- content: ""
33
- display: block
34
- width: 10px
35
- height: 0
36
- margin-top: -1px
37
- position: absolute
38
- top: 0.8em
39
- left: 0
40
- li:not(.has-children):before
41
- width: 26px
42
-
43
- li:last-child:before
44
- background: #fff
45
- height: auto
46
- top: 1em
47
- bottom: 0
48
-
49
- li
50
- margin: 0
51
- padding: 0 0.8em
52
- color: #555
53
- font-weight: 700
54
- position: relative
55
-
56
- .tree-item
57
- display: flex
58
- column-gap: 0.4em
59
- align-items: center
60
- padding: 4px 0
61
-
62
- .no-arrow
63
- padding-left: .5rem
64
-
65
- .arrow
66
- cursor: pointer
67
- display: inline-block
68
-
69
- .arrowDown
70
- transform: rotate(90deg)
71
-
72
- .invisible
73
- visibility: hidden
74
-
75
- .inserting-highlighted
76
- color: red
77
- .hover
78
- font-weight: bold
79
- .insert-line
80
- position: absolute
81
- left: 0
82
- z-index: 99
83
- height: 2px
84
- width: 200px
85
- background-color: blue
86
- display: block
87
- border-radius: 3px
88
- margin-left: 28px
89
- pointer-events: none //! this is needed to fix flickering issue
90
- // margin-bottom: -2px
91
- // margin-top: -2px
92
- .insert-line-child
93
- margin-left: calc( 28px + 5em )
94
- background-color: red
95
- height: 6px
96
-
97
- .insert-line-wrapper
98
- position: relative
99
-
100
- .currently-dragged
101
- color: LightGray
102
- .pointer-cursor
103
- cursor: grab
104
- .expansion-button
105
- all: unset
106
- .fixed-icon
107
- text-align: center
108
- width: 1.25em
109
- // fix deformation on small screens
110
- min-width: 1.25em
3
+ padding: 0
4
+ //will show lines if you set show-lines to root element
5
+ &.show-lines
6
+ ul
7
+ &:before
8
+ border-left: $treeview-lines
9
+
10
+ li:before
11
+ border-top: $treeview-lines
12
+ margin: 0
13
+ padding: 0
14
+ list-style: none
15
+
16
+ ul, li
17
+ margin: 0
18
+ padding: 0
19
+ list-style: none
20
+
21
+ ul
22
+ margin-left: 0.4em
23
+ position: relative
24
+ margin-left: .3em
25
+
26
+ &:before
27
+ content: ""
28
+ display: block
29
+ width: 0
30
+ position: absolute
31
+ top: 0
32
+ bottom: 0
33
+ left: 0
34
+
35
+ li:before
36
+ content: ""
37
+ display: block
38
+ width: 10px
39
+ height: 0
40
+ margin-top: -1px
41
+ position: absolute
42
+ top: 0.8em
43
+ left: 0
44
+
45
+ li:not(.has-children):before
46
+ width: 26px
47
+
48
+ li:last-child:before
49
+ background: #fff
50
+ height: auto
51
+ top: 1em
52
+ bottom: 0
53
+
54
+ li
55
+ margin: 0
56
+ padding: 0 0.8em
57
+ color: #555
58
+ font-weight: 700
59
+ position: relative
60
+
61
+ .tree-item
62
+ display: flex
63
+ column-gap: 0.4em
64
+ align-items: center
65
+ padding: 4px 0
66
+
67
+ .no-arrow
68
+ padding-left: .5rem
69
+
70
+ .arrow
71
+ cursor: pointer
72
+ display: inline-block
73
+
74
+ .arrowDown
75
+ transform: rotate(90deg)
76
+
77
+ .invisible
78
+ visibility: hidden
79
+
80
+ .inserting-highlighted
81
+ color: red
82
+
83
+ .hover
84
+ font-weight: bold
85
+
86
+ .insert-line
87
+ position: absolute
88
+ left: 0
89
+ z-index: 99
90
+ height: 2px
91
+ width: 200px
92
+ background-color: blue
93
+ display: block
94
+ border-radius: 3px
95
+ margin-left: 28px
96
+ pointer-events: none
97
+ //! this is needed to fix flickering issue
98
+ // margin-bottom: -2px
99
+ // margin-top: -2px
100
+ .insert-line-child
101
+ margin-left: calc(28px + 5em)
102
+ background-color: red
103
+ height: 6px
104
+
105
+ .insert-line-wrapper
106
+ position: relative
107
+
108
+ .currently-dragged
109
+ color: LightGray
110
+
111
+ .pointer-cursor
112
+ cursor: grab
113
+
114
+ .expansion-button
115
+ all: unset
116
+
117
+ .fixed-icon
118
+ text-align: center
119
+ width: 1.25em
120
+ // fix deformation on small screens
121
+ min-width: 1.25em
package/dist/types.d.ts CHANGED
@@ -63,6 +63,7 @@ export type BeforeMovedCallback = (draggedNode: Node, oldParent: Node, newParent
63
63
  export type ExpandedCallback = (node: Node) => Promise<void>;
64
64
  export type HelperConfig = {
65
65
  separator: string;
66
+ nodeSorter?: NodeSorter | null;
66
67
  };
67
68
  export declare enum InsertionType {
68
69
  nest = "nest",
@@ -81,3 +82,4 @@ export declare enum KeyboardMovement {
81
82
  Left = "ArrowLeft",
82
83
  Right = "ArrowRight"
83
84
  }
85
+ export type NodeSorter = (left: Node, right: Node) => number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenmate/svelte-treeview",
3
- "version": "1.2.11",
3
+ "version": "2.0.0",
4
4
  "repository": {
5
5
  "url": "https://github.com/keenmate/svelte-treeview"
6
6
  },