wunderbaum 0.0.6 → 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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Wunderbaum - util
3
3
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
4
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  /** @module util */
7
7
  /** Readable names for `MouseEvent.button` */
@@ -281,11 +281,17 @@ function setValueToElem(elem, value) {
281
281
  case "week":
282
282
  case "datetime":
283
283
  case "datetime-local":
284
- input.valueAsDate = value;
284
+ input.valueAsDate = new Date(value);
285
+ // input.valueAsDate = value; // breaks in Edge?
285
286
  break;
286
287
  case "number":
287
288
  case "range":
288
- input.valueAsNumber = value;
289
+ if (value == null) {
290
+ input.value = value;
291
+ }
292
+ else {
293
+ input.valueAsNumber = value;
294
+ }
289
295
  break;
290
296
  case "radio":
291
297
  error("Not implemented");
@@ -302,7 +308,7 @@ function setValueToElem(elem, value) {
302
308
  break;
303
309
  case "text":
304
310
  default:
305
- input.value = value || "";
311
+ input.value = value !== null && value !== void 0 ? value : "";
306
312
  }
307
313
  }
308
314
  else if (tag === "SELECT") {
@@ -460,7 +466,7 @@ function onEvent(rootTarget, eventNames, selectorOrHandler, handlerOrNone) {
460
466
  });
461
467
  });
462
468
  }
463
- /** Return a wrapped handler method, that provides `this._super`.
469
+ /** Return a wrapped handler method, that provides `this._super` and `this._superApply`.
464
470
  *
465
471
  * ```ts
466
472
  // Implement `opts.createNode` event to add the 'draggable' attribute
@@ -701,7 +707,7 @@ var util = /*#__PURE__*/Object.freeze({
701
707
  /*!
702
708
  * Wunderbaum - types
703
709
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
704
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
710
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
705
711
  */
706
712
  /** Possible values for `setModified()`. */
707
713
  var ChangeType;
@@ -753,7 +759,7 @@ var NavigationOptions;
753
759
  /*!
754
760
  * Wunderbaum - wb_extension_base
755
761
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
756
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
762
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
757
763
  */
758
764
  class WunderbaumExtension {
759
765
  constructor(tree, id, defaults) {
@@ -1044,7 +1050,7 @@ function debounce(func, wait = 0, options = {}) {
1044
1050
  /*!
1045
1051
  * Wunderbaum - ext-filter
1046
1052
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1047
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
1053
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1048
1054
  */
1049
1055
  const START_MARKER = "\uFFF7";
1050
1056
  const END_MARKER = "\uFFF8";
@@ -1346,107 +1352,10 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1346
1352
  return textPoses.join("");
1347
1353
  }
1348
1354
 
1349
- /*!
1350
- * Wunderbaum - common
1351
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1352
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
1353
- */
1354
- const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1355
- const ROW_HEIGHT = 22;
1356
- // export const HEADER_HEIGHT = ROW_HEIGHT;
1357
- const ICON_WIDTH = 20;
1358
- const ROW_EXTRA_PAD = 7; // 2x $col-padding-x + 3px rounding errors
1359
- const RENDER_MAX_PREFETCH = 5;
1360
- const TEST_IMG = new RegExp(/\.|\//); // strings are considered image urls if they contain '.' or '/'
1361
- // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
1362
- // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
1363
- let iconMap = {
1364
- error: "bi bi-exclamation-triangle",
1365
- // loading: "bi bi-hourglass-split wb-busy",
1366
- loading: "bi bi-chevron-right wb-busy",
1367
- // loading: "bi bi-arrow-repeat wb-spin",
1368
- // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
1369
- // noData: "bi bi-search",
1370
- noData: "bi bi-question-circle",
1371
- expanderExpanded: "bi bi-chevron-down",
1372
- // expanderExpanded: "bi bi-dash-square",
1373
- expanderCollapsed: "bi bi-chevron-right",
1374
- // expanderCollapsed: "bi bi-plus-square",
1375
- expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
1376
- // expanderLazy: "bi bi-chevron-bar-right",
1377
- checkChecked: "bi bi-check-square",
1378
- checkUnchecked: "bi bi-square",
1379
- checkUnknown: "bi dash-square-dotted",
1380
- radioChecked: "bi bi-circle-fill",
1381
- radioUnchecked: "bi bi-circle",
1382
- radioUnknown: "bi bi-circle-dotted",
1383
- folder: "bi bi-folder2",
1384
- folderOpen: "bi bi-folder2-open",
1385
- doc: "bi bi-file-earmark",
1386
- };
1387
- /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
1388
- const RESERVED_TREE_SOURCE_KEYS = new Set([
1389
- "children",
1390
- "columns",
1391
- "format",
1392
- "keyMap",
1393
- "positional",
1394
- "typeList",
1395
- "types",
1396
- "version", // reserved for future use
1397
- ]);
1398
- /** Key codes that trigger grid navigation, even when inside an input element. */
1399
- const INPUT_BREAKOUT_KEYS = new Set([
1400
- // "ArrowDown",
1401
- // "ArrowUp",
1402
- "Enter",
1403
- "Escape",
1404
- ]);
1405
- /** Map `KeyEvent.key` to navigation action. */
1406
- const KEY_TO_ACTION_DICT = {
1407
- " ": "toggleSelect",
1408
- "+": "expand",
1409
- Add: "expand",
1410
- ArrowDown: "down",
1411
- ArrowLeft: "left",
1412
- ArrowRight: "right",
1413
- ArrowUp: "up",
1414
- Backspace: "parent",
1415
- "/": "collapseAll",
1416
- Divide: "collapseAll",
1417
- End: "lastCol",
1418
- Home: "firstCol",
1419
- "Control+End": "last",
1420
- "Control+Home": "first",
1421
- "Meta+ArrowDown": "last",
1422
- "Meta+ArrowUp": "first",
1423
- "*": "expandAll",
1424
- Multiply: "expandAll",
1425
- PageDown: "pageDown",
1426
- PageUp: "pageUp",
1427
- "-": "collapse",
1428
- Subtract: "collapse",
1429
- };
1430
- /** Return a callback that returns true if the node title contains a substring (case-insensitive). */
1431
- function makeNodeTitleMatcher(s) {
1432
- s = escapeRegex(s.toLowerCase());
1433
- return function (node) {
1434
- return node.title.toLowerCase().indexOf(s) >= 0;
1435
- };
1436
- }
1437
- /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
1438
- function makeNodeTitleStartMatcher(s) {
1439
- s = escapeRegex(s);
1440
- const reMatch = new RegExp("^" + s, "i");
1441
- return function (node) {
1442
- return reMatch.test(node.title);
1443
- };
1444
- }
1445
-
1446
1355
  /*!
1447
1356
  * Wunderbaum - ext-keynav
1448
1357
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1449
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
1358
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1450
1359
  */
1451
1360
  const QUICKSEARCH_DELAY = 500;
1452
1361
  class KeynavExtension extends WunderbaumExtension {
@@ -1480,7 +1389,7 @@ class KeynavExtension extends WunderbaumExtension {
1480
1389
  const event = data.event, tree = this.tree, opts = data.options, activate = !event.ctrlKey || opts.autoActivate, curInput = this._getEmbeddedInputElem(event.target), navModeOption = opts.navigationModeOption;
1481
1390
  // isCellEditMode = tree.navMode === NavigationMode.cellEdit;
1482
1391
  let focusNode, eventName = eventToString(event), node = data.node, handled = true;
1483
- tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
1392
+ // tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
1484
1393
  if (!tree.isEnabled()) {
1485
1394
  // tree.logDebug(`onKeyEvent ignored for disabled tree: ${eventName}`);
1486
1395
  return false;
@@ -1603,10 +1512,11 @@ class KeynavExtension extends WunderbaumExtension {
1603
1512
  if (eventName === "Escape") {
1604
1513
  // Discard changes
1605
1514
  node.render();
1515
+ // } else if (!INPUT_BREAKOUT_KEYS.has(eventName)) {
1606
1516
  }
1607
- else if (!INPUT_BREAKOUT_KEYS.has(eventName)) {
1517
+ else if (eventName !== "Enter") {
1608
1518
  // Let current `<input>` handle it
1609
- node.logDebug(`Ignored ${eventName} inside input`);
1519
+ node.logDebug(`Ignored ${eventName} inside focused input`);
1610
1520
  return;
1611
1521
  }
1612
1522
  // const curInputType = curInput.type || curInput.tagName;
@@ -1677,24 +1587,24 @@ class KeynavExtension extends WunderbaumExtension {
1677
1587
  if (isColspan && node.isExpanded()) {
1678
1588
  node.setExpanded(false);
1679
1589
  }
1680
- else if (tree.activeColIdx > 0) {
1590
+ else if (!isColspan && tree.activeColIdx > 0) {
1681
1591
  tree.setColumn(tree.activeColIdx - 1);
1682
- handled = true;
1683
1592
  }
1684
1593
  else if (navModeOption !== NavigationOptions.cell) {
1685
1594
  tree.setCellNav(false); // row-nav mode
1686
- handled = true;
1687
1595
  }
1596
+ handled = true;
1688
1597
  break;
1689
1598
  case "ArrowRight":
1690
1599
  tree.setFocus(); // Blur prev. input if any
1691
1600
  if (isColspan && !node.isExpanded()) {
1692
1601
  node.setExpanded();
1693
1602
  }
1694
- else if (tree.activeColIdx < tree.columns.length - 1) {
1603
+ else if (!isColspan &&
1604
+ tree.activeColIdx < tree.columns.length - 1) {
1695
1605
  tree.setColumn(tree.activeColIdx + 1);
1696
- handled = true;
1697
1606
  }
1607
+ handled = true;
1698
1608
  break;
1699
1609
  case "ArrowDown":
1700
1610
  case "ArrowUp":
@@ -1727,7 +1637,7 @@ class KeynavExtension extends WunderbaumExtension {
1727
1637
  /*!
1728
1638
  * Wunderbaum - ext-logger
1729
1639
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1730
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
1640
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1731
1641
  */
1732
1642
  class LoggerExtension extends WunderbaumExtension {
1733
1643
  constructor(tree) {
@@ -1750,7 +1660,7 @@ class LoggerExtension extends WunderbaumExtension {
1750
1660
  if (ignoreEvents.has(name)) {
1751
1661
  return tree._superApply(arguments);
1752
1662
  }
1753
- let start = Date.now();
1663
+ const start = Date.now();
1754
1664
  const res = tree._superApply(arguments);
1755
1665
  console.debug(`${prefix}: callEvent('${name}') took ${Date.now() - start} ms.`, arguments[1]);
1756
1666
  return res;
@@ -1764,10 +1674,250 @@ class LoggerExtension extends WunderbaumExtension {
1764
1674
  }
1765
1675
  }
1766
1676
 
1677
+ /*!
1678
+ * Wunderbaum - common
1679
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1680
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1681
+ */
1682
+ const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1683
+ /**
1684
+ * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
1685
+ */
1686
+ const ROW_HEIGHT = 22;
1687
+ /**
1688
+ * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
1689
+ */
1690
+ const ICON_WIDTH = 20;
1691
+ /**
1692
+ * Adjust the width of the title span, so overflow ellipsis work.
1693
+ * (2 x `$col-padding-x` + 3px rounding errors).
1694
+ */
1695
+ const TITLE_SPAN_PAD_Y = 7;
1696
+ /** Render row markup for N nodes above and below the visible viewport. */
1697
+ const RENDER_MAX_PREFETCH = 5;
1698
+ /** Regular expression to detect if a string describes an image URL (in contrast
1699
+ * to a class name). Strings are considered image urls if they contain '.' or '/'.
1700
+ */
1701
+ const TEST_IMG = new RegExp(/\.|\//);
1702
+ // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
1703
+ // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
1704
+ /**
1705
+ * Default node icons.
1706
+ * Requires bootstrap icons https://icons.getbootstrap.com
1707
+ */
1708
+ const iconMap = {
1709
+ error: "bi bi-exclamation-triangle",
1710
+ // loading: "bi bi-hourglass-split wb-busy",
1711
+ loading: "bi bi-chevron-right wb-busy",
1712
+ // loading: "bi bi-arrow-repeat wb-spin",
1713
+ // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
1714
+ // noData: "bi bi-search",
1715
+ noData: "bi bi-question-circle",
1716
+ expanderExpanded: "bi bi-chevron-down",
1717
+ // expanderExpanded: "bi bi-dash-square",
1718
+ expanderCollapsed: "bi bi-chevron-right",
1719
+ // expanderCollapsed: "bi bi-plus-square",
1720
+ expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
1721
+ // expanderLazy: "bi bi-chevron-bar-right",
1722
+ checkChecked: "bi bi-check-square",
1723
+ checkUnchecked: "bi bi-square",
1724
+ checkUnknown: "bi dash-square-dotted",
1725
+ radioChecked: "bi bi-circle-fill",
1726
+ radioUnchecked: "bi bi-circle",
1727
+ radioUnknown: "bi bi-circle-dotted",
1728
+ folder: "bi bi-folder2",
1729
+ folderOpen: "bi bi-folder2-open",
1730
+ folderLazy: "bi bi-folder-symlink",
1731
+ doc: "bi bi-file-earmark",
1732
+ };
1733
+ /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
1734
+ const RESERVED_TREE_SOURCE_KEYS = new Set([
1735
+ "_format",
1736
+ "_keyMap",
1737
+ "_positional",
1738
+ "_typeList",
1739
+ "_version",
1740
+ "children",
1741
+ "columns",
1742
+ "types",
1743
+ ]);
1744
+ // /** Key codes that trigger grid navigation, even when inside an input element. */
1745
+ // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
1746
+ // // "ArrowDown",
1747
+ // // "ArrowUp",
1748
+ // "Enter",
1749
+ // "Escape",
1750
+ // ]);
1751
+ /** Map `KeyEvent.key` to navigation action. */
1752
+ const KEY_TO_ACTION_DICT = {
1753
+ " ": "toggleSelect",
1754
+ "+": "expand",
1755
+ Add: "expand",
1756
+ ArrowDown: "down",
1757
+ ArrowLeft: "left",
1758
+ ArrowRight: "right",
1759
+ ArrowUp: "up",
1760
+ Backspace: "parent",
1761
+ "/": "collapseAll",
1762
+ Divide: "collapseAll",
1763
+ End: "lastCol",
1764
+ Home: "firstCol",
1765
+ "Control+End": "last",
1766
+ "Control+Home": "first",
1767
+ "Meta+ArrowDown": "last",
1768
+ "Meta+ArrowUp": "first",
1769
+ "*": "expandAll",
1770
+ Multiply: "expandAll",
1771
+ PageDown: "pageDown",
1772
+ PageUp: "pageUp",
1773
+ "-": "collapse",
1774
+ Subtract: "collapse",
1775
+ };
1776
+ /** Return a callback that returns true if the node title matches the string
1777
+ * or regular expression.
1778
+ * @see {@link WunderbaumNode.findAll}
1779
+ */
1780
+ function makeNodeTitleMatcher(match) {
1781
+ if (match instanceof RegExp) {
1782
+ return function (node) {
1783
+ return match.test(node.title);
1784
+ };
1785
+ }
1786
+ assert(typeof match === "string");
1787
+ // s = escapeRegex(s.toLowerCase());
1788
+ return function (node) {
1789
+ return node.title === match;
1790
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
1791
+ // return node.title.toLowerCase().indexOf(match) >= 0;
1792
+ };
1793
+ }
1794
+ /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
1795
+ function makeNodeTitleStartMatcher(s) {
1796
+ s = escapeRegex(s);
1797
+ const reMatch = new RegExp("^" + s, "i");
1798
+ return function (node) {
1799
+ return reMatch.test(node.title);
1800
+ };
1801
+ }
1802
+ function unflattenSource(source) {
1803
+ var _a, _b, _c;
1804
+ const { _format, _keyMap, _positional, children } = source;
1805
+ if (_format !== "flat") {
1806
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
1807
+ }
1808
+ if (_positional && _positional.includes("children")) {
1809
+ throw new Error(`source._positional must not include "children": ${_positional}`);
1810
+ }
1811
+ // Inverse keyMap:
1812
+ let longToShort = {};
1813
+ if (_keyMap) {
1814
+ for (const [key, value] of Object.entries(_keyMap)) {
1815
+ longToShort[value] = key;
1816
+ }
1817
+ }
1818
+ const positionalShort = _positional.map((e) => longToShort[e]);
1819
+ const newChildren = [];
1820
+ const keyToNodeMap = {};
1821
+ const indexToNodeMap = {};
1822
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
1823
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
1824
+ for (const [index, node] of children.entries()) {
1825
+ // Node entry format:
1826
+ // [PARENT_ID, [POSITIONAL_ARGS]]
1827
+ // or
1828
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
1829
+ const [parentId, args, kwargs = {}] = node;
1830
+ // Free up some memory as we go
1831
+ node[1] = null;
1832
+ if (node[2] != null) {
1833
+ node[2] = null;
1834
+ }
1835
+ // console.log("flatten", parentId, args, kwargs)
1836
+ // We keep `kwargs` as our new node definition. Then we add all positional
1837
+ // values to this object:
1838
+ args.forEach((val, positionalIdx) => {
1839
+ kwargs[positionalShort[positionalIdx]] = val;
1840
+ });
1841
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
1842
+ // index of the source.children list. If PARENT_ID is a string, we search
1843
+ // a parent with node.key of this value.
1844
+ indexToNodeMap[index] = kwargs;
1845
+ const key = kwargs[keyAttrName];
1846
+ if (key != null) {
1847
+ keyToNodeMap[key] = kwargs;
1848
+ }
1849
+ let parentNode = null;
1850
+ if (parentId === null) ;
1851
+ else if (typeof parentId === "number") {
1852
+ parentNode = indexToNodeMap[parentId];
1853
+ if (parentNode === undefined) {
1854
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
1855
+ }
1856
+ }
1857
+ else {
1858
+ parentNode = keyToNodeMap[parentId];
1859
+ if (parentNode === undefined) {
1860
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
1861
+ }
1862
+ }
1863
+ if (parentNode) {
1864
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
1865
+ parentNode[childrenAttrName].push(kwargs);
1866
+ }
1867
+ else {
1868
+ newChildren.push(kwargs);
1869
+ }
1870
+ }
1871
+ delete source.children;
1872
+ source.children = newChildren;
1873
+ }
1874
+ function inflateSourceData(source) {
1875
+ const { _format, _keyMap, _typeList } = source;
1876
+ if (_format === "flat") {
1877
+ unflattenSource(source);
1878
+ }
1879
+ delete source._format;
1880
+ delete source._version;
1881
+ delete source._keyMap;
1882
+ delete source._typeList;
1883
+ delete source._positional;
1884
+ function _iter(childList) {
1885
+ for (let node of childList) {
1886
+ // Expand short alias names
1887
+ if (_keyMap) {
1888
+ // Iterate over a list of names, because we modify inside the loop:
1889
+ Object.getOwnPropertyNames(node).forEach((propName) => {
1890
+ var _a;
1891
+ const long = (_a = _keyMap[propName]) !== null && _a !== void 0 ? _a : propName;
1892
+ if (long !== propName) {
1893
+ node[long] = node[propName];
1894
+ delete node[propName];
1895
+ }
1896
+ });
1897
+ }
1898
+ // `node` now has long attribute names
1899
+ // Resolve node type indexes
1900
+ const type = node.type;
1901
+ if (_typeList && type != null && typeof type === "number") {
1902
+ const newType = _typeList[type];
1903
+ if (newType == null) {
1904
+ throw new Error(`Expected typeList[${type}] entry in [${_typeList}]`);
1905
+ }
1906
+ node.type = newType;
1907
+ }
1908
+ // Recursion
1909
+ if (node.children) {
1910
+ _iter(node.children);
1911
+ }
1912
+ }
1913
+ }
1914
+ _iter(source.children);
1915
+ }
1916
+
1767
1917
  /*!
1768
1918
  * Wunderbaum - ext-dnd
1769
1919
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1770
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
1920
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
1771
1921
  */
1772
1922
  const nodeMimeType = "application/x-wunderbaum-node";
1773
1923
  class DndExtension extends WunderbaumExtension {
@@ -2035,7 +2185,7 @@ class DndExtension extends WunderbaumExtension {
2035
2185
  /*!
2036
2186
  * Wunderbaum - drag_observer
2037
2187
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2038
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
2188
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2039
2189
  */
2040
2190
  /**
2041
2191
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2169,7 +2319,7 @@ class DragObserver {
2169
2319
  /*!
2170
2320
  * Wunderbaum - ext-grid
2171
2321
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2172
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
2322
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2173
2323
  */
2174
2324
  class GridExtension extends WunderbaumExtension {
2175
2325
  constructor(tree) {
@@ -2206,7 +2356,7 @@ class GridExtension extends WunderbaumExtension {
2206
2356
  /*!
2207
2357
  * Wunderbaum - deferred
2208
2358
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2209
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
2359
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2210
2360
  */
2211
2361
  /**
2212
2362
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2259,7 +2409,7 @@ class Deferred {
2259
2409
  /*!
2260
2410
  * Wunderbaum - wunderbaum_node
2261
2411
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2262
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
2412
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
2263
2413
  */
2264
2414
  /** Top-level properties that can be passed with `data`. */
2265
2415
  const NODE_PROPS = new Set([
@@ -2400,57 +2550,63 @@ class WunderbaumNode {
2400
2550
  /**
2401
2551
  * Append (or insert) a list of child nodes.
2402
2552
  *
2403
- * Tip: pass `{ before: 0 }` to prepend children
2404
- * @param {NodeData[]} nodeData array of child node definitions (also single child accepted)
2405
- * @param child node (or key or index of such).
2406
- * If omitted, the new children are appended.
2553
+ * Tip: pass `{ before: 0 }` to prepend new nodes as first children.
2554
+ *
2407
2555
  * @returns first child added
2408
2556
  */
2409
2557
  addChildren(nodeData, options) {
2410
- let insertBefore = options
2411
- ? options.before
2412
- : null,
2413
- // redraw = options ? options.redraw !== false : true,
2414
- nodeList = [];
2558
+ const tree = this.tree;
2559
+ let { before = null, applyMinExpanLevel = true, _level } = options !== null && options !== void 0 ? options : {};
2560
+ // let { before, loadLazy=true, _level } = options ?? {};
2561
+ // const isTopCall = _level == null;
2562
+ _level !== null && _level !== void 0 ? _level : (_level = this.getLevel());
2563
+ const nodeList = [];
2415
2564
  try {
2416
- this.tree.enableUpdate(false);
2565
+ tree.enableUpdate(false);
2417
2566
  if (isPlainObject(nodeData)) {
2418
2567
  nodeData = [nodeData];
2419
2568
  }
2569
+ const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
2420
2570
  for (let child of nodeData) {
2421
- let subChildren = child.children;
2571
+ const subChildren = child.children;
2422
2572
  delete child.children;
2423
- let n = new WunderbaumNode(this.tree, this, child);
2573
+ const n = new WunderbaumNode(tree, this, child);
2574
+ if (forceExpand && !n.isUnloaded()) {
2575
+ n.expanded = true;
2576
+ }
2424
2577
  nodeList.push(n);
2425
2578
  if (subChildren) {
2426
- n.addChildren(subChildren, { redraw: false });
2579
+ n.addChildren(subChildren, { _level: _level + 1 });
2427
2580
  }
2428
2581
  }
2429
2582
  if (!this.children) {
2430
2583
  this.children = nodeList;
2431
2584
  }
2432
- else if (insertBefore == null || this.children.length === 0) {
2585
+ else if (before == null || this.children.length === 0) {
2433
2586
  this.children = this.children.concat(nodeList);
2434
2587
  }
2435
2588
  else {
2436
- // Returns null if insertBefore is not a direct child:
2437
- insertBefore = this.findDirectChild(insertBefore);
2438
- let pos = this.children.indexOf(insertBefore);
2439
- assert(pos >= 0, "insertBefore must be an existing child");
2589
+ // Returns null if before is not a direct child:
2590
+ before = this.findDirectChild(before);
2591
+ let pos = this.children.indexOf(before);
2592
+ assert(pos >= 0, `options.before must be a direct child of ${this}`);
2440
2593
  // insert nodeList after children[pos]
2441
2594
  this.children.splice(pos, 0, ...nodeList);
2442
2595
  }
2443
2596
  // TODO:
2444
- // if (this.tree.options.selectMode === 3) {
2597
+ // if (tree.options.selectMode === 3) {
2445
2598
  // this.fixSelection3FromEndNodes();
2446
2599
  // }
2447
2600
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
2448
- this.tree.setModified(ChangeType.structure);
2449
- return nodeList[0];
2601
+ tree.setModified(ChangeType.structure);
2450
2602
  }
2451
2603
  finally {
2452
- this.tree.enableUpdate(true);
2604
+ tree.enableUpdate(true);
2453
2605
  }
2606
+ // if(isTopCall && loadLazy){
2607
+ // this.logWarn("addChildren(): loadLazy is not yet implemented.")
2608
+ // }
2609
+ return nodeList[0];
2454
2610
  }
2455
2611
  /**
2456
2612
  * Append or prepend a node, or append a child node.
@@ -2524,21 +2680,98 @@ class WunderbaumNode {
2524
2680
  }
2525
2681
  }
2526
2682
  }
2527
- /** Call `setExpanded()` on al child nodes*/
2528
- async expandAll(flag = true) {
2529
- this.visit((node) => {
2530
- node.setExpanded(flag);
2531
- });
2683
+ /** Call `setExpanded()` on all descendant nodes. */
2684
+ async expandAll(flag = true, options) {
2685
+ const tree = this.tree;
2686
+ const minExpandLevel = this.tree.options.minExpandLevel;
2687
+ let { depth = 99, loadLazy, force } = options !== null && options !== void 0 ? options : {};
2688
+ const expand_opts = {
2689
+ scrollIntoView: false,
2690
+ force: force,
2691
+ loadLazy: loadLazy,
2692
+ };
2693
+ // this.logInfo(`expandAll(${flag})`);
2694
+ // Expand all direct children in parallel:
2695
+ async function _iter(n, level) {
2696
+ var _a;
2697
+ // n.logInfo(` _iter(${level})`);
2698
+ if (level === 0) {
2699
+ return;
2700
+ }
2701
+ // if (!flag && minExpandLevel && !force && n.getLevel() <= minExpandLevel) {
2702
+ // return; // Do not collapse until minExpandLevel
2703
+ // }
2704
+ const level_1 = level == null ? null : level - 1;
2705
+ const promises = [];
2706
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach((cn) => {
2707
+ if (flag) {
2708
+ if (!cn.expanded && (cn.children || (loadLazy && cn.lazy))) {
2709
+ // Node is collapsed and may be expanded (i.e. has children or is lazy)
2710
+ // Expanding may be async, so we store the promise.
2711
+ // Also the recursion is delayed until expansion finished.
2712
+ const p = cn.setExpanded(true, expand_opts);
2713
+ promises.push(p);
2714
+ p.then(async () => {
2715
+ await _iter(cn, level_1);
2716
+ });
2717
+ }
2718
+ else {
2719
+ // We don't expand the node, but still visit descendants.
2720
+ // There we may find lazy nodes, so we
2721
+ promises.push(_iter(cn, level_1));
2722
+ }
2723
+ }
2724
+ else {
2725
+ // Collapsing is always synchronous, so no promises required
2726
+ if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
2727
+ // Do not collapse until minExpandLevel
2728
+ cn.setExpanded(false, expand_opts);
2729
+ }
2730
+ _iter(cn, level_1); // recursion, even if cn was already collapsed
2731
+ }
2732
+ });
2733
+ return new Promise((resolve) => {
2734
+ Promise.all(promises).then(() => {
2735
+ resolve(true);
2736
+ });
2737
+ });
2738
+ }
2739
+ const tag = tree.logTime(`${this}.expandAll(${flag})`);
2740
+ try {
2741
+ tree.enableUpdate(false);
2742
+ await _iter(this, depth);
2743
+ }
2744
+ finally {
2745
+ tree.enableUpdate(true);
2746
+ tree.logTimeEnd(tag);
2747
+ }
2532
2748
  }
2533
- /**Find all nodes that match condition (excluding self).
2749
+ /**
2750
+ * Find all descendant nodes that match condition (excluding self).
2534
2751
  *
2535
- * @param {string | function(node)} match title string to search for, or a
2536
- * callback function that returns `true` if a node is matched.
2752
+ * If `match` is a string, search for exact node title.
2753
+ * If `match` is a RegExp expression, apply it to node.title, using
2754
+ * [RegExp.test()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test).
2755
+ * If `match` is a callback, match all nodes for that the callback(node) returns true.
2756
+ *
2757
+ * Returns an empty array if no nodes were found.
2758
+ *
2759
+ * Examples:
2760
+ * ```js
2761
+ * // Match all node titles that match exactly 'Joe':
2762
+ * nodeList = node.findAll("Joe")
2763
+ * // Match all node titles that start with 'Joe' case sensitive:
2764
+ * nodeList = node.findAll(/^Joe/)
2765
+ * // Match all node titles that contain 'oe', case insensitive:
2766
+ * nodeList = node.findAll(/oe/i)
2767
+ * // Match all nodes with `data.price` >= 99:
2768
+ * nodeList = node.findAll((n) => {
2769
+ * return n.data.price >= 99;
2770
+ * })
2771
+ * ```
2537
2772
  */
2538
2773
  findAll(match) {
2539
- const matcher = isFunction(match)
2540
- ? match
2541
- : makeNodeTitleMatcher(match);
2774
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2542
2775
  const res = [];
2543
2776
  this.visit((n) => {
2544
2777
  if (matcher(n)) {
@@ -2568,15 +2801,13 @@ class WunderbaumNode {
2568
2801
  }
2569
2802
  return null;
2570
2803
  }
2571
- /**Find first node that matches condition (excluding self).
2804
+ /**
2805
+ * Find first descendant node that matches condition (excluding self) or null.
2572
2806
  *
2573
- * @param match title string to search for, or a
2574
- * callback function that returns `true` if a node is matched.
2807
+ * @see {@link WunderbaumNode.findAll} for examples.
2575
2808
  */
2576
2809
  findFirst(match) {
2577
- const matcher = isFunction(match)
2578
- ? match
2579
- : makeNodeTitleMatcher(match);
2810
+ const matcher = typeof match === "function" ? match : makeNodeTitleMatcher(match);
2580
2811
  let res = null;
2581
2812
  this.visit((n) => {
2582
2813
  if (matcher(n)) {
@@ -2740,7 +2971,8 @@ class WunderbaumNode {
2740
2971
  * an expand operation is currently possible.
2741
2972
  */
2742
2973
  isExpandable(andCollapsed = false) {
2743
- return !!this.children && (!this.expanded || !andCollapsed);
2974
+ // return !!this.children && (!this.expanded || !andCollapsed);
2975
+ return !!(this.children || this.lazy) && (!this.expanded || !andCollapsed);
2744
2976
  }
2745
2977
  /** Return true if this node is currently in edit-title mode. */
2746
2978
  isEditing() {
@@ -2841,14 +3073,20 @@ class WunderbaumNode {
2841
3073
  // this.debug("isVisible: VISIBLE");
2842
3074
  return true;
2843
3075
  }
2844
- _loadSourceObject(source) {
3076
+ _loadSourceObject(source, level) {
3077
+ var _a;
2845
3078
  const tree = this.tree;
3079
+ level !== null && level !== void 0 ? level : (level = this.getLevel());
2846
3080
  // Let caller modify the parsed JSON response:
2847
3081
  this._callEvent("receive", { response: source });
2848
3082
  if (isArray(source)) {
2849
3083
  source = { children: source };
2850
3084
  }
2851
3085
  assert(isPlainObject(source));
3086
+ const format = (_a = source.format) !== null && _a !== void 0 ? _a : "nested";
3087
+ assert(format === "nested" || format === "flat");
3088
+ // Pre-rocess for 'nested' or 'flat' format
3089
+ inflateSourceData(source);
2852
3090
  assert(source.children, "If `source` is an object, it must have a `children` property");
2853
3091
  if (source.types) {
2854
3092
  tree.logInfo("Redefine types", source.columns);
@@ -2862,7 +3100,6 @@ class WunderbaumNode {
2862
3100
  tree.updateColumns({ calculateCols: false });
2863
3101
  }
2864
3102
  this.addChildren(source.children);
2865
- delete source.columns;
2866
3103
  // Add extra data to `tree.data`
2867
3104
  for (const [key, value] of Object.entries(source)) {
2868
3105
  if (!RESERVED_TREE_SOURCE_KEYS.has(key)) {
@@ -3009,17 +3246,16 @@ class WunderbaumNode {
3009
3246
  }
3010
3247
  /** Expand all parents and optionally scroll into visible area as neccessary.
3011
3248
  * Promise is resolved, when lazy loading and animations are done.
3012
- * @param {object} [opts] passed to `setExpanded()`.
3249
+ * @param {object} [options] passed to `setExpanded()`.
3013
3250
  * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
3014
3251
  */
3015
- async makeVisible(opts) {
3016
- let i, dfd = new Deferred(), deferreds = [], parents = this.getParentList(false, false), len = parents.length,
3017
- // effects = !(opts && opts.noAnimation === true),
3018
- scroll = !(opts && opts.scrollIntoView === false);
3252
+ async makeVisible(options) {
3253
+ let i, dfd = new Deferred(), deferreds = [], parents = this.getParentList(false, false), len = parents.length, noAnimation = getOption(options, "noAnimation", false), scroll = getOption(options, "scrollIntoView", true);
3254
+ // scroll = !(options && options.scrollIntoView === false);
3019
3255
  // Expand bottom-up, so only the top node is animated
3020
3256
  for (i = len - 1; i >= 0; i--) {
3021
3257
  // self.debug("pushexpand" + parents[i]);
3022
- const seOpts = { noAnimation: opts === null || opts === void 0 ? void 0 : opts.noAnimation };
3258
+ const seOpts = { noAnimation: noAnimation };
3023
3259
  deferreds.push(parents[i].setExpanded(true, seOpts));
3024
3260
  }
3025
3261
  Promise.all(deferreds).then(() => {
@@ -3261,6 +3497,9 @@ class WunderbaumNode {
3261
3497
  else if (this.children) {
3262
3498
  icon = iconMap.folder;
3263
3499
  }
3500
+ else if (this.lazy) {
3501
+ icon = iconMap.folderLazy;
3502
+ }
3264
3503
  else {
3265
3504
  icon = iconMap.doc;
3266
3505
  }
@@ -3424,13 +3663,13 @@ class WunderbaumNode {
3424
3663
  if (isColspan) {
3425
3664
  let vpWidth = tree.element.clientWidth;
3426
3665
  titleSpan.style.width =
3427
- vpWidth - nodeElem._ofsTitlePx - ROW_EXTRA_PAD + "px";
3666
+ vpWidth - nodeElem._ofsTitlePx - TITLE_SPAN_PAD_Y + "px";
3428
3667
  }
3429
3668
  else {
3430
3669
  titleSpan.style.width =
3431
3670
  columns[0]._widthPx -
3432
3671
  nodeElem._ofsTitlePx -
3433
- ROW_EXTRA_PAD +
3672
+ TITLE_SPAN_PAD_Y +
3434
3673
  "px";
3435
3674
  }
3436
3675
  }
@@ -3743,22 +3982,32 @@ class WunderbaumNode {
3743
3982
  * Expand or collapse this node.
3744
3983
  */
3745
3984
  async setExpanded(flag = true, options) {
3985
+ const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
3746
3986
  if (!flag &&
3747
3987
  this.isExpanded() &&
3748
3988
  this.getLevel() <= this.tree.getOption("minExpandLevel") &&
3749
- !getOption(options, "force")) {
3989
+ !force) {
3750
3990
  this.logDebug("Ignored collapse request below expandLevel.");
3751
3991
  return;
3752
3992
  }
3753
3993
  if (!flag === !this.expanded) {
3754
3994
  return; // Nothing to do
3755
3995
  }
3996
+ // this.log("setExpanded()");
3756
3997
  if (flag && this.lazy && this.children == null) {
3757
3998
  await this.loadLazy();
3758
3999
  }
3759
4000
  this.expanded = flag;
3760
- const updateOpts = { immediate: !!getOption(options, "immediate") };
4001
+ const updateOpts = { immediate: immediate };
4002
+ // const updateOpts = { immediate: !!util.getOption(options, "immediate") };
3761
4003
  this.tree.setModified(ChangeType.structure, updateOpts);
4004
+ if (flag && scrollIntoView !== false) {
4005
+ const lastChild = this.getLastChild();
4006
+ if (lastChild) {
4007
+ this.tree.updatePendingModifications();
4008
+ lastChild.scrollIntoView({ topNode: this });
4009
+ }
4010
+ }
3762
4011
  }
3763
4012
  /**
3764
4013
  * Set keyboard focus here.
@@ -3987,7 +4236,7 @@ WunderbaumNode.sequence = 0;
3987
4236
  /*!
3988
4237
  * Wunderbaum - ext-edit
3989
4238
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
3990
- * v0.0.6, Sat, 10 Sep 2022 19:29:21 GMT (https://github.com/mar10/wunderbaum)
4239
+ * v0.0.8, Fri, 23 Sep 2022 20:47:29 GMT (https://github.com/mar10/wunderbaum)
3991
4240
  */
3992
4241
  // const START_MARKER = "\uFFF7";
3993
4242
  class EditExtension extends WunderbaumExtension {
@@ -4133,8 +4382,9 @@ class EditExtension extends WunderbaumExtension {
4133
4382
  node.logInfo("beforeEdit canceled operation.");
4134
4383
  return;
4135
4384
  }
4136
- // `beforeEdit(e)` may return an input HTML string. Otherwise use a default:
4137
- if (!inputHtml) {
4385
+ // `beforeEdit(e)` may return an input HTML string. Otherwise use a default.
4386
+ // (we also treat a `true` return value as 'use default'):
4387
+ if (inputHtml === true || !inputHtml) {
4138
4388
  const title = escapeHtml(node.title);
4139
4389
  inputHtml = `<input type=text class="wb-input-edit" value="${title}" required autocorrect=off>`;
4140
4390
  }
@@ -4279,8 +4529,8 @@ class EditExtension extends WunderbaumExtension {
4279
4529
  * https://github.com/mar10/wunderbaum
4280
4530
  *
4281
4531
  * Released under the MIT license.
4282
- * @version v0.0.6
4283
- * @date Sat, 10 Sep 2022 19:29:21 GMT
4532
+ * @version v0.0.8
4533
+ * @date Fri, 23 Sep 2022 20:47:29 GMT
4284
4534
  */
4285
4535
  class WbSystemRoot extends WunderbaumNode {
4286
4536
  constructor(tree) {
@@ -4851,22 +5101,22 @@ class Wunderbaum {
4851
5101
  * - 'down', 'first', 'last', 'left', 'parent', 'right', 'up': navigate
4852
5102
  *
4853
5103
  */
4854
- applyCommand(cmd, nodeOrOpts, opts) {
5104
+ applyCommand(cmd, nodeOrOpts, options) {
4855
5105
  let // clipboard,
4856
5106
  node, refNode;
4857
- // opts = $.extend(
5107
+ // options = $.extend(
4858
5108
  // { setActive: true, clipboard: CLIPBOARD },
4859
- // opts_
5109
+ // options_
4860
5110
  // );
4861
5111
  if (nodeOrOpts instanceof WunderbaumNode) {
4862
5112
  node = nodeOrOpts;
4863
5113
  }
4864
5114
  else {
4865
5115
  node = this.getActiveNode();
4866
- assert(opts === undefined);
4867
- opts = nodeOrOpts;
5116
+ assert(options === undefined);
5117
+ options = nodeOrOpts;
4868
5118
  }
4869
- // clipboard = opts.clipboard;
5119
+ // clipboard = options.clipboard;
4870
5120
  switch (cmd) {
4871
5121
  // Sorting and indentation:
4872
5122
  case "moveUp":
@@ -5072,16 +5322,8 @@ class Wunderbaum {
5072
5322
  }
5073
5323
  }
5074
5324
  /** Recursively expand all expandable nodes (triggers lazy load id needed). */
5075
- async expandAll(flag = true) {
5076
- const tag = this.logTime("expandAll(" + flag + ")");
5077
- try {
5078
- this.enableUpdate(false);
5079
- await this.root.expandAll(flag);
5080
- }
5081
- finally {
5082
- this.enableUpdate(true);
5083
- this.logTimeEnd(tag);
5084
- }
5325
+ async expandAll(flag = true, options) {
5326
+ await this.root.expandAll(flag, options);
5085
5327
  }
5086
5328
  /** Recursively select all nodes. */
5087
5329
  selectAll(flag = true) {
@@ -5115,10 +5357,7 @@ class Wunderbaum {
5115
5357
  // util.assert(this.keyMap.size === i);
5116
5358
  }
5117
5359
  /**
5118
- * Find all nodes that matches condition.
5119
- *
5120
- * @param match title string to search for, or a
5121
- * callback function that returns `true` if a node is matched.
5360
+ * Find all nodes that match condition.
5122
5361
  *
5123
5362
  * @see {@link WunderbaumNode.findAll}
5124
5363
  */
@@ -5128,10 +5367,7 @@ class Wunderbaum {
5128
5367
  /**
5129
5368
  * Find first node that matches condition.
5130
5369
  *
5131
- * @param match title string to search for, or a
5132
- * callback function that returns `true` if a node is matched.
5133
5370
  * @see {@link WunderbaumNode.findFirst}
5134
- *
5135
5371
  */
5136
5372
  findFirst(match) {
5137
5373
  return this.root.findFirst(match);
@@ -5145,7 +5381,7 @@ class Wunderbaum {
5145
5381
  *
5146
5382
  */
5147
5383
  findKey(key) {
5148
- return this.keyMap.get(key);
5384
+ return this.keyMap.get(key) || null;
5149
5385
  }
5150
5386
  /**
5151
5387
  * Find the next visible node that starts with `match`, starting at `startNode`
@@ -5434,17 +5670,20 @@ class Wunderbaum {
5434
5670
  }
5435
5671
  /**
5436
5672
  * Make sure that this node is vertically scrolled into the viewport.
5673
+ *
5674
+ * Nodes that are above the visible area become the top row, nodes that are
5675
+ * below the viewport become the bottom row.
5437
5676
  */
5438
5677
  scrollTo(nodeOrOpts) {
5439
5678
  const PADDING = 2; // leave some pixels between viewport bounds
5440
5679
  let node;
5441
- let opts;
5680
+ let options;
5442
5681
  if (nodeOrOpts instanceof WunderbaumNode) {
5443
5682
  node = nodeOrOpts;
5444
5683
  }
5445
5684
  else {
5446
- opts = nodeOrOpts;
5447
- node = opts.node;
5685
+ options = nodeOrOpts;
5686
+ node = options.node;
5448
5687
  }
5449
5688
  assert(node && node._rowIdx != null);
5450
5689
  const scrollParent = this.element;
@@ -5455,6 +5694,7 @@ class Wunderbaum {
5455
5694
  const vpTop = headerHeight;
5456
5695
  const vpRowTop = rowTop - scrollTop;
5457
5696
  const vpRowBottom = vpRowTop + ROW_HEIGHT;
5697
+ const topNode = options === null || options === void 0 ? void 0 : options.topNode;
5458
5698
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts );
5459
5699
  let newScrollTop = null;
5460
5700
  if (vpRowTop >= vpTop) {
@@ -5462,17 +5702,21 @@ class Wunderbaum {
5462
5702
  else {
5463
5703
  // Node is below viewport
5464
5704
  // this.log("Below viewport");
5465
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between vieeport bounds
5705
+ newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
5466
5706
  }
5467
5707
  }
5468
5708
  else {
5469
5709
  // Node is above viewport
5470
5710
  // this.log("Above viewport");
5471
- newScrollTop = rowTop - vpTop - PADDING; // leave some pixels between vieeport bounds
5711
+ newScrollTop = rowTop - vpTop - PADDING; // leave some pixels between viewport bounds
5472
5712
  }
5473
5713
  if (newScrollTop != null) {
5474
5714
  this.log(`scrollTo(${rowTop}): ${scrollTop} => ${newScrollTop}`);
5475
5715
  scrollParent.scrollTop = newScrollTop;
5716
+ if (topNode) {
5717
+ // Make sure the topNode is always visible
5718
+ this.scrollTo(topNode);
5719
+ }
5476
5720
  // this.setModified(ChangeType.vscroll);
5477
5721
  }
5478
5722
  }
@@ -5501,10 +5745,9 @@ class Wunderbaum {
5501
5745
  newLeft = colRight - vpWidth;
5502
5746
  }
5503
5747
  // util.assert(node._rowIdx != null);
5504
- // const curLeft = this.scrollContainer.scrollLeft;
5505
- this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
5506
- // const nodeOfs = node._rowIdx * ROW_HEIGHT;
5507
- // let newLeft;
5748
+ // this.log(
5749
+ // `scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`
5750
+ // );
5508
5751
  this.element.scrollLeft = newLeft;
5509
5752
  // this.setModified(ChangeType.vscroll);
5510
5753
  // }
@@ -5695,11 +5938,13 @@ class Wunderbaum {
5695
5938
  }
5696
5939
  }
5697
5940
  /** Update column headers and width. */
5698
- updateColumns(opts) {
5699
- opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
5941
+ updateColumns(options) {
5942
+ options = Object.assign({ calculateCols: true, updateRows: true }, options);
5700
5943
  const defaultMinWidth = 4;
5701
5944
  const vpWidth = this.element.clientWidth;
5702
5945
  const isGrid = this.isGrid();
5946
+ // Shorten last column width to avoid h-scrollbar
5947
+ const FIX_ADJUST_LAST_COL = 2;
5703
5948
  let totalWidth = 0;
5704
5949
  let totalWeight = 0;
5705
5950
  let fixedWidth = 0;
@@ -5708,7 +5953,7 @@ class Wunderbaum {
5708
5953
  if (!isGrid && this.isCellNav()) {
5709
5954
  this.setCellNav(false);
5710
5955
  }
5711
- if (opts.calculateCols) {
5956
+ if (options.calculateCols) {
5712
5957
  // Gather width definitions
5713
5958
  this._columnsById = {};
5714
5959
  for (let col of this.columns) {
@@ -5760,7 +6005,8 @@ class Wunderbaum {
5760
6005
  col._ofsPx = ofsPx;
5761
6006
  ofsPx += col._widthPx;
5762
6007
  }
5763
- totalWidth = ofsPx;
6008
+ this.columns[this.columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6009
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
5764
6010
  }
5765
6011
  // if (this.options.fixedCol) {
5766
6012
  // 'position: fixed' requires that the content has the correct size
@@ -5774,7 +6020,7 @@ class Wunderbaum {
5774
6020
  // util.error("BREAK");
5775
6021
  if (modified) {
5776
6022
  this._renderHeaderMarkup();
5777
- if (opts.updateRows) {
6023
+ if (options.updateRows) {
5778
6024
  this._updateRows();
5779
6025
  }
5780
6026
  }
@@ -5837,6 +6083,8 @@ class Wunderbaum {
5837
6083
  */
5838
6084
  _updateViewportImmediately() {
5839
6085
  var _a;
6086
+ // Shorten container height to avoid v-scrollbar
6087
+ const FIX_ADJUST_HEIGHT = 1;
5840
6088
  if (this._disableUpdateCount) {
5841
6089
  this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
5842
6090
  return;
@@ -5850,7 +6098,7 @@ class Wunderbaum {
5850
6098
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
5851
6099
  // const headerHeight = this.options.headerHeightPx;
5852
6100
  const headerHeight = this.headerElement.clientHeight; // May be 0
5853
- const wantHeight = this.element.clientHeight - headerHeight;
6101
+ const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
5854
6102
  if (Math.abs(height - wantHeight) > 1.0) {
5855
6103
  // this.log("resize", height, wantHeight);
5856
6104
  this.scrollContainerElement.style.height = wantHeight + "px";
@@ -5907,14 +6155,15 @@ class Wunderbaum {
5907
6155
  * (including upper and lower prefetch)
5908
6156
  * -
5909
6157
  */
5910
- _updateRows(opts) {
6158
+ _updateRows(options) {
5911
6159
  // const label = this.logTime("_updateRows");
5912
6160
  // this.log("_updateRows", opts)
5913
- opts = Object.assign({ newNodesOnly: false }, opts);
5914
- const newNodesOnly = !!opts.newNodesOnly;
6161
+ options = Object.assign({ newNodesOnly: false }, options);
6162
+ const newNodesOnly = !!options.newNodesOnly;
5915
6163
  const row_height = ROW_HEIGHT;
5916
6164
  const vp_height = this.element.clientHeight;
5917
6165
  const prefetch = RENDER_MAX_PREFETCH;
6166
+ // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
5918
6167
  const ofs = this.element.scrollTop;
5919
6168
  let startIdx = Math.max(0, ofs / row_height - prefetch);
5920
6169
  startIdx = Math.floor(startIdx);
@@ -6013,16 +6262,16 @@ class Wunderbaum {
6013
6262
  * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6014
6263
  * @returns {boolean} false if iteration was canceled
6015
6264
  */
6016
- visitRows(callback, opts) {
6265
+ visitRows(callback, options) {
6017
6266
  if (!this.root.hasChildren()) {
6018
6267
  return false;
6019
6268
  }
6020
- if (opts && opts.reverse) {
6021
- delete opts.reverse;
6022
- return this._visitRowsUp(callback, opts);
6269
+ if (options && options.reverse) {
6270
+ delete options.reverse;
6271
+ return this._visitRowsUp(callback, options);
6023
6272
  }
6024
- opts = opts || {};
6025
- 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];
6273
+ options = options || {};
6274
+ 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];
6026
6275
  parent = node.parent;
6027
6276
  while (parent) {
6028
6277
  // visit siblings
@@ -6071,11 +6320,11 @@ class Wunderbaum {
6071
6320
  node = parent;
6072
6321
  parent = parent.parent;
6073
6322
  siblingOfs = 1; //
6074
- if (!parent && opts.wrap) {
6323
+ if (!parent && options.wrap) {
6075
6324
  this.logDebug("visitRows(): wrap around");
6076
- assert(opts.start, "`wrap` option requires `start`");
6077
- stopNode = opts.start;
6078
- opts.wrap = false;
6325
+ assert(options.start, "`wrap` option requires `start`");
6326
+ stopNode = options.start;
6327
+ options.wrap = false;
6079
6328
  parent = this.root;
6080
6329
  siblingOfs = 0;
6081
6330
  }
@@ -6210,7 +6459,7 @@ class Wunderbaum {
6210
6459
  }
6211
6460
  Wunderbaum.sequence = 0;
6212
6461
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6213
- Wunderbaum.version = "v0.0.6"; // Set to semver by 'grunt release'
6462
+ Wunderbaum.version = "v0.0.8"; // Set to semver by 'grunt release'
6214
6463
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6215
6464
  Wunderbaum.util = util;
6216
6465