@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 +28 -3
- package/dist/Branch.svelte +7 -13
- package/dist/Branch.svelte.d.ts +1 -1
- package/dist/Checkbox.svelte +4 -1
- package/dist/TreeView.svelte +178 -155
- package/dist/TreeView.svelte.d.ts +1 -2
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +5 -0
- package/dist/helpers/tree-helper.d.ts +17 -17
- package/dist/helpers/tree-helper.js +18 -8
- package/dist/{helpers/drag-drop-helpers.d.ts → providers/drag-drop-provider.d.ts} +6 -10
- package/dist/{helpers/drag-drop-helpers.js → providers/drag-drop-provider.js} +14 -28
- package/dist/{helpers/selection-helpers.d.ts → providers/selection-provider.d.ts} +3 -3
- package/dist/{helpers/selection-helpers.js → providers/selection-provider.js} +1 -1
- package/dist/stores/drag-and-drop-store.d.ts +13 -0
- package/dist/stores/drag-and-drop-store.js +23 -0
- package/dist/tree-styles.sass +1 -0
- package/dist/types.d.ts +12 -2
- package/dist/types.js +13 -0
- package/package.json +2 -2
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
|
-
##
|
|
14
|
+
## Instalation
|
|
12
15
|
|
|
13
|
-
svelte-treeview
|
|
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
|
+
```
|
package/dist/Branch.svelte
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
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
|
|
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 ==
|
|
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={() =>
|
|
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
|
|
205
|
+
{#if insPos === InsertionType.below && insertHighlighted}
|
|
212
206
|
<div class="insert-line-wrapper">
|
|
213
207
|
<div class="insert-line {classes.inserLineClass}" />
|
|
214
208
|
</div>
|
package/dist/Branch.svelte.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
|
-
import { SelectionModes,
|
|
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: {
|
package/dist/Checkbox.svelte
CHANGED
|
@@ -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
|
|
29
|
+
{#if selectionProvider.isSelectable(node, checkboxes)}
|
|
27
30
|
<!-- select node -->
|
|
28
31
|
{#if !recursive || (recursive && !helper.props.hasChildren(node))}
|
|
29
32
|
<input
|
package/dist/TreeView.svelte
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 = !
|
|
201
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
function handleDragDrop(e, node, el) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
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
|
|
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
|
|
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
|
};
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
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 {
|
|
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:
|
|
15
|
-
findNode(tree:
|
|
10
|
+
hasChildren(tree: Tree, nodePath: NodePath): unknown;
|
|
11
|
+
findNode(tree: Tree, nodePath: NodePath): Node;
|
|
16
12
|
nodePathIsChild(nodePath: NodePath): boolean | undefined;
|
|
17
|
-
getDirectChildren(tree:
|
|
18
|
-
allCHildren(tree:
|
|
19
|
-
getAllLeafNodes(tree:
|
|
20
|
-
joinTrees(filteredTree:
|
|
21
|
-
mergeTrees(oldTree:
|
|
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:
|
|
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:
|
|
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:
|
|
26
|
+
expandToLevel(tree: Tree, level: number): unknown[];
|
|
31
27
|
getDepthLevel(nodePath: NodePath): number;
|
|
32
|
-
searchTree(tree:
|
|
33
|
-
getParents(tree:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
2
|
-
import type { TreeHelper } from '
|
|
3
|
-
import
|
|
4
|
-
export declare class
|
|
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:
|
|
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):
|
|
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
|
-
|
|
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
|
|
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 ==
|
|
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 =
|
|
89
|
+
nodeId = this.helper.getNodeId(movedNodePath);
|
|
89
90
|
}
|
|
90
|
-
|
|
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 ==
|
|
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 ==
|
|
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.
|
|
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
|
|
146
|
+
return InsertionType.below;
|
|
144
147
|
}
|
|
145
|
-
return
|
|
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 '
|
|
2
|
-
import type { TreeHelper } from '
|
|
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
|
|
4
|
+
export declare class SelectionProvider {
|
|
5
5
|
helper: TreeHelper;
|
|
6
6
|
props: PropertyHelper;
|
|
7
7
|
recursiveMode: boolean;
|
|
@@ -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
|
+
}
|
package/dist/tree-styles.sass
CHANGED
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
|
|
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
|
|
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.
|
|
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.
|
|
56
|
+
"typescript": "^5.4.4",
|
|
57
57
|
"vite": "^4.3.6",
|
|
58
58
|
"vitest": "^1.4.0"
|
|
59
59
|
},
|