wunderbaum 0.0.7 → 0.0.9

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-2022, Martin Wendt. Released under the MIT license.
10
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
10
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
11
11
  */
12
12
  /** @module util */
13
13
  /** Readable names for `MouseEvent.button` */
@@ -287,11 +287,17 @@
287
287
  case "week":
288
288
  case "datetime":
289
289
  case "datetime-local":
290
- input.valueAsDate = value;
290
+ input.valueAsDate = new Date(value);
291
+ // input.valueAsDate = value; // breaks in Edge?
291
292
  break;
292
293
  case "number":
293
294
  case "range":
294
- input.valueAsNumber = value;
295
+ if (value == null) {
296
+ input.value = value;
297
+ }
298
+ else {
299
+ input.valueAsNumber = value;
300
+ }
295
301
  break;
296
302
  case "radio":
297
303
  error("Not implemented");
@@ -308,7 +314,7 @@
308
314
  break;
309
315
  case "text":
310
316
  default:
311
- input.value = value || "";
317
+ input.value = value !== null && value !== void 0 ? value : "";
312
318
  }
313
319
  }
314
320
  else if (tag === "SELECT") {
@@ -707,7 +713,7 @@
707
713
  /*!
708
714
  * Wunderbaum - types
709
715
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
710
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
716
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
711
717
  */
712
718
  /** Possible values for `setModified()`. */
713
719
  var ChangeType;
@@ -759,7 +765,7 @@
759
765
  /*!
760
766
  * Wunderbaum - wb_extension_base
761
767
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
762
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
768
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
763
769
  */
764
770
  class WunderbaumExtension {
765
771
  constructor(tree, id, defaults) {
@@ -1050,7 +1056,7 @@
1050
1056
  /*!
1051
1057
  * Wunderbaum - ext-filter
1052
1058
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1053
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
1059
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
1054
1060
  */
1055
1061
  const START_MARKER = "\uFFF7";
1056
1062
  const END_MARKER = "\uFFF8";
@@ -1355,7 +1361,7 @@
1355
1361
  /*!
1356
1362
  * Wunderbaum - ext-keynav
1357
1363
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1358
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
1364
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
1359
1365
  */
1360
1366
  const QUICKSEARCH_DELAY = 500;
1361
1367
  class KeynavExtension extends WunderbaumExtension {
@@ -1453,7 +1459,8 @@
1453
1459
  if (!node.expanded && (node.children || node.lazy)) {
1454
1460
  eventName = "Add"; // expand
1455
1461
  }
1456
- else if (navModeOption === NavigationOptions.startRow) {
1462
+ else if (navModeOption === NavigationOptions.startCell ||
1463
+ navModeOption === NavigationOptions.startRow) {
1457
1464
  tree.setCellNav();
1458
1465
  return;
1459
1466
  }
@@ -1606,17 +1613,31 @@
1606
1613
  }
1607
1614
  handled = true;
1608
1615
  break;
1616
+ case "Home": // Generated by FN + ArrowLeft on Mac
1617
+ case "Meta+ArrowLeft":
1618
+ tree.setFocus(); // Blur prev. input if any
1619
+ if (!isColspan && tree.activeColIdx > 0) {
1620
+ tree.setColumn(0);
1621
+ }
1622
+ handled = true;
1623
+ break;
1624
+ case "End": // Generated by FN + ArrowRight on Mac
1625
+ case "Meta+ArrowRight":
1626
+ tree.setFocus(); // Blur prev. input if any
1627
+ if (!isColspan && tree.activeColIdx < tree.columns.length - 1) {
1628
+ tree.setColumn(tree.columns.length - 1);
1629
+ }
1630
+ handled = true;
1631
+ break;
1609
1632
  case "ArrowDown":
1610
1633
  case "ArrowUp":
1611
1634
  case "Backspace":
1612
- case "End":
1613
- case "Home":
1614
- case "Control+End":
1615
- case "Control+Home":
1635
+ case "Control+End": // Generated by FN + Control + ArrowRight on Mac
1636
+ case "Control+Home": // Generated by FN + Control + Arrowleft on Mac
1616
1637
  case "Meta+ArrowDown":
1617
1638
  case "Meta+ArrowUp":
1618
- case "PageDown":
1619
- case "PageUp":
1639
+ case "PageDown": // Generated by FN + ArrowDown on Mac
1640
+ case "PageUp": // Generated by FN + ArrowUp on Mac
1620
1641
  node.navigate(eventName, { activate: activate, event: event });
1621
1642
  // if (isCellEditMode) {
1622
1643
  // this._getEmbeddedInputElem(null, true); // set focus to input
@@ -1637,7 +1658,7 @@
1637
1658
  /*!
1638
1659
  * Wunderbaum - ext-logger
1639
1660
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1640
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
1661
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
1641
1662
  */
1642
1663
  class LoggerExtension extends WunderbaumExtension {
1643
1664
  constructor(tree) {
@@ -1677,7 +1698,7 @@
1677
1698
  /*!
1678
1699
  * Wunderbaum - common
1679
1700
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1680
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
1701
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
1681
1702
  */
1682
1703
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1683
1704
  /**
@@ -1732,14 +1753,14 @@
1732
1753
  };
1733
1754
  /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
1734
1755
  const RESERVED_TREE_SOURCE_KEYS = new Set([
1756
+ "_format",
1757
+ "_keyMap",
1758
+ "_positional",
1759
+ "_typeList",
1760
+ "_version",
1735
1761
  "children",
1736
1762
  "columns",
1737
- "format",
1738
- "keyMap",
1739
- "positional",
1740
- "typeList",
1741
1763
  "types",
1742
- "version", // reserved for future use
1743
1764
  ]);
1744
1765
  // /** Key codes that trigger grid navigation, even when inside an input element. */
1745
1766
  // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
@@ -1773,11 +1794,22 @@
1773
1794
  "-": "collapse",
1774
1795
  Subtract: "collapse",
1775
1796
  };
1776
- /** Return a callback that returns true if the node title contains a substring (case-insensitive). */
1777
- function makeNodeTitleMatcher(s) {
1778
- s = escapeRegex(s.toLowerCase());
1797
+ /** Return a callback that returns true if the node title matches the string
1798
+ * or regular expression.
1799
+ * @see {@link WunderbaumNode.findAll}
1800
+ */
1801
+ function makeNodeTitleMatcher(match) {
1802
+ if (match instanceof RegExp) {
1803
+ return function (node) {
1804
+ return match.test(node.title);
1805
+ };
1806
+ }
1807
+ assert(typeof match === "string");
1808
+ // s = escapeRegex(s.toLowerCase());
1779
1809
  return function (node) {
1780
- return node.title.toLowerCase().indexOf(s) >= 0;
1810
+ return node.title === match;
1811
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
1812
+ // return node.title.toLowerCase().indexOf(match) >= 0;
1781
1813
  };
1782
1814
  }
1783
1815
  /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
@@ -1788,11 +1820,125 @@
1788
1820
  return reMatch.test(node.title);
1789
1821
  };
1790
1822
  }
1823
+ function unflattenSource(source) {
1824
+ var _a, _b, _c;
1825
+ const { _format, _keyMap, _positional, children } = source;
1826
+ if (_format !== "flat") {
1827
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
1828
+ }
1829
+ if (_positional && _positional.includes("children")) {
1830
+ throw new Error(`source._positional must not include "children": ${_positional}`);
1831
+ }
1832
+ // Inverse keyMap:
1833
+ let longToShort = {};
1834
+ if (_keyMap) {
1835
+ for (const [key, value] of Object.entries(_keyMap)) {
1836
+ longToShort[value] = key;
1837
+ }
1838
+ }
1839
+ const positionalShort = _positional.map((e) => longToShort[e]);
1840
+ const newChildren = [];
1841
+ const keyToNodeMap = {};
1842
+ const indexToNodeMap = {};
1843
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
1844
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
1845
+ for (const [index, node] of children.entries()) {
1846
+ // Node entry format:
1847
+ // [PARENT_ID, [POSITIONAL_ARGS]]
1848
+ // or
1849
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
1850
+ const [parentId, args, kwargs = {}] = node;
1851
+ // Free up some memory as we go
1852
+ node[1] = null;
1853
+ if (node[2] != null) {
1854
+ node[2] = null;
1855
+ }
1856
+ // console.log("flatten", parentId, args, kwargs)
1857
+ // We keep `kwargs` as our new node definition. Then we add all positional
1858
+ // values to this object:
1859
+ args.forEach((val, positionalIdx) => {
1860
+ kwargs[positionalShort[positionalIdx]] = val;
1861
+ });
1862
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
1863
+ // index of the source.children list. If PARENT_ID is a string, we search
1864
+ // a parent with node.key of this value.
1865
+ indexToNodeMap[index] = kwargs;
1866
+ const key = kwargs[keyAttrName];
1867
+ if (key != null) {
1868
+ keyToNodeMap[key] = kwargs;
1869
+ }
1870
+ let parentNode = null;
1871
+ if (parentId === null) ;
1872
+ else if (typeof parentId === "number") {
1873
+ parentNode = indexToNodeMap[parentId];
1874
+ if (parentNode === undefined) {
1875
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
1876
+ }
1877
+ }
1878
+ else {
1879
+ parentNode = keyToNodeMap[parentId];
1880
+ if (parentNode === undefined) {
1881
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
1882
+ }
1883
+ }
1884
+ if (parentNode) {
1885
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
1886
+ parentNode[childrenAttrName].push(kwargs);
1887
+ }
1888
+ else {
1889
+ newChildren.push(kwargs);
1890
+ }
1891
+ }
1892
+ delete source.children;
1893
+ source.children = newChildren;
1894
+ }
1895
+ function inflateSourceData(source) {
1896
+ const { _format, _keyMap, _typeList } = source;
1897
+ if (_format === "flat") {
1898
+ unflattenSource(source);
1899
+ }
1900
+ delete source._format;
1901
+ delete source._version;
1902
+ delete source._keyMap;
1903
+ delete source._typeList;
1904
+ delete source._positional;
1905
+ function _iter(childList) {
1906
+ for (let node of childList) {
1907
+ // Expand short alias names
1908
+ if (_keyMap) {
1909
+ // Iterate over a list of names, because we modify inside the loop:
1910
+ Object.getOwnPropertyNames(node).forEach((propName) => {
1911
+ var _a;
1912
+ const long = (_a = _keyMap[propName]) !== null && _a !== void 0 ? _a : propName;
1913
+ if (long !== propName) {
1914
+ node[long] = node[propName];
1915
+ delete node[propName];
1916
+ }
1917
+ });
1918
+ }
1919
+ // `node` now has long attribute names
1920
+ // Resolve node type indexes
1921
+ const type = node.type;
1922
+ if (_typeList && type != null && typeof type === "number") {
1923
+ const newType = _typeList[type];
1924
+ if (newType == null) {
1925
+ throw new Error(`Expected typeList[${type}] entry in [${_typeList}]`);
1926
+ }
1927
+ node.type = newType;
1928
+ }
1929
+ // Recursion
1930
+ if (node.children) {
1931
+ _iter(node.children);
1932
+ }
1933
+ }
1934
+ }
1935
+ _iter(source.children);
1936
+ }
1791
1937
 
1792
1938
  /*!
1793
1939
  * Wunderbaum - ext-dnd
1794
1940
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1795
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
1941
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
1796
1942
  */
1797
1943
  const nodeMimeType = "application/x-wunderbaum-node";
1798
1944
  class DndExtension extends WunderbaumExtension {
@@ -1936,6 +2082,11 @@
1936
2082
  if (e.type === "dragstart") {
1937
2083
  // Set a default definition of allowed effects
1938
2084
  e.dataTransfer.effectAllowed = dndOpts.effectAllowed; //"copyMove"; // "all";
2085
+ if (srcNode.isEditing()) {
2086
+ srcNode.logDebug("Prevented dragging node in edit mode.");
2087
+ e.preventDefault();
2088
+ return false;
2089
+ }
1939
2090
  // Let user cancel the drag operation, override effectAllowed, etc.:
1940
2091
  const res = srcNode._callEvent("dnd.dragStart", { event: e });
1941
2092
  if (!res) {
@@ -2060,7 +2211,7 @@
2060
2211
  /*!
2061
2212
  * Wunderbaum - drag_observer
2062
2213
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2063
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
2214
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
2064
2215
  */
2065
2216
  /**
2066
2217
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2079,8 +2230,10 @@
2079
2230
  this.dragging = false;
2080
2231
  // TODO: touch events
2081
2232
  this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2082
- assert(opts.root);
2083
- this.opts = extend({ thresh: 5 }, opts);
2233
+ if (!opts.root) {
2234
+ throw new Error("Missing `root` option.");
2235
+ }
2236
+ this.opts = Object.assign({ thresh: 5 }, opts);
2084
2237
  this.root = opts.root;
2085
2238
  this._handler = this.handleEvent.bind(this);
2086
2239
  this.events.forEach((type) => {
@@ -2194,7 +2347,7 @@
2194
2347
  /*!
2195
2348
  * Wunderbaum - ext-grid
2196
2349
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2197
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
2350
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
2198
2351
  */
2199
2352
  class GridExtension extends WunderbaumExtension {
2200
2353
  constructor(tree) {
@@ -2231,7 +2384,7 @@
2231
2384
  /*!
2232
2385
  * Wunderbaum - deferred
2233
2386
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2234
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
2387
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
2235
2388
  */
2236
2389
  /**
2237
2390
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2284,7 +2437,7 @@
2284
2437
  /*!
2285
2438
  * Wunderbaum - wunderbaum_node
2286
2439
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2287
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
2440
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
2288
2441
  */
2289
2442
  /** Top-level properties that can be passed with `data`. */
2290
2443
  const NODE_PROPS = new Set([
@@ -2395,6 +2548,35 @@
2395
2548
  toString() {
2396
2549
  return `WunderbaumNode@${this.key}<'${this.title}'>`;
2397
2550
  }
2551
+ /**
2552
+ * Iterate all descendant nodes depth-first, pre-order using `for ... of ...` syntax.
2553
+ * More concise, but slightly slower than {@link WunderbaumNode.visit}.
2554
+ *
2555
+ * Example:
2556
+ * ```js
2557
+ * for(const n of node) {
2558
+ * ...
2559
+ * }
2560
+ * ```
2561
+ */
2562
+ *[Symbol.iterator]() {
2563
+ // let node: WunderbaumNode | null = this;
2564
+ const cl = this.children;
2565
+ if (cl) {
2566
+ for (let i = 0, l = cl.length; i < l; i++) {
2567
+ const n = cl[i];
2568
+ yield n;
2569
+ if (n.children) {
2570
+ yield* n;
2571
+ }
2572
+ }
2573
+ // Slower:
2574
+ // for (let node of this.children) {
2575
+ // yield node;
2576
+ // yield* node : 0;
2577
+ // }
2578
+ }
2579
+ }
2398
2580
  // /** Return an option value. */
2399
2581
  // protected _getOpt(
2400
2582
  // name: string,
@@ -2425,48 +2607,46 @@
2425
2607
  /**
2426
2608
  * Append (or insert) a list of child nodes.
2427
2609
  *
2428
- * Tip: pass `{ before: 0 }` to prepend children
2429
- * @param {NodeData[]} nodeData array of child node definitions (also single child accepted)
2430
- * @param child node (or key or index of such).
2431
- * If omitted, the new children are appended.
2610
+ * Tip: pass `{ before: 0 }` to prepend new nodes as first children.
2611
+ *
2432
2612
  * @returns first child added
2433
2613
  */
2434
2614
  addChildren(nodeData, options) {
2435
2615
  const tree = this.tree;
2436
- const level = options ? options.level : this.getLevel();
2437
- let insertBefore = options
2438
- ? options.before
2439
- : null,
2440
- // redraw = options ? options.redraw !== false : true,
2441
- nodeList = [];
2616
+ let { before = null, applyMinExpanLevel = true, _level } = options !== null && options !== void 0 ? options : {};
2617
+ // let { before, loadLazy=true, _level } = options ?? {};
2618
+ // const isTopCall = _level == null;
2619
+ _level !== null && _level !== void 0 ? _level : (_level = this.getLevel());
2620
+ const nodeList = [];
2442
2621
  try {
2443
2622
  tree.enableUpdate(false);
2444
2623
  if (isPlainObject(nodeData)) {
2445
2624
  nodeData = [nodeData];
2446
2625
  }
2447
- const forceExpand = level < tree.options.minExpandLevel;
2626
+ const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
2448
2627
  for (let child of nodeData) {
2449
- let subChildren = child.children;
2628
+ const subChildren = child.children;
2450
2629
  delete child.children;
2451
- let n = new WunderbaumNode(tree, this, child);
2452
- if (forceExpand && !n.lazy)
2630
+ const n = new WunderbaumNode(tree, this, child);
2631
+ if (forceExpand && !n.isUnloaded()) {
2453
2632
  n.expanded = true;
2633
+ }
2454
2634
  nodeList.push(n);
2455
2635
  if (subChildren) {
2456
- n.addChildren(subChildren, { redraw: false, level: level + 1 });
2636
+ n.addChildren(subChildren, { _level: _level + 1 });
2457
2637
  }
2458
2638
  }
2459
2639
  if (!this.children) {
2460
2640
  this.children = nodeList;
2461
2641
  }
2462
- else if (insertBefore == null || this.children.length === 0) {
2642
+ else if (before == null || this.children.length === 0) {
2463
2643
  this.children = this.children.concat(nodeList);
2464
2644
  }
2465
2645
  else {
2466
- // Returns null if insertBefore is not a direct child:
2467
- insertBefore = this.findDirectChild(insertBefore);
2468
- let pos = this.children.indexOf(insertBefore);
2469
- assert(pos >= 0, "insertBefore must be an existing child");
2646
+ // Returns null if before is not a direct child:
2647
+ before = this.findDirectChild(before);
2648
+ let pos = this.children.indexOf(before);
2649
+ assert(pos >= 0, `options.before must be a direct child of ${this}`);
2470
2650
  // insert nodeList after children[pos]
2471
2651
  this.children.splice(pos, 0, ...nodeList);
2472
2652
  }
@@ -2476,11 +2656,14 @@
2476
2656
  // }
2477
2657
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
2478
2658
  tree.setModified(ChangeType.structure);
2479
- return nodeList[0];
2480
2659
  }
2481
2660
  finally {
2482
2661
  tree.enableUpdate(true);
2483
2662
  }
2663
+ // if(isTopCall && loadLazy){
2664
+ // this.logWarn("addChildren(): loadLazy is not yet implemented.")
2665
+ // }
2666
+ return nodeList[0];
2484
2667
  }
2485
2668
  /**
2486
2669
  * Append or prepend a node, or append a child node.
@@ -2554,21 +2737,98 @@
2554
2737
  }
2555
2738
  }
2556
2739
  }
2557
- /** Call `setExpanded()` on al child nodes*/
2558
- async expandAll(flag = true) {
2559
- this.visit((node) => {
2560
- node.setExpanded(flag);
2561
- });
2740
+ /** Call `setExpanded()` on all descendant nodes. */
2741
+ async expandAll(flag = true, options) {
2742
+ const tree = this.tree;
2743
+ const minExpandLevel = this.tree.options.minExpandLevel;
2744
+ let { depth = 99, loadLazy, force } = options !== null && options !== void 0 ? options : {};
2745
+ const expand_opts = {
2746
+ scrollIntoView: false,
2747
+ force: force,
2748
+ loadLazy: loadLazy,
2749
+ };
2750
+ // this.logInfo(`expandAll(${flag})`);
2751
+ // Expand all direct children in parallel:
2752
+ async function _iter(n, level) {
2753
+ var _a;
2754
+ // n.logInfo(` _iter(${level})`);
2755
+ if (level === 0) {
2756
+ return;
2757
+ }
2758
+ // if (!flag && minExpandLevel && !force && n.getLevel() <= minExpandLevel) {
2759
+ // return; // Do not collapse until minExpandLevel
2760
+ // }
2761
+ const level_1 = level == null ? null : level - 1;
2762
+ const promises = [];
2763
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach((cn) => {
2764
+ if (flag) {
2765
+ if (!cn.expanded && (cn.children || (loadLazy && cn.lazy))) {
2766
+ // Node is collapsed and may be expanded (i.e. has children or is lazy)
2767
+ // Expanding may be async, so we store the promise.
2768
+ // Also the recursion is delayed until expansion finished.
2769
+ const p = cn.setExpanded(true, expand_opts);
2770
+ promises.push(p);
2771
+ p.then(async () => {
2772
+ await _iter(cn, level_1);
2773
+ });
2774
+ }
2775
+ else {
2776
+ // We don't expand the node, but still visit descendants.
2777
+ // There we may find lazy nodes, so we
2778
+ promises.push(_iter(cn, level_1));
2779
+ }
2780
+ }
2781
+ else {
2782
+ // Collapsing is always synchronous, so no promises required
2783
+ if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
2784
+ // Do not collapse until minExpandLevel
2785
+ cn.setExpanded(false, expand_opts);
2786
+ }
2787
+ _iter(cn, level_1); // recursion, even if cn was already collapsed
2788
+ }
2789
+ });
2790
+ return new Promise((resolve) => {
2791
+ Promise.all(promises).then(() => {
2792
+ resolve(true);
2793
+ });
2794
+ });
2795
+ }
2796
+ const tag = tree.logTime(`${this}.expandAll(${flag})`);
2797
+ try {
2798
+ tree.enableUpdate(false);
2799
+ await _iter(this, depth);
2800
+ }
2801
+ finally {
2802
+ tree.enableUpdate(true);
2803
+ tree.logTimeEnd(tag);
2804
+ }
2562
2805
  }
2563
- /**Find all nodes that match condition (excluding self).
2806
+ /**
2807
+ * Find all descendant nodes that match condition (excluding self).
2564
2808
  *
2565
- * @param {string | function(node)} match title string to search for, or a
2566
- * callback function that returns `true` if a node is matched.
2809
+ * If `match` is a string, search for exact node title.
2810
+ * If `match` is a RegExp expression, apply it to node.title, using
2811
+ * [RegExp.test()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test).
2812
+ * If `match` is a callback, match all nodes for that the callback(node) returns true.
2813
+ *
2814
+ * Returns an empty array if no nodes were found.
2815
+ *
2816
+ * Examples:
2817
+ * ```js
2818
+ * // Match all node titles that match exactly 'Joe':
2819
+ * nodeList = node.findAll("Joe")
2820
+ * // Match all node titles that start with 'Joe' case sensitive:
2821
+ * nodeList = node.findAll(/^Joe/)
2822
+ * // Match all node titles that contain 'oe', case insensitive:
2823
+ * nodeList = node.findAll(/oe/i)
2824
+ * // Match all nodes with `data.price` >= 99:
2825
+ * nodeList = node.findAll((n) => {
2826
+ * return n.data.price >= 99;
2827
+ * })
2828
+ * ```
2567
2829
  */
2568
2830
  findAll(match) {
2569
- const matcher = isFunction(match)
2570
- ? match
2571
- : makeNodeTitleMatcher(match);
2831
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2572
2832
  const res = [];
2573
2833
  this.visit((n) => {
2574
2834
  if (matcher(n)) {
@@ -2598,15 +2858,13 @@
2598
2858
  }
2599
2859
  return null;
2600
2860
  }
2601
- /**Find first node that matches condition (excluding self).
2861
+ /**
2862
+ * Find first descendant node that matches condition (excluding self) or null.
2602
2863
  *
2603
- * @param match title string to search for, or a
2604
- * callback function that returns `true` if a node is matched.
2864
+ * @see {@link WunderbaumNode.findAll} for examples.
2605
2865
  */
2606
2866
  findFirst(match) {
2607
- const matcher = isFunction(match)
2608
- ? match
2609
- : makeNodeTitleMatcher(match);
2867
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2610
2868
  let res = null;
2611
2869
  this.visit((n) => {
2612
2870
  if (matcher(n)) {
@@ -2623,6 +2881,57 @@
2623
2881
  findRelatedNode(where, includeHidden = false) {
2624
2882
  return this.tree.findRelatedNode(this, where, includeHidden);
2625
2883
  }
2884
+ /**
2885
+ * Iterator version of {@link WunderbaumNode.format}.
2886
+ */
2887
+ *format_iter(name_cb, connectors) {
2888
+ connectors !== null && connectors !== void 0 ? connectors : (connectors = [" ", " | ", " ╰─ ", " ├─ "]);
2889
+ name_cb !== null && name_cb !== void 0 ? name_cb : (name_cb = (node) => "" + node);
2890
+ function _is_last(node) {
2891
+ const ca = node.parent.children;
2892
+ return node === ca[ca.length - 1];
2893
+ }
2894
+ const _format_line = (node) => {
2895
+ // https://www.measurethat.net/Benchmarks/Show/12196/0/arr-unshift-vs-push-reverse-small-array
2896
+ const parts = [name_cb(node)];
2897
+ parts.unshift(connectors[_is_last(node) ? 2 : 3]);
2898
+ let p = node.parent;
2899
+ while (p && p !== this) {
2900
+ // `this` is the top node
2901
+ parts.unshift(connectors[_is_last(p) ? 0 : 1]);
2902
+ p = p.parent;
2903
+ }
2904
+ return parts.join("");
2905
+ };
2906
+ yield name_cb(this);
2907
+ for (let node of this) {
2908
+ yield _format_line(node);
2909
+ }
2910
+ }
2911
+ /**
2912
+ * Return multiline string representation of a node/subnode hierarchy.
2913
+ * Mostly useful for debugging.
2914
+ *
2915
+ * Example:
2916
+ * ```js
2917
+ * console.info(tree.getActiveNode().format((n)=>n.title));
2918
+ * ```
2919
+ * logs
2920
+ * ```
2921
+ * Books
2922
+ * ├─ Art of War
2923
+ * ╰─ Don Quixote
2924
+ * ...
2925
+ * ```
2926
+ * @see {@link WunderbaumNode.format_iter}
2927
+ */
2928
+ format(name_cb, connectors) {
2929
+ const a = [];
2930
+ for (let line of this.format_iter(name_cb, connectors)) {
2931
+ a.push(line);
2932
+ }
2933
+ return a.join("\n");
2934
+ }
2626
2935
  /** Return the `<span class='wb-col'>` element with a given index or id.
2627
2936
  * @returns {WunderbaumNode | null}
2628
2937
  */
@@ -2770,7 +3079,8 @@
2770
3079
  * an expand operation is currently possible.
2771
3080
  */
2772
3081
  isExpandable(andCollapsed = false) {
2773
- return !!this.children && (!this.expanded || !andCollapsed);
3082
+ // return !!this.children && (!this.expanded || !andCollapsed);
3083
+ return !!(this.children || this.lazy) && (!this.expanded || !andCollapsed);
2774
3084
  }
2775
3085
  /** Return true if this node is currently in edit-title mode. */
2776
3086
  isEditing() {
@@ -2872,14 +3182,22 @@
2872
3182
  return true;
2873
3183
  }
2874
3184
  _loadSourceObject(source, level) {
3185
+ var _a;
2875
3186
  const tree = this.tree;
2876
3187
  level !== null && level !== void 0 ? level : (level = this.getLevel());
2877
3188
  // Let caller modify the parsed JSON response:
2878
- this._callEvent("receive", { response: source });
3189
+ const res = this._callEvent("receive", { response: source });
3190
+ if (res != null) {
3191
+ source = res;
3192
+ }
2879
3193
  if (isArray(source)) {
2880
3194
  source = { children: source };
2881
3195
  }
2882
3196
  assert(isPlainObject(source));
3197
+ const format = (_a = source.format) !== null && _a !== void 0 ? _a : "nested";
3198
+ assert(format === "nested" || format === "flat");
3199
+ // Pre-rocess for 'nested' or 'flat' format
3200
+ inflateSourceData(source);
2883
3201
  assert(source.children, "If `source` is an object, it must have a `children` property");
2884
3202
  if (source.types) {
2885
3203
  tree.logInfo("Redefine types", source.columns);
@@ -3039,17 +3357,16 @@
3039
3357
  }
3040
3358
  /** Expand all parents and optionally scroll into visible area as neccessary.
3041
3359
  * Promise is resolved, when lazy loading and animations are done.
3042
- * @param {object} [opts] passed to `setExpanded()`.
3360
+ * @param {object} [options] passed to `setExpanded()`.
3043
3361
  * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
3044
3362
  */
3045
- async makeVisible(opts) {
3046
- let i, dfd = new Deferred(), deferreds = [], parents = this.getParentList(false, false), len = parents.length,
3047
- // effects = !(opts && opts.noAnimation === true),
3048
- scroll = !(opts && opts.scrollIntoView === false);
3363
+ async makeVisible(options) {
3364
+ let i, dfd = new Deferred(), deferreds = [], parents = this.getParentList(false, false), len = parents.length, noAnimation = getOption(options, "noAnimation", false), scroll = getOption(options, "scrollIntoView", true);
3365
+ // scroll = !(options && options.scrollIntoView === false);
3049
3366
  // Expand bottom-up, so only the top node is animated
3050
3367
  for (i = len - 1; i >= 0; i--) {
3051
3368
  // self.debug("pushexpand" + parents[i]);
3052
- const seOpts = { noAnimation: opts === null || opts === void 0 ? void 0 : opts.noAnimation };
3369
+ const seOpts = { noAnimation: noAnimation };
3053
3370
  deferreds.push(parents[i].setExpanded(true, seOpts));
3054
3371
  }
3055
3372
  Promise.all(deferreds).then(() => {
@@ -3269,13 +3586,15 @@
3269
3586
  renderColInfosById: renderColInfosById,
3270
3587
  };
3271
3588
  }
3272
- _createIcon(parentElem, replaceChild) {
3589
+ _createIcon(parentElem, replaceChild, showLoading) {
3273
3590
  let iconSpan;
3274
3591
  let icon = this.getOption("icon");
3275
3592
  if (this._errorInfo) {
3276
3593
  icon = iconMap.error;
3277
3594
  }
3278
- else if (this._isLoading) {
3595
+ else if (this._isLoading && showLoading) {
3596
+ // Status nodes, or nodes without expander (< minExpandLevel) should
3597
+ // display the 'loading' status with the i.wb-icon span
3279
3598
  icon = iconMap.loading;
3280
3599
  }
3281
3600
  if (icon === false) {
@@ -3370,7 +3689,9 @@
3370
3689
  nodeElem.appendChild(expanderSpan);
3371
3690
  ofsTitlePx += ICON_WIDTH;
3372
3691
  }
3373
- iconSpan = this._createIcon(nodeElem);
3692
+ // Render the icon (show a 'loading' icon if we do not have an expander that
3693
+ // we would prefer).
3694
+ iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
3374
3695
  if (iconSpan) {
3375
3696
  ofsTitlePx += ICON_WIDTH;
3376
3697
  }
@@ -3528,7 +3849,10 @@
3528
3849
  rowDiv.classList.add(...typeInfo.classes);
3529
3850
  }
3530
3851
  if (expanderSpan) {
3531
- if (this.isExpandable(false)) {
3852
+ if (this._isLoading) {
3853
+ expanderSpan.className = "wb-expander " + iconMap.loading;
3854
+ }
3855
+ else if (this.isExpandable(false)) {
3532
3856
  if (this.expanded) {
3533
3857
  expanderSpan.className = "wb-expander " + iconMap.expanderExpanded;
3534
3858
  }
@@ -3536,9 +3860,6 @@
3536
3860
  expanderSpan.className = "wb-expander " + iconMap.expanderCollapsed;
3537
3861
  }
3538
3862
  }
3539
- else if (this._isLoading) {
3540
- expanderSpan.className = "wb-expander " + iconMap.loading;
3541
- }
3542
3863
  else if (this.lazy && this.children == null) {
3543
3864
  expanderSpan.className = "wb-expander " + iconMap.expanderLazy;
3544
3865
  }
@@ -3563,7 +3884,7 @@
3563
3884
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
3564
3885
  const iconSpan = nodeElem.querySelector("i.wb-icon");
3565
3886
  if (iconSpan) {
3566
- this._createIcon(nodeElem, iconSpan);
3887
+ this._createIcon(nodeElem, iconSpan, !expanderSpan);
3567
3888
  }
3568
3889
  }
3569
3890
  }
@@ -3572,11 +3893,14 @@
3572
3893
  *
3573
3894
  * `options.change` defaults to ChangeType.data, which updates the title,
3574
3895
  * icon, and status. It also triggers the `render` event, that lets the user
3575
- * create or update the content of embeded cell elements.<br>
3896
+ * create or update the content of embeded cell elements.
3576
3897
  *
3577
3898
  * If only the status or other class-only modifications have changed,
3578
3899
  * `options.change` should be set to ChangeType.status instead for best
3579
3900
  * efficiency.
3901
+ *
3902
+ * Calling `setModified` instead may be a better alternative.
3903
+ * @see {@link WunderbaumNode.setModified}
3580
3904
  */
3581
3905
  render(options) {
3582
3906
  // this.log("render", options);
@@ -3776,25 +4100,29 @@
3776
4100
  * Expand or collapse this node.
3777
4101
  */
3778
4102
  async setExpanded(flag = true, options) {
4103
+ const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
3779
4104
  if (!flag &&
3780
4105
  this.isExpanded() &&
3781
4106
  this.getLevel() <= this.tree.getOption("minExpandLevel") &&
3782
- !getOption(options, "force")) {
4107
+ !force) {
3783
4108
  this.logDebug("Ignored collapse request below expandLevel.");
3784
4109
  return;
3785
4110
  }
3786
4111
  if (!flag === !this.expanded) {
3787
4112
  return; // Nothing to do
3788
4113
  }
4114
+ // this.log("setExpanded()");
3789
4115
  if (flag && this.lazy && this.children == null) {
3790
4116
  await this.loadLazy();
3791
4117
  }
3792
4118
  this.expanded = flag;
3793
- const updateOpts = { immediate: !!getOption(options, "immediate") };
4119
+ const updateOpts = { immediate: immediate };
4120
+ // const updateOpts = { immediate: !!util.getOption(options, "immediate") };
3794
4121
  this.tree.setModified(ChangeType.structure, updateOpts);
3795
- if (getOption(options, "scrollIntoView") !== false) {
4122
+ if (flag && scrollIntoView !== false) {
3796
4123
  const lastChild = this.getLastChild();
3797
4124
  if (lastChild) {
4125
+ this.tree.updatePendingModifications();
3798
4126
  lastChild.scrollIntoView({ topNode: this });
3799
4127
  }
3800
4128
  }
@@ -3872,8 +4200,13 @@
3872
4200
  this._errorInfo = null;
3873
4201
  break;
3874
4202
  case "loading":
3875
- // If this is the invisible root, add a visible top-level node
3876
- if (!this.parent) {
4203
+ this._isLoading = true;
4204
+ this._errorInfo = null;
4205
+ if (this.parent) {
4206
+ this.setModified(ChangeType.status);
4207
+ }
4208
+ else {
4209
+ // If this is the invisible root, add a visible top-level node
3877
4210
  _setStatusNode({
3878
4211
  statusNodeType: status,
3879
4212
  title: tree.options.strings.loading +
@@ -3883,8 +4216,6 @@
3883
4216
  tooltip: details,
3884
4217
  });
3885
4218
  }
3886
- this._isLoading = true;
3887
- this._errorInfo = null;
3888
4219
  // this.render();
3889
4220
  break;
3890
4221
  case "error":
@@ -3947,7 +4278,7 @@
3947
4278
  this.parent.triggerModifyChild(operation, this, extra);
3948
4279
  }
3949
4280
  /**
3950
- * Call fn(node) for all child nodes in hierarchical order (depth-first).
4281
+ * Call `callback(node)` for all child nodes in hierarchical order (depth-first, pre-order).
3951
4282
  *
3952
4283
  * Stop iteration, if fn() returns false. Skip current branch, if fn()
3953
4284
  * returns "skip".<br>
@@ -3956,6 +4287,7 @@
3956
4287
  * @param {function} callback the callback function.
3957
4288
  * Return false to stop iteration, return "skip" to skip this node and
3958
4289
  * its children only.
4290
+ * @see {@link WunderbaumNode.*[Symbol.iterator]}, {@link Wunderbaum.visit}.
3959
4291
  */
3960
4292
  visit(callback, includeSelf = false) {
3961
4293
  let i, l, res = true, children = this.children;
@@ -4026,7 +4358,7 @@
4026
4358
  /*!
4027
4359
  * Wunderbaum - ext-edit
4028
4360
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4029
- * v0.0.7, Sun, 11 Sep 2022 16:02:08 GMT (https://github.com/mar10/wunderbaum)
4361
+ * v0.0.9, Mon, 31 Oct 2022 17:06:10 GMT (https://github.com/mar10/wunderbaum)
4030
4362
  */
4031
4363
  // const START_MARKER = "\uFFF7";
4032
4364
  class EditExtension extends WunderbaumExtension {
@@ -4319,8 +4651,8 @@
4319
4651
  * https://github.com/mar10/wunderbaum
4320
4652
  *
4321
4653
  * Released under the MIT license.
4322
- * @version v0.0.7
4323
- * @date Sun, 11 Sep 2022 16:02:08 GMT
4654
+ * @version v0.0.9
4655
+ * @date Mon, 31 Oct 2022 17:06:10 GMT
4324
4656
  */
4325
4657
  class WbSystemRoot extends WunderbaumNode {
4326
4658
  constructor(tree) {
@@ -4707,6 +5039,20 @@
4707
5039
  }
4708
5040
  return null;
4709
5041
  }
5042
+ /**
5043
+ * Iterate all descendant nodes depth-first, pre-order using `for ... of ...` syntax.
5044
+ * More concise, but slightly slower than {@link Wunderbaum.visit}.
5045
+ *
5046
+ * Example:
5047
+ * ```js
5048
+ * for(const node of tree) {
5049
+ * ...
5050
+ * }
5051
+ * ```
5052
+ */
5053
+ *[Symbol.iterator]() {
5054
+ yield* this.root;
5055
+ }
4710
5056
  /** @internal */
4711
5057
  _registerExtension(extension) {
4712
5058
  this.extensionList.push(extension);
@@ -4721,8 +5067,8 @@
4721
5067
  }
4722
5068
  /** Add node to tree's bookkeeping data structures. */
4723
5069
  _registerNode(node) {
4724
- let key = node.key;
4725
- assert(key != null && !this.keyMap.has(key));
5070
+ const key = node.key;
5071
+ assert(key != null && !this.keyMap.has(key), `Missing or duplicate key: '${key}'.`);
4726
5072
  this.keyMap.set(key, node);
4727
5073
  let rk = node.refKey;
4728
5074
  if (rk) {
@@ -4891,22 +5237,22 @@
4891
5237
  * - 'down', 'first', 'last', 'left', 'parent', 'right', 'up': navigate
4892
5238
  *
4893
5239
  */
4894
- applyCommand(cmd, nodeOrOpts, opts) {
5240
+ applyCommand(cmd, nodeOrOpts, options) {
4895
5241
  let // clipboard,
4896
5242
  node, refNode;
4897
- // opts = $.extend(
5243
+ // options = $.extend(
4898
5244
  // { setActive: true, clipboard: CLIPBOARD },
4899
- // opts_
5245
+ // options_
4900
5246
  // );
4901
5247
  if (nodeOrOpts instanceof WunderbaumNode) {
4902
5248
  node = nodeOrOpts;
4903
5249
  }
4904
5250
  else {
4905
5251
  node = this.getActiveNode();
4906
- assert(opts === undefined);
4907
- opts = nodeOrOpts;
5252
+ assert(options === undefined);
5253
+ options = nodeOrOpts;
4908
5254
  }
4909
- // clipboard = opts.clipboard;
5255
+ // clipboard = options.clipboard;
4910
5256
  switch (cmd) {
4911
5257
  // Sorting and indentation:
4912
5258
  case "moveUp":
@@ -5112,16 +5458,8 @@
5112
5458
  }
5113
5459
  }
5114
5460
  /** Recursively expand all expandable nodes (triggers lazy load id needed). */
5115
- async expandAll(flag = true) {
5116
- const tag = this.logTime("expandAll(" + flag + ")");
5117
- try {
5118
- this.enableUpdate(false);
5119
- await this.root.expandAll(flag);
5120
- }
5121
- finally {
5122
- this.enableUpdate(true);
5123
- this.logTimeEnd(tag);
5124
- }
5461
+ async expandAll(flag = true, options) {
5462
+ await this.root.expandAll(flag, options);
5125
5463
  }
5126
5464
  /** Recursively select all nodes. */
5127
5465
  selectAll(flag = true) {
@@ -5155,10 +5493,7 @@
5155
5493
  // util.assert(this.keyMap.size === i);
5156
5494
  }
5157
5495
  /**
5158
- * Find all nodes that matches condition.
5159
- *
5160
- * @param match title string to search for, or a
5161
- * callback function that returns `true` if a node is matched.
5496
+ * Find all nodes that match condition.
5162
5497
  *
5163
5498
  * @see {@link WunderbaumNode.findAll}
5164
5499
  */
@@ -5168,10 +5503,7 @@
5168
5503
  /**
5169
5504
  * Find first node that matches condition.
5170
5505
  *
5171
- * @param match title string to search for, or a
5172
- * callback function that returns `true` if a node is matched.
5173
5506
  * @see {@link WunderbaumNode.findFirst}
5174
- *
5175
5507
  */
5176
5508
  findFirst(match) {
5177
5509
  return this.root.findFirst(match);
@@ -5185,7 +5517,7 @@
5185
5517
  *
5186
5518
  */
5187
5519
  findKey(key) {
5188
- return this.keyMap.get(key);
5520
+ return this.keyMap.get(key) || null;
5189
5521
  }
5190
5522
  /**
5191
5523
  * Find the next visible node that starts with `match`, starting at `startNode`
@@ -5314,6 +5646,35 @@
5314
5646
  }
5315
5647
  return res;
5316
5648
  }
5649
+ /**
5650
+ * Iterator version of {@link Wunderbaum.format}.
5651
+ */
5652
+ *format_iter(name_cb, connectors) {
5653
+ return this.root.format_iter(name_cb, connectors);
5654
+ }
5655
+ /**
5656
+ * Return multiline string representation of the node hierarchy.
5657
+ * Mostly useful for debugging.
5658
+ *
5659
+ * Example:
5660
+ * ```js
5661
+ * console.info(tree.format((n)=>n.title));
5662
+ * ```
5663
+ * logs
5664
+ * ```
5665
+ * Playground
5666
+ * ├─ Books
5667
+ * | ├─ Art of War
5668
+ * | ╰─ Don Quixote
5669
+ * ├─ Music
5670
+ * ...
5671
+ * ```
5672
+ *
5673
+ * @see {@link Wunderbaum.format_iter} and {@link WunderbaumNode.format}.
5674
+ */
5675
+ format(name_cb, connectors) {
5676
+ return this.root.format(name_cb, connectors);
5677
+ }
5317
5678
  /**
5318
5679
  * Return the active cell (`span.wb-col`) of the currently active node or null.
5319
5680
  */
@@ -5481,13 +5842,13 @@
5481
5842
  scrollTo(nodeOrOpts) {
5482
5843
  const PADDING = 2; // leave some pixels between viewport bounds
5483
5844
  let node;
5484
- let opts;
5845
+ let options;
5485
5846
  if (nodeOrOpts instanceof WunderbaumNode) {
5486
5847
  node = nodeOrOpts;
5487
5848
  }
5488
5849
  else {
5489
- opts = nodeOrOpts;
5490
- node = opts.node;
5850
+ options = nodeOrOpts;
5851
+ node = options.node;
5491
5852
  }
5492
5853
  assert(node && node._rowIdx != null);
5493
5854
  const scrollParent = this.element;
@@ -5498,7 +5859,7 @@
5498
5859
  const vpTop = headerHeight;
5499
5860
  const vpRowTop = rowTop - scrollTop;
5500
5861
  const vpRowBottom = vpRowTop + ROW_HEIGHT;
5501
- const topNode = opts === null || opts === void 0 ? void 0 : opts.topNode;
5862
+ const topNode = options === null || options === void 0 ? void 0 : options.topNode;
5502
5863
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts );
5503
5864
  let newScrollTop = null;
5504
5865
  if (vpRowTop >= vpTop) {
@@ -5742,11 +6103,13 @@
5742
6103
  }
5743
6104
  }
5744
6105
  /** Update column headers and width. */
5745
- updateColumns(opts) {
5746
- opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
6106
+ updateColumns(options) {
6107
+ options = Object.assign({ calculateCols: true, updateRows: true }, options);
5747
6108
  const defaultMinWidth = 4;
5748
6109
  const vpWidth = this.element.clientWidth;
5749
6110
  const isGrid = this.isGrid();
6111
+ // Shorten last column width to avoid h-scrollbar
6112
+ const FIX_ADJUST_LAST_COL = 2;
5750
6113
  let totalWidth = 0;
5751
6114
  let totalWeight = 0;
5752
6115
  let fixedWidth = 0;
@@ -5755,7 +6118,7 @@
5755
6118
  if (!isGrid && this.isCellNav()) {
5756
6119
  this.setCellNav(false);
5757
6120
  }
5758
- if (opts.calculateCols) {
6121
+ if (options.calculateCols) {
5759
6122
  // Gather width definitions
5760
6123
  this._columnsById = {};
5761
6124
  for (let col of this.columns) {
@@ -5807,7 +6170,8 @@
5807
6170
  col._ofsPx = ofsPx;
5808
6171
  ofsPx += col._widthPx;
5809
6172
  }
5810
- totalWidth = ofsPx;
6173
+ this.columns[this.columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6174
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
5811
6175
  }
5812
6176
  // if (this.options.fixedCol) {
5813
6177
  // 'position: fixed' requires that the content has the correct size
@@ -5821,7 +6185,7 @@
5821
6185
  // util.error("BREAK");
5822
6186
  if (modified) {
5823
6187
  this._renderHeaderMarkup();
5824
- if (opts.updateRows) {
6188
+ if (options.updateRows) {
5825
6189
  this._updateRows();
5826
6190
  }
5827
6191
  }
@@ -5884,6 +6248,8 @@
5884
6248
  */
5885
6249
  _updateViewportImmediately() {
5886
6250
  var _a;
6251
+ // Shorten container height to avoid v-scrollbar
6252
+ const FIX_ADJUST_HEIGHT = 1;
5887
6253
  if (this._disableUpdateCount) {
5888
6254
  this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
5889
6255
  return;
@@ -5897,7 +6263,7 @@
5897
6263
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
5898
6264
  // const headerHeight = this.options.headerHeightPx;
5899
6265
  const headerHeight = this.headerElement.clientHeight; // May be 0
5900
- const wantHeight = this.element.clientHeight - headerHeight;
6266
+ const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
5901
6267
  if (Math.abs(height - wantHeight) > 1.0) {
5902
6268
  // this.log("resize", height, wantHeight);
5903
6269
  this.scrollContainerElement.style.height = wantHeight + "px";
@@ -5954,11 +6320,11 @@
5954
6320
  * (including upper and lower prefetch)
5955
6321
  * -
5956
6322
  */
5957
- _updateRows(opts) {
6323
+ _updateRows(options) {
5958
6324
  // const label = this.logTime("_updateRows");
5959
6325
  // this.log("_updateRows", opts)
5960
- opts = Object.assign({ newNodesOnly: false }, opts);
5961
- const newNodesOnly = !!opts.newNodesOnly;
6326
+ options = Object.assign({ newNodesOnly: false }, options);
6327
+ const newNodesOnly = !!options.newNodesOnly;
5962
6328
  const row_height = ROW_HEIGHT;
5963
6329
  const vp_height = this.element.clientHeight;
5964
6330
  const prefetch = RENDER_MAX_PREFETCH;
@@ -6035,7 +6401,8 @@
6035
6401
  return modified;
6036
6402
  }
6037
6403
  /**
6038
- * Call callback(node) for all nodes in hierarchical order (depth-first).
6404
+ * Call `callback(node)` for all nodes in hierarchical order (depth-first, pre-order).
6405
+ * @see {@link Wunderbaum.*[Symbol.iterator]}, {@link WunderbaumNode.visit}.
6039
6406
  *
6040
6407
  * @param {function} callback the callback function.
6041
6408
  * Return false to stop iteration, return "skip" to skip this node and
@@ -6061,16 +6428,16 @@
6061
6428
  * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6062
6429
  * @returns {boolean} false if iteration was canceled
6063
6430
  */
6064
- visitRows(callback, opts) {
6431
+ visitRows(callback, options) {
6065
6432
  if (!this.root.hasChildren()) {
6066
6433
  return false;
6067
6434
  }
6068
- if (opts && opts.reverse) {
6069
- delete opts.reverse;
6070
- return this._visitRowsUp(callback, opts);
6435
+ if (options && options.reverse) {
6436
+ delete options.reverse;
6437
+ return this._visitRowsUp(callback, options);
6071
6438
  }
6072
- opts = opts || {};
6073
- let i, nextIdx, parent, res, siblings, stopNode, siblingOfs = 0, skipFirstNode = opts.includeSelf === false, includeHidden = !!opts.includeHidden, checkFilter = !includeHidden && this.filterMode === "hide", node = opts.start || this.root.children[0];
6439
+ options = options || {};
6440
+ let i, nextIdx, parent, res, siblings, stopNode, siblingOfs = 0, skipFirstNode = options.includeSelf === false, includeHidden = !!options.includeHidden, checkFilter = !includeHidden && this.filterMode === "hide", node = options.start || this.root.children[0];
6074
6441
  parent = node.parent;
6075
6442
  while (parent) {
6076
6443
  // visit siblings
@@ -6119,11 +6486,11 @@
6119
6486
  node = parent;
6120
6487
  parent = parent.parent;
6121
6488
  siblingOfs = 1; //
6122
- if (!parent && opts.wrap) {
6489
+ if (!parent && options.wrap) {
6123
6490
  this.logDebug("visitRows(): wrap around");
6124
- assert(opts.start, "`wrap` option requires `start`");
6125
- stopNode = opts.start;
6126
- opts.wrap = false;
6491
+ assert(options.start, "`wrap` option requires `start`");
6492
+ stopNode = options.start;
6493
+ options.wrap = false;
6127
6494
  parent = this.root;
6128
6495
  siblingOfs = 0;
6129
6496
  }
@@ -6258,7 +6625,7 @@
6258
6625
  }
6259
6626
  Wunderbaum.sequence = 0;
6260
6627
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6261
- Wunderbaum.version = "v0.0.7"; // Set to semver by 'grunt release'
6628
+ Wunderbaum.version = "v0.0.9"; // Set to semver by 'grunt release'
6262
6629
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6263
6630
  Wunderbaum.util = util;
6264
6631