wunderbaum 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/wunderbaum.css +31 -11
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +535 -296
- package/dist/wunderbaum.esm.js +619 -221
- package/dist/wunderbaum.esm.min.js +28 -28
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +619 -221
- package/dist/wunderbaum.umd.min.js +32 -32
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/common.ts +23 -4
- package/src/types.ts +86 -40
- package/src/util.ts +75 -2
- package/src/wb_ext_dnd.ts +21 -20
- package/src/wb_ext_edit.ts +1 -1
- package/src/wb_ext_filter.ts +6 -2
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_extension_base.ts +16 -1
- package/src/wb_node.ts +222 -106
- package/src/wb_options.ts +169 -108
- package/src/wunderbaum.ts +351 -127
package/dist/wunderbaum.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - debounce.ts
|
|
3
3
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
4
|
-
* v0.
|
|
4
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
/*
|
|
7
7
|
* debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
|
|
@@ -293,7 +293,7 @@ function throttle(func, wait = 0, options = {}) {
|
|
|
293
293
|
/*!
|
|
294
294
|
* Wunderbaum - util
|
|
295
295
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
296
|
-
* v0.
|
|
296
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
297
297
|
*/
|
|
298
298
|
/** @module util */
|
|
299
299
|
/** Readable names for `MouseEvent.button` */
|
|
@@ -443,7 +443,7 @@ function each(obj, callback) {
|
|
|
443
443
|
}
|
|
444
444
|
return obj;
|
|
445
445
|
}
|
|
446
|
-
/** Shortcut for `throw new Error(msg)
|
|
446
|
+
/** Shortcut for `throw new Error(msg)`. */
|
|
447
447
|
function error(msg) {
|
|
448
448
|
throw new Error(msg);
|
|
449
449
|
}
|
|
@@ -958,6 +958,10 @@ function toPixel(...defaults) {
|
|
|
958
958
|
}
|
|
959
959
|
throw new Error(`Expected a string like '123px': ${defaults}`);
|
|
960
960
|
}
|
|
961
|
+
/** Cast any value to <T>. */
|
|
962
|
+
function unsafeCast(value) {
|
|
963
|
+
return value;
|
|
964
|
+
}
|
|
961
965
|
/** Return the the boolean value of the first non-null element.
|
|
962
966
|
* Example:
|
|
963
967
|
* ```js
|
|
@@ -1031,7 +1035,7 @@ function adaptiveThrottle(callback, options) {
|
|
|
1031
1035
|
const throttledFn = (...args) => {
|
|
1032
1036
|
if (waiting) {
|
|
1033
1037
|
pendingArgs = args;
|
|
1034
|
-
// console.log(`adaptiveThrottle()
|
|
1038
|
+
// console.log(`adaptiveThrottle() queueing request #${waiting}...`, args);
|
|
1035
1039
|
waiting += 1;
|
|
1036
1040
|
}
|
|
1037
1041
|
else {
|
|
@@ -1086,6 +1090,60 @@ function adaptiveThrottle(callback, options) {
|
|
|
1086
1090
|
};
|
|
1087
1091
|
return throttledFn;
|
|
1088
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* MurmurHash3 implementation for strings.
|
|
1095
|
+
* @param key The input string to hash.
|
|
1096
|
+
* @param asString Optional convert result to zero-padded string of 8 characters.
|
|
1097
|
+
* @param seed Optional seed value.
|
|
1098
|
+
* @returns A 32-bit hash as a number or string.
|
|
1099
|
+
*/
|
|
1100
|
+
function murmurHash3(key, asString = true, seed = 0) {
|
|
1101
|
+
let h1 = seed;
|
|
1102
|
+
const remainder = key.length & 3; // key.length % 4
|
|
1103
|
+
const bytes = key.length - remainder;
|
|
1104
|
+
const c1 = 0xcc9e2d51;
|
|
1105
|
+
const c2 = 0x1b873593;
|
|
1106
|
+
let i = 0;
|
|
1107
|
+
while (i < bytes) {
|
|
1108
|
+
let k1 = (key.charCodeAt(i) & 0xff) |
|
|
1109
|
+
((key.charCodeAt(++i) & 0xff) << 8) |
|
|
1110
|
+
((key.charCodeAt(++i) & 0xff) << 16) |
|
|
1111
|
+
((key.charCodeAt(++i) & 0xff) << 24);
|
|
1112
|
+
++i;
|
|
1113
|
+
k1 = Math.imul(k1, c1);
|
|
1114
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1115
|
+
k1 = Math.imul(k1, c2);
|
|
1116
|
+
h1 ^= k1;
|
|
1117
|
+
h1 = (h1 << 13) | (h1 >>> 19);
|
|
1118
|
+
h1 = Math.imul(h1, 5) + 0xe6546b64;
|
|
1119
|
+
}
|
|
1120
|
+
let k1 = 0;
|
|
1121
|
+
switch (remainder) {
|
|
1122
|
+
case 3:
|
|
1123
|
+
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
1124
|
+
// fall through
|
|
1125
|
+
case 2:
|
|
1126
|
+
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
1127
|
+
// fall through
|
|
1128
|
+
case 1:
|
|
1129
|
+
k1 ^= key.charCodeAt(i) & 0xff;
|
|
1130
|
+
k1 = Math.imul(k1, c1);
|
|
1131
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1132
|
+
k1 = Math.imul(k1, c2);
|
|
1133
|
+
h1 ^= k1;
|
|
1134
|
+
}
|
|
1135
|
+
h1 ^= key.length;
|
|
1136
|
+
h1 ^= h1 >>> 16;
|
|
1137
|
+
h1 = Math.imul(h1, 0x85ebca6b);
|
|
1138
|
+
h1 ^= h1 >>> 13;
|
|
1139
|
+
h1 = Math.imul(h1, 0xc2b2ae35);
|
|
1140
|
+
h1 ^= h1 >>> 16;
|
|
1141
|
+
if (asString) {
|
|
1142
|
+
// Convert to 8 digit hex string
|
|
1143
|
+
return (h1 >>> 0).toString(16).padStart(8, "0");
|
|
1144
|
+
}
|
|
1145
|
+
return h1 >>> 0; // Convert to unsigned 32-bit integer
|
|
1146
|
+
}
|
|
1089
1147
|
|
|
1090
1148
|
var util = /*#__PURE__*/Object.freeze({
|
|
1091
1149
|
__proto__: null,
|
|
@@ -1116,6 +1174,7 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
1116
1174
|
isFunction: isFunction,
|
|
1117
1175
|
isMac: isMac,
|
|
1118
1176
|
isPlainObject: isPlainObject,
|
|
1177
|
+
murmurHash3: murmurHash3,
|
|
1119
1178
|
noop: noop,
|
|
1120
1179
|
onEvent: onEvent,
|
|
1121
1180
|
overrideMethod: overrideMethod,
|
|
@@ -1129,13 +1188,14 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
1129
1188
|
toPixel: toPixel,
|
|
1130
1189
|
toSet: toSet,
|
|
1131
1190
|
toggleCheckbox: toggleCheckbox,
|
|
1132
|
-
type: type
|
|
1191
|
+
type: type,
|
|
1192
|
+
unsafeCast: unsafeCast
|
|
1133
1193
|
});
|
|
1134
1194
|
|
|
1135
1195
|
/*!
|
|
1136
1196
|
* Wunderbaum - types
|
|
1137
1197
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1138
|
-
* v0.
|
|
1198
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1139
1199
|
*/
|
|
1140
1200
|
/**
|
|
1141
1201
|
* Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
|
|
@@ -1203,7 +1263,7 @@ var NavModeEnum;
|
|
|
1203
1263
|
/*!
|
|
1204
1264
|
* Wunderbaum - wb_extension_base
|
|
1205
1265
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1206
|
-
* v0.
|
|
1266
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1207
1267
|
*/
|
|
1208
1268
|
class WunderbaumExtension {
|
|
1209
1269
|
constructor(tree, id, defaults) {
|
|
@@ -1262,7 +1322,7 @@ class WunderbaumExtension {
|
|
|
1262
1322
|
/*!
|
|
1263
1323
|
* Wunderbaum - ext-filter
|
|
1264
1324
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1265
|
-
* v0.
|
|
1325
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1266
1326
|
*/
|
|
1267
1327
|
const START_MARKER = "\uFFF7";
|
|
1268
1328
|
const END_MARKER = "\uFFF8";
|
|
@@ -1550,6 +1610,10 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1550
1610
|
*/
|
|
1551
1611
|
filterBranches(filter, options) {
|
|
1552
1612
|
assert(options.matchBranch === undefined, "filterBranches() is deprecated.");
|
|
1613
|
+
this.tree.logDeprecate("filterBranches()", {
|
|
1614
|
+
since: "0.9.0",
|
|
1615
|
+
hint: "Use `filterNodes` instead and set `options.matchBranch: true`",
|
|
1616
|
+
});
|
|
1553
1617
|
options.matchBranch = true;
|
|
1554
1618
|
return this._applyFilterNoUpdate(filter, options);
|
|
1555
1619
|
}
|
|
@@ -1614,7 +1678,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1614
1678
|
}
|
|
1615
1679
|
}
|
|
1616
1680
|
/**
|
|
1617
|
-
* @description Marks the matching
|
|
1681
|
+
* @description Marks the matching characters of `text` either by `mark` or
|
|
1618
1682
|
* by exotic*Chars (if `escapeTitles` is `true`) based on `matches`
|
|
1619
1683
|
* which is an array of matching groups.
|
|
1620
1684
|
* @param {string} text
|
|
@@ -1653,7 +1717,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
|
|
|
1653
1717
|
/*!
|
|
1654
1718
|
* Wunderbaum - ext-keynav
|
|
1655
1719
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1656
|
-
* v0.
|
|
1720
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1657
1721
|
*/
|
|
1658
1722
|
const QUICKSEARCH_DELAY = 500;
|
|
1659
1723
|
class KeynavExtension extends WunderbaumExtension {
|
|
@@ -2017,7 +2081,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
2017
2081
|
/*!
|
|
2018
2082
|
* Wunderbaum - ext-logger
|
|
2019
2083
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2020
|
-
* v0.
|
|
2084
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2021
2085
|
*/
|
|
2022
2086
|
class LoggerExtension extends WunderbaumExtension {
|
|
2023
2087
|
constructor(tree) {
|
|
@@ -2059,7 +2123,7 @@ class LoggerExtension extends WunderbaumExtension {
|
|
|
2059
2123
|
/*!
|
|
2060
2124
|
* Wunderbaum - ext-dnd
|
|
2061
2125
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2062
|
-
* v0.
|
|
2126
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2063
2127
|
*/
|
|
2064
2128
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
2065
2129
|
class DndExtension extends WunderbaumExtension {
|
|
@@ -2295,7 +2359,6 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2295
2359
|
*/
|
|
2296
2360
|
onDragEvent(e) {
|
|
2297
2361
|
var _a;
|
|
2298
|
-
// const tree = this.tree;
|
|
2299
2362
|
const dndOpts = this.treeOpts.dnd;
|
|
2300
2363
|
const srcNode = Wunderbaum.getNode(e);
|
|
2301
2364
|
if (!srcNode) {
|
|
@@ -2321,7 +2384,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2321
2384
|
return false;
|
|
2322
2385
|
}
|
|
2323
2386
|
const nodeData = srcNode.toDict(true, (n) => {
|
|
2324
|
-
// We don't want to
|
|
2387
|
+
// We don't want to reuse the key on drop:
|
|
2325
2388
|
n._orgKey = n.key;
|
|
2326
2389
|
delete n.key;
|
|
2327
2390
|
});
|
|
@@ -2383,6 +2446,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2383
2446
|
};
|
|
2384
2447
|
if (!targetNode) {
|
|
2385
2448
|
this._leaveNode();
|
|
2449
|
+
e.preventDefault(); // Don't open file in browser when dropped in empty area
|
|
2386
2450
|
return;
|
|
2387
2451
|
}
|
|
2388
2452
|
if (["drop"].includes(e.type)) {
|
|
@@ -2488,19 +2552,20 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2488
2552
|
nodeData = nodeData ? JSON.parse(nodeData) : null;
|
|
2489
2553
|
const srcNode = this.srcNode;
|
|
2490
2554
|
const lastDropEffect = this.lastDropEffect;
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2555
|
+
/* Before v0.14.0, we decoupled `_callEvent` like so:
|
|
2556
|
+
Decouple this call, because drop actions may prevent the dragend
|
|
2557
|
+
event from being fired on some browsers.
|
|
2558
|
+
setTimeout(() => {...}, 10);
|
|
2559
|
+
however this made e.dataTransfer.items inaccessible */
|
|
2560
|
+
targetNode._callEvent("dnd.drop", {
|
|
2561
|
+
event: e,
|
|
2562
|
+
region: region,
|
|
2563
|
+
suggestedDropMode: region === "over" ? "appendChild" : region,
|
|
2564
|
+
suggestedDropEffect: lastDropEffect,
|
|
2565
|
+
sourceNode: srcNode,
|
|
2566
|
+
sourceNodeData: nodeData,
|
|
2567
|
+
dataTransfer: e.dataTransfer,
|
|
2568
|
+
});
|
|
2504
2569
|
}
|
|
2505
2570
|
return false;
|
|
2506
2571
|
}
|
|
@@ -2509,7 +2574,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2509
2574
|
/*!
|
|
2510
2575
|
* Wunderbaum - drag_observer
|
|
2511
2576
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2512
|
-
* v0.
|
|
2577
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2513
2578
|
*/
|
|
2514
2579
|
/**
|
|
2515
2580
|
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
@@ -2658,7 +2723,7 @@ class DragObserver {
|
|
|
2658
2723
|
/*!
|
|
2659
2724
|
* Wunderbaum - common
|
|
2660
2725
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2661
|
-
* v0.
|
|
2726
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2662
2727
|
*/
|
|
2663
2728
|
const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
|
|
2664
2729
|
/**
|
|
@@ -2678,12 +2743,18 @@ const TITLE_SPAN_PAD_Y = 7;
|
|
|
2678
2743
|
const RENDER_MAX_PREFETCH = 5;
|
|
2679
2744
|
/** Minimum column width if not set otherwise. */
|
|
2680
2745
|
const DEFAULT_MIN_COL_WIDTH = 4;
|
|
2746
|
+
/**
|
|
2747
|
+
* A value for `node.type` that by convention may be used to mark a node as directory.
|
|
2748
|
+
* It may be used to sort 'directories' to the top.
|
|
2749
|
+
*/
|
|
2750
|
+
const NODE_TYPE_FOLDER = "folder";
|
|
2681
2751
|
/** Regular expression to detect if a string describes an image URL (in contrast
|
|
2682
2752
|
* to a class name). Strings are considered image urls if they contain '.' or '/'.
|
|
2753
|
+
* `<` is ignored, because it is probably an html tag.
|
|
2683
2754
|
*/
|
|
2684
|
-
const
|
|
2685
|
-
|
|
2686
|
-
|
|
2755
|
+
const TEST_FILE_PATH = /^(?!.*<).*[/.]/;
|
|
2756
|
+
/** Regular expression to detect if a string describes an HTML element. */
|
|
2757
|
+
const TEST_HTML = /</;
|
|
2687
2758
|
/**
|
|
2688
2759
|
* Default node icons for icon libraries
|
|
2689
2760
|
*
|
|
@@ -2691,7 +2762,7 @@ const TEST_IMG = new RegExp(/\.|\//);
|
|
|
2691
2762
|
* - 'fontawesome6' {@link https://fontawesome.com/icons}
|
|
2692
2763
|
*
|
|
2693
2764
|
*/
|
|
2694
|
-
const
|
|
2765
|
+
const defaultIconMaps = {
|
|
2695
2766
|
bootstrap: {
|
|
2696
2767
|
error: "bi bi-exclamation-triangle",
|
|
2697
2768
|
// loading: "bi bi-hourglass-split wb-busy",
|
|
@@ -2739,7 +2810,7 @@ const iconMaps = {
|
|
|
2739
2810
|
radioChecked: "fa-solid fa-circle",
|
|
2740
2811
|
radioUnchecked: "fa-regular fa-circle",
|
|
2741
2812
|
radioUnknown: "fa-regular fa-circle-question",
|
|
2742
|
-
folder: "fa-
|
|
2813
|
+
folder: "fa-regular fa-folder-closed",
|
|
2743
2814
|
folderOpen: "fa-regular fa-folder-open",
|
|
2744
2815
|
folderLazy: "fa-solid fa-folder-plus",
|
|
2745
2816
|
doc: "fa-regular fa-file",
|
|
@@ -2812,12 +2883,20 @@ function makeNodeTitleStartMatcher(s) {
|
|
|
2812
2883
|
return reMatch.test(node.title);
|
|
2813
2884
|
};
|
|
2814
2885
|
}
|
|
2815
|
-
/** Compare two nodes by title (case-insensitive).
|
|
2886
|
+
/** Compare two nodes by title (case-insensitive).
|
|
2887
|
+
* @deprecated Use `key` option instead of `cmp` in sort methods.
|
|
2888
|
+
*/
|
|
2816
2889
|
function nodeTitleSorter(a, b) {
|
|
2817
2890
|
const x = a.title.toLowerCase();
|
|
2818
2891
|
const y = b.title.toLowerCase();
|
|
2819
2892
|
return x === y ? 0 : x > y ? 1 : -1;
|
|
2820
2893
|
}
|
|
2894
|
+
// /** Compare nodes by title (case-insensitive). */
|
|
2895
|
+
// export function nodeTitleKeyGetter(
|
|
2896
|
+
// node: WunderbaumNode
|
|
2897
|
+
// ): string | number | Array<any> {
|
|
2898
|
+
// return node.title.toLowerCase();
|
|
2899
|
+
// }
|
|
2821
2900
|
/**
|
|
2822
2901
|
* Convert 'flat' to 'nested' format.
|
|
2823
2902
|
*
|
|
@@ -3008,7 +3087,7 @@ function decompressSourceData(source) {
|
|
|
3008
3087
|
/*!
|
|
3009
3088
|
* Wunderbaum - ext-grid
|
|
3010
3089
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3011
|
-
* v0.
|
|
3090
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3012
3091
|
*/
|
|
3013
3092
|
class GridExtension extends WunderbaumExtension {
|
|
3014
3093
|
constructor(tree) {
|
|
@@ -3068,7 +3147,7 @@ class GridExtension extends WunderbaumExtension {
|
|
|
3068
3147
|
super.init();
|
|
3069
3148
|
}
|
|
3070
3149
|
/**
|
|
3071
|
-
*
|
|
3150
|
+
* Handles drag and sragstop events for column resizing.
|
|
3072
3151
|
*/
|
|
3073
3152
|
handleDrag(e) {
|
|
3074
3153
|
const custom = e.customData;
|
|
@@ -3099,7 +3178,7 @@ class GridExtension extends WunderbaumExtension {
|
|
|
3099
3178
|
/*!
|
|
3100
3179
|
* Wunderbaum - deferred
|
|
3101
3180
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3102
|
-
* v0.
|
|
3181
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3103
3182
|
*/
|
|
3104
3183
|
/**
|
|
3105
3184
|
* Implement a ES6 Promise, that exposes a resolve() and reject() method.
|
|
@@ -3152,7 +3231,7 @@ class Deferred {
|
|
|
3152
3231
|
/*!
|
|
3153
3232
|
* Wunderbaum - wunderbaum_node
|
|
3154
3233
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3155
|
-
* v0.
|
|
3234
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3156
3235
|
*/
|
|
3157
3236
|
/** WunderbaumNode properties that can be passed with source data.
|
|
3158
3237
|
* (Any other source properties will be stored as `node.data.PROP`.)
|
|
@@ -3203,7 +3282,7 @@ NODE_DICT_PROPS.delete("unselectable");
|
|
|
3203
3282
|
*/
|
|
3204
3283
|
class WunderbaumNode {
|
|
3205
3284
|
constructor(tree, parent, data) {
|
|
3206
|
-
var _a
|
|
3285
|
+
var _a;
|
|
3207
3286
|
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
3208
3287
|
* times within a tree (in this case we have 'clone nodes').
|
|
3209
3288
|
* @see Use {@link setKey} to modify.
|
|
@@ -3233,8 +3312,8 @@ class WunderbaumNode {
|
|
|
3233
3312
|
assert(!data.children, "'children' not allowed here");
|
|
3234
3313
|
this.tree = tree;
|
|
3235
3314
|
this.parent = parent;
|
|
3236
|
-
this.key =
|
|
3237
|
-
this.title = "" + ((
|
|
3315
|
+
this.key = tree._calculateKey(data, parent);
|
|
3316
|
+
this.title = "" + ((_a = data.title) !== null && _a !== void 0 ? _a : "<" + this.key + ">");
|
|
3238
3317
|
this.expanded = !!data.expanded;
|
|
3239
3318
|
this.lazy = !!data.lazy;
|
|
3240
3319
|
// We set the following node properties only if a matching data value is
|
|
@@ -3355,8 +3434,14 @@ class WunderbaumNode {
|
|
|
3355
3434
|
const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
|
|
3356
3435
|
for (const child of nodeData) {
|
|
3357
3436
|
const subChildren = child.children;
|
|
3437
|
+
// Remove children property from source data because it should not be
|
|
3438
|
+
// passed to the constructor of WunderbaumNode:
|
|
3358
3439
|
delete child.children;
|
|
3359
3440
|
const n = new WunderbaumNode(tree, this, child);
|
|
3441
|
+
// Set `children` property again, so it can be used in `reload()`
|
|
3442
|
+
if (subChildren != null) {
|
|
3443
|
+
child.children = subChildren;
|
|
3444
|
+
}
|
|
3360
3445
|
if (forceExpand && !n.isUnloaded()) {
|
|
3361
3446
|
n.expanded = true;
|
|
3362
3447
|
}
|
|
@@ -3772,15 +3857,12 @@ class WunderbaumNode {
|
|
|
3772
3857
|
}
|
|
3773
3858
|
return l;
|
|
3774
3859
|
}
|
|
3775
|
-
/** Return a string representing the
|
|
3860
|
+
/** Return a string representing the hierarchical node path, e.g. "a/b/c".
|
|
3776
3861
|
* @param includeSelf
|
|
3777
3862
|
* @param part property name or callback
|
|
3778
3863
|
* @param separator
|
|
3779
3864
|
*/
|
|
3780
3865
|
getPath(includeSelf = true, part = "title", separator = "/") {
|
|
3781
|
-
// includeSelf = includeSelf !== false;
|
|
3782
|
-
// part = part || "title";
|
|
3783
|
-
// separator = separator || "/";
|
|
3784
3866
|
let val;
|
|
3785
3867
|
const path = [];
|
|
3786
3868
|
const isFunc = typeof part === "function";
|
|
@@ -3795,7 +3877,7 @@ class WunderbaumNode {
|
|
|
3795
3877
|
}, includeSelf);
|
|
3796
3878
|
return path.join(separator);
|
|
3797
3879
|
}
|
|
3798
|
-
/** Return the
|
|
3880
|
+
/** Return the preceding node (under the same parent) or null. */
|
|
3799
3881
|
getPrevSibling() {
|
|
3800
3882
|
const ac = this.parent.children;
|
|
3801
3883
|
const idx = ac.indexOf(this);
|
|
@@ -3824,7 +3906,7 @@ class WunderbaumNode {
|
|
|
3824
3906
|
hasClass(className) {
|
|
3825
3907
|
return this.classes ? this.classes.has(className) : false;
|
|
3826
3908
|
}
|
|
3827
|
-
/** Return true if node
|
|
3909
|
+
/** Return true if node is the currently focused node. @since 0.9.0 */
|
|
3828
3910
|
hasFocus() {
|
|
3829
3911
|
return this.tree.focusNode === this;
|
|
3830
3912
|
}
|
|
@@ -3879,7 +3961,7 @@ class WunderbaumNode {
|
|
|
3879
3961
|
* an expand operation is currently possible.
|
|
3880
3962
|
*/
|
|
3881
3963
|
isExpandable(andCollapsed = false) {
|
|
3882
|
-
// `false` is never expandable (
|
|
3964
|
+
// `false` is never expandable (unofficial)
|
|
3883
3965
|
if ((andCollapsed && this.expanded) || this.children === false) {
|
|
3884
3966
|
return false;
|
|
3885
3967
|
}
|
|
@@ -3942,11 +4024,11 @@ class WunderbaumNode {
|
|
|
3942
4024
|
isPartsel() {
|
|
3943
4025
|
return !this.selected && !!this._partsel;
|
|
3944
4026
|
}
|
|
3945
|
-
/** Return true if this node has DOM
|
|
4027
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3946
4028
|
isRadio() {
|
|
3947
4029
|
return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
|
|
3948
4030
|
}
|
|
3949
|
-
/** Return true if this node has DOM
|
|
4031
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3950
4032
|
isRendered() {
|
|
3951
4033
|
return !!this._rowElem;
|
|
3952
4034
|
}
|
|
@@ -4701,9 +4783,9 @@ class WunderbaumNode {
|
|
|
4701
4783
|
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
4702
4784
|
const rowDiv = this._rowElem;
|
|
4703
4785
|
// Row markup already exists
|
|
4704
|
-
const
|
|
4705
|
-
const
|
|
4706
|
-
const
|
|
4786
|
+
const nodeSpan = rowDiv.querySelector("span.wb-node");
|
|
4787
|
+
const expanderElem = nodeSpan.querySelector("i.wb-expander");
|
|
4788
|
+
const checkboxElem = nodeSpan.querySelector("i.wb-checkbox");
|
|
4707
4789
|
const rowClasses = ["wb-row"];
|
|
4708
4790
|
this.expanded ? rowClasses.push("wb-expanded") : 0;
|
|
4709
4791
|
this.lazy ? rowClasses.push("wb-lazy") : 0;
|
|
@@ -4728,7 +4810,7 @@ class WunderbaumNode {
|
|
|
4728
4810
|
if (typeInfo && typeInfo.classes) {
|
|
4729
4811
|
rowDiv.classList.add(...typeInfo.classes);
|
|
4730
4812
|
}
|
|
4731
|
-
if (
|
|
4813
|
+
if (expanderElem) {
|
|
4732
4814
|
let image = null;
|
|
4733
4815
|
if (this._isLoading) {
|
|
4734
4816
|
image = iconMap.loading;
|
|
@@ -4745,16 +4827,20 @@ class WunderbaumNode {
|
|
|
4745
4827
|
image = iconMap.expanderLazy;
|
|
4746
4828
|
}
|
|
4747
4829
|
if (image == null) {
|
|
4748
|
-
|
|
4830
|
+
expanderElem.className = "wb-expander";
|
|
4831
|
+
expanderElem.classList.add("wb-indent");
|
|
4832
|
+
}
|
|
4833
|
+
else if (TEST_HTML.test(image)) {
|
|
4834
|
+
expanderElem.replaceWith(elemFromHtml(image));
|
|
4749
4835
|
}
|
|
4750
|
-
else if (
|
|
4751
|
-
|
|
4836
|
+
else if (TEST_FILE_PATH.test(image)) {
|
|
4837
|
+
expanderElem.style.backgroundImage = `url('${image}')`;
|
|
4752
4838
|
}
|
|
4753
4839
|
else {
|
|
4754
|
-
|
|
4840
|
+
expanderElem.className = "wb-expander " + image;
|
|
4755
4841
|
}
|
|
4756
4842
|
}
|
|
4757
|
-
if (
|
|
4843
|
+
if (checkboxElem) {
|
|
4758
4844
|
let cbclass = "wb-checkbox ";
|
|
4759
4845
|
if (this.isRadio()) {
|
|
4760
4846
|
cbclass += "wb-radio ";
|
|
@@ -4778,7 +4864,7 @@ class WunderbaumNode {
|
|
|
4778
4864
|
cbclass += iconMap.checkUnchecked;
|
|
4779
4865
|
}
|
|
4780
4866
|
}
|
|
4781
|
-
|
|
4867
|
+
checkboxElem.className = cbclass;
|
|
4782
4868
|
}
|
|
4783
4869
|
// Fix active cell in cell-nav mode
|
|
4784
4870
|
if (!opts.isNew) {
|
|
@@ -4788,9 +4874,9 @@ class WunderbaumNode {
|
|
|
4788
4874
|
colSpan.classList.remove("wb-error", "wb-invalid");
|
|
4789
4875
|
}
|
|
4790
4876
|
// Update icon (if not opts.isNew, which would rebuild markup anyway)
|
|
4791
|
-
const iconSpan =
|
|
4877
|
+
const iconSpan = nodeSpan.querySelector("i.wb-icon");
|
|
4792
4878
|
if (iconSpan) {
|
|
4793
|
-
this._createIcon(
|
|
4879
|
+
this._createIcon(nodeSpan, iconSpan, !expanderElem);
|
|
4794
4880
|
}
|
|
4795
4881
|
}
|
|
4796
4882
|
// Adjust column width
|
|
@@ -5095,6 +5181,32 @@ class WunderbaumNode {
|
|
|
5095
5181
|
setKey(key, refKey) {
|
|
5096
5182
|
throw new Error("Not yet implemented");
|
|
5097
5183
|
}
|
|
5184
|
+
// /**
|
|
5185
|
+
// * Calculate a *stable*, unique key for this node from its refKey (or title).
|
|
5186
|
+
// * We also add information from the parent, because a refKey may occur multiple
|
|
5187
|
+
// * times in a tree.
|
|
5188
|
+
// */
|
|
5189
|
+
// calcUniqueKey() {
|
|
5190
|
+
// // Assuming that the parent's key was calculated the same way, we implicitly
|
|
5191
|
+
// // involve the whole refKey-path:
|
|
5192
|
+
// const s = this.key + (this.refKey || this.title);
|
|
5193
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5194
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5195
|
+
// const h1 = util.murmurHash3(s, true);
|
|
5196
|
+
// return "id_" + h1 + util.murmurHash3(h1 + s, true);
|
|
5197
|
+
// // const l = [];
|
|
5198
|
+
// // // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
5199
|
+
// // let node: WunderbaumNode = this;
|
|
5200
|
+
// // while (node.parent) {
|
|
5201
|
+
// // l.unshift(node.refKey || node.key);
|
|
5202
|
+
// // node = node.parent;
|
|
5203
|
+
// // }
|
|
5204
|
+
// // const path = l.join("/");
|
|
5205
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5206
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5207
|
+
// // const h1 = util.murmurHash3(path, true);
|
|
5208
|
+
// // return "id_" + h1 + util.murmurHash3(h1 + path, true);
|
|
5209
|
+
// }
|
|
5098
5210
|
/**
|
|
5099
5211
|
* Trigger a repaint, typically after a status or data change.
|
|
5100
5212
|
*
|
|
@@ -5126,6 +5238,23 @@ class WunderbaumNode {
|
|
|
5126
5238
|
});
|
|
5127
5239
|
return nodeList;
|
|
5128
5240
|
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Return an array of refKey values.
|
|
5243
|
+
*
|
|
5244
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
5245
|
+
* clones.
|
|
5246
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
5247
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
5248
|
+
*/
|
|
5249
|
+
getRefKeys(selected = false) {
|
|
5250
|
+
const refKeys = new Set();
|
|
5251
|
+
this.visit((node) => {
|
|
5252
|
+
if (node.refKey != null && (!selected || node.selected)) {
|
|
5253
|
+
refKeys.add(node.refKey);
|
|
5254
|
+
}
|
|
5255
|
+
});
|
|
5256
|
+
return Array.from(refKeys);
|
|
5257
|
+
}
|
|
5129
5258
|
/** Toggle the check/uncheck state. */
|
|
5130
5259
|
toggleSelected(options) {
|
|
5131
5260
|
let flag = this.isSelected();
|
|
@@ -5305,9 +5434,11 @@ class WunderbaumNode {
|
|
|
5305
5434
|
if (selectMode === "hier") {
|
|
5306
5435
|
this.fixSelection3AfterClick();
|
|
5307
5436
|
}
|
|
5308
|
-
else if (selectMode === "single") {
|
|
5437
|
+
else if (selectMode === "single" && flag) {
|
|
5309
5438
|
tree.visit((n) => {
|
|
5310
|
-
n
|
|
5439
|
+
if (n !== this) {
|
|
5440
|
+
n.selected = false;
|
|
5441
|
+
}
|
|
5311
5442
|
});
|
|
5312
5443
|
}
|
|
5313
5444
|
}
|
|
@@ -5409,30 +5540,16 @@ class WunderbaumNode {
|
|
|
5409
5540
|
this.tooltip = tooltip;
|
|
5410
5541
|
this.update();
|
|
5411
5542
|
}
|
|
5412
|
-
_sortChildren(cmp, deep) {
|
|
5413
|
-
const cl = this.children;
|
|
5414
|
-
if (!cl) {
|
|
5415
|
-
return;
|
|
5416
|
-
}
|
|
5417
|
-
cl.sort(cmp);
|
|
5418
|
-
if (deep) {
|
|
5419
|
-
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5420
|
-
if (cl[i].children) {
|
|
5421
|
-
cl[i]._sortChildren(cmp, deep);
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
}
|
|
5425
|
-
}
|
|
5426
5543
|
/**
|
|
5427
5544
|
* Sort child list by title or custom criteria.
|
|
5428
5545
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
5429
5546
|
* (defaults to sorting by title).
|
|
5430
5547
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
5548
|
+
* @deprecated use {@link sort}
|
|
5431
5549
|
*/
|
|
5432
5550
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
5433
|
-
this.
|
|
5434
|
-
this.
|
|
5435
|
-
// this.triggerModify("sort"); // TODO
|
|
5551
|
+
this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
|
|
5552
|
+
return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
|
|
5436
5553
|
}
|
|
5437
5554
|
/**
|
|
5438
5555
|
* Renumber nodes `_nativeIndex`. This is useful to allow to restore the
|
|
@@ -5454,74 +5571,142 @@ class WunderbaumNode {
|
|
|
5454
5571
|
/**
|
|
5455
5572
|
* Convenience method to implement column sorting.
|
|
5456
5573
|
* @since 0.11.0
|
|
5574
|
+
* @deprecated use {@link sort}
|
|
5457
5575
|
*/
|
|
5458
5576
|
sortByProperty(options) {
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5577
|
+
this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
|
|
5578
|
+
return this.sort(options);
|
|
5579
|
+
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Implement column sorting.
|
|
5582
|
+
* @since 0.14.0
|
|
5583
|
+
*/
|
|
5584
|
+
sort(options) {
|
|
5585
|
+
const tree = this.tree;
|
|
5586
|
+
let { propName = undefined, deep = true, key = undefined, order = undefined, caseInsensitive = true, cmp = undefined,
|
|
5587
|
+
// Support click on column sort header:
|
|
5588
|
+
updateColInfo = false, nativeOrderPropName = "_nativeIndex", colId = undefined, } = options;
|
|
5589
|
+
propName !== null && propName !== void 0 ? propName : (propName = colId);
|
|
5590
|
+
if (propName === "*") {
|
|
5591
|
+
propName = "title";
|
|
5592
|
+
}
|
|
5593
|
+
const isFolder = tree.options.sortFoldersFirst === true
|
|
5594
|
+
? (node) => node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
|
|
5595
|
+
: tree.options.sortFoldersFirst;
|
|
5463
5596
|
if (updateColInfo) {
|
|
5464
|
-
colDef = this.tree["_columnsById"][options.colId];
|
|
5597
|
+
const colDef = this.tree["_columnsById"][options.colId];
|
|
5465
5598
|
assert(colDef, `Invalid colId specified: ${options.colId}`);
|
|
5466
|
-
order =
|
|
5467
|
-
(_a = options.order) !== null && _a !== void 0 ? _a : rotate(colDef.sortOrder, ["asc", "desc", undefined]);
|
|
5599
|
+
order !== null && order !== void 0 ? order : (order = rotate(colDef.sortOrder, ["asc", "desc", undefined]));
|
|
5468
5600
|
for (const col of this.tree.columns) {
|
|
5469
5601
|
col.sortOrder = col === colDef ? order : undefined;
|
|
5470
5602
|
}
|
|
5603
|
+
if (order === undefined) {
|
|
5604
|
+
propName = nativeOrderPropName;
|
|
5605
|
+
order = "asc";
|
|
5606
|
+
}
|
|
5471
5607
|
this.tree.update(ChangeType.colStructure);
|
|
5472
5608
|
}
|
|
5473
5609
|
else {
|
|
5474
|
-
|
|
5610
|
+
propName !== null && propName !== void 0 ? propName : (propName = "title");
|
|
5611
|
+
order !== null && order !== void 0 ? order : (order = "asc");
|
|
5612
|
+
}
|
|
5613
|
+
this.logDebug(`sort(), propName=${propName}, ${order}`, options);
|
|
5614
|
+
assert(propName || cmp || key, "No `propName` or `key` specified");
|
|
5615
|
+
// Define a key callback from the parameters we have
|
|
5616
|
+
if (key == null && cmp == null) {
|
|
5617
|
+
key = (node) => {
|
|
5618
|
+
let val;
|
|
5619
|
+
if (NODE_DICT_PROPS.has(propName)) {
|
|
5620
|
+
val = node[propName];
|
|
5621
|
+
}
|
|
5622
|
+
else {
|
|
5623
|
+
val = node.data[propName];
|
|
5624
|
+
}
|
|
5625
|
+
if (caseInsensitive && typeof val === "string") {
|
|
5626
|
+
val = val.toLowerCase();
|
|
5627
|
+
}
|
|
5628
|
+
return val;
|
|
5629
|
+
};
|
|
5475
5630
|
}
|
|
5476
|
-
|
|
5477
|
-
if (
|
|
5478
|
-
|
|
5631
|
+
// Define a compare callback that uses the key callback
|
|
5632
|
+
if (cmp) {
|
|
5633
|
+
assert(!key, "`key` and `cmp` are mutually exclusive");
|
|
5634
|
+
tree.logDeprecate("SortOptions.cmp", {
|
|
5635
|
+
since: "0.14.0",
|
|
5636
|
+
hint: "use the `key` callback instead",
|
|
5637
|
+
});
|
|
5479
5638
|
}
|
|
5480
|
-
|
|
5481
|
-
propName
|
|
5482
|
-
|
|
5639
|
+
else {
|
|
5640
|
+
if (options.propName || options.caseInsensitive) {
|
|
5641
|
+
tree.logWarn("sort(): ignoring propName, caseInsensitive");
|
|
5642
|
+
}
|
|
5643
|
+
cmp = (a, b) => {
|
|
5644
|
+
if (isFolder) {
|
|
5645
|
+
const isFolderA = isFolder(a);
|
|
5646
|
+
if (isFolderA !== isFolder(b)) {
|
|
5647
|
+
return isFolderA ? -1 : 1;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
let x = key(a);
|
|
5651
|
+
let y = key(b);
|
|
5652
|
+
// Assure we have reasonable comparisons with null values:
|
|
5653
|
+
if (x == null) {
|
|
5654
|
+
x = typeof y === "string" ? "" : 0;
|
|
5655
|
+
}
|
|
5656
|
+
else if (typeof x === "boolean") {
|
|
5657
|
+
x = x ? 1 : 0;
|
|
5658
|
+
}
|
|
5659
|
+
if (y == null) {
|
|
5660
|
+
y = typeof x === "string" ? "" : 0;
|
|
5661
|
+
}
|
|
5662
|
+
else if (typeof y === "boolean") {
|
|
5663
|
+
y = y ? 1 : 0;
|
|
5664
|
+
}
|
|
5665
|
+
if (order === "desc") {
|
|
5666
|
+
return x === y ? 0 : x > y ? -1 : 1;
|
|
5667
|
+
}
|
|
5668
|
+
return x === y ? 0 : x > y ? 1 : -1;
|
|
5669
|
+
};
|
|
5483
5670
|
}
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
let av, bv;
|
|
5488
|
-
if (NODE_DICT_PROPS.has(propName)) {
|
|
5489
|
-
av = a[propName];
|
|
5490
|
-
bv = b[propName];
|
|
5491
|
-
}
|
|
5492
|
-
else {
|
|
5493
|
-
av = a.data[propName];
|
|
5494
|
-
bv = b.data[propName];
|
|
5495
|
-
}
|
|
5496
|
-
if (av == null && bv == null) {
|
|
5497
|
-
return 0;
|
|
5498
|
-
}
|
|
5499
|
-
if (av == null) {
|
|
5500
|
-
av = typeof bv === "string" ? "" : 0;
|
|
5501
|
-
}
|
|
5502
|
-
else if (typeof av === "boolean") {
|
|
5503
|
-
av = av ? 1 : 0;
|
|
5504
|
-
}
|
|
5505
|
-
if (bv == null) {
|
|
5506
|
-
bv = typeof av === "string" ? "" : 0;
|
|
5507
|
-
}
|
|
5508
|
-
else if (typeof bv === "boolean") {
|
|
5509
|
-
bv = bv ? 1 : 0;
|
|
5671
|
+
function _sortChildren(cl) {
|
|
5672
|
+
if (!cl) {
|
|
5673
|
+
return;
|
|
5510
5674
|
}
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5675
|
+
cl.sort(cmp);
|
|
5676
|
+
if (deep) {
|
|
5677
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5678
|
+
if (cl[i].children) {
|
|
5679
|
+
_sortChildren(cl[i].children);
|
|
5680
|
+
}
|
|
5517
5681
|
}
|
|
5518
5682
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5683
|
+
}
|
|
5684
|
+
if (this.children) {
|
|
5685
|
+
_sortChildren(this.children);
|
|
5686
|
+
}
|
|
5687
|
+
this.tree.update(ChangeType.structure);
|
|
5688
|
+
// this.triggerModify("sort"); // TODO
|
|
5689
|
+
}
|
|
5690
|
+
/**
|
|
5691
|
+
* Re-apply current sorting if any (use after lazy load).
|
|
5692
|
+
* Example:
|
|
5693
|
+
* ```js
|
|
5694
|
+
* load: function (e) {
|
|
5695
|
+
* // Whe loading a lazy branch, apply current sort order if any
|
|
5696
|
+
* e.node.resort();
|
|
5697
|
+
* },
|
|
5698
|
+
* ```
|
|
5699
|
+
* @since 0.14.0
|
|
5700
|
+
*/
|
|
5701
|
+
resort(options = {}) {
|
|
5702
|
+
for (const colDef of this.tree.columns) {
|
|
5703
|
+
if (colDef.sortOrder) {
|
|
5704
|
+
options.colId = colDef.id;
|
|
5705
|
+
options.order = colDef.sortOrder;
|
|
5706
|
+
this.sort(options);
|
|
5707
|
+
break;
|
|
5521
5708
|
}
|
|
5522
|
-
|
|
5523
|
-
};
|
|
5524
|
-
return this.sortChildren(cmp, deep);
|
|
5709
|
+
}
|
|
5525
5710
|
}
|
|
5526
5711
|
/**
|
|
5527
5712
|
* Trigger `modifyChild` event on a parent to signal that a child was modified.
|
|
@@ -5558,7 +5743,8 @@ class WunderbaumNode {
|
|
|
5558
5743
|
* @param {function} callback the callback function.
|
|
5559
5744
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
5560
5745
|
* its children only.
|
|
5561
|
-
* @see
|
|
5746
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
5747
|
+
* @see {@link Wunderbaum.visit}.
|
|
5562
5748
|
*/
|
|
5563
5749
|
visit(callback, includeSelf = false) {
|
|
5564
5750
|
let res = true;
|
|
@@ -5631,7 +5817,7 @@ WunderbaumNode.sequence = 0;
|
|
|
5631
5817
|
/*!
|
|
5632
5818
|
* Wunderbaum - ext-edit
|
|
5633
5819
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
5634
|
-
* v0.
|
|
5820
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5635
5821
|
*/
|
|
5636
5822
|
// const START_MARKER = "\uFFF7";
|
|
5637
5823
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -5866,7 +6052,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
5866
6052
|
newValue = newValue.trim();
|
|
5867
6053
|
}
|
|
5868
6054
|
if (!node) {
|
|
5869
|
-
this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
6055
|
+
// this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
5870
6056
|
return;
|
|
5871
6057
|
}
|
|
5872
6058
|
node.logDebug(`stopEditTitle(${apply})`, options, focusElem, newValue);
|
|
@@ -5966,8 +6152,8 @@ class EditExtension extends WunderbaumExtension {
|
|
|
5966
6152
|
* https://github.com/mar10/wunderbaum
|
|
5967
6153
|
*
|
|
5968
6154
|
* Released under the MIT license.
|
|
5969
|
-
* @version v0.
|
|
5970
|
-
* @date
|
|
6155
|
+
* @version v0.14.0
|
|
6156
|
+
* @date Fri, 20 Mar 2026 16:58:31 GMT
|
|
5971
6157
|
*/
|
|
5972
6158
|
// import "./wunderbaum.scss";
|
|
5973
6159
|
class WbSystemRoot extends WunderbaumNode {
|
|
@@ -6016,17 +6202,17 @@ class Wunderbaum {
|
|
|
6016
6202
|
this._disableUpdateIgnoreCount = 0;
|
|
6017
6203
|
this._activeNode = null;
|
|
6018
6204
|
this._focusNode = null;
|
|
6205
|
+
this._initialSource = null;
|
|
6019
6206
|
/** Shared properties, referenced by `node.type`. */
|
|
6020
6207
|
this.types = {};
|
|
6021
6208
|
/** List of column definitions. */
|
|
6022
|
-
this.columns = [];
|
|
6209
|
+
this.columns = [];
|
|
6023
6210
|
this._columnsById = {};
|
|
6024
6211
|
// Modification Status
|
|
6025
6212
|
this.pendingChangeTypes = new Set();
|
|
6026
6213
|
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
6027
6214
|
this._util = util;
|
|
6028
6215
|
// --- SELECT ---
|
|
6029
|
-
// /** @internal */
|
|
6030
6216
|
// public selectRangeAnchor: WunderbaumNode | null = null;
|
|
6031
6217
|
// --- BREADCRUMB ---
|
|
6032
6218
|
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
@@ -6045,37 +6231,44 @@ class Wunderbaum {
|
|
|
6045
6231
|
this.lastQuicksearchTerm = "";
|
|
6046
6232
|
// --- EDIT ---
|
|
6047
6233
|
this.lastClickTime = 0;
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6234
|
+
// Set default options and merge with user options
|
|
6235
|
+
const initOptions = Object.assign({
|
|
6236
|
+
id: undefined,
|
|
6237
|
+
source: [], // URL for GET/PUT, Ajax options, or callback
|
|
6238
|
+
element: unsafeCast(null),
|
|
6052
6239
|
debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
|
|
6053
6240
|
header: null, // Show/hide header (pass bool or string)
|
|
6054
|
-
// headerHeightPx: ROW_HEIGHT,
|
|
6055
6241
|
rowHeightPx: DEFAULT_ROW_HEIGHT,
|
|
6056
6242
|
iconMap: "bootstrap",
|
|
6057
|
-
columns: null,
|
|
6058
|
-
types:
|
|
6059
|
-
// escapeTitles: true,
|
|
6243
|
+
columns: [], //util.unsafeCast<ColumnDefinitionList>(null),
|
|
6244
|
+
types: {},
|
|
6060
6245
|
enabled: true,
|
|
6061
6246
|
fixedCol: false,
|
|
6062
6247
|
showSpinner: false,
|
|
6063
6248
|
checkbox: false,
|
|
6064
6249
|
minExpandLevel: 0,
|
|
6065
6250
|
emptyChildListExpandable: false,
|
|
6066
|
-
// updateThrottleWait: 200,
|
|
6067
6251
|
skeleton: false,
|
|
6252
|
+
autoCollapse: false,
|
|
6253
|
+
adjustHeight: true,
|
|
6068
6254
|
connectTopBreadcrumb: null,
|
|
6255
|
+
columnsFilterable: false,
|
|
6256
|
+
columnsMenu: false,
|
|
6257
|
+
columnsResizable: false,
|
|
6258
|
+
columnsSortable: false,
|
|
6069
6259
|
selectMode: "multi", // SelectModeType
|
|
6260
|
+
scrollIntoViewOnExpandClick: true,
|
|
6261
|
+
// --- Extensions (actually set by exensions on init)
|
|
6262
|
+
dnd: unsafeCast(null),
|
|
6263
|
+
edit: unsafeCast(null),
|
|
6264
|
+
filter: unsafeCast(null),
|
|
6070
6265
|
// --- KeyNav ---
|
|
6071
|
-
navigationModeOption: null,
|
|
6266
|
+
navigationModeOption: unsafeCast(null),
|
|
6072
6267
|
quicksearch: true,
|
|
6073
6268
|
// --- Events ---
|
|
6074
|
-
iconBadge: null,
|
|
6075
|
-
change: null,
|
|
6076
|
-
//
|
|
6077
|
-
error: null,
|
|
6078
|
-
receive: null,
|
|
6269
|
+
// iconBadge: null,
|
|
6270
|
+
// change: null,
|
|
6271
|
+
// ...
|
|
6079
6272
|
// --- Strings ---
|
|
6080
6273
|
strings: {
|
|
6081
6274
|
loadError: "Error",
|
|
@@ -6086,7 +6279,9 @@ class Wunderbaum {
|
|
|
6086
6279
|
noMatch: "No results",
|
|
6087
6280
|
matchIndex: "${match} of ${matches}",
|
|
6088
6281
|
},
|
|
6089
|
-
}, options)
|
|
6282
|
+
}, options);
|
|
6283
|
+
const opts = initOptions;
|
|
6284
|
+
this.options = opts;
|
|
6090
6285
|
const readyDeferred = new Deferred();
|
|
6091
6286
|
this.ready = readyDeferred.promise();
|
|
6092
6287
|
let readyOk = false;
|
|
@@ -6113,7 +6308,8 @@ class Wunderbaum {
|
|
|
6113
6308
|
this._callEvent("init", { error: err });
|
|
6114
6309
|
}
|
|
6115
6310
|
});
|
|
6116
|
-
this.id =
|
|
6311
|
+
this.id = initOptions.id || "wb_" + ++Wunderbaum.sequence;
|
|
6312
|
+
delete initOptions.id;
|
|
6117
6313
|
this.root = new WbSystemRoot(this);
|
|
6118
6314
|
this._registerExtension(new KeynavExtension(this));
|
|
6119
6315
|
this._registerExtension(new EditExtension(this));
|
|
@@ -6123,19 +6319,20 @@ class Wunderbaum {
|
|
|
6123
6319
|
this._registerExtension(new LoggerExtension(this));
|
|
6124
6320
|
this._updateViewportThrottled = adaptiveThrottle(this._updateViewportImmediately.bind(this), {});
|
|
6125
6321
|
// --- Evaluate options
|
|
6126
|
-
this.columns =
|
|
6127
|
-
delete
|
|
6322
|
+
this.columns = initOptions.columns || [];
|
|
6323
|
+
delete initOptions.columns;
|
|
6128
6324
|
if (!this.columns || !this.columns.length) {
|
|
6129
6325
|
const title = typeof opts.header === "string" ? opts.header : this.id;
|
|
6130
6326
|
this.columns = [{ id: "*", title: title, width: "*" }];
|
|
6131
6327
|
}
|
|
6132
|
-
if (
|
|
6133
|
-
this.setTypes(
|
|
6328
|
+
if (initOptions.types) {
|
|
6329
|
+
this.setTypes(initOptions.types, true);
|
|
6134
6330
|
}
|
|
6135
|
-
delete
|
|
6331
|
+
delete initOptions.types;
|
|
6136
6332
|
// --- Create Markup
|
|
6137
|
-
this.element = elemFromSelector(
|
|
6138
|
-
assert(!!this.element, `Invalid 'element' option: ${
|
|
6333
|
+
this.element = elemFromSelector(initOptions.element);
|
|
6334
|
+
assert(!!this.element, `Invalid 'element' option: ${initOptions.element}`);
|
|
6335
|
+
delete initOptions.element;
|
|
6139
6336
|
this.element.classList.add("wunderbaum");
|
|
6140
6337
|
if (!this.element.getAttribute("tabindex")) {
|
|
6141
6338
|
this.element.tabIndex = 0;
|
|
@@ -6211,11 +6408,11 @@ class Wunderbaum {
|
|
|
6211
6408
|
}
|
|
6212
6409
|
});
|
|
6213
6410
|
// --- Load initial data
|
|
6214
|
-
if (
|
|
6411
|
+
if (initOptions.source) {
|
|
6215
6412
|
if (opts.showSpinner) {
|
|
6216
6413
|
this.nodeListElement.innerHTML = `<progress class='spinner'>${opts.strings.loading}</progress>`;
|
|
6217
6414
|
}
|
|
6218
|
-
this.load(
|
|
6415
|
+
this.load(initOptions.source)
|
|
6219
6416
|
.then(() => {
|
|
6220
6417
|
// The source may have defined columns, so we may adjust the nav mode
|
|
6221
6418
|
if (opts.navigationModeOption == null) {
|
|
@@ -6248,15 +6445,18 @@ class Wunderbaum {
|
|
|
6248
6445
|
// has a wrong value at start???
|
|
6249
6446
|
this.update(ChangeType.any);
|
|
6250
6447
|
// --- Bind listeners
|
|
6251
|
-
this.
|
|
6252
|
-
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6253
|
-
this.update(ChangeType.scroll);
|
|
6254
|
-
});
|
|
6448
|
+
this._registerEventHandlers();
|
|
6255
6449
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
6256
6450
|
// this.log("ResizeObserver: Size changed", entries);
|
|
6257
6451
|
this.update(ChangeType.resize);
|
|
6258
6452
|
});
|
|
6259
6453
|
this.resizeObserver.observe(this.element);
|
|
6454
|
+
}
|
|
6455
|
+
_registerEventHandlers() {
|
|
6456
|
+
this.element.addEventListener("scroll", (e) => {
|
|
6457
|
+
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6458
|
+
this.update(ChangeType.scroll);
|
|
6459
|
+
});
|
|
6260
6460
|
onEvent(this.element, "click", ".wb-button,.wb-col-icon", (e) => {
|
|
6261
6461
|
var _a, _b;
|
|
6262
6462
|
const info = Wunderbaum.getEventInfo(e);
|
|
@@ -6272,9 +6472,6 @@ class Wunderbaum {
|
|
|
6272
6472
|
const node = info.node;
|
|
6273
6473
|
const mouseEvent = e;
|
|
6274
6474
|
// this.log("click", info);
|
|
6275
|
-
// if (this._selectRange(info) === false) {
|
|
6276
|
-
// return;
|
|
6277
|
-
// }
|
|
6278
6475
|
if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
|
|
6279
6476
|
this.lastClickTime = Date.now();
|
|
6280
6477
|
return false;
|
|
@@ -6293,20 +6490,22 @@ class Wunderbaum {
|
|
|
6293
6490
|
(!slowClickDelay || Date.now() - this.lastClickTime < slowClickDelay)) {
|
|
6294
6491
|
node.startEditTitle();
|
|
6295
6492
|
}
|
|
6296
|
-
if (info.colIdx >= 0) {
|
|
6297
|
-
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6298
|
-
}
|
|
6299
|
-
else {
|
|
6300
|
-
node.setActive(true, { event: e });
|
|
6301
|
-
}
|
|
6302
6493
|
if (info.region === NodeRegion.expander) {
|
|
6303
6494
|
node.setExpanded(!node.isExpanded(), {
|
|
6304
|
-
scrollIntoView: options.scrollIntoViewOnExpandClick !== false,
|
|
6495
|
+
scrollIntoView: this.options.scrollIntoViewOnExpandClick !== false,
|
|
6305
6496
|
});
|
|
6306
6497
|
}
|
|
6307
6498
|
else if (info.region === NodeRegion.checkbox) {
|
|
6308
6499
|
node.toggleSelected();
|
|
6309
6500
|
}
|
|
6501
|
+
else {
|
|
6502
|
+
if (info.colIdx >= 0) {
|
|
6503
|
+
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6504
|
+
}
|
|
6505
|
+
else {
|
|
6506
|
+
node.setActive(true, { event: e });
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6310
6509
|
}
|
|
6311
6510
|
this.lastClickTime = Date.now();
|
|
6312
6511
|
});
|
|
@@ -6342,7 +6541,7 @@ class Wunderbaum {
|
|
|
6342
6541
|
const targetNode = Wunderbaum.getNode(e);
|
|
6343
6542
|
this._callEvent("focus", { flag: flag, event: e });
|
|
6344
6543
|
if (flag && this.isRowNav() && !this.isEditingTitle()) {
|
|
6345
|
-
if (
|
|
6544
|
+
if (this.options.navigationModeOption === NavModeEnum.row) {
|
|
6346
6545
|
targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
|
|
6347
6546
|
}
|
|
6348
6547
|
else {
|
|
@@ -6409,11 +6608,12 @@ class Wunderbaum {
|
|
|
6409
6608
|
}
|
|
6410
6609
|
/**
|
|
6411
6610
|
* Return the icon-function -> icon-definition mapping.
|
|
6611
|
+
* @deprecated Use {@link Wunderbaum.iconMaps}
|
|
6412
6612
|
*/
|
|
6413
6613
|
get iconMap() {
|
|
6414
6614
|
const map = this.options.iconMap;
|
|
6415
6615
|
if (typeof map === "string") {
|
|
6416
|
-
return
|
|
6616
|
+
return defaultIconMaps[map];
|
|
6417
6617
|
}
|
|
6418
6618
|
return map;
|
|
6419
6619
|
}
|
|
@@ -6466,7 +6666,38 @@ class Wunderbaum {
|
|
|
6466
6666
|
ext.init();
|
|
6467
6667
|
}
|
|
6468
6668
|
}
|
|
6469
|
-
/**
|
|
6669
|
+
/**
|
|
6670
|
+
* Calculate a *stable*, unique key for a node from its refKey (or title).
|
|
6671
|
+
* We also add information from the parent, because a refKey may occur multiple
|
|
6672
|
+
* times in a tree (but not as child of the same parent).
|
|
6673
|
+
* @internal
|
|
6674
|
+
*/
|
|
6675
|
+
_calculateKey(data, parent) {
|
|
6676
|
+
if (data.key) {
|
|
6677
|
+
// Always use an explicitly passed key
|
|
6678
|
+
return data.key;
|
|
6679
|
+
}
|
|
6680
|
+
// Auto-keys are optional, use a monotonic counter by default:
|
|
6681
|
+
if (!this.options.autoKeys) {
|
|
6682
|
+
return "" + ++WunderbaumNode.sequence;
|
|
6683
|
+
}
|
|
6684
|
+
// Add the parent's key to the hash. Assuming this was generated by the
|
|
6685
|
+
// same algorithm, this should incorporate the whole path:
|
|
6686
|
+
const s = (parent ? parent.key : "") + (data.refKey || data.title);
|
|
6687
|
+
// 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
6688
|
+
// https://security.stackexchange.com/q/209882/207588
|
|
6689
|
+
const h1 = murmurHash3(s, true);
|
|
6690
|
+
let key = "id_" + h1 + murmurHash3(h1 + s, true);
|
|
6691
|
+
// Check for collisions
|
|
6692
|
+
// (Most likely if the same title occurs multiple in the same parent).
|
|
6693
|
+
const existingNode = this.keyMap.get(key);
|
|
6694
|
+
if (existingNode) {
|
|
6695
|
+
key += "." + ++Wunderbaum.sequence;
|
|
6696
|
+
this.logWarn(`Node with existing key: '${existingNode}', using ${key}.`, data);
|
|
6697
|
+
}
|
|
6698
|
+
return key;
|
|
6699
|
+
}
|
|
6700
|
+
/** Add node to tree's bookkeeping data structures. @internal */
|
|
6470
6701
|
_registerNode(node) {
|
|
6471
6702
|
const key = node.key;
|
|
6472
6703
|
assert(key != null, `Missing key: '${node}'.`);
|
|
@@ -6483,7 +6714,7 @@ class Wunderbaum {
|
|
|
6483
6714
|
}
|
|
6484
6715
|
}
|
|
6485
6716
|
}
|
|
6486
|
-
/** Remove node from tree's bookkeeping data structures. */
|
|
6717
|
+
/** Remove node from tree's bookkeeping data structures. @internal */
|
|
6487
6718
|
_unregisterNode(node) {
|
|
6488
6719
|
// Remove refKey reference from map (if any)
|
|
6489
6720
|
const rk = node.refKey;
|
|
@@ -6602,6 +6833,16 @@ class Wunderbaum {
|
|
|
6602
6833
|
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
6603
6834
|
return this._getNodeByRowIdx(bottomIdx);
|
|
6604
6835
|
}
|
|
6836
|
+
/** Return preceding visible node in the viewport. */
|
|
6837
|
+
_getPrevNodeInView(node, ofs = 1) {
|
|
6838
|
+
this.visitRows((n) => {
|
|
6839
|
+
node = n;
|
|
6840
|
+
if (ofs-- <= 0) {
|
|
6841
|
+
return false;
|
|
6842
|
+
}
|
|
6843
|
+
}, { reverse: true, start: node || this.getActiveNode() });
|
|
6844
|
+
return node;
|
|
6845
|
+
}
|
|
6605
6846
|
/** Return following visible node in the viewport. */
|
|
6606
6847
|
_getNextNodeInView(node, options) {
|
|
6607
6848
|
let ofs = (options === null || options === void 0 ? void 0 : options.ofs) || 1;
|
|
@@ -6853,22 +7094,39 @@ class Wunderbaum {
|
|
|
6853
7094
|
/** Run code, but defer rendering of viewport until done.
|
|
6854
7095
|
*
|
|
6855
7096
|
* ```js
|
|
6856
|
-
* tree.runWithDeferredUpdate(() => {
|
|
6857
|
-
* return
|
|
7097
|
+
* const res = tree.runWithDeferredUpdate(() => {
|
|
7098
|
+
* return someFunctionThatWouldUpdateManyNodes();
|
|
6858
7099
|
* });
|
|
6859
7100
|
* ```
|
|
6860
7101
|
*/
|
|
6861
|
-
runWithDeferredUpdate(func
|
|
7102
|
+
runWithDeferredUpdate(func) {
|
|
6862
7103
|
try {
|
|
6863
7104
|
this.enableUpdate(false);
|
|
6864
7105
|
const res = func();
|
|
6865
|
-
assert(!(res instanceof Promise), `Promise return not allowed: ${res}`);
|
|
7106
|
+
assert(!(res instanceof Promise), `Promise return not allowed (see 'runWithDeferredUpdateAsync()'): ${res}`);
|
|
6866
7107
|
return res;
|
|
6867
7108
|
}
|
|
6868
7109
|
finally {
|
|
6869
7110
|
this.enableUpdate(true);
|
|
6870
7111
|
}
|
|
6871
7112
|
}
|
|
7113
|
+
/** Run code, but defer rendering of viewport until done.
|
|
7114
|
+
*
|
|
7115
|
+
* ```js
|
|
7116
|
+
* const res = await tree.runWithDeferredUpdate(async () => {
|
|
7117
|
+
* return someAsyncFunctionThatWouldUpdateManyNodes();
|
|
7118
|
+
* });
|
|
7119
|
+
* ```
|
|
7120
|
+
*/
|
|
7121
|
+
async runWithDeferredUpdateAsync(func) {
|
|
7122
|
+
try {
|
|
7123
|
+
this.enableUpdate(false);
|
|
7124
|
+
return await func();
|
|
7125
|
+
}
|
|
7126
|
+
finally {
|
|
7127
|
+
this.enableUpdate(true);
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
6872
7130
|
/** Recursively expand all expandable nodes (triggers lazy load if needed). */
|
|
6873
7131
|
async expandAll(flag = true, options) {
|
|
6874
7132
|
await this.root.expandAll(flag, options);
|
|
@@ -6888,6 +7146,17 @@ class Wunderbaum {
|
|
|
6888
7146
|
getSelectedNodes(stopOnParents = false) {
|
|
6889
7147
|
return this.root.getSelectedNodes(stopOnParents);
|
|
6890
7148
|
}
|
|
7149
|
+
/**
|
|
7150
|
+
* Return an array of refKey values.
|
|
7151
|
+
*
|
|
7152
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
7153
|
+
* clones.
|
|
7154
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
7155
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
7156
|
+
*/
|
|
7157
|
+
getRefKeys(selected = false) {
|
|
7158
|
+
return this.root.getRefKeys(selected);
|
|
7159
|
+
}
|
|
6891
7160
|
/*
|
|
6892
7161
|
* Return an array of selected nodes.
|
|
6893
7162
|
*/
|
|
@@ -7169,6 +7438,18 @@ class Wunderbaum {
|
|
|
7169
7438
|
format(name_cb, connectors) {
|
|
7170
7439
|
return this.root.format(name_cb, connectors);
|
|
7171
7440
|
}
|
|
7441
|
+
/**
|
|
7442
|
+
* Always returns null (so a tree instance behaves as `tree.root`).
|
|
7443
|
+
*/
|
|
7444
|
+
get parent() {
|
|
7445
|
+
return null;
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Return a list of top-level nodes.
|
|
7449
|
+
*/
|
|
7450
|
+
get children() {
|
|
7451
|
+
return this.root.children || [];
|
|
7452
|
+
}
|
|
7172
7453
|
/**
|
|
7173
7454
|
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
7174
7455
|
*/
|
|
@@ -7287,7 +7568,7 @@ class Wunderbaum {
|
|
|
7287
7568
|
}
|
|
7288
7569
|
/** Return true if any node title or grid cell is currently beeing edited.
|
|
7289
7570
|
*
|
|
7290
|
-
* See also {@link
|
|
7571
|
+
* See also {@link isEditingTitle}.
|
|
7291
7572
|
*/
|
|
7292
7573
|
isEditing() {
|
|
7293
7574
|
const focusElem = this.nodeListElement.querySelector("input:focus,select:focus");
|
|
@@ -7295,7 +7576,7 @@ class Wunderbaum {
|
|
|
7295
7576
|
}
|
|
7296
7577
|
/** Return true if any node is currently in edit-title mode.
|
|
7297
7578
|
*
|
|
7298
|
-
* See also {@link WunderbaumNode.isEditingTitle} and {@link
|
|
7579
|
+
* See also {@link WunderbaumNode.isEditingTitle} and {@link isEditing}.
|
|
7299
7580
|
*/
|
|
7300
7581
|
isEditingTitle() {
|
|
7301
7582
|
return this._callMethod("edit.isEditingTitle");
|
|
@@ -7315,7 +7596,7 @@ class Wunderbaum {
|
|
|
7315
7596
|
return res;
|
|
7316
7597
|
}
|
|
7317
7598
|
/** Write to `console.log` with tree name as prefix if opts.debugLevel >= 4.
|
|
7318
|
-
* @see {@link
|
|
7599
|
+
* @see {@link logDebug}
|
|
7319
7600
|
*/
|
|
7320
7601
|
log(...args) {
|
|
7321
7602
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7324,7 +7605,7 @@ class Wunderbaum {
|
|
|
7324
7605
|
}
|
|
7325
7606
|
/** Write to `console.debug` with tree name as prefix if opts.debugLevel >= 4.
|
|
7326
7607
|
* and browser console level includes debug/verbose messages.
|
|
7327
|
-
* @see {@link
|
|
7608
|
+
* @see {@link log}
|
|
7328
7609
|
*/
|
|
7329
7610
|
logDebug(...args) {
|
|
7330
7611
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7362,6 +7643,19 @@ class Wunderbaum {
|
|
|
7362
7643
|
console.warn(this.toString(), ...args); // eslint-disable-line no-console
|
|
7363
7644
|
}
|
|
7364
7645
|
}
|
|
7646
|
+
/** Emit a warning for deprecated methods. @internal */
|
|
7647
|
+
logDeprecate(method, options) {
|
|
7648
|
+
if (this.options.debugLevel >= 2) {
|
|
7649
|
+
let msg = `${this}: ${method} is deprecated`;
|
|
7650
|
+
if (options === null || options === void 0 ? void 0 : options.since) {
|
|
7651
|
+
msg += ` since ${options.since}`;
|
|
7652
|
+
}
|
|
7653
|
+
if (options === null || options === void 0 ? void 0 : options.hint) {
|
|
7654
|
+
msg += ` (${options.since})`;
|
|
7655
|
+
}
|
|
7656
|
+
console.warn(msg + "."); // eslint-disable-line no-console
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7365
7659
|
/** Reset column widths to default. @since 0.10.0 */
|
|
7366
7660
|
resetColumns() {
|
|
7367
7661
|
this.columns.forEach((col) => {
|
|
@@ -7530,46 +7824,64 @@ class Wunderbaum {
|
|
|
7530
7824
|
this._focusNode = node;
|
|
7531
7825
|
}
|
|
7532
7826
|
/** Return the current selection/expansion/activation status. @experimental */
|
|
7533
|
-
getState(options) {
|
|
7827
|
+
getState(options = {}) {
|
|
7534
7828
|
var _a, _b;
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7829
|
+
const { activeKey = true, expandedKeys = false, selectedKeys = false, } = options;
|
|
7830
|
+
const expandSet = new Set();
|
|
7831
|
+
if (expandedKeys) {
|
|
7538
7832
|
for (const node of this) {
|
|
7539
|
-
if (node.
|
|
7540
|
-
|
|
7833
|
+
if (node.isExpanded() && node.hasChildren()) {
|
|
7834
|
+
expandSet.add(node.key);
|
|
7541
7835
|
}
|
|
7542
7836
|
}
|
|
7543
7837
|
}
|
|
7838
|
+
// Parents of active node are always expanded
|
|
7839
|
+
if (activeKey && this.activeNode) {
|
|
7840
|
+
this.activeNode.visitParents((n) => {
|
|
7841
|
+
if (n.parent) {
|
|
7842
|
+
expandSet.add(n.key);
|
|
7843
|
+
}
|
|
7844
|
+
}, false);
|
|
7845
|
+
}
|
|
7544
7846
|
const state = {
|
|
7847
|
+
expandedKeys: expandSet.size ? Array.from(expandSet) : undefined,
|
|
7545
7848
|
activeKey: (_b = (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null,
|
|
7546
7849
|
activeColIdx: this.activeColIdx,
|
|
7547
|
-
selectedKeys:
|
|
7548
|
-
?
|
|
7549
|
-
:
|
|
7550
|
-
expandedKeys: expandedKeys,
|
|
7850
|
+
selectedKeys: selectedKeys
|
|
7851
|
+
? this.getSelectedNodes().flatMap((n) => n.key)
|
|
7852
|
+
: undefined,
|
|
7551
7853
|
};
|
|
7552
7854
|
return state;
|
|
7553
7855
|
}
|
|
7554
7856
|
/** Apply selection/expansion/activation status. @experimental */
|
|
7555
|
-
setState(state, options) {
|
|
7556
|
-
|
|
7857
|
+
async setState(state, options = {}) {
|
|
7858
|
+
const { expandLazy = true } = options;
|
|
7859
|
+
return this.runWithDeferredUpdateAsync(async () => {
|
|
7557
7860
|
var _a, _b;
|
|
7558
|
-
if (state.
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7861
|
+
if (state.expandedKeys && state.expandedKeys.length) {
|
|
7862
|
+
if (expandLazy) {
|
|
7863
|
+
// Expand all keys recursively, even if they are not in the tree yet
|
|
7864
|
+
await this._loadLazyNodes(state.expandedKeys, {
|
|
7865
|
+
expand: true,
|
|
7866
|
+
noEvents: true,
|
|
7867
|
+
});
|
|
7562
7868
|
}
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7869
|
+
else {
|
|
7870
|
+
for (const key of state.expandedKeys) {
|
|
7871
|
+
(_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setExpanded(true);
|
|
7872
|
+
}
|
|
7567
7873
|
}
|
|
7568
7874
|
}
|
|
7569
7875
|
if (state.activeKey) {
|
|
7570
7876
|
this.setActiveNode(state.activeKey);
|
|
7571
7877
|
}
|
|
7572
|
-
if (state.
|
|
7878
|
+
if (state.selectedKeys) {
|
|
7879
|
+
this.selectAll(false);
|
|
7880
|
+
for (const key of state.selectedKeys) {
|
|
7881
|
+
(_b = this.findKey(key)) === null || _b === void 0 ? void 0 : _b.setSelected(true);
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7884
|
+
if (this.isCellNav() && state.activeColIdx != null) {
|
|
7573
7885
|
this.setColumn(state.activeColIdx);
|
|
7574
7886
|
}
|
|
7575
7887
|
});
|
|
@@ -7730,18 +8042,33 @@ class Wunderbaum {
|
|
|
7730
8042
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
7731
8043
|
* (defaults to sorting by title).
|
|
7732
8044
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
8045
|
+
* @deprecated use {@link sort}
|
|
7733
8046
|
*/
|
|
7734
8047
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
7735
|
-
this.
|
|
8048
|
+
this.logDeprecate("sortChildren()", { since: "0.14.0" });
|
|
8049
|
+
return this.sort({
|
|
8050
|
+
cmp: cmp ? cmp : undefined,
|
|
8051
|
+
deep: deep,
|
|
8052
|
+
propName: "title",
|
|
8053
|
+
});
|
|
7736
8054
|
}
|
|
7737
8055
|
/**
|
|
7738
8056
|
* Convenience method to implement column sorting.
|
|
7739
8057
|
* @see {@link WunderbaumNode.sortByProperty}.
|
|
7740
8058
|
* @since 0.11.0
|
|
8059
|
+
* @deprecated use {@link sort}
|
|
7741
8060
|
*/
|
|
7742
8061
|
sortByProperty(options) {
|
|
8062
|
+
this.logDeprecate("sortByProperty()", { since: "0.14.0" });
|
|
7743
8063
|
this.root.sortByProperty(options);
|
|
7744
8064
|
}
|
|
8065
|
+
/**
|
|
8066
|
+
* Sort nodes list by title or custom criteria.
|
|
8067
|
+
* @since 0.14.0
|
|
8068
|
+
*/
|
|
8069
|
+
sort(options) {
|
|
8070
|
+
this.root.sort(options);
|
|
8071
|
+
}
|
|
7745
8072
|
/** Convert tree to an array of plain objects.
|
|
7746
8073
|
*
|
|
7747
8074
|
* @param callback is called for every node, in order to allow
|
|
@@ -7993,12 +8320,10 @@ class Wunderbaum {
|
|
|
7993
8320
|
iconElem = document.createElement("i");
|
|
7994
8321
|
iconElem.className = "wb-icon";
|
|
7995
8322
|
}
|
|
7996
|
-
else if (
|
|
7997
|
-
// HTML
|
|
8323
|
+
else if (TEST_HTML.test(icon)) {
|
|
7998
8324
|
iconElem = elemFromHtml(icon);
|
|
7999
8325
|
}
|
|
8000
|
-
else if (
|
|
8001
|
-
// Image URL
|
|
8326
|
+
else if (TEST_FILE_PATH.test(icon)) {
|
|
8002
8327
|
iconElem = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
|
|
8003
8328
|
}
|
|
8004
8329
|
else {
|
|
@@ -8236,7 +8561,8 @@ class Wunderbaum {
|
|
|
8236
8561
|
}
|
|
8237
8562
|
/**
|
|
8238
8563
|
* Call `callback(node)` for all nodes in hierarchical order (depth-first, pre-order).
|
|
8239
|
-
* @see
|
|
8564
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
8565
|
+
* @see {@link WunderbaumNode.visit}.
|
|
8240
8566
|
*
|
|
8241
8567
|
* @param {function} callback the callback function.
|
|
8242
8568
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
@@ -8379,11 +8705,71 @@ class Wunderbaum {
|
|
|
8379
8705
|
*
|
|
8380
8706
|
* Previous data is cleared. Note that also column- and type defintions may
|
|
8381
8707
|
* be passed with the `source` object.
|
|
8708
|
+
* @see {@link Wunderbaum.reload} for a shortcut to reload the last ajax request
|
|
8709
|
+
* and restore the previous state.
|
|
8382
8710
|
*/
|
|
8383
|
-
load(source) {
|
|
8711
|
+
async load(source) {
|
|
8384
8712
|
this.clear();
|
|
8713
|
+
this._initialSource = source;
|
|
8385
8714
|
return this.root.load(source);
|
|
8386
8715
|
}
|
|
8716
|
+
/** Reload the tree and optionally restore state.
|
|
8717
|
+
* Source defaults to last ajax url if any.
|
|
8718
|
+
* Restoring the active node requires stable keys
|
|
8719
|
+
* @see {@link WunderbaumOptions.autoKeys}
|
|
8720
|
+
* @see {@link Wunderbaum.load}
|
|
8721
|
+
* @experimental
|
|
8722
|
+
*/
|
|
8723
|
+
async reload(options = {}) {
|
|
8724
|
+
const { source = this._initialSource, reactivate = true } = options;
|
|
8725
|
+
if (!source) {
|
|
8726
|
+
this.logWarn("No previous ajax source to reload.");
|
|
8727
|
+
return;
|
|
8728
|
+
}
|
|
8729
|
+
if (!reactivate) {
|
|
8730
|
+
return this.load(source);
|
|
8731
|
+
}
|
|
8732
|
+
const state = this.getState();
|
|
8733
|
+
await this.load(source);
|
|
8734
|
+
return this.setState(state);
|
|
8735
|
+
}
|
|
8736
|
+
/**
|
|
8737
|
+
* Make sure that all nodes in the given keyList are accessible.
|
|
8738
|
+
* This may include loading lazy parent nodes.
|
|
8739
|
+
* Recursively load (and optionally expand) all requested node paths.
|
|
8740
|
+
*/
|
|
8741
|
+
async _loadLazyNodes(keyList, options = {}) {
|
|
8742
|
+
const { expand = true } = options;
|
|
8743
|
+
const keySet = new Set(keyList);
|
|
8744
|
+
// Make sure that all parent nodes are loaded (and expand if requested)
|
|
8745
|
+
while (keySet.size > 0) {
|
|
8746
|
+
const pendingNodes = [];
|
|
8747
|
+
const curSet = new Set(keySet);
|
|
8748
|
+
for (const key of curSet) {
|
|
8749
|
+
const node = this.findKey(key);
|
|
8750
|
+
if (!node) {
|
|
8751
|
+
continue; // key not yet found (need to load lazy parent?)
|
|
8752
|
+
}
|
|
8753
|
+
keySet.delete(key);
|
|
8754
|
+
if (expand) {
|
|
8755
|
+
pendingNodes.push(node.setExpanded(true));
|
|
8756
|
+
}
|
|
8757
|
+
else if (node.isUnloaded()) {
|
|
8758
|
+
pendingNodes.push(node.loadLazy());
|
|
8759
|
+
}
|
|
8760
|
+
if (node._rowElem) {
|
|
8761
|
+
node._render(); // show spinner even is update is suppressed
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
if (pendingNodes.length === 0) {
|
|
8765
|
+
// will not load any more nodes, so if if there are still keys
|
|
8766
|
+
// left in the set, we will never find them
|
|
8767
|
+
this.logWarn(`Could not expand ${keySet.size} nodes:`, keySet);
|
|
8768
|
+
break;
|
|
8769
|
+
}
|
|
8770
|
+
await Promise.allSettled(pendingNodes);
|
|
8771
|
+
}
|
|
8772
|
+
}
|
|
8387
8773
|
/**
|
|
8388
8774
|
* Disable render requests during operations that would trigger many updates.
|
|
8389
8775
|
*
|
|
@@ -8481,8 +8867,20 @@ class Wunderbaum {
|
|
|
8481
8867
|
}
|
|
8482
8868
|
Wunderbaum.sequence = 0;
|
|
8483
8869
|
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
8484
|
-
Wunderbaum.version = "v0.
|
|
8870
|
+
Wunderbaum.version = "v0.14.0"; // Set to semver by 'grunt release'
|
|
8485
8871
|
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
8486
8872
|
Wunderbaum.util = util;
|
|
8873
|
+
/** A map of default iconMaps.
|
|
8874
|
+
* May be used as default, when passing partial icon definition maps:
|
|
8875
|
+
* ```js
|
|
8876
|
+
* const tree = new mar10.Wunderbaum({
|
|
8877
|
+
* ...
|
|
8878
|
+
* iconMap: Object.assign(Wunderbaum.iconMaps.bootstrap, {
|
|
8879
|
+
* folder: "bi bi-archive",
|
|
8880
|
+
* }),
|
|
8881
|
+
* });
|
|
8882
|
+
* ```
|
|
8883
|
+
*/
|
|
8884
|
+
Wunderbaum.iconMaps = defaultIconMaps;
|
|
8487
8885
|
|
|
8488
8886
|
export { Wunderbaum };
|