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.
@@ -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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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() queing request #${waiting}...`, args);
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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 charecters of `text` either by `mark` or
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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 re-use the key on drop:
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
- setTimeout(() => {
2492
- // Decouple this call, because drop actions may prevent the dragend event
2493
- // from being fired on some browsers
2494
- targetNode._callEvent("dnd.drop", {
2495
- event: e,
2496
- region: region,
2497
- suggestedDropMode: region === "over" ? "appendChild" : region,
2498
- suggestedDropEffect: lastDropEffect,
2499
- // suggestedDropEffect: e.dataTransfer?.dropEffect,
2500
- sourceNode: srcNode,
2501
- sourceNodeData: nodeData,
2502
- });
2503
- }, 10);
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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 TEST_IMG = new RegExp(/\.|\//);
2685
- // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2686
- // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
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 iconMaps = {
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-solid fa-folder-closed",
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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
- * Hanldes drag and sragstop events for column resizing.
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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, _b;
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 = "" + ((_a = data.key) !== null && _a !== void 0 ? _a : ++WunderbaumNode.sequence);
3237
- this.title = "" + ((_b = data.title) !== null && _b !== void 0 ? _b : "<" + this.key + ">");
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 hierachical node path, e.g. "a/b/c".
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 preceeding node (under the same parent) or null. */
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 ist the currently focused node. @since 0.9.0 */
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 (unoffical)
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 representaion, i.e. is displayed in the viewport. */
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 representaion, i.e. is displayed in the viewport. */
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 nodeElem = rowDiv.querySelector("span.wb-node");
4705
- const expanderSpan = nodeElem.querySelector("i.wb-expander");
4706
- const checkboxSpan = nodeElem.querySelector("i.wb-checkbox");
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 (expanderSpan) {
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
- expanderSpan.classList.add("wb-indent");
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 (TEST_IMG.test(image)) {
4751
- expanderSpan.style.backgroundImage = `url('${image}')`;
4836
+ else if (TEST_FILE_PATH.test(image)) {
4837
+ expanderElem.style.backgroundImage = `url('${image}')`;
4752
4838
  }
4753
4839
  else {
4754
- expanderSpan.className = "wb-expander " + image;
4840
+ expanderElem.className = "wb-expander " + image;
4755
4841
  }
4756
4842
  }
4757
- if (checkboxSpan) {
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
- checkboxSpan.className = cbclass;
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 = nodeElem.querySelector("i.wb-icon");
4877
+ const iconSpan = nodeSpan.querySelector("i.wb-icon");
4792
4878
  if (iconSpan) {
4793
- this._createIcon(nodeElem, iconSpan, !expanderSpan);
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.selected = false;
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._sortChildren(cmp || nodeTitleSorter, deep);
5434
- this.tree.update(ChangeType.structure);
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
- var _a, _b, _c;
5460
- const { caseInsensitive = true, deep = true, nativeOrderPropName = "_nativeIndex", updateColInfo = false, } = options;
5461
- let order;
5462
- let colDef;
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
- order = (_b = options.order) !== null && _b !== void 0 ? _b : "asc";
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
- let propName = (_c = options.propName) !== null && _c !== void 0 ? _c : (options.colId || "");
5477
- if (propName === "*") {
5478
- propName = "title";
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
- if (order == null) {
5481
- propName = nativeOrderPropName;
5482
- order = "asc";
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
- this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
5485
- assert(propName, "No property name specified");
5486
- const cmp = (a, b) => {
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
- if (caseInsensitive) {
5512
- if (typeof av === "string") {
5513
- av = av.toLowerCase();
5514
- }
5515
- if (typeof bv === "string") {
5516
- bv = bv.toLowerCase();
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
- if (order === "desc") {
5520
- return av === bv ? 0 : av > bv ? -1 : 1;
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
- return av === bv ? 0 : av > bv ? 1 : -1;
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 {@link IterableIterator<WunderbaumNode>}, {@link Wunderbaum.visit}.
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.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
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.13.0
5970
- * @date Sat, 08 Mar 2025 14:16:31 GMT
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 = []; // any[] = [];
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
- const opts = (this.options = extend({
6049
- id: null,
6050
- source: null, // URL for GET/PUT, Ajax options, or callback
6051
- element: null, // <div class="wunderbaum">
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: null,
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, // NavModeEnum,
6266
+ navigationModeOption: unsafeCast(null),
6072
6267
  quicksearch: true,
6073
6268
  // --- Events ---
6074
- iconBadge: null,
6075
- change: null,
6076
- // enhanceTitle: null,
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 = opts.id || "wb_" + ++Wunderbaum.sequence;
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 = opts.columns;
6127
- delete opts.columns;
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 (opts.types) {
6133
- this.setTypes(opts.types, true);
6328
+ if (initOptions.types) {
6329
+ this.setTypes(initOptions.types, true);
6134
6330
  }
6135
- delete opts.types;
6331
+ delete initOptions.types;
6136
6332
  // --- Create Markup
6137
- this.element = elemFromSelector(opts.element);
6138
- assert(!!this.element, `Invalid 'element' option: ${opts.element}`);
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 (opts.source) {
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(opts.source)
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.element.addEventListener("scroll", (e) => {
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 (opts.navigationModeOption === NavModeEnum.row) {
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 iconMaps[map];
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
- /** Add node to tree's bookkeeping data structures. */
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 someFuncThatWouldUpdateManyNodes();
7097
+ * const res = tree.runWithDeferredUpdate(() => {
7098
+ * return someFunctionThatWouldUpdateManyNodes();
6858
7099
  * });
6859
7100
  * ```
6860
7101
  */
6861
- runWithDeferredUpdate(func, hint = null) {
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 Wunderbaum.isEditingTitle}.
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 Wunderbaum.isEditing}.
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 Wunderbaum.logDebug}
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 Wunderbaum.log}
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
- let expandedKeys = undefined;
7536
- if (options.expandedKeys !== false) {
7537
- expandedKeys = [];
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.expanded) {
7540
- expandedKeys.push(node.key);
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: options.selectedKeys === false
7548
- ? undefined
7549
- : this.getSelectedNodes().flatMap((n) => n.key),
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
- this.runWithDeferredUpdate(() => {
7857
+ async setState(state, options = {}) {
7858
+ const { expandLazy = true } = options;
7859
+ return this.runWithDeferredUpdateAsync(async () => {
7557
7860
  var _a, _b;
7558
- if (state.selectedKeys) {
7559
- this.selectAll(false);
7560
- for (const key of state.selectedKeys) {
7561
- (_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setSelected(true);
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
- if (state.expandedKeys) {
7565
- for (const key of state.expandedKeys) {
7566
- (_b = this.findKey(key)) === null || _b === void 0 ? void 0 : _b.setExpanded(true);
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.activeColIdx != null) {
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.root.sortChildren(cmp, deep);
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 (icon.indexOf("<") >= 0) {
7997
- // HTML
8323
+ else if (TEST_HTML.test(icon)) {
7998
8324
  iconElem = elemFromHtml(icon);
7999
8325
  }
8000
- else if (TEST_IMG.test(icon)) {
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 {@link IterableIterator<WunderbaumNode>}, {@link WunderbaumNode.visit}.
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.13.0"; // Set to semver by 'grunt release'
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 };