@keenmate/svelte-treeview 0.3.4 → 0.3.6

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 CHANGED
@@ -4,10 +4,35 @@ The most elaborate treeview for svelte on earth (or even in our galaxy).
4
4
 
5
5
  ## Features
6
6
 
7
+ - load new nodes whne expanding
7
8
  - automatically expanding to given depth
8
9
  - customization of all object properties
9
- -
10
+ - checkboxes enabled on whole tree or based on property
11
+ - recursive seletion mode, where leafes can be selected
12
+ - build-in support for search
10
13
 
11
- ## Current state
14
+ ## Instalation
12
15
 
13
- svelte-treeview is undergoing big upgrade righ now, so docs will be added later.
16
+ install the package `@keenmate/svelte-treeview` using your favourite package manager.
17
+
18
+ Font awesome is required for expand/collapse icons.
19
+
20
+ ## Minimal usage
21
+
22
+
23
+ ```svelte
24
+ <script lang="ts">
25
+ import { TreeView } from '$lib/index.js';
26
+
27
+ let tree = [
28
+ { nodePath: 'animals', title: 'Animals', hasChildren: true },
29
+ //...
30
+ { nodePath: 'animals.insects.butterflies', title: 'Butterflies' }
31
+ ];
32
+ </script>
33
+
34
+ <TreeView {tree} treeId="my-tree" let:node>
35
+ {node.title}
36
+ </TreeView>
37
+
38
+ ```
@@ -1,6 +1,6 @@
1
1
  <script>import { createEventDispatcher } from 'svelte';
2
2
  import Checkbox from './Checkbox.svelte';
3
- import { SelectionModes } from './types.js';
3
+ import { SelectionModes, InsertionType } from './types.js';
4
4
  const dispatch = createEventDispatcher();
5
5
  export let tree;
6
6
  export let treeId;
@@ -22,14 +22,8 @@ export let canNest;
22
22
  export let validTarget;
23
23
  export let insPos;
24
24
  const getNodeId = (node) => `${treeId}-${helper.path(node)}`;
25
- // get children nodes
26
- function getChildren(tree) {
27
- const directChildren = helper.getDirectChildren(tree, helper.path(branchRootNode));
28
- const orderedChildren = helper.dragDrop.OrderByPriority(directChildren);
29
- return orderedChildren;
30
- }
31
- function toggleExpansion(node) {
32
- dispatch('internal-expand', { node: node });
25
+ function setExpansion(node, changeTo) {
26
+ dispatch('internal-expand', { node: node, changeTo });
33
27
  }
34
28
  function isExpanded(node, depth, expandToDepth) {
35
29
  const nodeExpanded = helper.props.expanded(node);
@@ -100,7 +94,7 @@ let liElements = {};
100
94
  class:child-menu={childDepth > 0}
101
95
  class={childDepth === 0 ? classes.treeClass : ''}
102
96
  >
103
- {#each getChildren(tree) as node (getNodeId(node))}
97
+ {#each helper.getDirectChildren(tree, helper.path(branchRootNode)) as node (getNodeId(node))}
104
98
  {@const nesthighlighed = highlightNesting(node, highlightedNode, validTarget, canNest)}
105
99
  {@const insertHighlighted = highlightInsert(node, highlightedNode, validTarget, canNest)}
106
100
  {@const expanded = isExpanded(node, childDepth, expandTo)}
@@ -122,7 +116,7 @@ let liElements = {};
122
116
  on:dragleave|stopPropagation={(e) => handleDragLeave(e, node, liElements[getNodeId(node)])}
123
117
  bind:this={liElements[getNodeId(node)]}
124
118
  >
125
- {#if insPos == 1 && insertHighlighted}
119
+ {#if insPos == InsertionType.above && insertHighlighted}
126
120
  <div class="insert-line-wrapper">
127
121
  <div class="insert-line {classes.inserLineClass}" />
128
122
  </div>
@@ -141,7 +135,7 @@ let liElements = {};
141
135
  >
142
136
  {#if hasChildren}
143
137
  <!-- svelte-ignore a11y-click-events-have-key-events -->
144
- <span on:click={() => toggleExpansion(node)}>
138
+ <span on:click={() => setExpansion(node, !expanded)}>
145
139
  <!-- use callback overrides expanded -->
146
140
  <i
147
141
  class="far {expanded ? classes.expandedToggleClass : classes.collapsedToggleClass}"
@@ -208,7 +202,7 @@ let liElements = {};
208
202
  <ul class:child-menu={childDepth > 0} />
209
203
  {/if}
210
204
  <!-- Show line if insering -->
211
- {#if insPos == -1 && insertHighlighted}
205
+ {#if insPos === InsertionType.below && insertHighlighted}
212
206
  <div class="insert-line-wrapper">
213
207
  <div class="insert-line {classes.inserLineClass}" />
214
208
  </div>
@@ -1,5 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { SelectionModes, type InsertionType, type Node } from './types.js';
2
+ import { SelectionModes, InsertionType, type Node } from './types.js';
3
3
  import type { CustomizableClasses, TreeHelper } from './index.js';
4
4
  declare const __propDef: {
5
5
  props: {
@@ -1,5 +1,6 @@
1
1
  <script>import { createEventDispatcher } from 'svelte';
2
2
  import { SelectionModes } from './types.js';
3
+ import { SelectionProvider } from './providers/selection-provider.js';
3
4
  export let checkboxes;
4
5
  export let helper;
5
6
  export let recursive;
@@ -16,6 +17,8 @@ $: {
16
17
  indeterminate = false;
17
18
  }
18
19
  }
20
+ // TODO pass from root
21
+ $: selectionProvider = new SelectionProvider(helper, recursive);
19
22
  const dispatch = createEventDispatcher();
20
23
  function onSelect(node) {
21
24
  dispatch('select', { node });
@@ -23,7 +26,7 @@ function onSelect(node) {
23
26
  </script>
24
27
 
25
28
  {#if checkboxes == SelectionModes.perNode || checkboxes == SelectionModes.all}
26
- {#if helper.selection.isSelectable(node, checkboxes)}
29
+ {#if selectionProvider.isSelectable(node, checkboxes)}
27
30
  <!-- select node -->
28
31
  {#if !recursive || (recursive && !helper.props.hasChildren(node))}
29
32
  <input
@@ -5,12 +5,13 @@ import { SelectionModes as SelectionModes } from './types.js';
5
5
  import { TreeHelper } from './index.js';
6
6
  import Branch from './Branch.svelte';
7
7
  import { PropertyHelper } from './helpers/property-helper.js';
8
+ import { SelectionProvider } from './providers/selection-provider.js';
8
9
  const dispatch = createEventDispatcher();
9
10
  export let treeId;
10
11
  /**
11
12
  * Array of nodes that represent tree structure.
12
13
  * Each node should have unique path
13
- * All tree modifications are node by modyfing this array, so you need to bind it to parent component
14
+ * All tree modifications are made by modifying this array, so you need to bind it to parent component
14
15
  */
15
16
  export let tree;
16
17
  /**
@@ -118,6 +119,7 @@ $: helper = new TreeHelper(propHelper, {
118
119
  checkboxes: selectionMode,
119
120
  separator
120
121
  });
122
+ $: selectionProvider = new SelectionProvider(helper, recursiveSelection);
121
123
  $: filteredTree = helper.searchTree(tree, filter);
122
124
  // compute vissual tree still caleed twice, because if we force update changes tree
123
125
  // which fires this event again
@@ -129,8 +131,7 @@ $: canNest =
129
131
  (propHelper.insertDisabled(highlightedNode) || canNestPos || canNestTime) &&
130
132
  propHelper.nestDisabled(highlightedNode) !== true;
131
133
  function onExpand(event) {
132
- const { node } = event.detail;
133
- const changeTo = !propHelper.expanded(node);
134
+ const { node, changeTo } = event.detail;
134
135
  helper.changeExpansion(tree, node, changeTo);
135
136
  debugLog("changed expansion of node '", helper.path(node), "' to ", changeTo);
136
137
  forceUpdate();
@@ -192,13 +193,13 @@ function computeVisualTree(_tree) {
192
193
  return;
193
194
  }
194
195
  debugLog('computing visual tree', { tree: _tree });
195
- helper.selection.recomputeAllVisualStates(_tree);
196
+ selectionProvider.recomputeAllVisualStates(_tree);
196
197
  }
197
198
  function onSelectionChanged(event) {
198
199
  const { node } = event.detail;
199
200
  const nodePath = helper.path(node);
200
- const changeTo = !helper.selection.isSelected(node);
201
- helper.selection.setSelection(tree, nodePath, changeTo);
201
+ const changeTo = !selectionProvider.isSelected(node);
202
+ selectionProvider.setSelection(tree, nodePath, changeTo);
202
203
  debugLog("changing selection of node '", nodePath, "' to ", !propHelper.selected(node));
203
204
  forceUpdate();
204
205
  dispatch('selection', {
@@ -227,154 +228,173 @@ function debugLog(...data) {
227
228
  function forceUpdate() {
228
229
  tree = tree;
229
230
  }
230
- //#region drag and drop
231
- function handleDragStart(e, node) {
232
- // dont allos drag if is draggable is false
233
- if (propHelper.isDraggable(node) === false) {
234
- e.preventDefault();
235
- return;
236
- }
237
- console.log('dragstart from: ' + helper.path(node));
238
- //@ts-ignore
239
- e.dataTransfer.dropEffect = 'move';
240
- //@ts-ignore
241
- e.dataTransfer.setData('node_id', helper.path(node));
242
- draggedPath = helper.path(node);
243
- }
244
- function handleDragDrop(e, node, el) {
245
- //should be necesary but just in case
246
- highlightedNode = null;
247
- if (readonly || !dragAndDrop)
248
- return;
249
- //@ts-ignore
250
- draggedPath = e.dataTransfer.getData('node_id');
251
- console.log(draggedPath + ' dropped on: ' + helper.path(node));
252
- //important to check if timetonest is set, otherwise you could spend 30 minutes fixing this shit :)
253
- if (timeToNest) {
254
- const nowTimestamp = new Date();
255
- canNestTime =
256
- (dragenterTimestamp ? nowTimestamp.getTime() - dragenterTimestamp.getTime() : 1) >
257
- timeToNest;
258
- }
259
- let newNode = helper.findNode(tree, draggedPath);
260
- let oldNode = { ...newNode };
261
- let oldParent = helper.findNode(tree, helper.getParentNodePath(draggedPath));
262
- let insType = canNest ? 0 : helper.dragDrop.getInsertionPosition(e, el);
263
- //cancel move if its not valid
264
- if (insType == 0 && propHelper.nestDisabled(node) === true)
265
- return;
266
- else if ((insType == -1 || insType == 1) && propHelper.insertDisabled(node) === true)
267
- return;
268
- //callback can cancell move
269
- if (beforeMovedCallback &&
270
- beforeMovedCallback(oldNode, oldParent, node, helper.dragDrop.huminifyInsType(insType)) ===
271
- false)
272
- return;
273
- tree = helper.dragDrop.moveNode(tree, draggedPath, helper.path(node), insType, recalculateNodePath);
274
- let newParent = helper.findNode(tree, helper.getParentNodePath(helper.path(newNode))) ?? null;
275
- dispatch('moved', {
276
- oldParent: oldParent,
277
- newParent: newParent,
278
- oldNode: oldNode,
279
- newNode: newNode,
280
- targetNode: node,
281
- insType: helper.dragDrop.huminifyInsType(insType)
282
- });
283
- //reset props
284
- dragenterTimestamp = null;
285
- draggedPath = null;
286
- highlightedNode = null;
287
- }
288
- function handleDragOver(e, node, el) {
289
- insPos = helper.dragDrop.getInsertionPosition(e, el);
290
- //if you are further away from right then treshold allow nesting
291
- // @ts-ignore
292
- let diff = e.x - e.target?.getBoundingClientRect()?.x;
293
- if (pixelNestTreshold && diff > pixelNestTreshold) {
294
- canNestPos = true;
295
- }
296
- else {
297
- canNestPos = false;
298
- }
299
- //allow drop if valid target
300
- if (validTarget)
301
- e.preventDefault();
302
- }
303
- function handleDragEnter(e, node, el) {
304
- setTimeout(() => {
305
- insPos = helper.dragDrop.getInsertionPosition(e, el);
306
- validTarget = true;
307
- dragenterTimestamp = new Date();
308
- // will cause flashing when moving wrom node to node while be able to nest
309
- //* have to be here if you only use time
310
- highlightedNode = node;
311
- if (timeToNest) {
312
- canNestTime = false;
313
- //this is so that only one timeout is ticking at one time
314
- clearTimeout(dragTimeout);
315
- dragTimeout = setTimeout(() => {
316
- canNestTime = true;
317
- }, timeToNest);
318
- }
319
- //dont allow drop on child element and if both insertDisabled and nestDisabled to true
320
- if (helper.path(node)?.startsWith(draggedPath ?? '') ||
321
- (propHelper.insertDisabled(node) === true && propHelper.nestDisabled(node) === true)) {
322
- validTarget = false;
323
- }
324
- //if defined calling callback
325
- if (dragEnterCallback) {
326
- //get node for event
327
- let draggedNode = helper.findNode(tree, draggedPath ?? '');
328
- let oldParent = helper.findNode(tree, helper.getParentNodePath(draggedPath ?? ''));
329
- //callback returning false means that it isnt valid target
330
- if (dragEnterCallback(draggedNode, oldParent, node) === false) {
331
- validTarget = false;
332
- }
333
- }
334
- }, 1);
335
- e.preventDefault();
336
- }
337
- function handleDragEnd(e, node) {
338
- //reset prop on next tick
339
- setTimeout(() => {
340
- draggedPath = null;
341
- highlightedNode = null;
342
- }, 1);
343
- }
344
- function handleDragleave(e, node, el) {
345
- // highlightedNode = null;
346
- }
347
- /**
348
- *check if this node is one being hovered over (highlited) and is valid target
349
- */
350
- function highlighThisNode(node, highlitedNode, validTarget) {
351
- return validTarget && helper.path(highlitedNode) == helper.path(node);
352
- }
353
- /**
354
- * returns true, it should highlight nesting on this node
355
- * @param node node
356
- * @param highlitedNode highlited node
357
- * @param validTarget valid target
358
- * @param canNest can nest
359
- */
360
- function highlightNesting(node, highlitedNode, validTarget, canNest) {
361
- return (canNest &&
362
- highlighThisNode(node, highlitedNode, validTarget) &&
363
- propHelper.nestDisabled(node) !== true);
364
- }
365
- /**
366
- * returns true, it should highlight nesting on this node
367
- * @param node node
368
- * @param highlitedNode highlited node
369
- * @param validTarget valid target
370
- * @param canNest can nest
371
- */
372
- function highlightInsert(node, highlitedNode, validTarget, canNest) {
373
- return (!canNest &&
374
- highlighThisNode(node, highlitedNode, validTarget) &&
375
- propHelper.insertDisabled(node) !== true);
376
- }
377
- //#endregion
231
+ // //#region drag and drop
232
+ // function handleDragStart(e: DragEvent, node: Node) {
233
+ // // dont allos drag if is draggable is false
234
+ // if (propHelper.isDraggable(node) === false) {
235
+ // e.preventDefault();
236
+ // return;
237
+ // }
238
+ // console.log('dragstart from: ' + helper.path(node));
239
+ // //@ts-ignore
240
+ // e.dataTransfer.dropEffect = 'move';
241
+ // //@ts-ignore
242
+ // e.dataTransfer.setData('node_id', helper.path(node));
243
+ // draggedPath = helper.path(node);
244
+ // }
245
+ // function handleDragDrop(e: DragEvent, node: Node, el: HTMLElement) {
246
+ // //should be necesary but just in case
247
+ // highlightedNode = null;
248
+ // if (readonly || !dragAndDrop) return;
249
+ // //@ts-ignore
250
+ // draggedPath = e.dataTransfer.getData('node_id');
251
+ // console.log(draggedPath + ' dropped on: ' + helper.path(node));
252
+ // //important to check if timetonest is set, otherwise you could spend 30 minutes fixing this shit :)
253
+ // if (timeToNest) {
254
+ // const nowTimestamp = new Date();
255
+ // canNestTime =
256
+ // (dragenterTimestamp ? nowTimestamp.getTime() - dragenterTimestamp.getTime() : 1) >
257
+ // timeToNest;
258
+ // }
259
+ // let newNode = helper.findNode(tree, draggedPath);
260
+ // let oldNode = { ...(newNode as any) };
261
+ // let oldParent = helper.findNode(tree, helper.getParentNodePath(draggedPath));
262
+ // let insType = canNest ? 0 : helper.dragDrop.getInsertionPosition(e, el);
263
+ // //cancel move if its not valid
264
+ // if (insType == 0 && propHelper.nestDisabled(node) === true) return;
265
+ // else if ((insType == -1 || insType == 1) && propHelper.insertDisabled(node) === true) return;
266
+ // //callback can cancell move
267
+ // if (
268
+ // beforeMovedCallback &&
269
+ // beforeMovedCallback(oldNode, oldParent, node, helper.dragDrop.huminifyInsType(insType)) ===
270
+ // false
271
+ // )
272
+ // return;
273
+ // tree = helper.dragDrop.moveNode(
274
+ // tree,
275
+ // draggedPath,
276
+ // helper.path(node),
277
+ // insType,
278
+ // recalculateNodePath
279
+ // );
280
+ // let newParent = helper.findNode(tree, helper.getParentNodePath(helper.path(newNode))) ?? null;
281
+ // dispatch('moved', {
282
+ // oldParent: oldParent,
283
+ // newParent: newParent,
284
+ // oldNode: oldNode,
285
+ // newNode: newNode,
286
+ // targetNode: node,
287
+ // insType: helper.dragDrop.huminifyInsType(insType)
288
+ // });
289
+ // //reset props
290
+ // dragenterTimestamp = null;
291
+ // draggedPath = null;
292
+ // highlightedNode = null;
293
+ // }
294
+ // function handleDragOver(e: DragEvent, node: Node, el: HTMLElement) {
295
+ // insPos = helper.dragDrop.getInsertionPosition(e, el);
296
+ // //if you are further away from right then treshold allow nesting
297
+ // // @ts-ignore
298
+ // let diff = e.x - e.target?.getBoundingClientRect()?.x;
299
+ // if (pixelNestTreshold && diff > pixelNestTreshold) {
300
+ // canNestPos = true;
301
+ // } else {
302
+ // canNestPos = false;
303
+ // }
304
+ // //allow drop if valid target
305
+ // if (validTarget) e.preventDefault();
306
+ // }
307
+ // function handleDragEnter(e: DragEvent, node: Node, el: HTMLElement) {
308
+ // setTimeout(() => {
309
+ // insPos = helper.dragDrop.getInsertionPosition(e, el);
310
+ // validTarget = true;
311
+ // dragenterTimestamp = new Date();
312
+ // // will cause flashing when moving wrom node to node while be able to nest
313
+ // //* have to be here if you only use time
314
+ // highlightedNode = node;
315
+ // if (timeToNest) {
316
+ // canNestTime = false;
317
+ // //this is so that only one timeout is ticking at one time
318
+ // clearTimeout(dragTimeout);
319
+ // dragTimeout = setTimeout(() => {
320
+ // canNestTime = true;
321
+ // }, timeToNest);
322
+ // }
323
+ // //dont allow drop on child element and if both insertDisabled and nestDisabled to true
324
+ // if (
325
+ // helper.path(node)?.startsWith(draggedPath ?? '') ||
326
+ // (propHelper.insertDisabled(node) === true && propHelper.nestDisabled(node) === true)
327
+ // ) {
328
+ // validTarget = false;
329
+ // }
330
+ // //if defined calling callback
331
+ // if (dragEnterCallback) {
332
+ // //get node for event
333
+ // let draggedNode = helper.findNode(tree, draggedPath ?? '');
334
+ // let oldParent = helper.findNode(tree, helper.getParentNodePath(draggedPath ?? ''));
335
+ // //callback returning false means that it isnt valid target
336
+ // if (dragEnterCallback(draggedNode, oldParent, node) === false) {
337
+ // validTarget = false;
338
+ // }
339
+ // }
340
+ // }, 1);
341
+ // e.preventDefault();
342
+ // }
343
+ // function handleDragEnd(e: DragEvent, node: Node) {
344
+ // //reset prop on next tick
345
+ // setTimeout(() => {
346
+ // draggedPath = null;
347
+ // highlightedNode = null;
348
+ // }, 1);
349
+ // }
350
+ // function handleDragleave(e: DragEvent, node: Node, el: HTMLElement) {
351
+ // // highlightedNode = null;
352
+ // }
353
+ // /**
354
+ // *check if this node is one being hovered over (highlited) and is valid target
355
+ // */
356
+ // function highlighThisNode(node: Node, highlitedNode: Node, validTarget: boolean) {
357
+ // return validTarget && helper.path(highlitedNode) == helper.path(node);
358
+ // }
359
+ // /**
360
+ // * returns true, it should highlight nesting on this node
361
+ // * @param node node
362
+ // * @param highlitedNode highlited node
363
+ // * @param validTarget valid target
364
+ // * @param canNest can nest
365
+ // */
366
+ // function highlightNesting(
367
+ // node: Node,
368
+ // highlitedNode: Node,
369
+ // validTarget: boolean,
370
+ // canNest: boolean
371
+ // ) {
372
+ // return (
373
+ // canNest &&
374
+ // highlighThisNode(node, highlitedNode, validTarget) &&
375
+ // propHelper.nestDisabled(node) !== true
376
+ // );
377
+ // }
378
+ // /**
379
+ // * returns true, it should highlight nesting on this node
380
+ // * @param node node
381
+ // * @param highlitedNode highlited node
382
+ // * @param validTarget valid target
383
+ // * @param canNest can nest
384
+ // */
385
+ // function highlightInsert(
386
+ // node: Node,
387
+ // highlitedNode: Node,
388
+ // validTarget: boolean,
389
+ // canNest: boolean
390
+ // ) {
391
+ // return (
392
+ // !canNest &&
393
+ // highlighThisNode(node, highlitedNode, validTarget) &&
394
+ // propHelper.insertDisabled(node) !== true
395
+ // );
396
+ // }
397
+ // //#endregion
378
398
  </script>
379
399
 
380
400
  <Branch
@@ -411,7 +431,10 @@ function highlightInsert(node, highlitedNode, validTarget, canNest) {
411
431
  </svelte:fragment>
412
432
  </ContextMenu>
413
433
 
414
- <style global>:global(.treeview.show-lines) :global(ul:before) {
434
+ <style global>:global(.treeview) {
435
+ padding: 0;
436
+ }
437
+ :global(.treeview.show-lines) :global(ul:before) {
415
438
  border-left: solid black 1px;
416
439
  }
417
440
  :global(.treeview.show-lines) :global(ul) :global(li:before) {
@@ -6,7 +6,7 @@ declare const __propDef: {
6
6
  /**
7
7
  * Array of nodes that represent tree structure.
8
8
  * Each node should have unique path
9
- * All tree modifications are node by modyfing this array, so you need to bind it to parent component
9
+ * All tree modifications are made by modifying this array, so you need to bind it to parent component
10
10
  */ tree: Node[];
11
11
  /**
12
12
  * Object properties where information about node is stored
@@ -82,7 +82,6 @@ declare const __propDef: {
82
82
  selection: CustomEvent<any>;
83
83
  selected: CustomEvent<any>;
84
84
  unselected: CustomEvent<any>;
85
- moved: CustomEvent<any>;
86
85
  } & {
87
86
  [evt: string]: CustomEvent<any>;
88
87
  };
@@ -1,4 +1,5 @@
1
- import type { CustomizableClasses, Props } from './types.js';
1
+ import { type HelperConfig, type CustomizableClasses, type Props } from './types.js';
2
2
  export declare const defaultPropNames: Props;
3
3
  export declare const defaultPixelTreshold = 50;
4
4
  export declare const defaultClasses: CustomizableClasses;
5
+ export declare const defaultConfig: HelperConfig;
package/dist/constants.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SelectionModes } from './types.js';
1
2
  export const defaultPropNames = {
2
3
  nodePath: 'nodePath',
3
4
  hasChildren: 'hasChildren',
@@ -22,3 +23,7 @@ export const defaultClasses = {
22
23
  inserLineClass: '',
23
24
  inserLineNestClass: ''
24
25
  };
26
+ export const defaultConfig = {
27
+ separator: '.',
28
+ checkboxes: SelectionModes.none
29
+ };
@@ -1,34 +1,34 @@
1
- import { SelectionHelper } from './selection-helpers.js';
2
- import { DragAndDropHelper } from './drag-drop-helpers.js';
3
- import type { Node, NodePath, HelperConfig } from '../types.js';
1
+ import type { Node, NodePath, HelperConfig, Tree } from '../types.js';
4
2
  import type { PropertyHelper } from './property-helper.js';
5
3
  export declare class TreeHelper {
6
4
  props: PropertyHelper;
7
5
  config: HelperConfig;
8
- selection: SelectionHelper;
9
- dragDrop: DragAndDropHelper;
10
6
  constructor(props: PropertyHelper, config?: HelperConfig);
11
7
  path(node: Node | null): NodePath;
12
8
  getParentNodePath(nodePath: NodePath): NodePath;
13
9
  isChildrenOf(parentNodePath: NodePath, childrenNodePath: NodePath): boolean | undefined;
14
- hasChildren(tree: Node[], nodePath: NodePath): unknown;
15
- findNode(tree: Node[], nodePath: NodePath): Node;
10
+ hasChildren(tree: Tree, nodePath: NodePath): unknown;
11
+ findNode(tree: Tree, nodePath: NodePath): Node;
16
12
  nodePathIsChild(nodePath: NodePath): boolean | undefined;
17
- getDirectChildren(tree: Node[], parentNodePath: NodePath): unknown[];
18
- allCHildren(tree: Node[], parentNodePath: NodePath): unknown[];
19
- getAllLeafNodes(tree: Node[]): unknown[];
20
- joinTrees(filteredTree: Node[], tree: Node[]): unknown[];
21
- mergeTrees(oldTree: Node[], addedTree: Node[], nodePath?: string): unknown[];
13
+ getDirectChildren(tree: Tree, parentNodePath: NodePath): Tree;
14
+ allCHildren(tree: Tree, parentNodePath: NodePath): unknown[];
15
+ getAllLeafNodes(tree: Tree): unknown[];
16
+ joinTrees(filteredTree: Tree, tree: Tree): unknown[];
17
+ mergeTrees(oldTree: Tree, addedTree: Tree, nodePath?: string): unknown[];
22
18
  /** toggles expansion on
23
19
  */
24
- changeExpansion(tree: Node[], node: Node, changeTo: boolean): void;
20
+ changeExpansion(tree: Tree, node: Node, changeTo: boolean): void;
25
21
  /** changes expansion of every node that has this.hasChildren set to true
26
22
  */
27
- changeEveryExpansion(tree: Node[], changeTo: boolean): unknown[];
23
+ changeEveryExpansion(tree: Tree, changeTo: boolean): unknown[];
28
24
  /** changes expansion of every node that has this.hasChildren set to true if they are abose set level and expansion property isnt set
29
25
  */
30
- expandToLevel(tree: Node[], level: number): unknown[];
26
+ expandToLevel(tree: Tree, level: number): unknown[];
31
27
  getDepthLevel(nodePath: NodePath): number;
32
- searchTree(tree: Node[], filter: (node: unknown) => boolean): unknown[];
33
- getParents(tree: Node[], node: Node): unknown[];
28
+ searchTree(tree: Tree, filter: (node: unknown) => boolean): unknown[];
29
+ getParents(tree: Tree, node: Node): unknown[];
30
+ /** orders nodes by priorityProp
31
+ */
32
+ orderByPriority(tree: Tree): Tree;
33
+ getNodeId(nodePath: NodePath): string | null;
34
34
  }
@@ -1,17 +1,12 @@
1
1
  import orderBy from 'lodash.unionby'; // used by tree merge
2
2
  import uniqueBy from 'lodash.uniqby'; // used by tree merge
3
- import { SelectionHelper } from './selection-helpers.js';
4
- import { DragAndDropHelper } from './drag-drop-helpers.js';
3
+ import { defaultConfig } from '../constants.js';
5
4
  export class TreeHelper {
6
5
  props;
7
6
  config;
8
- selection;
9
- dragDrop;
10
- constructor(props, config = {}) {
7
+ constructor(props, config = defaultConfig) {
11
8
  this.props = props;
12
9
  this.config = config;
13
- this.selection = new SelectionHelper(this, config?.recursive ?? false);
14
- this.dragDrop = new DragAndDropHelper(this);
15
10
  }
16
11
  // replace with this.props.path
17
12
  path(node) {
@@ -47,7 +42,8 @@ export class TreeHelper {
47
42
  const children = (tree || []).filter((x) => !parentNodePath
48
43
  ? !this.nodePathIsChild(this.path(x))
49
44
  : this.getParentNodePath(this.path(x)) === parentNodePath);
50
- return children;
45
+ const ordered = this.orderByPriority(children);
46
+ return ordered;
51
47
  }
52
48
  allCHildren(tree, parentNodePath) {
53
49
  const children = tree.filter((x) => this.isChildrenOf(parentNodePath, this.path(x)));
@@ -126,4 +122,18 @@ export class TreeHelper {
126
122
  const parentNodes = tree.filter((n) => parentsPaths.some((parentNodePath) => this.path(n) === parentNodePath));
127
123
  return parentNodes;
128
124
  }
125
+ /** orders nodes by priorityProp
126
+ */
127
+ orderByPriority(tree) {
128
+ // TODO investigata that it really works
129
+ tree.sort((a, b) => this.props.priority(b) ? this.props.priority(a) - this.props.priority(b) : 1);
130
+ return tree;
131
+ }
132
+ getNodeId(nodePath) {
133
+ if (nodePath == null) {
134
+ console.warn('getting node id of null node path');
135
+ return null;
136
+ }
137
+ return nodePath?.split(this.config.separator).slice(-1)[0];
138
+ }
129
139
  }
@@ -1,7 +1,7 @@
1
- import type { PropertyHelper } from './property-helper.js';
2
- import type { TreeHelper } from './tree-helper.js';
3
- import type { InsertionType, Node, NodePath, Tree } from '../types.js';
4
- export declare class DragAndDropHelper {
1
+ import type { PropertyHelper } from '../helpers/property-helper.js';
2
+ import type { TreeHelper } from '../helpers/tree-helper.js';
3
+ import { InsertionType, type Node, type NodePath, type Tree } from '../types.js';
4
+ export declare class DragDropProvider {
5
5
  helper: TreeHelper;
6
6
  props: PropertyHelper;
7
7
  separator: string;
@@ -15,7 +15,7 @@ export declare class DragAndDropHelper {
15
15
  * @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
16
16
  * @param {boolean} recalculateNodePath - wont recalculare id of moved node, used when last part of nodePath is always unique
17
17
  */
18
- moveNode(tree: Node[], movedNodePath: NodePath, targetNodePath: NodePath, insType: -1 | 0 | 1, recalculateNodePath: boolean): Node[];
18
+ moveNode(tree: Node[], movedNodePath: NodePath, targetNodePath: NodePath, insType: InsertionType, recalculateNodePath: boolean): Node[];
19
19
  calculateNewNodePath(tree: Tree, parentNodePath: NodePath, movedNodePath: NodePath, recalculateNodePath: boolean): string;
20
20
  updatePriority(tree: Tree, node: Node, parentNodePath: NodePath, newNodePath: NodePath, targetNode: Node, insType: InsertionType): void;
21
21
  /** recomputes all priorities after inserted priority.F
@@ -23,10 +23,6 @@ export declare class DragAndDropHelper {
23
23
  */
24
24
  recalculatesPriorities(tree: Tree, parentNode: NodePath, movedNodePath: NodePath, insertedPriority?: number): void;
25
25
  /** return biggest value of nodepath number that children are using +1 */
26
- getNextNodeId(tree: Tree, parentPath: NodePath): number;
26
+ getNextNodeId(tree: Tree, parentPath: NodePath): string;
27
27
  getInsertionPosition(e: DragEvent, element: HTMLElement): InsertionType;
28
- huminifyInsType(insType: InsertionType): string;
29
- /** orders nodes by priorityProp
30
- */
31
- OrderByPriority(tree: Tree): Tree;
32
28
  }
@@ -1,4 +1,5 @@
1
- export class DragAndDropHelper {
1
+ import { InsertionType } from '../types.js';
2
+ export class DragDropProvider {
2
3
  helper;
3
4
  props;
4
5
  separator;
@@ -22,7 +23,7 @@ export class DragAndDropHelper {
22
23
  // cannot move root node
23
24
  if (!movedNodePath)
24
25
  return tree;
25
- const isNesting = insType == 0;
26
+ const isNesting = insType === InsertionType.nest;
26
27
  // if you are not isNestinging, you want to be on same level
27
28
  //so you will have same parent as target node
28
29
  const parentNodePath = isNesting
@@ -66,7 +67,7 @@ export class DragAndDropHelper {
66
67
  const oldIndex = tree.findIndex((x) => this.path(x) == newNodePath);
67
68
  tree.splice(oldIndex, 1);
68
69
  const index = tree.findIndex((x) => this.path(x) == this.path(targetNode));
69
- tree.splice(index + (insType == 1 ? 0 : 1), 0, movedNode);
70
+ tree.splice(index + (insType == InsertionType.above ? 0 : 1), 0, movedNode);
70
71
  //TODO maybe add option to setting this.hasChildren to false when moved last children
71
72
  //hide plus icon if parent of moved node doesnt have any more children
72
73
  const oldParent = this.helper.findNode(tree, this.helper.getParentNodePath(movedNodePath));
@@ -85,18 +86,20 @@ export class DragAndDropHelper {
85
86
  }
86
87
  else {
87
88
  //get last segment of path
88
- nodeId = movedNodePath?.split(this.separator).slice(-1)[0];
89
+ nodeId = this.helper.getNodeId(movedNodePath);
89
90
  }
90
- return (parentNodePath ? parentNodePath + this.separator : '') + nodeId;
91
+ if (parentNodePath === null)
92
+ return nodeId;
93
+ return `${parentNodePath}${this.separator}${nodeId}`;
91
94
  }
92
95
  updatePriority(tree, node, parentNodePath, newNodePath, targetNode, insType) {
93
- const isNesting = insType == 0;
96
+ const isNesting = insType == InsertionType.nest;
94
97
  if (isNesting || this.props.priority(targetNode) != null) {
95
98
  let newpriority = 0;
96
99
  if (!isNesting) {
97
100
  //calculate next
98
101
  newpriority = this.props.priority(targetNode) ?? 0;
99
- if (insType == -1) {
102
+ if (insType == InsertionType.below) {
100
103
  newpriority += 1;
101
104
  }
102
105
  else {
@@ -117,7 +120,7 @@ export class DragAndDropHelper {
117
120
  //? maybe it will recalculate properly if dont set insertedPriority
118
121
  recalculatesPriorities(tree, parentNode, movedNodePath, insertedPriority = -1) {
119
122
  let nextPriority = insertedPriority + 1;
120
- this.OrderByPriority(this.helper.allCHildren(tree, parentNode)).forEach((node) => {
123
+ this.helper.orderByPriority(this.helper.allCHildren(tree, parentNode)).forEach((node) => {
121
124
  if (this.props.priority(node) >= insertedPriority && this.path(node) != movedNodePath) {
122
125
  this.props.setPriority(node, nextPriority++);
123
126
  }
@@ -134,31 +137,14 @@ export class DragAndDropHelper {
134
137
  max = Math.max(max, num);
135
138
  }
136
139
  });
137
- return max + 1;
140
+ return (max + 1).toString();
138
141
  }
139
142
  getInsertionPosition(e, element) {
140
143
  const targetCords = element.getBoundingClientRect();
141
144
  const half = targetCords.bottom - targetCords.height / 2;
142
145
  if (e.y < half) {
143
- return 1;
146
+ return InsertionType.below;
144
147
  }
145
- return -1;
146
- }
147
- huminifyInsType(insType) {
148
- switch (insType) {
149
- case 1:
150
- return 'before';
151
- case 0:
152
- return 'inside';
153
- case -1:
154
- return 'after';
155
- }
156
- }
157
- /** orders nodes by priorityProp
158
- */
159
- OrderByPriority(tree) {
160
- // TODO investigata that it really works
161
- tree.sort((a, b) => this.props.priority(b) ? this.props.priority(a) - this.props.priority(b) : 1);
162
- return tree;
148
+ return InsertionType.above;
163
149
  }
164
150
  }
@@ -1,7 +1,7 @@
1
- import type { PropertyHelper } from './property-helper.js';
2
- import type { TreeHelper } from './tree-helper.js';
1
+ import type { PropertyHelper } from '../helpers/property-helper.js';
2
+ import type { TreeHelper } from '../helpers/tree-helper.js';
3
3
  import { SelectionModes, type Node, type NodePath, type Tree } from '../types.js';
4
- export declare class SelectionHelper {
4
+ export declare class SelectionProvider {
5
5
  helper: TreeHelper;
6
6
  props: PropertyHelper;
7
7
  recursiveMode: boolean;
@@ -1,5 +1,5 @@
1
1
  import { SelectionModes, VisualStates } from '../types.js';
2
- export class SelectionHelper {
2
+ export class SelectionProvider {
3
3
  helper;
4
4
  props;
5
5
  recursiveMode;
@@ -0,0 +1,13 @@
1
+ /// <reference types="svelte" />
2
+ import type { TreeHelper } from '../helpers/tree-helper.js';
3
+ import { type BeforeMovedCallback, type DragEnterCallback, HighlighType } from '../types.js';
4
+ import { type Readable } from 'svelte/store';
5
+ type dragConfig = {
6
+ dragEnterCallback: DragEnterCallback | null;
7
+ beforeMovedCallback: BeforeMovedCallback | null;
8
+ };
9
+ export declare function startDrag(config: dragConfig): {
10
+ highligh(helper: TreeHelper, node: Node): Readable<HighlighType>;
11
+ isDragged(helper: TreeHelper, node: Node): Readable<boolean>;
12
+ };
13
+ export {};
@@ -0,0 +1,23 @@
1
+ import { HighlighType } from '../types.js';
2
+ import { derived, writable } from 'svelte/store';
3
+ const storeDefaults = {
4
+ dragging: false,
5
+ x: 0,
6
+ y: 0
7
+ };
8
+ export function startDrag(config) {
9
+ const draggedNodeStore = writable(null);
10
+ return {
11
+ highligh(helper, node) {
12
+ // forces update when gragged nodes is changed
13
+ return derived([draggedNodeStore], ([store]) => {
14
+ return HighlighType.none;
15
+ });
16
+ },
17
+ isDragged(helper, node) {
18
+ return derived([draggedNodeStore], ([draggedNode]) => {
19
+ return helper.path(node) === helper.path(draggedNode);
20
+ });
21
+ }
22
+ };
23
+ }
@@ -1,6 +1,7 @@
1
1
 
2
2
  $treeview-lines: solid black 1px
3
3
  .treeview
4
+ padding: 0
4
5
  //will show lines if you set show-lines to root element
5
6
  &.show-lines
6
7
  ul
package/dist/types.d.ts CHANGED
@@ -23,7 +23,11 @@ export declare enum SelectionModes {
23
23
  }
24
24
  export type Node = unknown;
25
25
  export type Tree = Node[];
26
- export type InsertionType = -1 | 0 | 1;
26
+ export declare enum InsertionType {
27
+ above = "above",
28
+ below = "below",
29
+ nest = "nest"
30
+ }
27
31
  export type NodePath = string | null;
28
32
  export type CustomizableClasses = {
29
33
  treeClass: string;
@@ -39,8 +43,14 @@ export type DragEnterCallback = (draggendNode: Node, oldParent: Node, newParent:
39
43
  export type BeforeMovedCallback = (draggendNode: Node, oldParent: Node, newParent: Node, insertionType: string) => boolean;
40
44
  export type ExpandedCallback = (node: Node) => Promise<Node[]>;
41
45
  export type HelperConfig = {
42
- separator?: string;
46
+ separator: string;
43
47
  recursive?: boolean;
44
48
  recalculateNodePath?: boolean;
45
49
  checkboxes?: SelectionModes;
46
50
  };
51
+ export declare enum HighlighType {
52
+ nest = "nest",
53
+ insertAbove = "insert-above",
54
+ insertBelow = "insert-below",
55
+ none = "none"
56
+ }
package/dist/types.js CHANGED
@@ -10,3 +10,16 @@ export var SelectionModes;
10
10
  SelectionModes["perNode"] = "perNode";
11
11
  SelectionModes["none"] = "none";
12
12
  })(SelectionModes || (SelectionModes = {}));
13
+ export var InsertionType;
14
+ (function (InsertionType) {
15
+ InsertionType["above"] = "above";
16
+ InsertionType["below"] = "below";
17
+ InsertionType["nest"] = "nest";
18
+ })(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": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",
@@ -53,7 +53,7 @@
53
53
  "svelte-multiselect": "github:KeenMate/svelte-multiselect",
54
54
  "svelte-preprocess": "^5.0.4",
55
55
  "tslib": "^2.4.1",
56
- "typescript": "^5.0.0",
56
+ "typescript": "^5.4.4",
57
57
  "vite": "^4.3.6",
58
58
  "vitest": "^1.4.0"
59
59
  },