wunderbaum 0.0.7 → 0.0.8

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.8, Fri, 23 Sep 2022 20:47:29 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.8, Fri, 23 Sep 2022 20:47:29 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.8, Fri, 23 Sep 2022 20:47:29 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.8, Fri, 23 Sep 2022 20:47:29 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.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1359
1365
  */
1360
1366
  const QUICKSEARCH_DELAY = 500;
1361
1367
  class KeynavExtension extends WunderbaumExtension {
@@ -1637,7 +1643,7 @@
1637
1643
  /*!
1638
1644
  * Wunderbaum - ext-logger
1639
1645
  * 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)
1646
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1641
1647
  */
1642
1648
  class LoggerExtension extends WunderbaumExtension {
1643
1649
  constructor(tree) {
@@ -1677,7 +1683,7 @@
1677
1683
  /*!
1678
1684
  * Wunderbaum - common
1679
1685
  * 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)
1686
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1681
1687
  */
1682
1688
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1683
1689
  /**
@@ -1732,14 +1738,14 @@
1732
1738
  };
1733
1739
  /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
1734
1740
  const RESERVED_TREE_SOURCE_KEYS = new Set([
1741
+ "_format",
1742
+ "_keyMap",
1743
+ "_positional",
1744
+ "_typeList",
1745
+ "_version",
1735
1746
  "children",
1736
1747
  "columns",
1737
- "format",
1738
- "keyMap",
1739
- "positional",
1740
- "typeList",
1741
1748
  "types",
1742
- "version", // reserved for future use
1743
1749
  ]);
1744
1750
  // /** Key codes that trigger grid navigation, even when inside an input element. */
1745
1751
  // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
@@ -1773,11 +1779,22 @@
1773
1779
  "-": "collapse",
1774
1780
  Subtract: "collapse",
1775
1781
  };
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());
1782
+ /** Return a callback that returns true if the node title matches the string
1783
+ * or regular expression.
1784
+ * @see {@link WunderbaumNode.findAll}
1785
+ */
1786
+ function makeNodeTitleMatcher(match) {
1787
+ if (match instanceof RegExp) {
1788
+ return function (node) {
1789
+ return match.test(node.title);
1790
+ };
1791
+ }
1792
+ assert(typeof match === "string");
1793
+ // s = escapeRegex(s.toLowerCase());
1779
1794
  return function (node) {
1780
- return node.title.toLowerCase().indexOf(s) >= 0;
1795
+ return node.title === match;
1796
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
1797
+ // return node.title.toLowerCase().indexOf(match) >= 0;
1781
1798
  };
1782
1799
  }
1783
1800
  /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
@@ -1788,11 +1805,125 @@
1788
1805
  return reMatch.test(node.title);
1789
1806
  };
1790
1807
  }
1808
+ function unflattenSource(source) {
1809
+ var _a, _b, _c;
1810
+ const { _format, _keyMap, _positional, children } = source;
1811
+ if (_format !== "flat") {
1812
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
1813
+ }
1814
+ if (_positional && _positional.includes("children")) {
1815
+ throw new Error(`source._positional must not include "children": ${_positional}`);
1816
+ }
1817
+ // Inverse keyMap:
1818
+ let longToShort = {};
1819
+ if (_keyMap) {
1820
+ for (const [key, value] of Object.entries(_keyMap)) {
1821
+ longToShort[value] = key;
1822
+ }
1823
+ }
1824
+ const positionalShort = _positional.map((e) => longToShort[e]);
1825
+ const newChildren = [];
1826
+ const keyToNodeMap = {};
1827
+ const indexToNodeMap = {};
1828
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
1829
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
1830
+ for (const [index, node] of children.entries()) {
1831
+ // Node entry format:
1832
+ // [PARENT_ID, [POSITIONAL_ARGS]]
1833
+ // or
1834
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
1835
+ const [parentId, args, kwargs = {}] = node;
1836
+ // Free up some memory as we go
1837
+ node[1] = null;
1838
+ if (node[2] != null) {
1839
+ node[2] = null;
1840
+ }
1841
+ // console.log("flatten", parentId, args, kwargs)
1842
+ // We keep `kwargs` as our new node definition. Then we add all positional
1843
+ // values to this object:
1844
+ args.forEach((val, positionalIdx) => {
1845
+ kwargs[positionalShort[positionalIdx]] = val;
1846
+ });
1847
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
1848
+ // index of the source.children list. If PARENT_ID is a string, we search
1849
+ // a parent with node.key of this value.
1850
+ indexToNodeMap[index] = kwargs;
1851
+ const key = kwargs[keyAttrName];
1852
+ if (key != null) {
1853
+ keyToNodeMap[key] = kwargs;
1854
+ }
1855
+ let parentNode = null;
1856
+ if (parentId === null) ;
1857
+ else if (typeof parentId === "number") {
1858
+ parentNode = indexToNodeMap[parentId];
1859
+ if (parentNode === undefined) {
1860
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
1861
+ }
1862
+ }
1863
+ else {
1864
+ parentNode = keyToNodeMap[parentId];
1865
+ if (parentNode === undefined) {
1866
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
1867
+ }
1868
+ }
1869
+ if (parentNode) {
1870
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
1871
+ parentNode[childrenAttrName].push(kwargs);
1872
+ }
1873
+ else {
1874
+ newChildren.push(kwargs);
1875
+ }
1876
+ }
1877
+ delete source.children;
1878
+ source.children = newChildren;
1879
+ }
1880
+ function inflateSourceData(source) {
1881
+ const { _format, _keyMap, _typeList } = source;
1882
+ if (_format === "flat") {
1883
+ unflattenSource(source);
1884
+ }
1885
+ delete source._format;
1886
+ delete source._version;
1887
+ delete source._keyMap;
1888
+ delete source._typeList;
1889
+ delete source._positional;
1890
+ function _iter(childList) {
1891
+ for (let node of childList) {
1892
+ // Expand short alias names
1893
+ if (_keyMap) {
1894
+ // Iterate over a list of names, because we modify inside the loop:
1895
+ Object.getOwnPropertyNames(node).forEach((propName) => {
1896
+ var _a;
1897
+ const long = (_a = _keyMap[propName]) !== null && _a !== void 0 ? _a : propName;
1898
+ if (long !== propName) {
1899
+ node[long] = node[propName];
1900
+ delete node[propName];
1901
+ }
1902
+ });
1903
+ }
1904
+ // `node` now has long attribute names
1905
+ // Resolve node type indexes
1906
+ const type = node.type;
1907
+ if (_typeList && type != null && typeof type === "number") {
1908
+ const newType = _typeList[type];
1909
+ if (newType == null) {
1910
+ throw new Error(`Expected typeList[${type}] entry in [${_typeList}]`);
1911
+ }
1912
+ node.type = newType;
1913
+ }
1914
+ // Recursion
1915
+ if (node.children) {
1916
+ _iter(node.children);
1917
+ }
1918
+ }
1919
+ }
1920
+ _iter(source.children);
1921
+ }
1791
1922
 
1792
1923
  /*!
1793
1924
  * Wunderbaum - ext-dnd
1794
1925
  * 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)
1926
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1796
1927
  */
1797
1928
  const nodeMimeType = "application/x-wunderbaum-node";
1798
1929
  class DndExtension extends WunderbaumExtension {
@@ -2060,7 +2191,7 @@
2060
2191
  /*!
2061
2192
  * Wunderbaum - drag_observer
2062
2193
  * 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)
2194
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2064
2195
  */
2065
2196
  /**
2066
2197
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2194,7 +2325,7 @@
2194
2325
  /*!
2195
2326
  * Wunderbaum - ext-grid
2196
2327
  * 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)
2328
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2198
2329
  */
2199
2330
  class GridExtension extends WunderbaumExtension {
2200
2331
  constructor(tree) {
@@ -2231,7 +2362,7 @@
2231
2362
  /*!
2232
2363
  * Wunderbaum - deferred
2233
2364
  * 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)
2365
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2235
2366
  */
2236
2367
  /**
2237
2368
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2284,7 +2415,7 @@
2284
2415
  /*!
2285
2416
  * Wunderbaum - wunderbaum_node
2286
2417
  * 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)
2418
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2288
2419
  */
2289
2420
  /** Top-level properties that can be passed with `data`. */
2290
2421
  const NODE_PROPS = new Set([
@@ -2425,48 +2556,46 @@
2425
2556
  /**
2426
2557
  * Append (or insert) a list of child nodes.
2427
2558
  *
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.
2559
+ * Tip: pass `{ before: 0 }` to prepend new nodes as first children.
2560
+ *
2432
2561
  * @returns first child added
2433
2562
  */
2434
2563
  addChildren(nodeData, options) {
2435
2564
  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 = [];
2565
+ let { before = null, applyMinExpanLevel = true, _level } = options !== null && options !== void 0 ? options : {};
2566
+ // let { before, loadLazy=true, _level } = options ?? {};
2567
+ // const isTopCall = _level == null;
2568
+ _level !== null && _level !== void 0 ? _level : (_level = this.getLevel());
2569
+ const nodeList = [];
2442
2570
  try {
2443
2571
  tree.enableUpdate(false);
2444
2572
  if (isPlainObject(nodeData)) {
2445
2573
  nodeData = [nodeData];
2446
2574
  }
2447
- const forceExpand = level < tree.options.minExpandLevel;
2575
+ const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
2448
2576
  for (let child of nodeData) {
2449
- let subChildren = child.children;
2577
+ const subChildren = child.children;
2450
2578
  delete child.children;
2451
- let n = new WunderbaumNode(tree, this, child);
2452
- if (forceExpand && !n.lazy)
2579
+ const n = new WunderbaumNode(tree, this, child);
2580
+ if (forceExpand && !n.isUnloaded()) {
2453
2581
  n.expanded = true;
2582
+ }
2454
2583
  nodeList.push(n);
2455
2584
  if (subChildren) {
2456
- n.addChildren(subChildren, { redraw: false, level: level + 1 });
2585
+ n.addChildren(subChildren, { _level: _level + 1 });
2457
2586
  }
2458
2587
  }
2459
2588
  if (!this.children) {
2460
2589
  this.children = nodeList;
2461
2590
  }
2462
- else if (insertBefore == null || this.children.length === 0) {
2591
+ else if (before == null || this.children.length === 0) {
2463
2592
  this.children = this.children.concat(nodeList);
2464
2593
  }
2465
2594
  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");
2595
+ // Returns null if before is not a direct child:
2596
+ before = this.findDirectChild(before);
2597
+ let pos = this.children.indexOf(before);
2598
+ assert(pos >= 0, `options.before must be a direct child of ${this}`);
2470
2599
  // insert nodeList after children[pos]
2471
2600
  this.children.splice(pos, 0, ...nodeList);
2472
2601
  }
@@ -2476,11 +2605,14 @@
2476
2605
  // }
2477
2606
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
2478
2607
  tree.setModified(ChangeType.structure);
2479
- return nodeList[0];
2480
2608
  }
2481
2609
  finally {
2482
2610
  tree.enableUpdate(true);
2483
2611
  }
2612
+ // if(isTopCall && loadLazy){
2613
+ // this.logWarn("addChildren(): loadLazy is not yet implemented.")
2614
+ // }
2615
+ return nodeList[0];
2484
2616
  }
2485
2617
  /**
2486
2618
  * Append or prepend a node, or append a child node.
@@ -2554,21 +2686,98 @@
2554
2686
  }
2555
2687
  }
2556
2688
  }
2557
- /** Call `setExpanded()` on al child nodes*/
2558
- async expandAll(flag = true) {
2559
- this.visit((node) => {
2560
- node.setExpanded(flag);
2561
- });
2689
+ /** Call `setExpanded()` on all descendant nodes. */
2690
+ async expandAll(flag = true, options) {
2691
+ const tree = this.tree;
2692
+ const minExpandLevel = this.tree.options.minExpandLevel;
2693
+ let { depth = 99, loadLazy, force } = options !== null && options !== void 0 ? options : {};
2694
+ const expand_opts = {
2695
+ scrollIntoView: false,
2696
+ force: force,
2697
+ loadLazy: loadLazy,
2698
+ };
2699
+ // this.logInfo(`expandAll(${flag})`);
2700
+ // Expand all direct children in parallel:
2701
+ async function _iter(n, level) {
2702
+ var _a;
2703
+ // n.logInfo(` _iter(${level})`);
2704
+ if (level === 0) {
2705
+ return;
2706
+ }
2707
+ // if (!flag && minExpandLevel && !force && n.getLevel() <= minExpandLevel) {
2708
+ // return; // Do not collapse until minExpandLevel
2709
+ // }
2710
+ const level_1 = level == null ? null : level - 1;
2711
+ const promises = [];
2712
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach((cn) => {
2713
+ if (flag) {
2714
+ if (!cn.expanded && (cn.children || (loadLazy && cn.lazy))) {
2715
+ // Node is collapsed and may be expanded (i.e. has children or is lazy)
2716
+ // Expanding may be async, so we store the promise.
2717
+ // Also the recursion is delayed until expansion finished.
2718
+ const p = cn.setExpanded(true, expand_opts);
2719
+ promises.push(p);
2720
+ p.then(async () => {
2721
+ await _iter(cn, level_1);
2722
+ });
2723
+ }
2724
+ else {
2725
+ // We don't expand the node, but still visit descendants.
2726
+ // There we may find lazy nodes, so we
2727
+ promises.push(_iter(cn, level_1));
2728
+ }
2729
+ }
2730
+ else {
2731
+ // Collapsing is always synchronous, so no promises required
2732
+ if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
2733
+ // Do not collapse until minExpandLevel
2734
+ cn.setExpanded(false, expand_opts);
2735
+ }
2736
+ _iter(cn, level_1); // recursion, even if cn was already collapsed
2737
+ }
2738
+ });
2739
+ return new Promise((resolve) => {
2740
+ Promise.all(promises).then(() => {
2741
+ resolve(true);
2742
+ });
2743
+ });
2744
+ }
2745
+ const tag = tree.logTime(`${this}.expandAll(${flag})`);
2746
+ try {
2747
+ tree.enableUpdate(false);
2748
+ await _iter(this, depth);
2749
+ }
2750
+ finally {
2751
+ tree.enableUpdate(true);
2752
+ tree.logTimeEnd(tag);
2753
+ }
2562
2754
  }
2563
- /**Find all nodes that match condition (excluding self).
2755
+ /**
2756
+ * Find all descendant nodes that match condition (excluding self).
2564
2757
  *
2565
- * @param {string | function(node)} match title string to search for, or a
2566
- * callback function that returns `true` if a node is matched.
2758
+ * If `match` is a string, search for exact node title.
2759
+ * If `match` is a RegExp expression, apply it to node.title, using
2760
+ * [RegExp.test()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test).
2761
+ * If `match` is a callback, match all nodes for that the callback(node) returns true.
2762
+ *
2763
+ * Returns an empty array if no nodes were found.
2764
+ *
2765
+ * Examples:
2766
+ * ```js
2767
+ * // Match all node titles that match exactly 'Joe':
2768
+ * nodeList = node.findAll("Joe")
2769
+ * // Match all node titles that start with 'Joe' case sensitive:
2770
+ * nodeList = node.findAll(/^Joe/)
2771
+ * // Match all node titles that contain 'oe', case insensitive:
2772
+ * nodeList = node.findAll(/oe/i)
2773
+ * // Match all nodes with `data.price` >= 99:
2774
+ * nodeList = node.findAll((n) => {
2775
+ * return n.data.price >= 99;
2776
+ * })
2777
+ * ```
2567
2778
  */
2568
2779
  findAll(match) {
2569
- const matcher = isFunction(match)
2570
- ? match
2571
- : makeNodeTitleMatcher(match);
2780
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2572
2781
  const res = [];
2573
2782
  this.visit((n) => {
2574
2783
  if (matcher(n)) {
@@ -2598,15 +2807,13 @@
2598
2807
  }
2599
2808
  return null;
2600
2809
  }
2601
- /**Find first node that matches condition (excluding self).
2810
+ /**
2811
+ * Find first descendant node that matches condition (excluding self) or null.
2602
2812
  *
2603
- * @param match title string to search for, or a
2604
- * callback function that returns `true` if a node is matched.
2813
+ * @see {@link WunderbaumNode.findAll} for examples.
2605
2814
  */
2606
2815
  findFirst(match) {
2607
- const matcher = isFunction(match)
2608
- ? match
2609
- : makeNodeTitleMatcher(match);
2816
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2610
2817
  let res = null;
2611
2818
  this.visit((n) => {
2612
2819
  if (matcher(n)) {
@@ -2770,7 +2977,8 @@
2770
2977
  * an expand operation is currently possible.
2771
2978
  */
2772
2979
  isExpandable(andCollapsed = false) {
2773
- return !!this.children && (!this.expanded || !andCollapsed);
2980
+ // return !!this.children && (!this.expanded || !andCollapsed);
2981
+ return !!(this.children || this.lazy) && (!this.expanded || !andCollapsed);
2774
2982
  }
2775
2983
  /** Return true if this node is currently in edit-title mode. */
2776
2984
  isEditing() {
@@ -2872,6 +3080,7 @@
2872
3080
  return true;
2873
3081
  }
2874
3082
  _loadSourceObject(source, level) {
3083
+ var _a;
2875
3084
  const tree = this.tree;
2876
3085
  level !== null && level !== void 0 ? level : (level = this.getLevel());
2877
3086
  // Let caller modify the parsed JSON response:
@@ -2880,6 +3089,10 @@
2880
3089
  source = { children: source };
2881
3090
  }
2882
3091
  assert(isPlainObject(source));
3092
+ const format = (_a = source.format) !== null && _a !== void 0 ? _a : "nested";
3093
+ assert(format === "nested" || format === "flat");
3094
+ // Pre-rocess for 'nested' or 'flat' format
3095
+ inflateSourceData(source);
2883
3096
  assert(source.children, "If `source` is an object, it must have a `children` property");
2884
3097
  if (source.types) {
2885
3098
  tree.logInfo("Redefine types", source.columns);
@@ -3039,17 +3252,16 @@
3039
3252
  }
3040
3253
  /** Expand all parents and optionally scroll into visible area as neccessary.
3041
3254
  * Promise is resolved, when lazy loading and animations are done.
3042
- * @param {object} [opts] passed to `setExpanded()`.
3255
+ * @param {object} [options] passed to `setExpanded()`.
3043
3256
  * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
3044
3257
  */
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);
3258
+ async makeVisible(options) {
3259
+ let i, dfd = new Deferred(), deferreds = [], parents = this.getParentList(false, false), len = parents.length, noAnimation = getOption(options, "noAnimation", false), scroll = getOption(options, "scrollIntoView", true);
3260
+ // scroll = !(options && options.scrollIntoView === false);
3049
3261
  // Expand bottom-up, so only the top node is animated
3050
3262
  for (i = len - 1; i >= 0; i--) {
3051
3263
  // self.debug("pushexpand" + parents[i]);
3052
- const seOpts = { noAnimation: opts === null || opts === void 0 ? void 0 : opts.noAnimation };
3264
+ const seOpts = { noAnimation: noAnimation };
3053
3265
  deferreds.push(parents[i].setExpanded(true, seOpts));
3054
3266
  }
3055
3267
  Promise.all(deferreds).then(() => {
@@ -3776,25 +3988,29 @@
3776
3988
  * Expand or collapse this node.
3777
3989
  */
3778
3990
  async setExpanded(flag = true, options) {
3991
+ const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
3779
3992
  if (!flag &&
3780
3993
  this.isExpanded() &&
3781
3994
  this.getLevel() <= this.tree.getOption("minExpandLevel") &&
3782
- !getOption(options, "force")) {
3995
+ !force) {
3783
3996
  this.logDebug("Ignored collapse request below expandLevel.");
3784
3997
  return;
3785
3998
  }
3786
3999
  if (!flag === !this.expanded) {
3787
4000
  return; // Nothing to do
3788
4001
  }
4002
+ // this.log("setExpanded()");
3789
4003
  if (flag && this.lazy && this.children == null) {
3790
4004
  await this.loadLazy();
3791
4005
  }
3792
4006
  this.expanded = flag;
3793
- const updateOpts = { immediate: !!getOption(options, "immediate") };
4007
+ const updateOpts = { immediate: immediate };
4008
+ // const updateOpts = { immediate: !!util.getOption(options, "immediate") };
3794
4009
  this.tree.setModified(ChangeType.structure, updateOpts);
3795
- if (getOption(options, "scrollIntoView") !== false) {
4010
+ if (flag && scrollIntoView !== false) {
3796
4011
  const lastChild = this.getLastChild();
3797
4012
  if (lastChild) {
4013
+ this.tree.updatePendingModifications();
3798
4014
  lastChild.scrollIntoView({ topNode: this });
3799
4015
  }
3800
4016
  }
@@ -4026,7 +4242,7 @@
4026
4242
  /*!
4027
4243
  * Wunderbaum - ext-edit
4028
4244
  * 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)
4245
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
4030
4246
  */
4031
4247
  // const START_MARKER = "\uFFF7";
4032
4248
  class EditExtension extends WunderbaumExtension {
@@ -4319,8 +4535,8 @@
4319
4535
  * https://github.com/mar10/wunderbaum
4320
4536
  *
4321
4537
  * Released under the MIT license.
4322
- * @version v0.0.7
4323
- * @date Sun, 11 Sep 2022 16:02:08 GMT
4538
+ * @version v0.0.8
4539
+ * @date Fri, 23 Sep 2022 20:47:29 GMT
4324
4540
  */
4325
4541
  class WbSystemRoot extends WunderbaumNode {
4326
4542
  constructor(tree) {
@@ -4891,22 +5107,22 @@
4891
5107
  * - 'down', 'first', 'last', 'left', 'parent', 'right', 'up': navigate
4892
5108
  *
4893
5109
  */
4894
- applyCommand(cmd, nodeOrOpts, opts) {
5110
+ applyCommand(cmd, nodeOrOpts, options) {
4895
5111
  let // clipboard,
4896
5112
  node, refNode;
4897
- // opts = $.extend(
5113
+ // options = $.extend(
4898
5114
  // { setActive: true, clipboard: CLIPBOARD },
4899
- // opts_
5115
+ // options_
4900
5116
  // );
4901
5117
  if (nodeOrOpts instanceof WunderbaumNode) {
4902
5118
  node = nodeOrOpts;
4903
5119
  }
4904
5120
  else {
4905
5121
  node = this.getActiveNode();
4906
- assert(opts === undefined);
4907
- opts = nodeOrOpts;
5122
+ assert(options === undefined);
5123
+ options = nodeOrOpts;
4908
5124
  }
4909
- // clipboard = opts.clipboard;
5125
+ // clipboard = options.clipboard;
4910
5126
  switch (cmd) {
4911
5127
  // Sorting and indentation:
4912
5128
  case "moveUp":
@@ -5112,16 +5328,8 @@
5112
5328
  }
5113
5329
  }
5114
5330
  /** 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
- }
5331
+ async expandAll(flag = true, options) {
5332
+ await this.root.expandAll(flag, options);
5125
5333
  }
5126
5334
  /** Recursively select all nodes. */
5127
5335
  selectAll(flag = true) {
@@ -5155,10 +5363,7 @@
5155
5363
  // util.assert(this.keyMap.size === i);
5156
5364
  }
5157
5365
  /**
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.
5366
+ * Find all nodes that match condition.
5162
5367
  *
5163
5368
  * @see {@link WunderbaumNode.findAll}
5164
5369
  */
@@ -5168,10 +5373,7 @@
5168
5373
  /**
5169
5374
  * Find first node that matches condition.
5170
5375
  *
5171
- * @param match title string to search for, or a
5172
- * callback function that returns `true` if a node is matched.
5173
5376
  * @see {@link WunderbaumNode.findFirst}
5174
- *
5175
5377
  */
5176
5378
  findFirst(match) {
5177
5379
  return this.root.findFirst(match);
@@ -5185,7 +5387,7 @@
5185
5387
  *
5186
5388
  */
5187
5389
  findKey(key) {
5188
- return this.keyMap.get(key);
5390
+ return this.keyMap.get(key) || null;
5189
5391
  }
5190
5392
  /**
5191
5393
  * Find the next visible node that starts with `match`, starting at `startNode`
@@ -5481,13 +5683,13 @@
5481
5683
  scrollTo(nodeOrOpts) {
5482
5684
  const PADDING = 2; // leave some pixels between viewport bounds
5483
5685
  let node;
5484
- let opts;
5686
+ let options;
5485
5687
  if (nodeOrOpts instanceof WunderbaumNode) {
5486
5688
  node = nodeOrOpts;
5487
5689
  }
5488
5690
  else {
5489
- opts = nodeOrOpts;
5490
- node = opts.node;
5691
+ options = nodeOrOpts;
5692
+ node = options.node;
5491
5693
  }
5492
5694
  assert(node && node._rowIdx != null);
5493
5695
  const scrollParent = this.element;
@@ -5498,7 +5700,7 @@
5498
5700
  const vpTop = headerHeight;
5499
5701
  const vpRowTop = rowTop - scrollTop;
5500
5702
  const vpRowBottom = vpRowTop + ROW_HEIGHT;
5501
- const topNode = opts === null || opts === void 0 ? void 0 : opts.topNode;
5703
+ const topNode = options === null || options === void 0 ? void 0 : options.topNode;
5502
5704
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts );
5503
5705
  let newScrollTop = null;
5504
5706
  if (vpRowTop >= vpTop) {
@@ -5742,11 +5944,13 @@
5742
5944
  }
5743
5945
  }
5744
5946
  /** Update column headers and width. */
5745
- updateColumns(opts) {
5746
- opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
5947
+ updateColumns(options) {
5948
+ options = Object.assign({ calculateCols: true, updateRows: true }, options);
5747
5949
  const defaultMinWidth = 4;
5748
5950
  const vpWidth = this.element.clientWidth;
5749
5951
  const isGrid = this.isGrid();
5952
+ // Shorten last column width to avoid h-scrollbar
5953
+ const FIX_ADJUST_LAST_COL = 2;
5750
5954
  let totalWidth = 0;
5751
5955
  let totalWeight = 0;
5752
5956
  let fixedWidth = 0;
@@ -5755,7 +5959,7 @@
5755
5959
  if (!isGrid && this.isCellNav()) {
5756
5960
  this.setCellNav(false);
5757
5961
  }
5758
- if (opts.calculateCols) {
5962
+ if (options.calculateCols) {
5759
5963
  // Gather width definitions
5760
5964
  this._columnsById = {};
5761
5965
  for (let col of this.columns) {
@@ -5807,7 +6011,8 @@
5807
6011
  col._ofsPx = ofsPx;
5808
6012
  ofsPx += col._widthPx;
5809
6013
  }
5810
- totalWidth = ofsPx;
6014
+ this.columns[this.columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6015
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
5811
6016
  }
5812
6017
  // if (this.options.fixedCol) {
5813
6018
  // 'position: fixed' requires that the content has the correct size
@@ -5821,7 +6026,7 @@
5821
6026
  // util.error("BREAK");
5822
6027
  if (modified) {
5823
6028
  this._renderHeaderMarkup();
5824
- if (opts.updateRows) {
6029
+ if (options.updateRows) {
5825
6030
  this._updateRows();
5826
6031
  }
5827
6032
  }
@@ -5884,6 +6089,8 @@
5884
6089
  */
5885
6090
  _updateViewportImmediately() {
5886
6091
  var _a;
6092
+ // Shorten container height to avoid v-scrollbar
6093
+ const FIX_ADJUST_HEIGHT = 1;
5887
6094
  if (this._disableUpdateCount) {
5888
6095
  this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
5889
6096
  return;
@@ -5897,7 +6104,7 @@
5897
6104
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
5898
6105
  // const headerHeight = this.options.headerHeightPx;
5899
6106
  const headerHeight = this.headerElement.clientHeight; // May be 0
5900
- const wantHeight = this.element.clientHeight - headerHeight;
6107
+ const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
5901
6108
  if (Math.abs(height - wantHeight) > 1.0) {
5902
6109
  // this.log("resize", height, wantHeight);
5903
6110
  this.scrollContainerElement.style.height = wantHeight + "px";
@@ -5954,11 +6161,11 @@
5954
6161
  * (including upper and lower prefetch)
5955
6162
  * -
5956
6163
  */
5957
- _updateRows(opts) {
6164
+ _updateRows(options) {
5958
6165
  // const label = this.logTime("_updateRows");
5959
6166
  // this.log("_updateRows", opts)
5960
- opts = Object.assign({ newNodesOnly: false }, opts);
5961
- const newNodesOnly = !!opts.newNodesOnly;
6167
+ options = Object.assign({ newNodesOnly: false }, options);
6168
+ const newNodesOnly = !!options.newNodesOnly;
5962
6169
  const row_height = ROW_HEIGHT;
5963
6170
  const vp_height = this.element.clientHeight;
5964
6171
  const prefetch = RENDER_MAX_PREFETCH;
@@ -6061,16 +6268,16 @@
6061
6268
  * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6062
6269
  * @returns {boolean} false if iteration was canceled
6063
6270
  */
6064
- visitRows(callback, opts) {
6271
+ visitRows(callback, options) {
6065
6272
  if (!this.root.hasChildren()) {
6066
6273
  return false;
6067
6274
  }
6068
- if (opts && opts.reverse) {
6069
- delete opts.reverse;
6070
- return this._visitRowsUp(callback, opts);
6275
+ if (options && options.reverse) {
6276
+ delete options.reverse;
6277
+ return this._visitRowsUp(callback, options);
6071
6278
  }
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];
6279
+ options = options || {};
6280
+ 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
6281
  parent = node.parent;
6075
6282
  while (parent) {
6076
6283
  // visit siblings
@@ -6119,11 +6326,11 @@
6119
6326
  node = parent;
6120
6327
  parent = parent.parent;
6121
6328
  siblingOfs = 1; //
6122
- if (!parent && opts.wrap) {
6329
+ if (!parent && options.wrap) {
6123
6330
  this.logDebug("visitRows(): wrap around");
6124
- assert(opts.start, "`wrap` option requires `start`");
6125
- stopNode = opts.start;
6126
- opts.wrap = false;
6331
+ assert(options.start, "`wrap` option requires `start`");
6332
+ stopNode = options.start;
6333
+ options.wrap = false;
6127
6334
  parent = this.root;
6128
6335
  siblingOfs = 0;
6129
6336
  }
@@ -6258,7 +6465,7 @@
6258
6465
  }
6259
6466
  Wunderbaum.sequence = 0;
6260
6467
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6261
- Wunderbaum.version = "v0.0.7"; // Set to semver by 'grunt release'
6468
+ Wunderbaum.version = "v0.0.8"; // Set to semver by 'grunt release'
6262
6469
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6263
6470
  Wunderbaum.util = util;
6264
6471