wunderbaum 0.3.5 → 0.5.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.
@@ -7,7 +7,7 @@
7
7
  /*!
8
8
  * Wunderbaum - util
9
9
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
10
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
10
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
11
11
  */
12
12
  /** @module util */
13
13
  /** Readable names for `MouseEvent.button` */
@@ -762,10 +762,10 @@
762
762
  /*!
763
763
  * Wunderbaum - types
764
764
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
765
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
765
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
766
766
  */
767
767
  /**
768
- * Possible values for {@link WunderbaumNode.setModified()} and {@link Wunderbaum.setModified()}.
768
+ * Possible values for {@link WunderbaumNode.update()} and {@link Wunderbaum.update()}.
769
769
  */
770
770
  var ChangeType;
771
771
  (function (ChangeType) {
@@ -801,7 +801,7 @@
801
801
  NodeStatusType["loading"] = "loading";
802
802
  NodeStatusType["error"] = "error";
803
803
  NodeStatusType["noData"] = "noData";
804
- // paging = "paging",
804
+ NodeStatusType["paging"] = "paging";
805
805
  })(NodeStatusType || (NodeStatusType = {}));
806
806
  /** Define the subregion of a node, where an event occurred. */
807
807
  var NodeRegion;
@@ -826,7 +826,7 @@
826
826
  /*!
827
827
  * Wunderbaum - wb_extension_base
828
828
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
829
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
829
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
830
830
  */
831
831
  class WunderbaumExtension {
832
832
  constructor(tree, id, defaults) {
@@ -1113,11 +1113,75 @@
1113
1113
  debounced.pending = pending;
1114
1114
  return debounced;
1115
1115
  }
1116
+ /**
1117
+ * Creates a throttled function that only invokes `func` at most once per
1118
+ * every `wait` milliseconds (or once per browser frame). The throttled function
1119
+ * comes with a `cancel` method to cancel delayed `func` invocations and a
1120
+ * `flush` method to immediately invoke them. Provide `options` to indicate
1121
+ * whether `func` should be invoked on the leading and/or trailing edge of the
1122
+ * `wait` timeout. The `func` is invoked with the last arguments provided to the
1123
+ * throttled function. Subsequent calls to the throttled function return the
1124
+ * result of the last `func` invocation.
1125
+ *
1126
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
1127
+ * invoked on the trailing edge of the timeout only if the throttled function
1128
+ * is invoked more than once during the `wait` timeout.
1129
+ *
1130
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
1131
+ * until the next tick, similar to `setTimeout` with a timeout of `0`.
1132
+ *
1133
+ * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
1134
+ * invocation will be deferred until the next frame is drawn (typically about
1135
+ * 16ms).
1136
+ *
1137
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
1138
+ * for details over the differences between `throttle` and `debounce`.
1139
+ *
1140
+ * @since 0.1.0
1141
+ * @category Function
1142
+ * @param {Function} func The function to throttle.
1143
+ * @param {number} [wait=0]
1144
+ * The number of milliseconds to throttle invocations to; if omitted,
1145
+ * `requestAnimationFrame` is used (if available).
1146
+ * @param {Object} [options={}] The options object.
1147
+ * @param {boolean} [options.leading=true]
1148
+ * Specify invoking on the leading edge of the timeout.
1149
+ * @param {boolean} [options.trailing=true]
1150
+ * Specify invoking on the trailing edge of the timeout.
1151
+ * @returns {Function} Returns the new throttled function.
1152
+ * @example
1153
+ *
1154
+ * // Avoid excessively updating the position while scrolling.
1155
+ * jQuery(window).on('scroll', throttle(updatePosition, 100))
1156
+ *
1157
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
1158
+ * const throttled = throttle(renewToken, 300000, { 'trailing': false })
1159
+ * jQuery(element).on('click', throttled)
1160
+ *
1161
+ * // Cancel the trailing throttled invocation.
1162
+ * jQuery(window).on('popstate', throttled.cancel)
1163
+ */
1164
+ function throttle(func, wait = 0, options = {}) {
1165
+ let leading = true;
1166
+ let trailing = true;
1167
+ if (typeof func !== "function") {
1168
+ throw new TypeError("Expected a function");
1169
+ }
1170
+ if (isObject(options)) {
1171
+ leading = "leading" in options ? !!options.leading : leading;
1172
+ trailing = "trailing" in options ? !!options.trailing : trailing;
1173
+ }
1174
+ return debounce(func, wait, {
1175
+ leading,
1176
+ trailing,
1177
+ maxWait: wait,
1178
+ });
1179
+ }
1116
1180
 
1117
1181
  /*!
1118
1182
  * Wunderbaum - ext-filter
1119
1183
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1120
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
1184
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
1121
1185
  */
1122
1186
  const START_MARKER = "\uFFF7";
1123
1187
  const END_MARKER = "\uFFF8";
@@ -1162,7 +1226,7 @@
1162
1226
  }
1163
1227
  }
1164
1228
  _applyFilterNoUpdate(filter, branchMode, _opts) {
1165
- return this.tree.runWithoutUpdate(() => {
1229
+ return this.tree.runWithDeferredUpdate(() => {
1166
1230
  return this._applyFilterImpl(filter, branchMode, _opts);
1167
1231
  });
1168
1232
  }
@@ -1374,7 +1438,6 @@
1374
1438
  // "wb-ext-filter",
1375
1439
  "wb-ext-filter-dim", "wb-ext-filter-hide");
1376
1440
  // tree._callHook("treeStructureChanged", this, "clearFilter");
1377
- // tree.render();
1378
1441
  tree.enableUpdate(true);
1379
1442
  }
1380
1443
  }
@@ -1418,7 +1481,7 @@
1418
1481
  /*!
1419
1482
  * Wunderbaum - ext-keynav
1420
1483
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1421
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
1484
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
1422
1485
  */
1423
1486
  const QUICKSEARCH_DELAY = 500;
1424
1487
  class KeynavExtension extends WunderbaumExtension {
@@ -1495,7 +1558,7 @@
1495
1558
  tree.setFocus();
1496
1559
  break;
1497
1560
  case "Escape":
1498
- node.render();
1561
+ node._render();
1499
1562
  tree.setFocus();
1500
1563
  break;
1501
1564
  }
@@ -1565,7 +1628,7 @@
1565
1628
  // tree._triggerNodeEvent("clickPaging", ctx, event);
1566
1629
  // } else
1567
1630
  if (node.getOption("checkbox")) {
1568
- node.setSelected(!node.isSelected());
1631
+ node.toggleSelected();
1569
1632
  }
1570
1633
  else {
1571
1634
  node.setActive(true, { event: event });
@@ -1601,7 +1664,7 @@
1601
1664
  if (inputHasFocus) {
1602
1665
  if (eventName === "Escape") {
1603
1666
  // Discard changes
1604
- node.render();
1667
+ node._render();
1605
1668
  // Keep cell-nav mode
1606
1669
  node.logDebug(`Reset focused input`);
1607
1670
  tree.setFocus();
@@ -1651,7 +1714,7 @@
1651
1714
  break;
1652
1715
  case " ": // Space
1653
1716
  if (tree.activeColIdx === 0 && node.getOption("checkbox")) {
1654
- node.setSelected(!node.isSelected());
1717
+ node.toggleSelected();
1655
1718
  handled = true;
1656
1719
  }
1657
1720
  else if (curInput && curInputType === "checkbox") {
@@ -1758,7 +1821,7 @@
1758
1821
  /*!
1759
1822
  * Wunderbaum - ext-logger
1760
1823
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1761
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
1824
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
1762
1825
  */
1763
1826
  class LoggerExtension extends WunderbaumExtension {
1764
1827
  constructor(tree) {
@@ -1798,9 +1861,9 @@
1798
1861
  /*!
1799
1862
  * Wunderbaum - common
1800
1863
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1801
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
1864
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
1802
1865
  */
1803
- const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1866
+ const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
1804
1867
  /**
1805
1868
  * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
1806
1869
  */
@@ -1826,30 +1889,50 @@
1826
1889
  * Default node icons.
1827
1890
  * Requires bootstrap icons https://icons.getbootstrap.com
1828
1891
  */
1829
- const iconMap = {
1830
- error: "bi bi-exclamation-triangle",
1831
- // loading: "bi bi-hourglass-split wb-busy",
1832
- loading: "bi bi-chevron-right wb-busy",
1833
- // loading: "bi bi-arrow-repeat wb-spin",
1834
- // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
1835
- // noData: "bi bi-search",
1836
- noData: "bi bi-question-circle",
1837
- expanderExpanded: "bi bi-chevron-down",
1838
- // expanderExpanded: "bi bi-dash-square",
1839
- expanderCollapsed: "bi bi-chevron-right",
1840
- // expanderCollapsed: "bi bi-plus-square",
1841
- expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
1842
- // expanderLazy: "bi bi-chevron-bar-right",
1843
- checkChecked: "bi bi-check-square",
1844
- checkUnchecked: "bi bi-square",
1845
- checkUnknown: "bi dash-square-dotted",
1846
- radioChecked: "bi bi-circle-fill",
1847
- radioUnchecked: "bi bi-circle",
1848
- radioUnknown: "bi bi-circle-dotted",
1849
- folder: "bi bi-folder2",
1850
- folderOpen: "bi bi-folder2-open",
1851
- folderLazy: "bi bi-folder-symlink",
1852
- doc: "bi bi-file-earmark",
1892
+ const iconMaps = {
1893
+ bootstrap: {
1894
+ error: "bi bi-exclamation-triangle",
1895
+ // loading: "bi bi-hourglass-split wb-busy",
1896
+ loading: "bi bi-chevron-right wb-busy",
1897
+ // loading: "bi bi-arrow-repeat wb-spin",
1898
+ // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
1899
+ // noData: "bi bi-search",
1900
+ noData: "bi bi-question-circle",
1901
+ expanderExpanded: "bi bi-chevron-down",
1902
+ // expanderExpanded: "bi bi-dash-square",
1903
+ expanderCollapsed: "bi bi-chevron-right",
1904
+ // expanderCollapsed: "bi bi-plus-square",
1905
+ expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
1906
+ // expanderLazy: "bi bi-chevron-bar-right",
1907
+ checkChecked: "bi bi-check-square",
1908
+ checkUnchecked: "bi bi-square",
1909
+ checkUnknown: "bi bi-dash-square-dotted",
1910
+ radioChecked: "bi bi-circle-fill",
1911
+ radioUnchecked: "bi bi-circle",
1912
+ radioUnknown: "bi bi-record-circle",
1913
+ folder: "bi bi-folder2",
1914
+ folderOpen: "bi bi-folder2-open",
1915
+ folderLazy: "bi bi-folder-symlink",
1916
+ doc: "bi bi-file-earmark",
1917
+ },
1918
+ fontawesome6: {
1919
+ error: "fa-solid fa-triangle-exclamation",
1920
+ loading: "fa-regular fa-chevron-right fa-beat",
1921
+ noData: "fa-solid fa-circle-question",
1922
+ expanderExpanded: "fa-regular fa-chevron-down",
1923
+ expanderCollapsed: "fa-regular fa-chevron-right",
1924
+ expanderLazy: "fa-regular fa-chevron-right wb-helper-lazy-expander",
1925
+ checkChecked: "fa-regular fa-square-check",
1926
+ checkUnchecked: "fa-regular fa-square",
1927
+ checkUnknown: "fa-regular fa-square-minus",
1928
+ radioChecked: "fa-solid fa-circle",
1929
+ radioUnchecked: "fa-regular fa-circle",
1930
+ radioUnknown: "fa-regular fa-circle-question",
1931
+ folder: "fa-solid fa-folder-closed",
1932
+ folderOpen: "fa-regular fa-folder-open",
1933
+ folderLazy: "fa-solid fa-folder-plus",
1934
+ doc: "fa-regular fa-file",
1935
+ },
1853
1936
  };
1854
1937
  /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
1855
1938
  const RESERVED_TREE_SOURCE_KEYS = new Set([
@@ -2044,7 +2127,7 @@
2044
2127
  /*!
2045
2128
  * Wunderbaum - ext-dnd
2046
2129
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2047
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
2130
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
2048
2131
  */
2049
2132
  const nodeMimeType = "application/x-wunderbaum-node";
2050
2133
  class DndExtension extends WunderbaumExtension {
@@ -2067,6 +2150,7 @@
2067
2150
  preventVoidMoves: true,
2068
2151
  scroll: true,
2069
2152
  scrollSensitivity: 20,
2153
+ // scrollnterval: 50, // Generste event every 50 ms
2070
2154
  scrollSpeed: 5,
2071
2155
  // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2072
2156
  sourceCopyHook: null,
@@ -2088,6 +2172,8 @@
2088
2172
  this.lastAllowedDropRegions = null;
2089
2173
  this.lastDropEffect = null;
2090
2174
  this.lastDropRegion = false;
2175
+ this.currentScrollDir = 0;
2176
+ this.applyScrollDirThrottled = throttle(this.applyScrollDir, 50);
2091
2177
  }
2092
2178
  init() {
2093
2179
  super.init();
@@ -2158,22 +2244,55 @@
2158
2244
  // return "over";
2159
2245
  }
2160
2246
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
2161
- autoScroll(event) {
2162
- let tree = this.tree, dndOpts = tree.options.dnd, sp = tree.listContainerElement, sensitivity = dndOpts.scrollSensitivity, speed = dndOpts.scrollSpeed, scrolled = 0;
2163
- const scrollTop = sp.offsetTop;
2164
- if (scrollTop + sp.offsetHeight - event.pageY < sensitivity) {
2165
- const delta = sp.scrollHeight - sp.clientHeight - scrollTop;
2166
- if (delta > 0) {
2167
- sp.scrollTop = scrolled = scrollTop + speed;
2247
+ applyScrollDir() {
2248
+ if (this.isDragging() && this.currentScrollDir) {
2249
+ const dndOpts = this.tree.options.dnd;
2250
+ const sp = this.tree.element; // scroll parent
2251
+ const scrollTop = sp.scrollTop;
2252
+ if (this.currentScrollDir < 0) {
2253
+ sp.scrollTop = Math.max(0, scrollTop - dndOpts.scrollSpeed);
2254
+ }
2255
+ else if (this.currentScrollDir > 0) {
2256
+ sp.scrollTop = scrollTop + dndOpts.scrollSpeed;
2168
2257
  }
2169
2258
  }
2170
- else if (scrollTop > 0 && event.pageY - scrollTop < sensitivity) {
2171
- sp.scrollTop = scrolled = scrollTop - speed;
2172
- }
2173
- // if (scrolled) {
2174
- // tree.logDebug("autoScroll: " + scrolled + "px");
2175
- // }
2176
- return scrolled;
2259
+ }
2260
+ /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
2261
+ autoScroll(viewportY) {
2262
+ const tree = this.tree;
2263
+ const dndOpts = tree.options.dnd;
2264
+ const sensitivity = dndOpts.scrollSensitivity;
2265
+ const sp = tree.element; // scroll parent
2266
+ const headerHeight = tree.headerElement.clientHeight; // May be 0
2267
+ // const height = sp.clientHeight - headerHeight;
2268
+ // const height = sp.offsetHeight + headerHeight;
2269
+ const height = sp.offsetHeight;
2270
+ const scrollTop = sp.scrollTop;
2271
+ // tree.logDebug(
2272
+ // `autoScroll: height=${height}, scrollTop=${scrollTop}, viewportY=${viewportY}`
2273
+ // );
2274
+ this.currentScrollDir = 0;
2275
+ if (scrollTop > 0 &&
2276
+ viewportY > 0 &&
2277
+ viewportY <= sensitivity + headerHeight) {
2278
+ // Mouse in top 20px area: scroll up
2279
+ // sp.scrollTop = Math.max(0, scrollTop - dndOpts.scrollSpeed);
2280
+ this.currentScrollDir = -1;
2281
+ }
2282
+ else if (scrollTop < sp.scrollHeight - height &&
2283
+ viewportY >= height - sensitivity) {
2284
+ // Mouse in bottom 20px area: scroll down
2285
+ // sp.scrollTop = scrollTop + dndOpts.scrollSpeed;
2286
+ this.currentScrollDir = +1;
2287
+ }
2288
+ if (this.currentScrollDir) {
2289
+ this.applyScrollDirThrottled();
2290
+ }
2291
+ return sp.scrollTop - scrollTop;
2292
+ }
2293
+ /** Return true if a drag operation currently in progress. */
2294
+ isDragging() {
2295
+ return !!this.srcNode;
2177
2296
  }
2178
2297
  onDragEvent(e) {
2179
2298
  // const tree = this.tree;
@@ -2205,7 +2324,7 @@
2205
2324
  n._org_key = n.key;
2206
2325
  delete n.key;
2207
2326
  });
2208
- nodeData.treeId = srcNode.tree.id;
2327
+ nodeData._treeId = srcNode.tree.id;
2209
2328
  const json = JSON.stringify(nodeData);
2210
2329
  e.dataTransfer.setData(nodeMimeType, json);
2211
2330
  // e.dataTransfer!.setData("text/html", $(node.span).html());
@@ -2297,7 +2416,8 @@
2297
2416
  // --- dragover ---
2298
2417
  }
2299
2418
  else if (e.type === "dragover") {
2300
- this.autoScroll(e);
2419
+ const viewportY = e.clientY - this.tree.element.offsetTop;
2420
+ this.autoScroll(viewportY);
2301
2421
  const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
2302
2422
  this.lastDropRegion = region;
2303
2423
  if (dndOpts.autoExpandMS > 0 &&
@@ -2337,7 +2457,7 @@
2337
2457
  /*!
2338
2458
  * Wunderbaum - drag_observer
2339
2459
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2340
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
2460
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
2341
2461
  */
2342
2462
  /**
2343
2463
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2473,7 +2593,7 @@
2473
2593
  /*!
2474
2594
  * Wunderbaum - ext-grid
2475
2595
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2476
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
2596
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
2477
2597
  */
2478
2598
  class GridExtension extends WunderbaumExtension {
2479
2599
  constructor(tree) {
@@ -2510,7 +2630,7 @@
2510
2630
  /*!
2511
2631
  * Wunderbaum - deferred
2512
2632
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2513
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
2633
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
2514
2634
  */
2515
2635
  /**
2516
2636
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2563,32 +2683,20 @@
2563
2683
  /*!
2564
2684
  * Wunderbaum - wunderbaum_node
2565
2685
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2566
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
2686
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
2687
+ */
2688
+ /** WunderbaumNode properties that can be passed with source data.
2689
+ * (Any other source properties will be stored as `node.data.PROP`.)
2567
2690
  */
2568
- /** Top-level properties that can be passed with `data`. */
2569
2691
  const NODE_PROPS = new Set([
2570
- // TODO: use NODE_ATTRS instead?
2571
- "classes",
2572
- "expanded",
2573
- "icon",
2574
- "key",
2575
- "lazy",
2576
- "refKey",
2577
- "selected",
2578
- "title",
2579
- "tooltip",
2580
- "type",
2581
- ]);
2582
- const NODE_ATTRS = new Set([
2583
2692
  "checkbox",
2584
- "expanded",
2585
2693
  "classes",
2586
- "folder",
2694
+ "expanded",
2587
2695
  "icon",
2588
2696
  "iconTooltip",
2589
2697
  "key",
2590
2698
  "lazy",
2591
- "partsel",
2699
+ "_partsel",
2592
2700
  "radiogroup",
2593
2701
  "refKey",
2594
2702
  "selected",
@@ -2597,9 +2705,12 @@
2597
2705
  "tooltip",
2598
2706
  "type",
2599
2707
  "unselectable",
2600
- "unselectableIgnore",
2601
- "unselectableStatus",
2602
2708
  ]);
2709
+ /** WunderbaumNode properties that will be returned by `node.toDict()`.)
2710
+ */
2711
+ const NODE_DICT_PROPS = new Set(NODE_PROPS);
2712
+ NODE_DICT_PROPS.delete("_partsel");
2713
+ NODE_DICT_PROPS.delete("unselectable");
2603
2714
  /**
2604
2715
  * A single tree node.
2605
2716
  *
@@ -2616,13 +2727,6 @@
2616
2727
  */
2617
2728
  this.refKey = undefined;
2618
2729
  this.children = null;
2619
- this.lazy = false;
2620
- /** Expansion state.
2621
- * @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
2622
- this.expanded = false;
2623
- /** Selection state.
2624
- * @see {@link isSelected}, {@link setSelected}. */
2625
- this.selected = false;
2626
2730
  /** Additional classes added to `div.wb-row`.
2627
2731
  * @see {@link hasClass}, {@link setClass}. */
2628
2732
  this.classes = null; //new Set<string>();
@@ -2643,16 +2747,19 @@
2643
2747
  this.key = "" + ((_a = data.key) !== null && _a !== void 0 ? _a : ++WunderbaumNode.sequence);
2644
2748
  this.title = "" + ((_b = data.title) !== null && _b !== void 0 ? _b : "<" + this.key + ">");
2645
2749
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
2646
- data.statusNodeType != null
2647
- ? (this.statusNodeType = "" + data.statusNodeType)
2648
- : 0;
2649
2750
  data.type != null ? (this.type = "" + data.type) : 0;
2650
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
2651
- data.colspan != null ? (this.colspan = !!data.colspan) : 0;
2652
2751
  this.expanded = data.expanded === true;
2653
2752
  data.icon != null ? (this.icon = data.icon) : 0;
2654
2753
  this.lazy = data.lazy === true;
2754
+ data.statusNodeType != null
2755
+ ? (this.statusNodeType = ("" + data.statusNodeType))
2756
+ : 0;
2757
+ data.colspan != null ? (this.colspan = !!data.colspan) : 0;
2758
+ // Selection
2759
+ data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
2760
+ data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
2655
2761
  this.selected = data.selected === true;
2762
+ data.unselectable === true ? (this.unselectable = true) : 0;
2656
2763
  if (data.classes) {
2657
2764
  this.setClass(data.classes);
2658
2765
  }
@@ -2776,14 +2883,17 @@
2776
2883
  // insert nodeList after children[pos]
2777
2884
  this.children.splice(pos, 0, ...nodeList);
2778
2885
  }
2779
- // TODO:
2780
- // if (tree.options.selectMode === 3) {
2781
- // this.fixSelection3FromEndNodes();
2782
- // }
2783
2886
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
2784
- tree.setModified(ChangeType.structure);
2887
+ tree.update(ChangeType.structure);
2785
2888
  }
2786
2889
  finally {
2890
+ // if (tree.options.selectMode === "hier") {
2891
+ // if (this.parent && this.parent.children) {
2892
+ // this.fixSelection3FromEndNodes();
2893
+ // } else {
2894
+ // // may happen when loading __root__;
2895
+ // }
2896
+ // }
2787
2897
  tree.enableUpdate(true);
2788
2898
  }
2789
2899
  // if(isTopCall && loadLazy){
@@ -2829,6 +2939,17 @@
2829
2939
  applyCommand(cmd, options) {
2830
2940
  return this.tree.applyCommand(cmd, this, options);
2831
2941
  }
2942
+ /**
2943
+ * Collapse all expanded sibling nodes if any.
2944
+ * (Automatically called when `autoCollapse` is true.)
2945
+ */
2946
+ collapseSiblings(options) {
2947
+ for (let node of this.parent.children) {
2948
+ if (node !== this && node.expanded) {
2949
+ node.setExpanded(false, options);
2950
+ }
2951
+ }
2952
+ }
2832
2953
  /**
2833
2954
  * Add/remove one or more classes to `<div class='wb-row'>`.
2834
2955
  *
@@ -2868,7 +2989,7 @@
2868
2989
  const tree = this.tree;
2869
2990
  const minExpandLevel = this.tree.options.minExpandLevel;
2870
2991
  let { depth = 99, loadLazy, force } = options !== null && options !== void 0 ? options : {};
2871
- const expand_opts = {
2992
+ const expandOpts = {
2872
2993
  scrollIntoView: false,
2873
2994
  force: force,
2874
2995
  loadLazy: loadLazy,
@@ -2892,7 +3013,7 @@
2892
3013
  // Node is collapsed and may be expanded (i.e. has children or is lazy)
2893
3014
  // Expanding may be async, so we store the promise.
2894
3015
  // Also the recursion is delayed until expansion finished.
2895
- const p = cn.setExpanded(true, expand_opts);
3016
+ const p = cn.setExpanded(true, expandOpts);
2896
3017
  promises.push(p);
2897
3018
  p.then(async () => {
2898
3019
  await _iter(cn, level_1);
@@ -2908,7 +3029,7 @@
2908
3029
  // Collapsing is always synchronous, so no promises required
2909
3030
  if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
2910
3031
  // Do not collapse until minExpandLevel
2911
- cn.setExpanded(false, expand_opts);
3032
+ cn.setExpanded(false, expandOpts);
2912
3033
  }
2913
3034
  _iter(cn, level_1); // recursion, even if cn was already collapsed
2914
3035
  }
@@ -3215,7 +3336,7 @@
3215
3336
  return false;
3216
3337
  }
3217
3338
  if (this.children == null) {
3218
- return this.lazy; // null or undefined can trigger lazy load
3339
+ return !!this.lazy; // null or undefined can trigger lazy load
3219
3340
  }
3220
3341
  if (this.children.length === 0) {
3221
3342
  return !!this.tree.options.emptyChildListExpandable;
@@ -3280,9 +3401,11 @@
3280
3401
  isRootNode() {
3281
3402
  return this.tree.root === this;
3282
3403
  }
3283
- /** Return true if this node is selected, i.e. the checkbox is set. */
3404
+ /** Return true if this node is selected, i.e. the checkbox is set.
3405
+ * `undefined` if partly selected (tri-state), false otherwise.
3406
+ */
3284
3407
  isSelected() {
3285
- return !!this.selected;
3408
+ return this.selected ? true : this._partsel ? undefined : false;
3286
3409
  }
3287
3410
  /** Return true if this node is a temporarily generated system node like
3288
3411
  * 'loading', 'paging', or 'error' (node.statusNodeType contains the type).
@@ -3354,7 +3477,7 @@
3354
3477
  tree.logInfo("Redefine columns", source.columns);
3355
3478
  tree.columns = source.columns;
3356
3479
  delete source.columns;
3357
- tree.setModified(ChangeType.colStructure);
3480
+ tree.update(ChangeType.colStructure);
3358
3481
  }
3359
3482
  this.addChildren(source.children);
3360
3483
  // Add extra data to `tree.data`
@@ -3364,6 +3487,9 @@
3364
3487
  tree.logDebug(`Add source.${key} to tree.data.${key}`);
3365
3488
  }
3366
3489
  }
3490
+ if (tree.options.selectMode === "hier") {
3491
+ this.fixSelection3FromEndNodes();
3492
+ }
3367
3493
  this._callEvent("load");
3368
3494
  }
3369
3495
  async _fetchWithOptions(source) {
@@ -3379,6 +3505,7 @@
3379
3505
  else if (isPlainObject(source)) {
3380
3506
  // source is a plain object with `.url` property.
3381
3507
  ({ url, params, body, options, ...rest } = source);
3508
+ assert(!rest || Object.keys(rest).length === 0, `Unexpected source properties: ${Object.keys(rest)}. Use 'options' instead.`);
3382
3509
  assert(typeof url === "string", `expected source.url as string`);
3383
3510
  if (isPlainObject(options)) {
3384
3511
  fetchOpts = options;
@@ -3493,10 +3620,10 @@
3493
3620
  await this.load(source); // also calls setStatus('ok')
3494
3621
  if (wasExpanded) {
3495
3622
  this.expanded = true;
3496
- this.tree.setModified(ChangeType.structure);
3623
+ this.tree.update(ChangeType.structure);
3497
3624
  }
3498
3625
  else {
3499
- this.setModified(); // Fix expander icon to 'loaded'
3626
+ this.update(); // Fix expander icon to 'loaded'
3500
3627
  }
3501
3628
  }
3502
3629
  catch (e) {
@@ -3653,7 +3780,7 @@
3653
3780
  // Fix node.tree for all source nodes
3654
3781
  // util.assert(false, "Cross-tree move is not yet implemented.");
3655
3782
  this.logWarn("Cross-tree moveTo is experimental!");
3656
- this.visit(function (n) {
3783
+ this.visit((n) => {
3657
3784
  // TODO: fix selection state and activation, ...
3658
3785
  n.tree = targetNode.tree;
3659
3786
  }, true);
@@ -3662,7 +3789,7 @@
3662
3789
  // DragAndDrop to generate a dragend event on the source node
3663
3790
  setTimeout(() => {
3664
3791
  // Even indentation may have changed:
3665
- tree.setModified(ChangeType.any);
3792
+ tree.update(ChangeType.any);
3666
3793
  }, 0);
3667
3794
  // TODO: fix selection state
3668
3795
  // TODO: fix active state
@@ -3709,7 +3836,7 @@
3709
3836
  n.removeMarkup();
3710
3837
  tree._unregisterNode(n);
3711
3838
  }, true);
3712
- tree.setModified(ChangeType.structure);
3839
+ tree.update(ChangeType.structure);
3713
3840
  }
3714
3841
  /** Remove all descendants of this node. */
3715
3842
  removeChildren() {
@@ -3741,7 +3868,7 @@
3741
3868
  if (!this.isRootNode()) {
3742
3869
  this.expanded = false;
3743
3870
  }
3744
- this.tree.setModified(ChangeType.structure);
3871
+ this.tree.update(ChangeType.structure);
3745
3872
  }
3746
3873
  /** Remove all HTML markup from the DOM. */
3747
3874
  removeMarkup() {
@@ -3777,7 +3904,7 @@
3777
3904
  renderColInfosById: renderColInfosById,
3778
3905
  };
3779
3906
  }
3780
- _createIcon(parentElem, replaceChild, showLoading) {
3907
+ _createIcon(iconMap, parentElem, replaceChild, showLoading) {
3781
3908
  let iconSpan;
3782
3909
  let icon = this.getOption("icon");
3783
3910
  if (this._errorInfo) {
@@ -3836,12 +3963,13 @@
3836
3963
  }
3837
3964
  /**
3838
3965
  * Create a whole new `<div class="wb-row">` element.
3839
- * @see {@link WunderbaumNode.render}
3966
+ * @see {@link WunderbaumNode._render}
3840
3967
  */
3841
3968
  _render_markup(opts) {
3842
3969
  const tree = this.tree;
3843
3970
  const treeOptions = tree.options;
3844
- const checkbox = this.getOption("checkbox") !== false;
3971
+ const checkbox = this.getOption("checkbox");
3972
+ // const checkbox = this.getOption("checkbox") !== false;
3845
3973
  const columns = tree.columns;
3846
3974
  const level = this.getLevel();
3847
3975
  let elem;
@@ -3869,6 +3997,9 @@
3869
3997
  if (checkbox) {
3870
3998
  checkboxSpan = document.createElement("i");
3871
3999
  checkboxSpan.classList.add("wb-checkbox");
4000
+ if (checkbox === "radio" || this.parent.radiogroup) {
4001
+ checkboxSpan.classList.add("wb-radio");
4002
+ }
3872
4003
  nodeElem.appendChild(checkboxSpan);
3873
4004
  ofsTitlePx += ICON_WIDTH;
3874
4005
  }
@@ -3886,7 +4017,7 @@
3886
4017
  }
3887
4018
  // Render the icon (show a 'loading' icon if we do not have an expander that
3888
4019
  // we would prefer).
3889
- iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
4020
+ iconSpan = this._createIcon(tree.iconMap, nodeElem, null, !expanderSpan);
3890
4021
  if (iconSpan) {
3891
4022
  ofsTitlePx += ICON_WIDTH;
3892
4023
  }
@@ -3949,7 +4080,7 @@
3949
4080
  /**
3950
4081
  * Render `node.title`, `.icon` into an existing row.
3951
4082
  *
3952
- * @see {@link WunderbaumNode.render}
4083
+ * @see {@link WunderbaumNode._render}
3953
4084
  */
3954
4085
  _render_data(opts) {
3955
4086
  assert(this._rowElem);
@@ -4015,11 +4146,12 @@
4015
4146
  }
4016
4147
  /**
4017
4148
  * Update row classes to reflect active, focuses, etc.
4018
- * @see {@link WunderbaumNode.render}
4149
+ * @see {@link WunderbaumNode._render}
4019
4150
  */
4020
4151
  _render_status(opts) {
4021
4152
  // this.log("_render_status", opts);
4022
4153
  const tree = this.tree;
4154
+ const iconMap = tree.iconMap;
4023
4155
  const treeOptions = tree.options;
4024
4156
  const typeInfo = this.type ? tree.types[this.type] : null;
4025
4157
  const rowDiv = this._rowElem;
@@ -4031,6 +4163,7 @@
4031
4163
  this.expanded ? rowClasses.push("wb-expanded") : 0;
4032
4164
  this.lazy ? rowClasses.push("wb-lazy") : 0;
4033
4165
  this.selected ? rowClasses.push("wb-selected") : 0;
4166
+ this._partsel ? rowClasses.push("wb-partsel") : 0;
4034
4167
  this === tree.activeNode ? rowClasses.push("wb-active") : 0;
4035
4168
  this === tree.focusNode ? rowClasses.push("wb-focus") : 0;
4036
4169
  this._errorInfo ? rowClasses.push("wb-error") : 0;
@@ -4070,12 +4203,30 @@
4070
4203
  }
4071
4204
  }
4072
4205
  if (checkboxSpan) {
4073
- if (this.selected) {
4074
- checkboxSpan.className = "wb-checkbox " + iconMap.checkChecked;
4206
+ let cbclass = "wb-checkbox ";
4207
+ if (this.parent.radiogroup) {
4208
+ cbclass += "wb-radio ";
4209
+ if (this.selected) {
4210
+ cbclass += iconMap.radioChecked;
4211
+ // } else if (this._partsel) {
4212
+ // cbclass += iconMap.radioUnknown;
4213
+ }
4214
+ else {
4215
+ cbclass += iconMap.radioUnchecked;
4216
+ }
4075
4217
  }
4076
4218
  else {
4077
- checkboxSpan.className = "wb-checkbox " + iconMap.checkUnchecked;
4219
+ if (this.selected) {
4220
+ cbclass += iconMap.checkChecked;
4221
+ }
4222
+ else if (this._partsel) {
4223
+ cbclass += iconMap.checkUnknown;
4224
+ }
4225
+ else {
4226
+ cbclass += iconMap.checkUnchecked;
4227
+ }
4078
4228
  }
4229
+ checkboxSpan.className = cbclass;
4079
4230
  }
4080
4231
  // Fix active cell in cell-nav mode
4081
4232
  if (!opts.isNew) {
@@ -4086,7 +4237,7 @@
4086
4237
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
4087
4238
  const iconSpan = nodeElem.querySelector("i.wb-icon");
4088
4239
  if (iconSpan) {
4089
- this._createIcon(nodeElem, iconSpan, !expanderSpan);
4240
+ this._createIcon(tree.iconMap, nodeElem, iconSpan, !expanderSpan);
4090
4241
  }
4091
4242
  }
4092
4243
  // Adjust column width
@@ -4103,7 +4254,7 @@
4103
4254
  }
4104
4255
  }
4105
4256
  }
4106
- /**
4257
+ /*
4107
4258
  * Create or update node's markup.
4108
4259
  *
4109
4260
  * `options.change` defaults to ChangeType.data, which updates the title,
@@ -4114,10 +4265,10 @@
4114
4265
  * `options.change` should be set to ChangeType.status instead for best
4115
4266
  * efficiency.
4116
4267
  *
4117
- * Calling `setModified` instead may be a better alternative.
4118
- * @see {@link WunderbaumNode.setModified}
4268
+ * Calling `update()` is almost always a better alternative.
4269
+ * @see {@link WunderbaumNode.update}
4119
4270
  */
4120
- render(options) {
4271
+ _render(options) {
4121
4272
  // this.log("render", options);
4122
4273
  const opts = Object.assign({ change: ChangeType.data }, options);
4123
4274
  if (!this._rowElem) {
@@ -4147,7 +4298,7 @@
4147
4298
  this.expanded = false;
4148
4299
  this.lazy = true;
4149
4300
  this.children = null;
4150
- this.tree.setModified(ChangeType.structure);
4301
+ this.tree.update(ChangeType.structure);
4151
4302
  }
4152
4303
  /** Convert node (or whole branch) into a plain object.
4153
4304
  *
@@ -4162,7 +4313,7 @@
4162
4313
  */
4163
4314
  toDict(recursive = false, callback) {
4164
4315
  const dict = {};
4165
- NODE_ATTRS.forEach((propName) => {
4316
+ NODE_DICT_PROPS.forEach((propName) => {
4166
4317
  const val = this[propName];
4167
4318
  if (val instanceof Set) {
4168
4319
  // Convert Set to string (or skip if set is empty)
@@ -4287,7 +4438,7 @@
4287
4438
  return;
4288
4439
  }
4289
4440
  tree.activeNode = null;
4290
- prev === null || prev === void 0 ? void 0 : prev.setModified(ChangeType.status);
4441
+ prev === null || prev === void 0 ? void 0 : prev.update(ChangeType.status);
4291
4442
  }
4292
4443
  }
4293
4444
  else if (prev === this || retrigger) {
@@ -4302,8 +4453,8 @@
4302
4453
  if (focusTree)
4303
4454
  tree.setFocus();
4304
4455
  }
4305
- prev === null || prev === void 0 ? void 0 : prev.setModified(ChangeType.status);
4306
- this.setModified(ChangeType.status);
4456
+ prev === null || prev === void 0 ? void 0 : prev.update(ChangeType.status);
4457
+ this.update(ChangeType.status);
4307
4458
  }
4308
4459
  if (options &&
4309
4460
  options.colIdx != null &&
@@ -4332,13 +4483,16 @@
4332
4483
  return; // Nothing to do
4333
4484
  }
4334
4485
  // this.log("setExpanded()");
4486
+ if (flag && this.getOption("autoCollapse")) {
4487
+ this.collapseSiblings(options);
4488
+ }
4335
4489
  if (flag && this.lazy && this.children == null) {
4336
4490
  await this.loadLazy();
4337
4491
  }
4338
4492
  this.expanded = flag;
4339
4493
  const updateOpts = { immediate: immediate };
4340
4494
  // const updateOpts = { immediate: !!util.getOption(options, "immediate") };
4341
- this.tree.setModified(ChangeType.structure, updateOpts);
4495
+ this.tree.update(ChangeType.structure, updateOpts);
4342
4496
  if (flag && scrollIntoView !== false) {
4343
4497
  const lastChild = this.getLastChild();
4344
4498
  if (lastChild) {
@@ -4355,18 +4509,25 @@
4355
4509
  assert(!!flag, "blur is not yet implemented");
4356
4510
  const prev = this.tree.focusNode;
4357
4511
  this.tree.focusNode = this;
4358
- prev === null || prev === void 0 ? void 0 : prev.setModified();
4359
- this.setModified();
4512
+ prev === null || prev === void 0 ? void 0 : prev.update();
4513
+ this.update();
4360
4514
  }
4361
4515
  /** Set a new icon path or class. */
4362
4516
  setIcon(icon) {
4363
4517
  this.icon = icon;
4364
- this.setModified();
4518
+ this.update();
4365
4519
  }
4366
4520
  /** Change node's {@link key} and/or {@link refKey}. */
4367
4521
  setKey(key, refKey) {
4368
4522
  throw new Error("Not yet implemented");
4369
4523
  }
4524
+ /**
4525
+ * @deprecated since v0.3.6: use `update()` instead.
4526
+ */
4527
+ setModified(change = ChangeType.data) {
4528
+ this.logWarn("setModified() is deprecated: use update() instead.");
4529
+ return this.update(change);
4530
+ }
4370
4531
  /**
4371
4532
  * Trigger a repaint, typically after a status or data change.
4372
4533
  *
@@ -4374,22 +4535,219 @@
4374
4535
  * and column content. It can be reduced to 'ChangeType.status' if only
4375
4536
  * active/focus/selected state has changed.
4376
4537
  *
4377
- * This method will eventually call {@link WunderbaumNode.render()} with
4538
+ * This method will eventually call {@link WunderbaumNode._render()} with
4378
4539
  * default options, but may be more consistent with the tree's
4379
- * {@link Wunderbaum.setModified()} API.
4540
+ * {@link Wunderbaum.update()} API.
4380
4541
  */
4381
- setModified(change = ChangeType.data) {
4542
+ update(change = ChangeType.data) {
4382
4543
  assert(change === ChangeType.status || change === ChangeType.data);
4383
- this.tree.setModified(change, this);
4544
+ this.tree.update(change, this);
4545
+ }
4546
+ /**
4547
+ * Return an array of selected nodes.
4548
+ * @param stopOnParents only return the topmost selected node (useful with selectMode 'hier')
4549
+ */
4550
+ getSelectedNodes(stopOnParents = false) {
4551
+ let nodeList = [];
4552
+ this.visit((node) => {
4553
+ if (node.selected) {
4554
+ nodeList.push(node);
4555
+ if (stopOnParents === true) {
4556
+ return "skip"; // stop processing this branch
4557
+ }
4558
+ }
4559
+ });
4560
+ return nodeList;
4561
+ }
4562
+ /** Toggle the check/uncheck state. */
4563
+ toggleSelected(options) {
4564
+ let flag = this.isSelected();
4565
+ if (flag === undefined) {
4566
+ flag = this._anySelectable();
4567
+ }
4568
+ else {
4569
+ flag = !flag;
4570
+ }
4571
+ return this.setSelected(flag, options);
4572
+ }
4573
+ /** Return true if at least on selectable descendant end-node is unselected. @internal */
4574
+ _anySelectable() {
4575
+ let found = false;
4576
+ this.visit((node) => {
4577
+ if (node.selected === false &&
4578
+ !node.unselectable &&
4579
+ !node.hasChildren() &&
4580
+ !node.parent.radiogroup) {
4581
+ found = true;
4582
+ return false; // Stop iteration
4583
+ }
4584
+ });
4585
+ return found;
4586
+ }
4587
+ /* Apply selection state to a single node. */
4588
+ _changeSelectStatusProps(state) {
4589
+ let changed = false;
4590
+ switch (state) {
4591
+ case false:
4592
+ changed = this.selected || this._partsel;
4593
+ this.selected = false;
4594
+ this._partsel = false;
4595
+ break;
4596
+ case true:
4597
+ changed = !this.selected || !this._partsel;
4598
+ this.selected = true;
4599
+ this._partsel = true;
4600
+ break;
4601
+ case undefined:
4602
+ changed = this.selected || !this._partsel;
4603
+ this.selected = false;
4604
+ this._partsel = true;
4605
+ break;
4606
+ default:
4607
+ error(`Invalid state: ${state}`);
4608
+ }
4609
+ if (changed) {
4610
+ this.update();
4611
+ }
4612
+ return changed;
4613
+ }
4614
+ /**
4615
+ * Fix selection status, after this node was (de)selected in `selectMode: 'hier'`.
4616
+ * This includes (de)selecting all descendants.
4617
+ */
4618
+ fixSelection3AfterClick(opts) {
4619
+ const force = !!(opts === null || opts === void 0 ? void 0 : opts.force);
4620
+ let flag = this.isSelected();
4621
+ this.visit((node) => {
4622
+ if (node.radiogroup) {
4623
+ return "skip"; // Don't (de)select this branch
4624
+ }
4625
+ if (force || !node.getOption("unselectable")) {
4626
+ node._changeSelectStatusProps(flag);
4627
+ }
4628
+ });
4629
+ this.fixSelection3FromEndNodes();
4630
+ }
4631
+ /**
4632
+ * Fix selection status for multi-hier mode.
4633
+ * Only end-nodes are considered to update the descendants branch and parents.
4634
+ * Should be called after this node has loaded new children or after
4635
+ * children have been modified using the API.
4636
+ */
4637
+ fixSelection3FromEndNodes(opts) {
4638
+ const force = !!(opts === null || opts === void 0 ? void 0 : opts.force);
4639
+ assert(this.tree.options.selectMode === "hier", "expected selectMode 'hier'");
4640
+ // Visit all end nodes and adjust their parent's `selected` and `_partsel`
4641
+ // attributes. Return selection state true, false, or undefined.
4642
+ const _walk = (node) => {
4643
+ let state;
4644
+ const children = node.children;
4645
+ if (children && children.length) {
4646
+ // check all children recursively
4647
+ let allSelected = true;
4648
+ let someSelected = false;
4649
+ for (let i = 0, l = children.length; i < l; i++) {
4650
+ const child = children[i];
4651
+ // the selection state of a node is not relevant; we need the end-nodes
4652
+ const s = _walk(child);
4653
+ if (s !== false) {
4654
+ someSelected = true;
4655
+ }
4656
+ if (s !== true) {
4657
+ allSelected = false;
4658
+ }
4659
+ }
4660
+ state = allSelected ? true : someSelected ? undefined : false;
4661
+ }
4662
+ else {
4663
+ // This is an end-node: simply report the status
4664
+ state = !!node.selected;
4665
+ }
4666
+ // #939: Keep a `_partsel` flag that was explicitly set on a lazy node
4667
+ if (node._partsel &&
4668
+ !node.selected &&
4669
+ node.lazy &&
4670
+ node.children == null) {
4671
+ state = undefined;
4672
+ }
4673
+ if (force || !node.getOption("unselectable")) {
4674
+ node._changeSelectStatusProps(state);
4675
+ }
4676
+ return state;
4677
+ };
4678
+ _walk(this);
4679
+ // Update parent's state
4680
+ this.visitParents((node) => {
4681
+ let state;
4682
+ const children = node.children;
4683
+ let allSelected = true;
4684
+ let someSelected = false;
4685
+ for (let i = 0, l = children.length; i < l; i++) {
4686
+ const child = children[i];
4687
+ state = !!child.selected;
4688
+ // When fixing the parents, we trust the sibling status (i.e. we don't recurse)
4689
+ if (state || child._partsel) {
4690
+ someSelected = true;
4691
+ }
4692
+ if (!state) {
4693
+ allSelected = false;
4694
+ }
4695
+ }
4696
+ state = allSelected ? true : someSelected ? undefined : false;
4697
+ node._changeSelectStatusProps(state);
4698
+ });
4384
4699
  }
4385
4700
  /** Modify the check/uncheck state. */
4386
4701
  setSelected(flag = true, options) {
4387
- const prev = this.selected;
4388
- if (!!flag !== prev) {
4702
+ const tree = this.tree;
4703
+ const sendEvents = !(options === null || options === void 0 ? void 0 : options.noEvents); // Default: send events
4704
+ const prev = this.isSelected();
4705
+ const isRadio = this.parent && this.parent.radiogroup;
4706
+ const selectMode = tree.options.selectMode;
4707
+ const canSelect = (options === null || options === void 0 ? void 0 : options.force) || !this.getOption("unselectable");
4708
+ flag = !!flag;
4709
+ // this.logDebug(`setSelected(${flag})`, this);
4710
+ if (!canSelect) {
4711
+ return prev;
4712
+ }
4713
+ if ((options === null || options === void 0 ? void 0 : options.propagateDown) && selectMode === "multi") {
4714
+ tree.runWithDeferredUpdate(() => {
4715
+ this.visit((node) => {
4716
+ node.setSelected(flag);
4717
+ });
4718
+ });
4719
+ return prev;
4720
+ }
4721
+ if (flag === prev ||
4722
+ (sendEvents && this._callEvent("beforeSelect", { flag: flag }) === false)) {
4723
+ return prev;
4724
+ }
4725
+ tree.runWithDeferredUpdate(() => {
4726
+ if (isRadio) {
4727
+ // Radiobutton Group
4728
+ if (!flag && !(options === null || options === void 0 ? void 0 : options.force)) {
4729
+ return prev; // don't uncheck radio buttons
4730
+ }
4731
+ for (let sibling of this.parent.children) {
4732
+ sibling.selected = sibling === this;
4733
+ }
4734
+ }
4735
+ else {
4736
+ this.selected = flag;
4737
+ if (selectMode === "hier") {
4738
+ this.fixSelection3AfterClick();
4739
+ }
4740
+ else if (selectMode === "single") {
4741
+ tree.visit((n) => {
4742
+ n.selected = false;
4743
+ });
4744
+ }
4745
+ }
4746
+ });
4747
+ if (sendEvents) {
4389
4748
  this._callEvent("select", { flag: flag });
4390
4749
  }
4391
- this.selected = !!flag;
4392
- this.setModified();
4750
+ return prev;
4393
4751
  }
4394
4752
  /** Display node status (ok, loading, error, noData) using styles and a dummy child node. */
4395
4753
  setStatus(status, options) {
@@ -4414,7 +4772,7 @@
4414
4772
  assert(!firstChild || !firstChild.isStatusNode());
4415
4773
  statusNode = this.addNode(data, "prependChild");
4416
4774
  statusNode.match = true;
4417
- tree.setModified(ChangeType.structure);
4775
+ tree.update(ChangeType.structure);
4418
4776
  return statusNode;
4419
4777
  };
4420
4778
  _clearStatusNode();
@@ -4427,7 +4785,7 @@
4427
4785
  this._isLoading = true;
4428
4786
  this._errorInfo = null;
4429
4787
  if (this.parent) {
4430
- this.setModified(ChangeType.status);
4788
+ this.update(ChangeType.status);
4431
4789
  }
4432
4790
  else {
4433
4791
  // If this is the invisible root, add a visible top-level node
@@ -4440,7 +4798,7 @@
4440
4798
  tooltip: details,
4441
4799
  });
4442
4800
  }
4443
- // this.render();
4801
+ // this.update();
4444
4802
  break;
4445
4803
  case "error":
4446
4804
  _setStatusNode({
@@ -4469,13 +4827,13 @@
4469
4827
  default:
4470
4828
  error("invalid node status " + status);
4471
4829
  }
4472
- tree.setModified(ChangeType.structure);
4830
+ tree.update(ChangeType.structure);
4473
4831
  return statusNode;
4474
4832
  }
4475
4833
  /** Rename this node. */
4476
4834
  setTitle(title) {
4477
4835
  this.title = title;
4478
- this.setModified();
4836
+ this.update();
4479
4837
  // this.triggerModify("rename"); // TODO
4480
4838
  }
4481
4839
  _sortChildren(cmp, deep) {
@@ -4500,7 +4858,7 @@
4500
4858
  */
4501
4859
  sortChildren(cmp = nodeTitleSorter, deep = false) {
4502
4860
  this._sortChildren(cmp || nodeTitleSorter, deep);
4503
- this.tree.setModified(ChangeType.structure);
4861
+ this.tree.update(ChangeType.structure);
4504
4862
  // this.triggerModify("sort"); // TODO
4505
4863
  }
4506
4864
  /**
@@ -4528,7 +4886,7 @@
4528
4886
  this.parent.triggerModifyChild(operation, this, extra);
4529
4887
  }
4530
4888
  /**
4531
- * Call `callback(node)` for all child nodes in hierarchical order (depth-first, pre-order).
4889
+ * Call `callback(node)` for all descendant nodes in hierarchical order (depth-first, pre-order).
4532
4890
  *
4533
4891
  * Stop iteration, if fn() returns false. Skip current branch, if fn()
4534
4892
  * returns "skip".<br>
@@ -4608,7 +4966,7 @@
4608
4966
  /*!
4609
4967
  * Wunderbaum - ext-edit
4610
4968
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
4611
- * v0.3.5, Mon, 19 Jun 2023 06:20:59 GMT (https://github.com/mar10/wunderbaum)
4969
+ * v0.5.0, Fri, 15 Sep 2023 14:34:23 GMT (https://github.com/mar10/wunderbaum)
4612
4970
  */
4613
4971
  // const START_MARKER = "\uFFF7";
4614
4972
  class EditExtension extends WunderbaumExtension {
@@ -4836,7 +5194,7 @@
4836
5194
  node === null || node === void 0 ? void 0 : node.setTitle(newValue);
4837
5195
  // NOTE: At least on Safari, this render call triggers a scroll event
4838
5196
  // probably because the focused input is replaced.
4839
- this.curEditNode.render({ preventScroll: true });
5197
+ this.curEditNode._render({ preventScroll: true });
4840
5198
  this.curEditNode = null;
4841
5199
  this.relatedNode = null;
4842
5200
  this.tree.setFocus(); // restore focus that was in the input element
@@ -4851,7 +5209,7 @@
4851
5209
  // Discard the embedded `<input>`
4852
5210
  // NOTE: At least on Safari, this render call triggers a scroll event
4853
5211
  // probably because the focused input is replaced.
4854
- this.curEditNode.render({ preventScroll: true });
5212
+ this.curEditNode._render({ preventScroll: true });
4855
5213
  this.curEditNode = null;
4856
5214
  this.relatedNode = null;
4857
5215
  // We discarded the <input>, so we have to acquire keyboard focus again
@@ -4904,9 +5262,10 @@
4904
5262
  * https://github.com/mar10/wunderbaum
4905
5263
  *
4906
5264
  * Released under the MIT license.
4907
- * @version v0.3.5
4908
- * @date Mon, 19 Jun 2023 06:20:59 GMT
5265
+ * @version v0.5.0
5266
+ * @date Fri, 15 Sep 2023 14:34:23 GMT
4909
5267
  */
5268
+ // import "./wunderbaum.scss";
4910
5269
  class WbSystemRoot extends WunderbaumNode {
4911
5270
  constructor(tree) {
4912
5271
  super(tree, null, {
@@ -4948,6 +5307,9 @@
4948
5307
  this.pendingChangeTypes = new Set();
4949
5308
  /** Expose some useful methods of the util.ts module as `tree._util`. */
4950
5309
  this._util = util;
5310
+ // --- SELECT ---
5311
+ // /** @internal */
5312
+ // public selectRangeAnchor: WunderbaumNode | null = null;
4951
5313
  // --- FILTER ---
4952
5314
  this.filterMode = null;
4953
5315
  // --- KEYNAV ---
@@ -4973,6 +5335,7 @@
4973
5335
  header: null,
4974
5336
  // headerHeightPx: ROW_HEIGHT,
4975
5337
  rowHeightPx: ROW_HEIGHT,
5338
+ iconMap: "bootstrap",
4976
5339
  columns: null,
4977
5340
  types: null,
4978
5341
  // escapeTitles: true,
@@ -4982,9 +5345,10 @@
4982
5345
  checkbox: false,
4983
5346
  minExpandLevel: 0,
4984
5347
  emptyChildListExpandable: false,
4985
- updateThrottleWait: 200,
5348
+ // updateThrottleWait: 200,
4986
5349
  skeleton: false,
4987
5350
  connectTopBreadcrumb: null,
5351
+ selectMode: "multi",
4988
5352
  // --- KeyNav ---
4989
5353
  navigationModeOption: null,
4990
5354
  quicksearch: true,
@@ -5140,26 +5504,34 @@
5140
5504
  }
5141
5505
  // Async mode is sometimes required, because this.element.clientWidth
5142
5506
  // has a wrong value at start???
5143
- this.setModified(ChangeType.any);
5507
+ this.update(ChangeType.any);
5144
5508
  // --- Bind listeners
5145
5509
  this.element.addEventListener("scroll", (e) => {
5146
5510
  // this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
5147
- this.setModified(ChangeType.scroll);
5511
+ this.update(ChangeType.scroll);
5148
5512
  });
5149
5513
  this.resizeObserver = new ResizeObserver((entries) => {
5150
5514
  // this.log("ResizeObserver: Size changed", entries);
5151
- this.setModified(ChangeType.resize);
5515
+ this.update(ChangeType.resize);
5152
5516
  });
5153
5517
  this.resizeObserver.observe(this.element);
5154
5518
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
5155
5519
  const info = Wunderbaum.getEventInfo(e);
5156
5520
  const node = info.node;
5157
- // this.log("click", info, e);
5521
+ const mouseEvent = e;
5522
+ // this.log("click", info);
5523
+ // if (this._selectRange(info) === false) {
5524
+ // return;
5525
+ // }
5158
5526
  if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
5159
5527
  this.lastClickTime = Date.now();
5160
5528
  return false;
5161
5529
  }
5162
5530
  if (node) {
5531
+ if (mouseEvent.ctrlKey) {
5532
+ node.toggleSelected();
5533
+ return;
5534
+ }
5163
5535
  // Edit title if 'clickActive' is triggered:
5164
5536
  const trigger = this.getOption("edit.trigger");
5165
5537
  const slowClickDelay = this.getOption("edit.slowClickDelay");
@@ -5179,7 +5551,7 @@
5179
5551
  node.setExpanded(!node.isExpanded());
5180
5552
  }
5181
5553
  else if (info.region === NodeRegion.checkbox) {
5182
- node.setSelected(!node.isSelected());
5554
+ node.toggleSelected();
5183
5555
  }
5184
5556
  }
5185
5557
  this.lastClickTime = Date.now();
@@ -5278,6 +5650,16 @@
5278
5650
  }
5279
5651
  return null;
5280
5652
  }
5653
+ /**
5654
+ * Return the icon-function -> icon-definition mapping.
5655
+ */
5656
+ get iconMap() {
5657
+ const map = this.options.iconMap;
5658
+ if (typeof map === "string") {
5659
+ return iconMaps[map];
5660
+ }
5661
+ return map;
5662
+ }
5281
5663
  /**
5282
5664
  * Return a WunderbaumNode instance from element or event.
5283
5665
  */
@@ -5626,7 +6008,7 @@
5626
6008
  // public cellNavMode = false;
5627
6009
  // public lastQuicksearchTime = 0;
5628
6010
  // public lastQuicksearchTerm = "";
5629
- this.setModified(ChangeType.structure);
6011
+ this.update(ChangeType.structure);
5630
6012
  }
5631
6013
  /**
5632
6014
  * Clear nodes and markup and detach events and observers.
@@ -5684,7 +6066,7 @@
5684
6066
  this.options[name] = value;
5685
6067
  switch (name) {
5686
6068
  case "checkbox":
5687
- this.setModified(ChangeType.any);
6069
+ this.update(ChangeType.any);
5688
6070
  break;
5689
6071
  case "enabled":
5690
6072
  this.setEnabled(!!value);
@@ -5707,8 +6089,15 @@
5707
6089
  const header = this.options.header;
5708
6090
  return this.isGrid() ? header !== false : !!header;
5709
6091
  }
5710
- /** Run code, but defer rendering of viewport until done. */
5711
- runWithoutUpdate(func, hint = null) {
6092
+ /** Run code, but defer rendering of viewport until done.
6093
+ *
6094
+ * ```
6095
+ * tree.runWithDeferredUpdate(() => {
6096
+ * return someFuncThatWouldUpdateManyNodes();
6097
+ * });
6098
+ * ```
6099
+ */
6100
+ runWithDeferredUpdate(func, hint = null) {
5712
6101
  try {
5713
6102
  this.enableUpdate(false);
5714
6103
  const res = func();
@@ -5719,29 +6108,66 @@
5719
6108
  this.enableUpdate(true);
5720
6109
  }
5721
6110
  }
5722
- /** Recursively expand all expandable nodes (triggers lazy load id needed). */
6111
+ /** Recursively expand all expandable nodes (triggers lazy load if needed). */
5723
6112
  async expandAll(flag = true, options) {
5724
6113
  await this.root.expandAll(flag, options);
5725
6114
  }
5726
6115
  /** Recursively select all nodes. */
5727
6116
  selectAll(flag = true) {
5728
- try {
5729
- this.enableUpdate(false);
5730
- this.visit((node) => {
5731
- node.setSelected(flag);
5732
- });
5733
- }
5734
- finally {
5735
- this.enableUpdate(true);
5736
- }
6117
+ return this.root.setSelected(flag, { propagateDown: true });
6118
+ }
6119
+ /** Toggle select all nodes. */
6120
+ toggleSelect() {
6121
+ this.selectAll(this.root._anySelectable());
6122
+ }
6123
+ /**
6124
+ * Return an array of selected nodes.
6125
+ * @param stopOnParents only return the topmost selected node (useful with selectMode 'hier')
6126
+ */
6127
+ getSelectedNodes(stopOnParents = false) {
6128
+ return this.root.getSelectedNodes(stopOnParents);
6129
+ }
6130
+ /*
6131
+ * Return an array of selected nodes.
6132
+ */
6133
+ _selectRange(eventInfo) {
6134
+ this.logDebug("_selectRange", eventInfo);
6135
+ error("Not yet implemented.");
6136
+ // const mode = this.options.selectMode!;
6137
+ // if (mode !== "multi") {
6138
+ // this.logDebug(`Range selection only available for selectMode 'multi'`);
6139
+ // return;
6140
+ // }
6141
+ // if (eventInfo.canonicalName === "Meta+click") {
6142
+ // eventInfo.node?.toggleSelected();
6143
+ // return false; // don't
6144
+ // } else if (eventInfo.canonicalName === "Shift+click") {
6145
+ // let from = this.activeNode;
6146
+ // let to = eventInfo.node;
6147
+ // if (!from || !to || from === to) {
6148
+ // return;
6149
+ // }
6150
+ // this.runWithDeferredUpdate(() => {
6151
+ // this.visitRows(
6152
+ // (node) => {
6153
+ // node.setSelected();
6154
+ // },
6155
+ // {
6156
+ // includeHidden: true,
6157
+ // includeSelf: false,
6158
+ // start: from,
6159
+ // reverse: from!._rowIdx! > to!._rowIdx!,
6160
+ // }
6161
+ // );
6162
+ // });
6163
+ // return false;
6164
+ // }
5737
6165
  }
5738
- /** Return the number of nodes in the data model.*/
6166
+ /** Return the number of nodes in the data model.
6167
+ * @param visible if true, nodes that are hidden due to collapsed parents are ignored.
6168
+ */
5739
6169
  count(visible = false) {
5740
- if (visible) {
5741
- return this.treeRowCount;
5742
- // return this.viewNodes.size;
5743
- }
5744
- return this.keyMap.size;
6170
+ return visible ? this.treeRowCount : this.keyMap.size;
5745
6171
  }
5746
6172
  /** @internal sanity check. */
5747
6173
  _check() {
@@ -5832,7 +6258,7 @@
5832
6258
  break;
5833
6259
  case "first":
5834
6260
  // First visible node
5835
- this.visit(function (n) {
6261
+ this.visit((n) => {
5836
6262
  if (n.isVisible()) {
5837
6263
  res = n;
5838
6264
  return false;
@@ -5840,7 +6266,7 @@
5840
6266
  });
5841
6267
  break;
5842
6268
  case "last":
5843
- this.visit(function (n) {
6269
+ this.visit((n) => {
5844
6270
  // last visible node
5845
6271
  if (n.isVisible()) {
5846
6272
  res = n;
@@ -5972,6 +6398,8 @@
5972
6398
  */
5973
6399
  static getEventInfo(event) {
5974
6400
  let target = event.target, cl = target.classList, parentCol = target.closest("span.wb-col"), node = Wunderbaum.getNode(target), tree = node ? node.tree : Wunderbaum.getTree(event), res = {
6401
+ event: event,
6402
+ canonicalName: eventToString(event),
5975
6403
  tree: tree,
5976
6404
  node: node,
5977
6405
  region: NodeRegion.unknown,
@@ -6137,7 +6565,7 @@
6137
6565
  // Make sure the topNode is always visible
6138
6566
  this.scrollTo(topNode);
6139
6567
  }
6140
- // this.setModified(ChangeType.scroll);
6568
+ // this.update(ChangeType.scroll);
6141
6569
  }
6142
6570
  }
6143
6571
  /**
@@ -6165,7 +6593,7 @@
6165
6593
  // util.assert(node._rowIdx != null);
6166
6594
  this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
6167
6595
  this.element.scrollLeft = newLeft;
6168
- // this.setModified(ChangeType.scroll);
6596
+ // this.update(ChangeType.scroll);
6169
6597
  }
6170
6598
  /**
6171
6599
  * Set column #colIdx to 'active'.
@@ -6187,7 +6615,7 @@
6187
6615
  }
6188
6616
  }
6189
6617
  }
6190
- (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
6618
+ (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.update(ChangeType.status);
6191
6619
  // Update `wb-active` class for all cell spans
6192
6620
  for (let rowDiv of this.nodeListElement.children) {
6193
6621
  let i = 0;
@@ -6214,16 +6642,25 @@
6214
6642
  this.element.blur();
6215
6643
  }
6216
6644
  }
6217
- setModified(change, node, options) {
6645
+ /**
6646
+ * @deprecated since v0.3.6: use `update()` instead.
6647
+ */
6648
+ setModified(change, ...args) {
6649
+ this.logWarn("setModified() is deprecated: use update() instead.");
6650
+ // @ts-ignore
6651
+ // (!) TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
6652
+ return this.update.call(this, change, ...args);
6653
+ }
6654
+ update(change, node, options) {
6218
6655
  if (this._disableUpdateCount) {
6219
6656
  // Assuming that we redraw all when enableUpdate() is re-enabled.
6220
6657
  // this.log(
6221
- // `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
6658
+ // `IGNORED update(${change}) node=${node} (disable level ${this._disableUpdateCount})`
6222
6659
  // );
6223
6660
  this._disableUpdateIgnoreCount++;
6224
6661
  return;
6225
6662
  }
6226
- // this.log(`setModified(${change}) node=${node}`);
6663
+ // this.log(`update(${change}) node=${node}`);
6227
6664
  if (!(node instanceof WunderbaumNode)) {
6228
6665
  options = node;
6229
6666
  node = null;
@@ -6257,7 +6694,7 @@
6257
6694
  // Single nodes are immediately updated if already inside the viewport
6258
6695
  // (otherwise we can ignore)
6259
6696
  if (node._rowElem) {
6260
- node.render({ change: change });
6697
+ node._render({ change: change });
6261
6698
  }
6262
6699
  break;
6263
6700
  default:
@@ -6315,7 +6752,7 @@
6315
6752
  this.setColumn(0);
6316
6753
  }
6317
6754
  this.element.classList.toggle("wb-cell-mode", flag);
6318
- (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
6755
+ (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.update(ChangeType.status);
6319
6756
  }
6320
6757
  /** Set the tree's navigation mode option. */
6321
6758
  setNavigationOption(mode, reset = false) {
@@ -6479,7 +6916,7 @@
6479
6916
  // if (modified) {
6480
6917
  // this._renderHeaderMarkup();
6481
6918
  // if (options.renderMarkup) {
6482
- // this.setModified(ChangeType.header, { removeMarkup: true });
6919
+ // this.update(ChangeType.header, { removeMarkup: true });
6483
6920
  // } else if (options.updateRows) {
6484
6921
  // this._updateRows();
6485
6922
  // }
@@ -6531,11 +6968,11 @@
6531
6968
  }
6532
6969
  }
6533
6970
  /**
6534
- * Render pending changes that were scheduled using {@link WunderbaumNode.setModified} if any.
6971
+ * Render pending changes that were scheduled using {@link WunderbaumNode.update} if any.
6535
6972
  *
6536
6973
  * This is hardly ever neccessary, since we normally either
6537
- * - call `setModified(ChangeType.TYPE)` (async, throttled), or
6538
- * - call `setModified(ChangeType.TYPE, {immediate: true})` (synchronous)
6974
+ * - call `update(ChangeType.TYPE)` (async, throttled), or
6975
+ * - call `update(ChangeType.TYPE, {immediate: true})` (synchronous)
6539
6976
  *
6540
6977
  * `updatePendingModifications()` will only force immediate execution of
6541
6978
  * pending async changes if any.
@@ -6550,7 +6987,7 @@
6550
6987
  * It calls `updateColumns()` and `_updateRows()`.
6551
6988
  *
6552
6989
  * This protected method should not be called directly but via
6553
- * {@link WunderbaumNode.setModified}`, {@link Wunderbaum.setModified},
6990
+ * {@link WunderbaumNode.update}`, {@link Wunderbaum.update},
6554
6991
  * or {@link Wunderbaum.updatePendingModifications}.
6555
6992
  * @internal
6556
6993
  */
@@ -6705,7 +7142,7 @@
6705
7142
  if (rowDiv) {
6706
7143
  rowDiv.style.top = idx * ROW_HEIGHT + "px";
6707
7144
  }
6708
- node.render({ top: top, after: prevElem });
7145
+ node._render({ top: top, after: prevElem });
6709
7146
  // node.log("render", top, prevElem, "=>", node._rowElem);
6710
7147
  prevElem = node._rowElem;
6711
7148
  }
@@ -6785,7 +7222,7 @@
6785
7222
  if (node.children &&
6786
7223
  node.children.length &&
6787
7224
  (includeHidden || node.expanded)) {
6788
- res = node.visit(function (n) {
7225
+ res = node.visit((n) => {
6789
7226
  if (n === stopNode) {
6790
7227
  return false;
6791
7228
  }
@@ -6905,7 +7342,7 @@
6905
7342
  if (this._disableUpdateCount === 0) {
6906
7343
  this.logDebug(`enableUpdate(): active again. Re-painting to catch up with ${this._disableUpdateIgnoreCount} ignored update requests...`);
6907
7344
  this._disableUpdateIgnoreCount = 0;
6908
- this.setModified(ChangeType.any, { immediate: true });
7345
+ this.update(ChangeType.any, { immediate: true });
6909
7346
  }
6910
7347
  }
6911
7348
  else {
@@ -6959,7 +7396,7 @@
6959
7396
  }
6960
7397
  Wunderbaum.sequence = 0;
6961
7398
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6962
- Wunderbaum.version = "v0.3.5"; // Set to semver by 'grunt release'
7399
+ Wunderbaum.version = "v0.5.0"; // Set to semver by 'grunt release'
6963
7400
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6964
7401
  Wunderbaum.util = util;
6965
7402