wunderbaum 0.0.4 → 0.0.5

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.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
4
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  /** @module util */
7
7
  /** Readable names for `MouseEvent.button` */
@@ -173,7 +173,7 @@ function extractHtmlText(s) {
173
173
  *
174
174
  * If a `<span class="wb-col">` is passed, the first child input is used.
175
175
  * Depending on the target element type, `value` is interpreted accordingly.
176
- * For example for a checkbox, a value of true, false, or null is returned if the
176
+ * For example for a checkbox, a value of true, false, or null is returned if
177
177
  * the element is checked, unchecked, or indeterminate.
178
178
  * For datetime input control a numerical value is assumed, etc.
179
179
  *
@@ -269,7 +269,9 @@ function setValueToElem(elem, value) {
269
269
  const type = input.type;
270
270
  switch (type) {
271
271
  case "checkbox":
272
- input.indeterminate = value == null;
272
+ // An explicit `null` value is interpreted as 'indeterminate'.
273
+ // `undefined` is interpreted as 'unchecked'
274
+ input.indeterminate = value === null;
273
275
  input.checked = !!value;
274
276
  break;
275
277
  case "date":
@@ -565,7 +567,7 @@ function toSet(val) {
565
567
  }
566
568
  throw new Error("Cannot convert to Set<string>: " + val);
567
569
  }
568
- /**Return a canonical string representation for an object's type (e.g. 'array', 'number', ...) */
570
+ /** Return a canonical string representation for an object's type (e.g. 'array', 'number', ...). */
569
571
  function type(obj) {
570
572
  return Object.prototype.toString
571
573
  .call(obj)
@@ -579,12 +581,12 @@ function type(obj) {
579
581
  * previous call.
580
582
  * Example:
581
583
  * ```js
582
- * throttledFoo = util.addaptiveThrottle(foo.bind(this), {});
584
+ * throttledFoo = util.adaptiveThrottle(foo.bind(this), {});
583
585
  * throttledFoo();
584
586
  * throttledFoo();
585
587
  * ```
586
588
  */
587
- function addaptiveThrottle(callback, options) {
589
+ function adaptiveThrottle(callback, options) {
588
590
  let waiting = 0; // Initially, we're not waiting
589
591
  let pendingArgs = null;
590
592
  const opts = Object.assign({
@@ -598,7 +600,7 @@ function addaptiveThrottle(callback, options) {
598
600
  const throttledFn = (...args) => {
599
601
  if (waiting) {
600
602
  pendingArgs = args;
601
- // console.log(`addaptiveThrottle() queing request #${waiting}...`, args);
603
+ // console.log(`adaptiveThrottle() queing request #${waiting}...`, args);
602
604
  waiting += 1;
603
605
  }
604
606
  else {
@@ -606,7 +608,7 @@ function addaptiveThrottle(callback, options) {
606
608
  waiting = 1;
607
609
  const useArgs = args; // pendingArgs || args;
608
610
  pendingArgs = null;
609
- // console.log(`addaptiveThrottle() execute...`, useArgs);
611
+ // console.log(`adaptiveThrottle() execute...`, useArgs);
610
612
  const start = Date.now();
611
613
  try {
612
614
  callback.apply(this, useArgs);
@@ -618,7 +620,7 @@ function addaptiveThrottle(callback, options) {
618
620
  const curDelay = Math.min(Math.max(minDelay, elap * opts.delayFactor), maxDelay);
619
621
  const useDelay = Math.max(minDelay, curDelay - elap);
620
622
  // console.log(
621
- // `addaptiveThrottle() calling worker took ${elap}ms. delay = ${curDelay}ms, using ${useDelay}ms`,
623
+ // `adaptiveThrottle() calling worker took ${elap}ms. delay = ${curDelay}ms, using ${useDelay}ms`,
622
624
  // pendingArgs
623
625
  // );
624
626
  setTimeout(() => {
@@ -628,7 +630,7 @@ function addaptiveThrottle(callback, options) {
628
630
  if (pendingArgs != null) {
629
631
  // There was another request while running or waiting
630
632
  // console.log(
631
- // `addaptiveThrottle() re-trigger (missed ${skipped})...`,
633
+ // `adaptiveThrottle() re-trigger (missed ${skipped})...`,
632
634
  // pendingArgs
633
635
  // );
634
636
  throttledFn.apply(this, pendingArgs);
@@ -674,13 +676,13 @@ var util = /*#__PURE__*/Object.freeze({
674
676
  getOption: getOption,
675
677
  toSet: toSet,
676
678
  type: type,
677
- addaptiveThrottle: addaptiveThrottle
679
+ adaptiveThrottle: adaptiveThrottle
678
680
  });
679
681
 
680
682
  /*!
681
683
  * Wunderbaum - common
682
684
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
683
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
685
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
684
686
  */
685
687
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
686
688
  const ROW_HEIGHT = 22;
@@ -688,6 +690,7 @@ const ICON_WIDTH = 20;
688
690
  const ROW_EXTRA_PAD = 7; // 2x $col-padding-x + 3px rounding errors
689
691
  const RENDER_MAX_PREFETCH = 5;
690
692
  const TEST_IMG = new RegExp(/\.|\//); // strings are considered image urls if they contain '.' or '/'
693
+ /** Possible values for `setModified()`. */
691
694
  var ChangeType;
692
695
  (function (ChangeType) {
693
696
  /** Re-render the whole viewport, headers, and all rows. */
@@ -705,6 +708,7 @@ var ChangeType;
705
708
  /** Update the 'top' property of all rows. */
706
709
  ChangeType["vscroll"] = "vscroll";
707
710
  })(ChangeType || (ChangeType = {}));
711
+ /** Possible values for `setStatus()`. */
708
712
  var NodeStatusType;
709
713
  (function (NodeStatusType) {
710
714
  NodeStatusType["ok"] = "ok";
@@ -713,7 +717,7 @@ var NodeStatusType;
713
717
  NodeStatusType["noData"] = "noData";
714
718
  // paging = "paging",
715
719
  })(NodeStatusType || (NodeStatusType = {}));
716
- /**Define the subregion of a node, where an event occurred. */
720
+ /** Define the subregion of a node, where an event occurred. */
717
721
  var TargetType;
718
722
  (function (TargetType) {
719
723
  TargetType["unknown"] = "";
@@ -726,8 +730,9 @@ var TargetType;
726
730
  })(TargetType || (TargetType = {}));
727
731
  let iconMap = {
728
732
  error: "bi bi-exclamation-triangle",
729
- // loading: "bi bi-hourglass-split",
730
- loading: "bi bi-arrow-repeat wb-spin",
733
+ // loading: "bi bi-hourglass-split wb-busy",
734
+ loading: "bi bi-chevron-right wb-busy",
735
+ // loading: "bi bi-arrow-repeat wb-spin",
731
736
  // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
732
737
  // noData: "bi bi-search",
733
738
  noData: "bi bi-question-circle",
@@ -747,6 +752,7 @@ let iconMap = {
747
752
  folderOpen: "bi bi-folder2-open",
748
753
  doc: "bi bi-file-earmark",
749
754
  };
755
+ /** Initial navigation mode and possible transition. */
750
756
  var NavigationModeOption;
751
757
  (function (NavigationModeOption) {
752
758
  NavigationModeOption["startRow"] = "startRow";
@@ -754,6 +760,7 @@ var NavigationModeOption;
754
760
  NavigationModeOption["startCell"] = "startCell";
755
761
  NavigationModeOption["row"] = "row";
756
762
  })(NavigationModeOption || (NavigationModeOption = {}));
763
+ /** Tree's current navigation mode (see `tree.setNavigationMode()`). */
757
764
  var NavigationMode;
758
765
  (function (NavigationMode) {
759
766
  NavigationMode["row"] = "row";
@@ -785,14 +792,14 @@ const KEY_TO_ACTION_DICT = {
785
792
  "-": "collapse",
786
793
  Subtract: "collapse",
787
794
  };
788
- /** */
795
+ /** Return a callback that returns true if the node title contains a substring (case-insensitive). */
789
796
  function makeNodeTitleMatcher(s) {
790
797
  s = escapeRegex(s.toLowerCase());
791
798
  return function (node) {
792
799
  return node.title.toLowerCase().indexOf(s) >= 0;
793
800
  };
794
801
  }
795
- /** */
802
+ /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
796
803
  function makeNodeTitleStartMatcher(s) {
797
804
  s = escapeRegex(s);
798
805
  const reMatch = new RegExp("^" + s, "i");
@@ -804,7 +811,7 @@ function makeNodeTitleStartMatcher(s) {
804
811
  /*!
805
812
  * Wunderbaum - wb_extension_base
806
813
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
807
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
814
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
808
815
  */
809
816
  class WunderbaumExtension {
810
817
  constructor(tree, id, defaults) {
@@ -1095,7 +1102,7 @@ function debounce(func, wait = 0, options = {}) {
1095
1102
  /*!
1096
1103
  * Wunderbaum - ext-filter
1097
1104
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1098
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1105
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
1099
1106
  */
1100
1107
  const START_MARKER = "\uFFF7";
1101
1108
  const END_MARKER = "\uFFF8";
@@ -1104,6 +1111,7 @@ const RE_END_MARTKER = new RegExp(escapeRegex(END_MARKER), "g");
1104
1111
  class FilterExtension extends WunderbaumExtension {
1105
1112
  constructor(tree) {
1106
1113
  super(tree, "filter", {
1114
+ attachInput: null,
1107
1115
  autoApply: true,
1108
1116
  autoExpand: false,
1109
1117
  counter: true,
@@ -1112,14 +1120,14 @@ class FilterExtension extends WunderbaumExtension {
1112
1120
  hideExpanders: false,
1113
1121
  highlight: true,
1114
1122
  leavesOnly: false,
1115
- mode: "hide",
1123
+ mode: "dim",
1116
1124
  noData: true, // Display a 'no data' status node if result is empty
1117
1125
  });
1118
1126
  this.lastFilterArgs = null;
1119
1127
  }
1120
1128
  init() {
1121
1129
  super.init();
1122
- let attachInput = this.getPluginOption("attachInput");
1130
+ const attachInput = this.getPluginOption("attachInput");
1123
1131
  if (attachInput) {
1124
1132
  this.queryInput = elemFromSelector(attachInput);
1125
1133
  onEvent(this.queryInput, "input", debounce((e) => {
@@ -1128,6 +1136,16 @@ class FilterExtension extends WunderbaumExtension {
1128
1136
  }, 700));
1129
1137
  }
1130
1138
  }
1139
+ setPluginOption(name, value) {
1140
+ // alert("filter opt=" + name + ", " + value)
1141
+ super.setPluginOption(name, value);
1142
+ switch (name) {
1143
+ case "mode":
1144
+ this.tree.filterMode = value === "hide" ? "hide" : "dim";
1145
+ this.tree.updateFilter();
1146
+ break;
1147
+ }
1148
+ }
1131
1149
  _applyFilterNoUpdate(filter, branchMode, _opts) {
1132
1150
  return this.tree.runWithoutUpdate(() => {
1133
1151
  return this._applyFilterImpl(filter, branchMode, _opts);
@@ -1389,16 +1407,39 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1389
1407
  /*!
1390
1408
  * Wunderbaum - ext-keynav
1391
1409
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1392
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1410
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
1393
1411
  */
1394
1412
  class KeynavExtension extends WunderbaumExtension {
1395
1413
  constructor(tree) {
1396
1414
  super(tree, "keynav", {});
1397
1415
  }
1416
+ _getEmbeddedInputElem(elem, setFocus = false) {
1417
+ var _a;
1418
+ let input = null;
1419
+ if (elem && elem.type != null) {
1420
+ input = elem;
1421
+ }
1422
+ else {
1423
+ // ,[contenteditable]
1424
+ const ace = (_a = this.tree.getActiveColElem()) === null || _a === void 0 ? void 0 : _a.querySelector("input,select");
1425
+ if (ace) {
1426
+ input = ace;
1427
+ }
1428
+ }
1429
+ if (setFocus && input) {
1430
+ this.tree.log("focus", input);
1431
+ input.focus();
1432
+ }
1433
+ return input;
1434
+ }
1398
1435
  onKeyEvent(data) {
1399
- let event = data.event, eventName = eventToString(event), focusNode, node = data.node, tree = this.tree, opts = data.options, handled = true, activate = !event.ctrlKey || opts.autoActivate;
1400
- const navModeOption = opts.navigationMode;
1401
- tree.logDebug(`onKeyEvent: ${eventName}`);
1436
+ const event = data.event, tree = this.tree, opts = data.options, activate = !event.ctrlKey || opts.autoActivate, curInput = this._getEmbeddedInputElem(event.target), navModeOption = opts.navigationMode, isCellEditMode = tree.navMode === NavigationMode.cellEdit;
1437
+ let focusNode, eventName = eventToString(event), node = data.node, handled = true;
1438
+ tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
1439
+ if (!tree.isEnabled()) {
1440
+ // tree.logDebug(`onKeyEvent ignored for disabled tree: ${eventName}`);
1441
+ return false;
1442
+ }
1402
1443
  // Let callback prevent default processing
1403
1444
  if (tree._callEvent("keydown", data) === false) {
1404
1445
  return false;
@@ -1424,12 +1465,14 @@ class KeynavExtension extends WunderbaumExtension {
1424
1465
  }
1425
1466
  }
1426
1467
  if (tree.navMode === NavigationMode.row) {
1468
+ // -----------------------------------------------------------------------
1469
+ // --- Row Mode ---
1470
+ // -----------------------------------------------------------------------
1427
1471
  // --- Quick-Search
1428
1472
  if (opts.quicksearch &&
1429
1473
  eventName.length === 1 &&
1430
- /^\w$/.test(eventName)
1431
- // && !$target.is(":input:enabled")
1432
- ) {
1474
+ /^\w$/.test(eventName) &&
1475
+ !curInput) {
1433
1476
  // Allow to search for longer streaks if typed in quickly
1434
1477
  const stamp = Date.now();
1435
1478
  if (stamp - tree.lastQuicksearchTime > 500) {
@@ -1506,7 +1549,18 @@ class KeynavExtension extends WunderbaumExtension {
1506
1549
  }
1507
1550
  }
1508
1551
  else {
1509
- // Standard navigation (cell mode)
1552
+ // -----------------------------------------------------------------------
1553
+ // --- Cell Mode ---
1554
+ // -----------------------------------------------------------------------
1555
+ // // Standard navigation (cell mode)
1556
+ // if (isCellEditMode && NAVIGATE_IN_INPUT_KEYS.has(eventName)) {
1557
+ // }
1558
+ if (eventName === "Tab") {
1559
+ eventName = "ArrowRight";
1560
+ }
1561
+ else if (eventName === "Shift+Tab") {
1562
+ eventName = tree.activeColIdx > 0 ? "ArrowLeft" : "";
1563
+ }
1510
1564
  switch (eventName) {
1511
1565
  case " ":
1512
1566
  if (tree.activeColIdx === 0 && node.getOption("checkbox")) {
@@ -1525,13 +1579,21 @@ class KeynavExtension extends WunderbaumExtension {
1525
1579
  node.setExpanded(!node.isExpanded());
1526
1580
  handled = true;
1527
1581
  }
1582
+ else if (!isCellEditMode &&
1583
+ (navModeOption === NavigationModeOption.startCell ||
1584
+ navModeOption === NavigationModeOption.startRow)) {
1585
+ tree.setNavigationMode(NavigationMode.cellEdit);
1586
+ this._getEmbeddedInputElem(null, true); // set focus to input
1587
+ handled = true;
1588
+ }
1528
1589
  break;
1529
1590
  case "Escape":
1530
1591
  if (tree.navMode === NavigationMode.cellEdit) {
1531
1592
  tree.setNavigationMode(NavigationMode.cellNav);
1532
1593
  handled = true;
1533
1594
  }
1534
- else if (tree.navMode === NavigationMode.cellNav) {
1595
+ else if (tree.navMode === NavigationMode.cellNav &&
1596
+ navModeOption !== NavigationModeOption.cell) {
1535
1597
  tree.setNavigationMode(NavigationMode.row);
1536
1598
  handled = true;
1537
1599
  }
@@ -1539,6 +1601,9 @@ class KeynavExtension extends WunderbaumExtension {
1539
1601
  case "ArrowLeft":
1540
1602
  if (tree.activeColIdx > 0) {
1541
1603
  tree.setColumn(tree.activeColIdx - 1);
1604
+ if (isCellEditMode) {
1605
+ this._getEmbeddedInputElem(null, true); // set focus to input
1606
+ }
1542
1607
  handled = true;
1543
1608
  }
1544
1609
  else if (navModeOption !== NavigationModeOption.cell) {
@@ -1549,6 +1614,9 @@ class KeynavExtension extends WunderbaumExtension {
1549
1614
  case "ArrowRight":
1550
1615
  if (tree.activeColIdx < tree.columns.length - 1) {
1551
1616
  tree.setColumn(tree.activeColIdx + 1);
1617
+ if (isCellEditMode) {
1618
+ this._getEmbeddedInputElem(null, true); // set focus to input
1619
+ }
1552
1620
  handled = true;
1553
1621
  }
1554
1622
  break;
@@ -1564,6 +1632,10 @@ class KeynavExtension extends WunderbaumExtension {
1564
1632
  case "PageDown":
1565
1633
  case "PageUp":
1566
1634
  node.navigate(eventName, { activate: activate, event: event });
1635
+ if (isCellEditMode) {
1636
+ this._getEmbeddedInputElem(null, true); // set focus to input
1637
+ }
1638
+ handled = true;
1567
1639
  break;
1568
1640
  default:
1569
1641
  handled = false;
@@ -1579,7 +1651,7 @@ class KeynavExtension extends WunderbaumExtension {
1579
1651
  /*!
1580
1652
  * Wunderbaum - ext-logger
1581
1653
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1582
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1654
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
1583
1655
  */
1584
1656
  class LoggerExtension extends WunderbaumExtension {
1585
1657
  constructor(tree) {
@@ -1619,7 +1691,7 @@ class LoggerExtension extends WunderbaumExtension {
1619
1691
  /*!
1620
1692
  * Wunderbaum - ext-dnd
1621
1693
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1622
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1694
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
1623
1695
  */
1624
1696
  const nodeMimeType = "application/x-wunderbaum-node";
1625
1697
  class DndExtension extends WunderbaumExtension {
@@ -1689,7 +1761,7 @@ class DndExtension extends WunderbaumExtension {
1689
1761
  const ltn = this.lastTargetNode;
1690
1762
  this.lastEnterStamp = 0;
1691
1763
  if (ltn) {
1692
- ltn.removeClass("wb-drop-target wb-drop-over wb-drop-after wb-drop-before");
1764
+ ltn.setClass("wb-drop-target wb-drop-over wb-drop-after wb-drop-before", false);
1693
1765
  this.lastTargetNode = null;
1694
1766
  }
1695
1767
  }
@@ -1733,7 +1805,7 @@ class DndExtension extends WunderbaumExtension {
1733
1805
  }
1734
1806
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
1735
1807
  autoScroll(event) {
1736
- let tree = this.tree, dndOpts = tree.options.dnd, sp = tree.scrollContainer, sensitivity = dndOpts.scrollSensitivity, speed = dndOpts.scrollSpeed, scrolled = 0;
1808
+ let tree = this.tree, dndOpts = tree.options.dnd, sp = tree.scrollContainerElement, sensitivity = dndOpts.scrollSensitivity, speed = dndOpts.scrollSpeed, scrolled = 0;
1737
1809
  const scrollTop = sp.offsetTop;
1738
1810
  if (scrollTop + sp.offsetHeight - event.pageY < sensitivity) {
1739
1811
  const delta = sp.scrollHeight - sp.clientHeight - scrollTop;
@@ -1783,13 +1855,13 @@ class DndExtension extends WunderbaumExtension {
1783
1855
  setTimeout(() => {
1784
1856
  // Decouple this call, so the CSS is applied to the node, but not to
1785
1857
  // the system generated drag image
1786
- srcNode.addClass("wb-drag-source");
1858
+ srcNode.setClass("wb-drag-source");
1787
1859
  }, 0);
1788
1860
  // --- drag ---
1789
1861
  }
1790
1862
  else if (e.type === "drag") ;
1791
1863
  else if (e.type === "dragend") {
1792
- srcNode.removeClass("wb-drag-source");
1864
+ srcNode.setClass("wb-drag-source", false);
1793
1865
  this.srcNode = null;
1794
1866
  if (this.lastTargetNode) {
1795
1867
  this._leaveNode();
@@ -1843,7 +1915,7 @@ class DndExtension extends WunderbaumExtension {
1843
1915
  }
1844
1916
  this.lastAllowedDropRegions = regionSet;
1845
1917
  this.lastDropEffect = dt.dropEffect;
1846
- targetNode.addClass("wb-drop-target");
1918
+ targetNode.setClass("wb-drop-target");
1847
1919
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
1848
1920
  return false;
1849
1921
  // --- dragover ---
@@ -1862,9 +1934,9 @@ class DndExtension extends WunderbaumExtension {
1862
1934
  if (!region) {
1863
1935
  return; // We already rejected in dragenter
1864
1936
  }
1865
- targetNode.toggleClass("wb-drop-over", region === "over");
1866
- targetNode.toggleClass("wb-drop-before", region === "before");
1867
- targetNode.toggleClass("wb-drop-after", region === "after");
1937
+ targetNode.setClass("wb-drop-over", region === "over");
1938
+ targetNode.setClass("wb-drop-before", region === "before");
1939
+ targetNode.setClass("wb-drop-after", region === "after");
1868
1940
  // console.log("dragover", e);
1869
1941
  // dt.dropEffect = this.lastDropEffect!;
1870
1942
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
@@ -1887,7 +1959,7 @@ class DndExtension extends WunderbaumExtension {
1887
1959
  /*!
1888
1960
  * Wunderbaum - drag_observer
1889
1961
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1890
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1962
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
1891
1963
  */
1892
1964
  /**
1893
1965
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2021,7 +2093,7 @@ class DragObserver {
2021
2093
  /*!
2022
2094
  * Wunderbaum - ext-grid
2023
2095
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2024
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2096
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
2025
2097
  */
2026
2098
  class GridExtension extends WunderbaumExtension {
2027
2099
  constructor(tree) {
@@ -2058,7 +2130,7 @@ class GridExtension extends WunderbaumExtension {
2058
2130
  /*!
2059
2131
  * Wunderbaum - deferred
2060
2132
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2061
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2133
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
2062
2134
  */
2063
2135
  /**
2064
2136
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2111,7 +2183,7 @@ class Deferred {
2111
2183
  /*!
2112
2184
  * Wunderbaum - wunderbaum_node
2113
2185
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2114
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2186
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
2115
2187
  */
2116
2188
  /** Top-level properties that can be passed with `data`. */
2117
2189
  const NODE_PROPS = new Set([
@@ -2130,7 +2202,7 @@ const NODE_PROPS = new Set([
2130
2202
  const NODE_ATTRS = new Set([
2131
2203
  "checkbox",
2132
2204
  "expanded",
2133
- "extraClasses",
2205
+ "classes",
2134
2206
  "folder",
2135
2207
  "icon",
2136
2208
  "iconTooltip",
@@ -2172,8 +2244,8 @@ class WunderbaumNode {
2172
2244
  * @see {@link isSelected}, {@link setSelected}. */
2173
2245
  this.selected = false;
2174
2246
  /** Additional classes added to `div.wb-row`.
2175
- * @see {@link addClass}, {@link removeClass}, {@link toggleClass}. */
2176
- this.extraClasses = new Set();
2247
+ * @see {@link hasClass}, {@link setClass}. */
2248
+ this.classes = null; //new Set<string>();
2177
2249
  /** Custom data that was passed to the constructor */
2178
2250
  this.data = {};
2179
2251
  this._isLoading = false;
@@ -2202,9 +2274,7 @@ class WunderbaumNode {
2202
2274
  this.lazy = data.lazy === true;
2203
2275
  this.selected = data.selected === true;
2204
2276
  if (data.classes) {
2205
- for (const c of data.classes.split(" ")) {
2206
- this.extraClasses.add(c.trim());
2207
- }
2277
+ this.setClass(data.classes);
2208
2278
  }
2209
2279
  // Store custom fields as `node.data`
2210
2280
  for (const [key, value] of Object.entries(data)) {
@@ -2222,7 +2292,7 @@ class WunderbaumNode {
2222
2292
  * @internal
2223
2293
  */
2224
2294
  toString() {
2225
- return "WunderbaumNode@" + this.key + "<'" + this.title + "'>";
2295
+ return `WunderbaumNode@${this.key}<'${this.title}'>`;
2226
2296
  }
2227
2297
  // /** Return an option value. */
2228
2298
  // protected _getOpt(
@@ -2344,29 +2414,39 @@ class WunderbaumNode {
2344
2414
  applyCommand(cmd, opts) {
2345
2415
  return this.tree.applyCommand(cmd, this, opts);
2346
2416
  }
2347
- addClass(className) {
2348
- const cnSet = toSet(className);
2349
- cnSet.forEach((cn) => {
2350
- var _a;
2351
- this.extraClasses.add(cn);
2352
- (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.classList.add(cn);
2353
- });
2354
- }
2355
- removeClass(className) {
2356
- const cnSet = toSet(className);
2357
- cnSet.forEach((cn) => {
2358
- var _a;
2359
- this.extraClasses.delete(cn);
2360
- (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.classList.remove(cn);
2361
- });
2362
- }
2363
- toggleClass(className, flag) {
2417
+ /**
2418
+ * Add/remove one or more classes to `<div class='wb-row'>`.
2419
+ *
2420
+ * This also maintains `node.classes`, so the class will survive a re-render.
2421
+ *
2422
+ * @param className one or more class names. Multiple classes can be passed
2423
+ * as space-separated string, array of strings, or set of strings.
2424
+ */
2425
+ setClass(className, flag = true) {
2364
2426
  const cnSet = toSet(className);
2365
- cnSet.forEach((cn) => {
2366
- var _a;
2367
- flag ? this.extraClasses.add(cn) : this.extraClasses.delete(cn);
2368
- (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.classList.toggle(cn, flag);
2369
- });
2427
+ if (flag) {
2428
+ if (this.classes === null) {
2429
+ this.classes = new Set();
2430
+ }
2431
+ cnSet.forEach((cn) => {
2432
+ var _a;
2433
+ this.classes.add(cn);
2434
+ (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.classList.toggle(cn, flag);
2435
+ });
2436
+ }
2437
+ else {
2438
+ if (this.classes === null) {
2439
+ return;
2440
+ }
2441
+ cnSet.forEach((cn) => {
2442
+ var _a;
2443
+ this.classes.delete(cn);
2444
+ (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.classList.toggle(cn, flag);
2445
+ });
2446
+ if (this.classes.size === 0) {
2447
+ this.classes = null;
2448
+ }
2449
+ }
2370
2450
  }
2371
2451
  /** */
2372
2452
  async expandAll(flag = true) {
@@ -2540,6 +2620,10 @@ class WunderbaumNode {
2540
2620
  }
2541
2621
  return !!(this.children && this.children.length);
2542
2622
  }
2623
+ /** Return true if node has className set. */
2624
+ hasClass(className) {
2625
+ return this.classes ? this.classes.has(className) : false;
2626
+ }
2543
2627
  /** Return true if this node is the currently active tree node. */
2544
2628
  isActive() {
2545
2629
  return this.tree.activeNode === this;
@@ -2685,8 +2769,11 @@ class WunderbaumNode {
2685
2769
  assert(isPlainObject(source));
2686
2770
  assert(source.children, "If `source` is an object, it must have a `children` property");
2687
2771
  if (source.types) {
2688
- // TODO: convert types.classes to Set()
2689
- extend(tree.types, source.types);
2772
+ tree.setTypes(source.types, false);
2773
+ }
2774
+ if (source.columns) {
2775
+ tree.columns = source.columns;
2776
+ tree.updateColumns({ calculateCols: false });
2690
2777
  }
2691
2778
  this.addChildren(source.children);
2692
2779
  this._callEvent("load");
@@ -3282,8 +3369,8 @@ class WunderbaumNode {
3282
3369
  treeOptions.skeleton ? rowClasses.push("wb-skeleton") : 0;
3283
3370
  // Replace previous classes:
3284
3371
  rowDiv.className = rowClasses.join(" ");
3285
- // Add classes from `node.extraClasses`
3286
- rowDiv.classList.add(...this.extraClasses);
3372
+ // Add classes from `node.classes`
3373
+ this.classes ? rowDiv.classList.add(...this.classes) : 0;
3287
3374
  // Add classes from `tree.types[node.type]`
3288
3375
  if (typeInfo && typeInfo.classes) {
3289
3376
  rowDiv.classList.add(...typeInfo.classes);
@@ -3678,9 +3765,9 @@ class WunderbaumNode {
3678
3765
  * @param {object} [extra]
3679
3766
  */
3680
3767
  triggerModify(operation, extra) {
3681
- if (!this.parent) {
3682
- return;
3683
- }
3768
+ // if (!this.parent) {
3769
+ // return;
3770
+ // }
3684
3771
  this.parent.triggerModifyChild(operation, this, extra);
3685
3772
  }
3686
3773
  /**
@@ -3763,7 +3850,7 @@ WunderbaumNode.sequence = 0;
3763
3850
  /*!
3764
3851
  * Wunderbaum - ext-edit
3765
3852
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
3766
- * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
3853
+ * v0.0.5, Sun, 21 Aug 2022 15:04:31 GMT (https://github.com/mar10/wunderbaum)
3767
3854
  */
3768
3855
  // const START_MARKER = "\uFFF7";
3769
3856
  class EditExtension extends WunderbaumExtension {
@@ -3793,7 +3880,7 @@ class EditExtension extends WunderbaumExtension {
3793
3880
  _applyChange(eventName, node, colElem, extra) {
3794
3881
  let res;
3795
3882
  node.log(`_applyChange(${eventName})`, extra);
3796
- colElem.classList.add("wb-dirty");
3883
+ colElem.classList.add("wb-busy");
3797
3884
  colElem.classList.remove("wb-error");
3798
3885
  try {
3799
3886
  res = node._callEvent(eventName, extra);
@@ -3801,7 +3888,7 @@ class EditExtension extends WunderbaumExtension {
3801
3888
  catch (err) {
3802
3889
  node.logError(`Error in ${eventName} event handler`, err);
3803
3890
  colElem.classList.add("wb-error");
3804
- colElem.classList.remove("wb-dirty");
3891
+ colElem.classList.remove("wb-busy");
3805
3892
  }
3806
3893
  // Convert scalar return value to a resolved promise
3807
3894
  if (!(res instanceof Promise)) {
@@ -3813,7 +3900,7 @@ class EditExtension extends WunderbaumExtension {
3813
3900
  colElem.classList.add("wb-error");
3814
3901
  })
3815
3902
  .finally(() => {
3816
- colElem.classList.remove("wb-dirty");
3903
+ colElem.classList.remove("wb-busy");
3817
3904
  });
3818
3905
  return res;
3819
3906
  }
@@ -3852,12 +3939,12 @@ class EditExtension extends WunderbaumExtension {
3852
3939
  const eventName = eventToString(event);
3853
3940
  const tree = this.tree;
3854
3941
  const trigger = this.getPluginOption("trigger");
3855
- const inputElem = event.target && event.target.closest("input,[contenteditable]");
3856
- // let handled = true;
3942
+ // const inputElem =
3943
+ // event.target && event.target.closest("input,[contenteditable]");
3857
3944
  tree.logDebug(`_preprocessKeyEvent: ${eventName}`);
3858
3945
  // --- Title editing: apply/discard ---
3859
- if (inputElem) {
3860
- //this.isEditingTitle()) {
3946
+ // if (inputElem) {
3947
+ if (this.isEditingTitle()) {
3861
3948
  switch (eventName) {
3862
3949
  case "Enter":
3863
3950
  this._stopEditTitle(true, { event: event });
@@ -3920,7 +4007,7 @@ class EditExtension extends WunderbaumExtension {
3920
4007
  titleSpan.innerHTML = inputHtml;
3921
4008
  const inputElem = titleSpan.firstElementChild;
3922
4009
  if (validity) {
3923
- // Permanently apply input validations (CSS and tooltip)
4010
+ // Permanently apply input validations (CSS and tooltip)
3924
4011
  inputElem.addEventListener("keydown", (e) => {
3925
4012
  inputElem.setCustomValidity("");
3926
4013
  if (!inputElem.reportValidity()) ;
@@ -4036,7 +4123,7 @@ class EditExtension extends WunderbaumExtension {
4036
4123
  return;
4037
4124
  }
4038
4125
  const newNode = node.addNode(init, mode);
4039
- newNode.addClass("wb-edit-new");
4126
+ newNode.setClass("wb-edit-new");
4040
4127
  this.relatedNode = node;
4041
4128
  // Don't filter new nodes:
4042
4129
  newNode.match = true;
@@ -4049,17 +4136,29 @@ class EditExtension extends WunderbaumExtension {
4049
4136
  /*!
4050
4137
  * wunderbaum.ts
4051
4138
  *
4052
- * A tree control.
4139
+ * A treegrid control.
4053
4140
  *
4054
4141
  * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
4055
- * Released under the MIT license.
4142
+ * https://github.com/mar10/wunderbaum
4056
4143
  *
4057
- * @version v0.0.4
4058
- * @date Tue, 03 May 2022 06:21:31 GMT
4144
+ * Released under the MIT license.
4145
+ * @version v0.0.5
4146
+ * @date Sun, 21 Aug 2022 15:04:31 GMT
4059
4147
  */
4060
4148
  // const class_prefix = "wb-";
4061
4149
  // const node_props: string[] = ["title", "key", "refKey"];
4062
4150
  // const MAX_CHANGED_NODES = 10;
4151
+ class WbSystemRoot extends WunderbaumNode {
4152
+ constructor(tree) {
4153
+ super(tree, null, {
4154
+ key: "__root__",
4155
+ title: tree.id,
4156
+ });
4157
+ }
4158
+ toString() {
4159
+ return `WbSystemRoot@${this.key}<'${this.tree.id}'>`;
4160
+ }
4161
+ }
4063
4162
  /**
4064
4163
  * A persistent plain object or array.
4065
4164
  *
@@ -4067,6 +4166,7 @@ class EditExtension extends WunderbaumExtension {
4067
4166
  */
4068
4167
  class Wunderbaum {
4069
4168
  constructor(options) {
4169
+ this.enabled = true;
4070
4170
  this.extensionList = [];
4071
4171
  this.extensions = {};
4072
4172
  this.keyMap = new Map();
@@ -4119,11 +4219,14 @@ class Wunderbaum {
4119
4219
  columns: null,
4120
4220
  types: null,
4121
4221
  // escapeTitles: true,
4222
+ enabled: true,
4223
+ fixedCol: false,
4122
4224
  showSpinner: false,
4123
- checkbox: true,
4225
+ checkbox: false,
4124
4226
  minExpandLevel: 0,
4125
4227
  updateThrottleWait: 200,
4126
4228
  skeleton: false,
4229
+ attachBreadcrumb: null,
4127
4230
  // --- KeyNav ---
4128
4231
  navigationMode: NavigationModeOption.startRow,
4129
4232
  quicksearch: true,
@@ -4167,10 +4270,7 @@ class Wunderbaum {
4167
4270
  }
4168
4271
  });
4169
4272
  this.id = opts.id || "wb_" + ++Wunderbaum.sequence;
4170
- this.root = new WunderbaumNode(this, null, {
4171
- key: "__root__",
4172
- // title: "__root__",
4173
- });
4273
+ this.root = new WbSystemRoot(this);
4174
4274
  this._registerExtension(new KeynavExtension(this));
4175
4275
  this._registerExtension(new EditExtension(this));
4176
4276
  this._registerExtension(new FilterExtension(this));
@@ -4180,18 +4280,14 @@ class Wunderbaum {
4180
4280
  // --- Evaluate options
4181
4281
  this.columns = opts.columns;
4182
4282
  delete opts.columns;
4183
- if (!this.columns) {
4283
+ if (!this.columns || !this.columns.length) {
4184
4284
  let defaultName = typeof opts.header === "string" ? opts.header : this.id;
4185
4285
  this.columns = [{ id: "*", title: defaultName, width: "*" }];
4186
4286
  }
4187
- this.types = opts.types || {};
4188
- delete opts.types;
4189
- // Convert `TYPE.classes` to a Set
4190
- for (let t of Object.values(this.types)) {
4191
- if (t.classes) {
4192
- t.classes = toSet(t.classes);
4193
- }
4287
+ if (opts.types) {
4288
+ this.setTypes(opts.types, true);
4194
4289
  }
4290
+ delete opts.types;
4195
4291
  if (this.columns.length === 1) {
4196
4292
  opts.navigationMode = NavigationModeOption.row;
4197
4293
  }
@@ -4199,7 +4295,7 @@ class Wunderbaum {
4199
4295
  opts.navigationMode === NavigationModeOption.startCell) {
4200
4296
  this.navMode = NavigationMode.cellNav;
4201
4297
  }
4202
- this._updateViewportThrottled = addaptiveThrottle(this._updateViewport.bind(this), {});
4298
+ this._updateViewportThrottled = adaptiveThrottle(this._updateViewport.bind(this), {});
4203
4299
  // --- Create Markup
4204
4300
  this.element = elemFromSelector(opts.element);
4205
4301
  assert(!!this.element, `Invalid 'element' option: ${opts.element}`);
@@ -4243,13 +4339,17 @@ class Wunderbaum {
4243
4339
  <div class="wb-scroll-container">
4244
4340
  <div class="wb-node-list"></div>
4245
4341
  </div>`;
4246
- this.scrollContainer = this.element.querySelector("div.wb-scroll-container");
4247
- this.nodeListElement = this.scrollContainer.querySelector("div.wb-node-list");
4342
+ this.scrollContainerElement = this.element.querySelector("div.wb-scroll-container");
4343
+ this.nodeListElement = this.scrollContainerElement.querySelector("div.wb-node-list");
4248
4344
  this.headerElement = this.element.querySelector("div.wb-header");
4249
- if (this.columns.length > 1) {
4250
- this.element.classList.add("wb-grid");
4251
- }
4345
+ this.element.classList.toggle("wb-grid", this.columns.length > 1);
4252
4346
  this._initExtensions();
4347
+ // --- apply initial options
4348
+ ["enabled", "fixedCol"].forEach((optName) => {
4349
+ if (opts[optName] != null) {
4350
+ this.setOption(optName, opts[optName]);
4351
+ }
4352
+ });
4253
4353
  // --- Load initial data
4254
4354
  if (opts.source) {
4255
4355
  if (opts.showSpinner) {
@@ -4278,9 +4378,14 @@ class Wunderbaum {
4278
4378
  this.updateViewport();
4279
4379
  }, 50);
4280
4380
  // --- Bind listeners
4281
- this.scrollContainer.addEventListener("scroll", (e) => {
4381
+ this.element.addEventListener("scroll", (e) => {
4382
+ // this.log("scroll", e);
4282
4383
  this.setModified(ChangeType.vscroll);
4283
4384
  });
4385
+ // this.scrollContainerElement.addEventListener("scroll", (e: Event) => {
4386
+ // this.log("scroll", e)
4387
+ // this.setModified(ChangeType.vscroll);
4388
+ // });
4284
4389
  this.resizeObserver = new ResizeObserver((entries) => {
4285
4390
  this.setModified(ChangeType.vscroll);
4286
4391
  // this.log("ResizeObserver: Size changed", entries);
@@ -4322,9 +4427,10 @@ class Wunderbaum {
4322
4427
  onEvent(this.element, "keydown", (e) => {
4323
4428
  const info = Wunderbaum.getEventInfo(e);
4324
4429
  const eventName = eventToString(e);
4430
+ const node = info.node || this.getFocusNode();
4325
4431
  this._callHook("onKeyEvent", {
4326
4432
  event: e,
4327
- node: info.node,
4433
+ node: node,
4328
4434
  info: info,
4329
4435
  eventName: eventName,
4330
4436
  });
@@ -4524,28 +4630,28 @@ class Wunderbaum {
4524
4630
  }
4525
4631
  /** Return the topmost visible node in the viewport. */
4526
4632
  getTopmostVpNode(complete = true) {
4527
- let topIdx;
4528
4633
  const gracePy = 1; // ignore subpixel scrolling
4634
+ const scrollParent = this.element;
4635
+ let topIdx;
4529
4636
  if (complete) {
4530
- topIdx = Math.ceil((this.scrollContainer.scrollTop - gracePy) / ROW_HEIGHT);
4637
+ topIdx = Math.ceil((scrollParent.scrollTop - gracePy) / ROW_HEIGHT);
4531
4638
  }
4532
4639
  else {
4533
- topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
4640
+ topIdx = Math.floor(scrollParent.scrollTop / ROW_HEIGHT);
4534
4641
  }
4535
4642
  return this._getNodeByRowIdx(topIdx);
4536
4643
  }
4537
4644
  /** Return the lowest visible node in the viewport. */
4538
4645
  getLowestVpNode(complete = true) {
4646
+ const scrollParent = this.element;
4539
4647
  let bottomIdx;
4540
4648
  if (complete) {
4541
4649
  bottomIdx =
4542
- Math.floor((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
4543
- ROW_HEIGHT) - 1;
4650
+ Math.floor((scrollParent.scrollTop + scrollParent.clientHeight) / ROW_HEIGHT) - 1;
4544
4651
  }
4545
4652
  else {
4546
4653
  bottomIdx =
4547
- Math.ceil((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
4548
- ROW_HEIGHT) - 1;
4654
+ Math.ceil((scrollParent.scrollTop + scrollParent.clientHeight) / ROW_HEIGHT) - 1;
4549
4655
  }
4550
4656
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
4551
4657
  return this._getNodeByRowIdx(bottomIdx);
@@ -4742,7 +4848,7 @@ class Wunderbaum {
4742
4848
  * Return `tree.option.NAME` (also resolving if this is a callback).
4743
4849
  *
4744
4850
  * See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
4745
- * to consider `node.NAME` setting and `tree.types[node.type].NAME`.
4851
+ * to evaluate `node.NAME` setting and `tree.types[node.type].NAME`.
4746
4852
  *
4747
4853
  * @param name option name (use dot notation to access extension option, e.g.
4748
4854
  * `filter.mode`)
@@ -4761,6 +4867,7 @@ class Wunderbaum {
4761
4867
  value = value({ type: "resolve", tree: this });
4762
4868
  }
4763
4869
  // Use value from value options dict, fallback do default
4870
+ // console.info(name, value, opts)
4764
4871
  return value !== null && value !== void 0 ? value : defaultValue;
4765
4872
  }
4766
4873
  /**
@@ -4769,14 +4876,20 @@ class Wunderbaum {
4769
4876
  * @param value
4770
4877
  */
4771
4878
  setOption(name, value) {
4879
+ // this.log(`setOption(${name}, ${value})`);
4772
4880
  if (name.indexOf(".") === -1) {
4773
4881
  this.options[name] = value;
4774
- // switch (name) {
4775
- // case value:
4776
- // break;
4777
- // default:
4778
- // break;
4779
- // }
4882
+ switch (name) {
4883
+ case "checkbox":
4884
+ this.setModified(ChangeType.any, { removeMarkup: true });
4885
+ break;
4886
+ case "enabled":
4887
+ this.setEnabled(!!value);
4888
+ break;
4889
+ case "fixedCol":
4890
+ this.element.classList.toggle("wb-fixed-col", !!value);
4891
+ break;
4892
+ }
4780
4893
  return;
4781
4894
  }
4782
4895
  const parts = name.split(".");
@@ -4811,6 +4924,18 @@ class Wunderbaum {
4811
4924
  this.logTimeEnd(tag);
4812
4925
  }
4813
4926
  }
4927
+ /** Recursively select all nodes. */
4928
+ selectAll(flag = true) {
4929
+ try {
4930
+ this.enableUpdate(false);
4931
+ this.visit((node) => {
4932
+ node.setSelected(flag);
4933
+ });
4934
+ }
4935
+ finally {
4936
+ this.enableUpdate(true);
4937
+ }
4938
+ }
4814
4939
  /** Return the number of nodes in the data model.*/
4815
4940
  count(visible = false) {
4816
4941
  if (visible) {
@@ -4852,6 +4977,17 @@ class Wunderbaum {
4852
4977
  findFirst(match) {
4853
4978
  return this.root.findFirst(match);
4854
4979
  }
4980
+ /**
4981
+ * Find first node that matches condition.
4982
+ *
4983
+ * @param match title string to search for, or a
4984
+ * callback function that returns `true` if a node is matched.
4985
+ * @see {@link WunderbaumNode.findFirst}
4986
+ *
4987
+ */
4988
+ findKey(key) {
4989
+ return this.keyMap.get(key);
4990
+ }
4855
4991
  /**
4856
4992
  * Find the next visible node that starts with `match`, starting at `startNode`
4857
4993
  * and wrap-around at the end.
@@ -4894,7 +5030,7 @@ class Wunderbaum {
4894
5030
  */
4895
5031
  findRelatedNode(node, where, includeHidden = false) {
4896
5032
  let res = null;
4897
- const pageSize = Math.floor(this.scrollContainer.clientHeight / ROW_HEIGHT);
5033
+ const pageSize = Math.floor(this.scrollContainerElement.clientHeight / ROW_HEIGHT);
4898
5034
  switch (where) {
4899
5035
  case "parent":
4900
5036
  if (node.parent && node.parent.parent) {
@@ -5044,6 +5180,10 @@ class Wunderbaum {
5044
5180
  const idx = Array.prototype.indexOf.call(parentCol.parentNode.children, parentCol);
5045
5181
  res.colIdx = idx;
5046
5182
  }
5183
+ else if (cl.contains("wb-row")) {
5184
+ // Plain tree
5185
+ res.region = TargetType.title;
5186
+ }
5047
5187
  else {
5048
5188
  // Somewhere near the title
5049
5189
  if (event.type !== "mousemove" && !(event instanceof KeyboardEvent)) {
@@ -5072,7 +5212,7 @@ class Wunderbaum {
5072
5212
  * @internal
5073
5213
  */
5074
5214
  toString() {
5075
- return "Wunderbaum<'" + this.id + "'>";
5215
+ return `Wunderbaum<'${this.id}'>`;
5076
5216
  }
5077
5217
  /** Return true if any node is currently in edit-title mode. */
5078
5218
  isEditing() {
@@ -5134,9 +5274,8 @@ class Wunderbaum {
5134
5274
  }
5135
5275
  }
5136
5276
  /**
5137
- * Make sure that this node is scrolled into the viewport.
5277
+ * Make sure that this node is vertically scrolled into the viewport.
5138
5278
  *
5139
- * @param {boolean | PlainObject} [effects=false] animation options.
5140
5279
  * @param {object} [options=null] {topNode: null, effects: ..., parent: ...}
5141
5280
  * this node will remain visible in
5142
5281
  * any case, even if `this` is outside the scroll pane.
@@ -5145,8 +5284,8 @@ class Wunderbaum {
5145
5284
  const MARGIN = 1;
5146
5285
  const node = opts.node || this.getActiveNode();
5147
5286
  assert(node._rowIdx != null);
5148
- const curTop = this.scrollContainer.scrollTop;
5149
- const height = this.scrollContainer.clientHeight;
5287
+ const curTop = this.scrollContainerElement.scrollTop;
5288
+ const height = this.scrollContainerElement.clientHeight;
5150
5289
  const nodeOfs = node._rowIdx * ROW_HEIGHT;
5151
5290
  let newTop;
5152
5291
  if (nodeOfs > curTop) {
@@ -5162,10 +5301,49 @@ class Wunderbaum {
5162
5301
  }
5163
5302
  if (newTop != null) {
5164
5303
  this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
5165
- this.scrollContainer.scrollTop = newTop;
5304
+ this.scrollContainerElement.scrollTop = newTop;
5166
5305
  this.setModified(ChangeType.vscroll);
5167
5306
  }
5168
5307
  }
5308
+ /**
5309
+ * Make sure that this node is horizontally scrolled into the viewport.
5310
+ *
5311
+ * Used for `fixedCol` mode.
5312
+ *
5313
+ * @param {boolean | PlainObject} [effects=false] animation options.
5314
+ * @param {object} [options=null] {topNode: null, effects: ..., parent: ...}
5315
+ * this node will remain visible in
5316
+ * any case, even if `this` is outside the scroll pane.
5317
+ */
5318
+ scrollToHorz(opts) {
5319
+ const fixedWidth = this.columns[0]._widthPx;
5320
+ const vpWidth = this.element.clientWidth;
5321
+ const scrollLeft = this.element.scrollLeft;
5322
+ // if (scrollLeft <= 0) {
5323
+ // return; // Not scrolled horizontally: Nothing to do
5324
+ // }
5325
+ // const MARGIN = 1;
5326
+ const colElem = this.getActiveColElem();
5327
+ const colLeft = Number.parseInt(colElem === null || colElem === void 0 ? void 0 : colElem.style.left, 10);
5328
+ const colRight = colLeft + Number.parseInt(colElem === null || colElem === void 0 ? void 0 : colElem.style.width, 10);
5329
+ let newLeft = scrollLeft;
5330
+ if (colLeft - scrollLeft < fixedWidth) {
5331
+ // The current column is scrolled behind the left fixed column
5332
+ newLeft = colLeft - fixedWidth;
5333
+ }
5334
+ else if (colRight - scrollLeft > vpWidth) {
5335
+ // The current column is scrolled outside the right side
5336
+ newLeft = colRight - vpWidth;
5337
+ }
5338
+ // util.assert(node._rowIdx != null);
5339
+ // const curLeft = this.scrollContainer.scrollLeft;
5340
+ this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
5341
+ // const nodeOfs = node._rowIdx * ROW_HEIGHT;
5342
+ // let newLeft;
5343
+ this.element.scrollLeft = newLeft;
5344
+ // this.setModified(ChangeType.vscroll);
5345
+ // }
5346
+ }
5169
5347
  /**
5170
5348
  * Set column #colIdx to 'active'.
5171
5349
  *
@@ -5194,6 +5372,15 @@ class Wunderbaum {
5194
5372
  colDiv.classList.toggle("wb-active", i++ === colIdx);
5195
5373
  }
5196
5374
  }
5375
+ // Vertical scroll into view
5376
+ // if (this.options.fixedCol) {
5377
+ this.scrollToHorz({});
5378
+ // }
5379
+ }
5380
+ /** Set or remove keybaord focus to the tree container. */
5381
+ setActiveNode(key, flag = true, options) {
5382
+ var _a;
5383
+ (_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setActive(flag, options);
5197
5384
  }
5198
5385
  /** Set or remove keybaord focus to the tree container. */
5199
5386
  setFocus(flag = true) {
@@ -5217,6 +5404,12 @@ class Wunderbaum {
5217
5404
  options = node;
5218
5405
  }
5219
5406
  const immediate = !!getOption(options, "immediate");
5407
+ const removeMarkup = !!getOption(options, "removeMarkup");
5408
+ if (removeMarkup) {
5409
+ this.visit((n) => {
5410
+ n.removeMarkup();
5411
+ });
5412
+ }
5220
5413
  switch (change) {
5221
5414
  case ChangeType.any:
5222
5415
  case ChangeType.structure:
@@ -5240,11 +5433,14 @@ class Wunderbaum {
5240
5433
  error(`Invalid change type ${change}`);
5241
5434
  }
5242
5435
  }
5436
+ /** Get the tree's navigation mode. */
5437
+ getNavigationMode() {
5438
+ return this.navMode;
5439
+ }
5243
5440
  /** Set the tree's navigation mode. */
5244
5441
  setNavigationMode(mode) {
5245
5442
  var _a;
5246
- // util.assert(this.cellNavMode);
5247
- // util.assert(0 <= colIdx && colIdx < this.columns.length);
5443
+ assert(mode in NavigationMode, `Invalid mode '${mode}'`);
5248
5444
  if (mode === this.navMode) {
5249
5445
  return;
5250
5446
  }
@@ -5259,20 +5455,56 @@ class Wunderbaum {
5259
5455
  // this.setModified(ChangeType.row, this.activeNode);
5260
5456
  (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
5261
5457
  }
5458
+ /** Disable mouse and keyboard interaction (return prev. state). */
5459
+ setEnabled(flag = true) {
5460
+ const prev = this.enabled;
5461
+ this.enabled = !!flag;
5462
+ this.element.classList.toggle("wb-disabled", !flag);
5463
+ return prev;
5464
+ }
5465
+ /** Return false if tree is disabled. */
5466
+ isEnabled() {
5467
+ return this.enabled;
5468
+ }
5469
+ /** Return true if tree has one or more data columns in addition to the plain nodes. */
5470
+ isGrid() {
5471
+ return this.columns && this.columns.length > 1;
5472
+ }
5262
5473
  /** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
5263
5474
  setStatus(status, message, details) {
5264
5475
  return this.root.setStatus(status, message, details);
5265
5476
  }
5477
+ /** Add or redefine node type definitions. */
5478
+ setTypes(types, replace = true) {
5479
+ assert(isPlainObject(types));
5480
+ if (replace) {
5481
+ this.types = types;
5482
+ }
5483
+ else {
5484
+ extend(this.types, types);
5485
+ }
5486
+ // Convert `TYPE.classes` to a Set
5487
+ for (let t of Object.values(this.types)) {
5488
+ if (t.classes) {
5489
+ t.classes = toSet(t.classes);
5490
+ }
5491
+ }
5492
+ }
5266
5493
  /** Update column headers and width. */
5267
5494
  updateColumns(opts) {
5268
5495
  opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
5269
- const minWidth = 4;
5496
+ const defaultMinWidth = 4;
5270
5497
  const vpWidth = this.element.clientWidth;
5498
+ let totalWidth = 0;
5271
5499
  let totalWeight = 0;
5272
5500
  let fixedWidth = 0;
5273
5501
  let modified = false;
5502
+ this.element.classList.toggle("wb-grid", this.columns.length > 1);
5503
+ if (this.columns.length < 2) {
5504
+ this.setNavigationMode(NavigationMode.row);
5505
+ }
5274
5506
  if (opts.calculateCols) {
5275
- // Gather width requests
5507
+ // Gather width definitions
5276
5508
  this._columnsById = {};
5277
5509
  for (let col of this.columns) {
5278
5510
  this._columnsById[col.id] = col;
@@ -5295,14 +5527,25 @@ class Wunderbaum {
5295
5527
  fixedWidth += px;
5296
5528
  }
5297
5529
  else {
5298
- error("Invalid column width: " + cw);
5530
+ error(`Invalid column width: ${cw}`);
5299
5531
  }
5300
5532
  }
5301
5533
  // Share remaining space between non-fixed columns
5302
5534
  const restPx = Math.max(0, vpWidth - fixedWidth);
5303
5535
  let ofsPx = 0;
5304
5536
  for (let col of this.columns) {
5537
+ let minWidth;
5305
5538
  if (col._weight) {
5539
+ const cmw = col.minWidth;
5540
+ if (typeof cmw === "number") {
5541
+ minWidth = cmw;
5542
+ }
5543
+ else if (typeof cmw === "string" && cmw.endsWith("px")) {
5544
+ minWidth = parseFloat(cmw.slice(0, -2));
5545
+ }
5546
+ else {
5547
+ minWidth = defaultMinWidth;
5548
+ }
5306
5549
  const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
5307
5550
  if (col._widthPx != px) {
5308
5551
  modified = true;
@@ -5312,7 +5555,14 @@ class Wunderbaum {
5312
5555
  col._ofsPx = ofsPx;
5313
5556
  ofsPx += col._widthPx;
5314
5557
  }
5558
+ totalWidth = ofsPx;
5315
5559
  }
5560
+ // if (this.options.fixedCol) {
5561
+ // 'position: fixed' requires that the content has the correct size
5562
+ const tw = `${totalWidth}px`;
5563
+ this.headerElement ? (this.headerElement.style.width = tw) : 0;
5564
+ this.scrollContainerElement.style.width = tw;
5565
+ // }
5316
5566
  // Every column has now a calculated `_ofsPx` and `_widthPx`
5317
5567
  // this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
5318
5568
  // console.trace();
@@ -5364,13 +5614,14 @@ class Wunderbaum {
5364
5614
  * @internal
5365
5615
  */
5366
5616
  _updateViewport() {
5617
+ var _a;
5367
5618
  if (this._disableUpdateCount) {
5368
5619
  this.log(`IGNORED _updateViewport() disable level: ${this._disableUpdateCount}`);
5369
5620
  return;
5370
5621
  }
5371
5622
  const newNodesOnly = !this.changeRedrawRequestPending;
5372
5623
  this.changeRedrawRequestPending = false;
5373
- let height = this.scrollContainer.clientHeight;
5624
+ let height = this.scrollContainerElement.clientHeight;
5374
5625
  // We cannot get the height for absolute positioned parent, so look at first col
5375
5626
  // let headerHeight = this.headerElement.clientHeight
5376
5627
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
@@ -5378,43 +5629,52 @@ class Wunderbaum {
5378
5629
  const wantHeight = this.element.clientHeight - headerHeight;
5379
5630
  if (Math.abs(height - wantHeight) > 1.0) {
5380
5631
  // this.log("resize", height, wantHeight);
5381
- this.scrollContainer.style.height = wantHeight + "px";
5632
+ this.scrollContainerElement.style.height = wantHeight + "px";
5382
5633
  height = wantHeight;
5383
5634
  }
5384
5635
  // console.profile(`_updateViewport()`)
5385
5636
  this.updateColumns({ updateRows: false });
5386
5637
  this._updateRows({ newNodesOnly: newNodesOnly });
5387
5638
  // console.profileEnd(`_updateViewport()`)
5639
+ if (this.options.attachBreadcrumb) {
5640
+ let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
5641
+ path = path ? path + " >" : "";
5642
+ this.options.attachBreadcrumb.textContent = path;
5643
+ }
5388
5644
  this._callEvent("update");
5389
5645
  }
5390
- /**
5391
- * Assert that TR order matches the natural node order
5392
- * @internal
5393
- */
5394
- _validateRows() {
5395
- let trs = this.nodeListElement.childNodes;
5396
- let i = 0;
5397
- let prev = -1;
5398
- let ok = true;
5399
- trs.forEach((element) => {
5400
- const tr = element;
5401
- const top = Number.parseInt(tr.style.top);
5402
- const n = tr._wb_node;
5403
- // if (i < 4) {
5404
- // console.info(
5405
- // `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
5406
- // );
5407
- // }
5408
- if (prev >= 0 && top !== prev + ROW_HEIGHT) {
5409
- n.logWarn(`TR order mismatch at index ${i}: top=${top}px != ${prev + ROW_HEIGHT}`);
5410
- // throw new Error("fault");
5411
- ok = false;
5412
- }
5413
- prev = top;
5414
- i++;
5415
- });
5416
- return ok;
5417
- }
5646
+ // /**
5647
+ // * Assert that TR order matches the natural node order
5648
+ // * @internal
5649
+ // */
5650
+ // protected _validateRows(): boolean {
5651
+ // let trs = this.nodeListElement.childNodes;
5652
+ // let i = 0;
5653
+ // let prev = -1;
5654
+ // let ok = true;
5655
+ // trs.forEach((element) => {
5656
+ // const tr = element as HTMLTableRowElement;
5657
+ // const top = Number.parseInt(tr.style.top);
5658
+ // const n = (<any>tr)._wb_node;
5659
+ // // if (i < 4) {
5660
+ // // console.info(
5661
+ // // `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
5662
+ // // );
5663
+ // // }
5664
+ // if (prev >= 0 && top !== prev + ROW_HEIGHT) {
5665
+ // n.logWarn(
5666
+ // `TR order mismatch at index ${i}: top=${top}px != ${
5667
+ // prev + ROW_HEIGHT
5668
+ // }`
5669
+ // );
5670
+ // // throw new Error("fault");
5671
+ // ok = false;
5672
+ // }
5673
+ // prev = top;
5674
+ // i++;
5675
+ // });
5676
+ // return ok;
5677
+ // }
5418
5678
  /*
5419
5679
  * - Traverse all *visible* of the whole tree, i.e. skip collapsed nodes.
5420
5680
  * - Store count of rows to `tree.treeRowCount`.
@@ -5429,9 +5689,9 @@ class Wunderbaum {
5429
5689
  opts = Object.assign({ newNodesOnly: false }, opts);
5430
5690
  const newNodesOnly = !!opts.newNodesOnly;
5431
5691
  const row_height = ROW_HEIGHT;
5432
- const vp_height = this.scrollContainer.clientHeight;
5692
+ const vp_height = this.element.clientHeight;
5433
5693
  const prefetch = RENDER_MAX_PREFETCH;
5434
- const ofs = this.scrollContainer.scrollTop;
5694
+ const ofs = this.element.scrollTop;
5435
5695
  let startIdx = Math.max(0, ofs / row_height - prefetch);
5436
5696
  startIdx = Math.floor(startIdx);
5437
5697
  // Make sure start is always even, so the alternating row colors don't
@@ -5499,7 +5759,7 @@ class Wunderbaum {
5499
5759
  // this.nodeListElement.style.height
5500
5760
  // );
5501
5761
  this.logTimeEnd(label);
5502
- this._validateRows();
5762
+ // this._validateRows();
5503
5763
  return modified;
5504
5764
  }
5505
5765
  /**
@@ -5646,17 +5906,11 @@ class Wunderbaum {
5646
5906
  /**
5647
5907
  * Reload the tree with a new source.
5648
5908
  *
5649
- * Previous data is cleared.
5650
- * Pass `options.columns` to define a header (may also be part of `source.columns`).
5909
+ * Previous data is cleared. Note that also column- and type defintions may
5910
+ * be passed with the `source` object.
5651
5911
  */
5652
- load(source, options = {}) {
5912
+ load(source) {
5653
5913
  this.clear();
5654
- const columns = options.columns || source.columns;
5655
- if (columns) {
5656
- this.columns = options.columns;
5657
- // this._renderHeaderMarkup();
5658
- this.updateColumns({ calculateCols: false });
5659
- }
5660
5914
  return this.root.load(source);
5661
5915
  }
5662
5916
  /**
@@ -5688,6 +5942,7 @@ class Wunderbaum {
5688
5942
  // `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
5689
5943
  // );
5690
5944
  if (this._disableUpdateCount === 0) {
5945
+ this.changeRedrawRequestPending = true; // make sure, we re-render all markup
5691
5946
  this.updateViewport();
5692
5947
  }
5693
5948
  }
@@ -5730,7 +5985,7 @@ class Wunderbaum {
5730
5985
  }
5731
5986
  Wunderbaum.sequence = 0;
5732
5987
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
5733
- Wunderbaum.version = "v0.0.4"; // Set to semver by 'grunt release'
5988
+ Wunderbaum.version = "v0.0.5"; // Set to semver by 'grunt release'
5734
5989
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
5735
5990
  Wunderbaum.util = util;
5736
5991