wunderbaum 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/wunderbaum.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A treegrid control.
5
5
  *
6
- * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
6
+ * Copyright (c) 2021-2023, Martin Wendt (https://wwWendt.de).
7
7
  * https://github.com/mar10/wunderbaum
8
8
  *
9
9
  * Released under the MIT license.
@@ -38,14 +38,17 @@ import {
38
38
  WbEventInfo,
39
39
  ApplyCommandOptions,
40
40
  AddChildrenOptions,
41
- UpdateColumnsOptions,
42
41
  VisitRowsOptions,
43
42
  NodeFilterCallback,
44
43
  FilterNodesOptions,
44
+ RenderFlag,
45
+ NodeVisitCallback,
46
+ SortCallback,
45
47
  } from "./types";
46
48
  import {
47
49
  DEFAULT_DEBUGLEVEL,
48
50
  makeNodeTitleStartMatcher,
51
+ nodeTitleSorter,
49
52
  RENDER_MAX_PREFETCH,
50
53
  ROW_HEIGHT,
51
54
  } from "./common";
@@ -104,6 +107,7 @@ export class Wunderbaum {
104
107
  protected refKeyMap = new Map<string, Set<WunderbaumNode>>();
105
108
  protected treeRowCount = 0;
106
109
  protected _disableUpdateCount = 0;
110
+ protected _disableUpdateIgnoreCount = 0;
107
111
 
108
112
  /** Currently active node if any. */
109
113
  public activeNode: WunderbaumNode | null = null;
@@ -119,8 +123,7 @@ export class Wunderbaum {
119
123
  protected resizeObserver: ResizeObserver;
120
124
 
121
125
  // Modification Status
122
- protected changeRedrawRequestPending = false;
123
- protected changeScrollRequestPending = false;
126
+ protected pendingChangeTypes: Set<RenderFlag> = new Set();
124
127
 
125
128
  /** A Promise that is resolved when the tree was initialized (similar to `init(e)` event). */
126
129
  public readonly ready: Promise<any>;
@@ -163,6 +166,7 @@ export class Wunderbaum {
163
166
  showSpinner: false,
164
167
  checkbox: false,
165
168
  minExpandLevel: 0,
169
+ emptyChildListExpandable: false,
166
170
  updateThrottleWait: 200,
167
171
  skeleton: false,
168
172
  connectTopBreadcrumb: null, // HTMLElement that receives the top nodes breadcrumb
@@ -362,12 +366,12 @@ export class Wunderbaum {
362
366
  // --- Bind listeners
363
367
  this.element.addEventListener("scroll", (e: Event) => {
364
368
  // this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
365
- this.setModified(ChangeType.vscroll);
369
+ this.setModified(ChangeType.scroll);
366
370
  });
367
371
 
368
372
  this.resizeObserver = new ResizeObserver((entries) => {
369
- this.setModified(ChangeType.vscroll);
370
373
  // this.log("ResizeObserver: Size changed", entries);
374
+ this.setModified(ChangeType.resize);
371
375
  });
372
376
  this.resizeObserver.observe(this.element);
373
377
 
@@ -978,7 +982,7 @@ export class Wunderbaum {
978
982
  (this.options as any)[name] = value;
979
983
  switch (name) {
980
984
  case "checkbox":
981
- this.setModified(ChangeType.any, { removeMarkup: true });
985
+ this.setModified(ChangeType.any);
982
986
  break;
983
987
  case "enabled":
984
988
  this.setEnabled(!!value);
@@ -1312,8 +1316,9 @@ export class Wunderbaum {
1312
1316
  if (cl.contains("wb-title")) {
1313
1317
  res.region = NodeRegion.title;
1314
1318
  } else if (cl.contains("wb-expander")) {
1315
- res.region =
1316
- node!.hasChildren() === false ? NodeRegion.prefix : NodeRegion.expander;
1319
+ res.region = node!.isExpandable()
1320
+ ? NodeRegion.expander
1321
+ : NodeRegion.prefix;
1317
1322
  } else if (cl.contains("wb-checkbox")) {
1318
1323
  res.region = NodeRegion.checkbox;
1319
1324
  } else if (cl.contains("wb-icon")) {
@@ -1483,7 +1488,7 @@ export class Wunderbaum {
1483
1488
  // Make sure the topNode is always visible
1484
1489
  this.scrollTo(topNode);
1485
1490
  }
1486
- // this.setModified(ChangeType.vscroll);
1491
+ // this.setModified(ChangeType.scroll);
1487
1492
  }
1488
1493
  }
1489
1494
 
@@ -1514,7 +1519,7 @@ export class Wunderbaum {
1514
1519
  `scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`
1515
1520
  );
1516
1521
  this.element.scrollLeft = newLeft;
1517
- // this.setModified(ChangeType.vscroll);
1522
+ // this.setModified(ChangeType.scroll);
1518
1523
  }
1519
1524
  /**
1520
1525
  * Set column #colIdx to 'active'.
@@ -1566,11 +1571,19 @@ export class Wunderbaum {
1566
1571
  }
1567
1572
  }
1568
1573
 
1569
- /** Schedule an update request to reflect a tree change. */
1574
+ /**
1575
+ * Schedule an update request to reflect a tree change.
1576
+ * The render operation is async and debounced unless the `immediate` option
1577
+ * is set.
1578
+ * Use {@link WunderbaumNode.setModified()} if only a single node has changed,
1579
+ * or {@link WunderbaumNode.render()}) to pass special options.
1580
+ */
1570
1581
  setModified(change: ChangeType, options?: SetModifiedOptions): void;
1571
1582
 
1572
- /** Schedule an update request to reflect a single node modification.
1573
- * @see {@link WunderbaumNode.setModified}
1583
+ /**
1584
+ * Update a row to reflect a single node's modification.
1585
+ *
1586
+ * @see {@link WunderbaumNode.setModified()}, {@link WunderbaumNode.render()}
1574
1587
  */
1575
1588
  setModified(
1576
1589
  change: ChangeType,
@@ -1588,35 +1601,41 @@ export class Wunderbaum {
1588
1601
  // this.log(
1589
1602
  // `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
1590
1603
  // );
1604
+ this._disableUpdateIgnoreCount++;
1591
1605
  return;
1592
1606
  }
1593
1607
  // this.log(`setModified(${change}) node=${node}`);
1594
1608
  if (!(node instanceof WunderbaumNode)) {
1595
1609
  options = node;
1610
+ node = null;
1596
1611
  }
1597
1612
  const immediate = !!util.getOption(options, "immediate");
1598
- const removeMarkup = !!util.getOption(options, "removeMarkup");
1599
- if (removeMarkup) {
1600
- this.visit((n) => {
1601
- n.removeMarkup();
1602
- });
1603
- }
1613
+ const RF = RenderFlag;
1614
+ const pending = this.pendingChangeTypes;
1604
1615
 
1605
- let callUpdate = false;
1606
1616
  switch (change) {
1607
1617
  case ChangeType.any:
1618
+ case ChangeType.colStructure:
1619
+ pending.add(RF.header);
1620
+ pending.add(RF.clearMarkup);
1621
+ pending.add(RF.redraw);
1622
+ pending.add(RF.scroll);
1623
+ break;
1624
+ case ChangeType.resize:
1625
+ // case ChangeType.colWidth:
1626
+ pending.add(RF.header);
1627
+ pending.add(RF.redraw);
1628
+ break;
1608
1629
  case ChangeType.structure:
1609
- case ChangeType.header:
1610
- this.changeRedrawRequestPending = true;
1611
- callUpdate = true;
1630
+ pending.add(RF.redraw);
1612
1631
  break;
1613
- case ChangeType.vscroll:
1614
- this.changeScrollRequestPending = true;
1615
- callUpdate = true;
1632
+ case ChangeType.scroll:
1633
+ pending.add(RF.scroll);
1616
1634
  break;
1617
1635
  case ChangeType.row:
1618
1636
  case ChangeType.data:
1619
1637
  case ChangeType.status:
1638
+ util.assert(node, `Option '${change}' requires a node.`);
1620
1639
  // Single nodes are immediately updated if already inside the viewport
1621
1640
  // (otherwise we can ignore)
1622
1641
  if (node._rowElem) {
@@ -1624,9 +1643,18 @@ export class Wunderbaum {
1624
1643
  }
1625
1644
  break;
1626
1645
  default:
1627
- util.error(`Invalid change type ${change}`);
1646
+ util.error(`Invalid change type '${change}'.`);
1647
+ }
1648
+
1649
+ if (change === ChangeType.colStructure) {
1650
+ const isGrid = this.isGrid();
1651
+ this.element.classList.toggle("wb-grid", isGrid);
1652
+ if (!isGrid && this.isCellNav()) {
1653
+ this.setCellNav(false);
1654
+ }
1628
1655
  }
1629
- if (callUpdate) {
1656
+
1657
+ if (pending.size > 0) {
1630
1658
  if (immediate) {
1631
1659
  this._updateViewportImmediately();
1632
1660
  } else {
@@ -1703,7 +1731,7 @@ export class Wunderbaum {
1703
1731
  }
1704
1732
  break;
1705
1733
  default:
1706
- util.error(`Invalid mode '${mode}'`);
1734
+ util.error(`Invalid mode '${mode}'.`);
1707
1735
  }
1708
1736
  }
1709
1737
 
@@ -1714,6 +1742,7 @@ export class Wunderbaum {
1714
1742
  ): WunderbaumNode | null {
1715
1743
  return this.root.setStatus(status, options);
1716
1744
  }
1745
+
1717
1746
  /** Add or redefine node type definitions. */
1718
1747
  setTypes(types: any, replace = true) {
1719
1748
  util.assert(util.isPlainObject(types));
@@ -1729,14 +1758,29 @@ export class Wunderbaum {
1729
1758
  }
1730
1759
  }
1731
1760
  }
1732
- /** Update column headers and width.
1761
+
1762
+ /**
1763
+ * Sort nodes list by title or custom criteria.
1764
+ * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
1765
+ * (defaults to sorting by title).
1766
+ * @param {boolean} deep pass true to sort all descendant nodes recursively
1767
+ */
1768
+ sortChildren(
1769
+ cmp: SortCallback | null = nodeTitleSorter,
1770
+ deep: boolean = false
1771
+ ): void {
1772
+ this.root.sortChildren(cmp, deep);
1773
+ }
1774
+
1775
+ /**
1776
+ * Update column headers and column width.
1733
1777
  * Return true if at least one column width changed.
1734
1778
  */
1735
- updateColumns(options?: UpdateColumnsOptions): boolean {
1736
- options = Object.assign({ calculateCols: true, updateRows: true }, options);
1779
+ // _updateColumnWidths(options?: UpdateColumnsOptions): boolean {
1780
+ _updateColumnWidths(): boolean {
1781
+ // options = Object.assign({ updateRows: true, renderMarkup: false }, options);
1737
1782
  const defaultMinWidth = 4;
1738
1783
  const vpWidth = this.element.clientWidth;
1739
- const isGrid = this.isGrid();
1740
1784
  // Shorten last column width to avoid h-scrollbar
1741
1785
  const FIX_ADJUST_LAST_COL = 2;
1742
1786
  const columns = this.columns;
@@ -1747,76 +1791,74 @@ export class Wunderbaum {
1747
1791
  let fixedWidth = 0;
1748
1792
  let modified = false;
1749
1793
 
1750
- this.element.classList.toggle("wb-grid", isGrid);
1751
- if (!isGrid && this.isCellNav()) {
1752
- this.setCellNav(false);
1753
- }
1794
+ // this.element.classList.toggle("wb-grid", isGrid);
1795
+ // if (!isGrid && this.isCellNav()) {
1796
+ // this.setCellNav(false);
1797
+ // }
1754
1798
 
1755
- if (options.calculateCols) {
1756
- if (col0.id !== "*") {
1757
- throw new Error(`First column must have id '*': got '${col0.id}'`);
1799
+ // if (options.calculateCols) {
1800
+ if (col0.id !== "*") {
1801
+ throw new Error(`First column must have id '*': got '${col0.id}'.`);
1802
+ }
1803
+ // Gather width definitions
1804
+ this._columnsById = {};
1805
+ for (let col of columns) {
1806
+ this._columnsById[<string>col.id] = col;
1807
+ let cw = col.width;
1808
+ if (col.id === "*" && col !== col0) {
1809
+ throw new Error(
1810
+ `Column id '*' must be defined only once: '${col.title}'.`
1811
+ );
1758
1812
  }
1759
- // Gather width definitions
1760
- this._columnsById = {};
1761
- for (let col of columns) {
1762
- this._columnsById[<string>col.id] = col;
1763
- let cw = col.width;
1764
- if (col.id === "*" && col !== col0) {
1765
- throw new Error(
1766
- `Column id '*' must be defined only once: '${col.title}'`
1767
- );
1813
+
1814
+ if (!cw || cw === "*") {
1815
+ col._weight = 1.0;
1816
+ totalWeight += 1.0;
1817
+ } else if (typeof cw === "number") {
1818
+ col._weight = cw;
1819
+ totalWeight += cw;
1820
+ } else if (typeof cw === "string" && cw.endsWith("px")) {
1821
+ col._weight = 0;
1822
+ let px = parseFloat(cw.slice(0, -2));
1823
+ if (col._widthPx != px) {
1824
+ modified = true;
1825
+ col._widthPx = px;
1768
1826
  }
1827
+ fixedWidth += px;
1828
+ } else {
1829
+ util.error(
1830
+ `Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>).`
1831
+ );
1832
+ }
1833
+ }
1834
+ // Share remaining space between non-fixed columns
1835
+ const restPx = Math.max(0, vpWidth - fixedWidth);
1836
+ let ofsPx = 0;
1769
1837
 
1770
- if (!cw || cw === "*") {
1771
- col._weight = 1.0;
1772
- totalWeight += 1.0;
1773
- } else if (typeof cw === "number") {
1774
- col._weight = cw;
1775
- totalWeight += cw;
1776
- } else if (typeof cw === "string" && cw.endsWith("px")) {
1777
- col._weight = 0;
1778
- let px = parseFloat(cw.slice(0, -2));
1779
- if (col._widthPx != px) {
1780
- modified = true;
1781
- col._widthPx = px;
1782
- }
1783
- fixedWidth += px;
1838
+ for (let col of columns) {
1839
+ let minWidth: number;
1840
+
1841
+ if (col._weight) {
1842
+ const cmw = col.minWidth;
1843
+ if (typeof cmw === "number") {
1844
+ minWidth = cmw;
1845
+ } else if (typeof cmw === "string" && cmw.endsWith("px")) {
1846
+ minWidth = parseFloat(cmw.slice(0, -2));
1784
1847
  } else {
1785
- util.error(
1786
- `Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>)`
1787
- );
1848
+ minWidth = defaultMinWidth;
1788
1849
  }
1789
- }
1790
- // Share remaining space between non-fixed columns
1791
- const restPx = Math.max(0, vpWidth - fixedWidth);
1792
- let ofsPx = 0;
1793
-
1794
- for (let col of columns) {
1795
- let minWidth: number;
1796
-
1797
- if (col._weight) {
1798
- const cmw = col.minWidth;
1799
- if (typeof cmw === "number") {
1800
- minWidth = cmw;
1801
- } else if (typeof cmw === "string" && cmw.endsWith("px")) {
1802
- minWidth = parseFloat(cmw.slice(0, -2));
1803
- } else {
1804
- minWidth = defaultMinWidth;
1805
- }
1806
- const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
1807
- if (col._widthPx != px) {
1808
- modified = true;
1809
- col._widthPx = px;
1810
- }
1850
+ const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
1851
+ if (col._widthPx != px) {
1852
+ modified = true;
1853
+ col._widthPx = px;
1811
1854
  }
1812
- col._ofsPx = ofsPx;
1813
- ofsPx += col._widthPx!;
1814
1855
  }
1815
- columns[columns.length - 1]._widthPx! -= FIX_ADJUST_LAST_COL;
1816
- totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
1856
+ col._ofsPx = ofsPx;
1857
+ ofsPx += col._widthPx!;
1817
1858
  }
1818
- // if (this.options.fixedCol) {
1819
- // 'position: fixed' requires that the content has the correct size
1859
+ columns[columns.length - 1]._widthPx! -= FIX_ADJUST_LAST_COL;
1860
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
1861
+
1820
1862
  const tw = `${totalWidth}px`;
1821
1863
  this.headerElement.style.width = tw;
1822
1864
  this.listContainerElement!.style.width = tw;
@@ -1826,12 +1868,14 @@ export class Wunderbaum {
1826
1868
  // this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
1827
1869
  // console.trace();
1828
1870
  // util.error("BREAK");
1829
- if (modified) {
1830
- this._renderHeaderMarkup();
1831
- if (options.updateRows) {
1832
- this._updateRows();
1833
- }
1834
- }
1871
+ // if (modified) {
1872
+ // this._renderHeaderMarkup();
1873
+ // if (options.renderMarkup) {
1874
+ // this.setModified(ChangeType.header, { removeMarkup: true });
1875
+ // } else if (options.updateRows) {
1876
+ // this._updateRows();
1877
+ // }
1878
+ // }
1835
1879
  return modified;
1836
1880
  }
1837
1881
 
@@ -1885,7 +1929,7 @@ export class Wunderbaum {
1885
1929
  * pending async changes if any.
1886
1930
  */
1887
1931
  updatePendingModifications() {
1888
- if (this.changeRedrawRequestPending || this.changeScrollRequestPending) {
1932
+ if (this.pendingChangeTypes.size > 0) {
1889
1933
  this._updateViewportImmediately();
1890
1934
  }
1891
1935
  }
@@ -1900,40 +1944,58 @@ export class Wunderbaum {
1900
1944
  * @internal
1901
1945
  */
1902
1946
  protected _updateViewportImmediately() {
1903
- // Shorten container height to avoid v-scrollbar
1904
- const FIX_ADJUST_HEIGHT = 1;
1905
-
1906
1947
  if (this._disableUpdateCount) {
1907
1948
  this.log(
1908
- `IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`
1949
+ `_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`
1909
1950
  );
1951
+ this._disableUpdateIgnoreCount++;
1910
1952
  return;
1911
1953
  }
1912
- const newNodesOnly = !this.changeRedrawRequestPending;
1913
- this.changeRedrawRequestPending = false;
1914
- this.changeScrollRequestPending = false;
1915
1954
 
1916
- let height = this.listContainerElement.clientHeight;
1917
- // We cannot get the height for absolute positioned parent, so look at first col
1918
- // let headerHeight = this.headerElement.clientHeight
1919
- // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
1920
- // const headerHeight = this.options.headerHeightPx;
1921
- const headerHeight = this.headerElement.clientHeight; // May be 0
1922
- const wantHeight =
1923
- this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
1955
+ // Shorten container height to avoid v-scrollbar
1956
+ const FIX_ADJUST_HEIGHT = 1;
1957
+ const RF = RenderFlag;
1924
1958
 
1925
- if (Math.abs(height - wantHeight) > 1.0) {
1926
- // this.log("resize", height, wantHeight);
1927
- this.listContainerElement.style.height = wantHeight + "px";
1928
- height = wantHeight;
1929
- }
1930
- // console.profile(`_updateViewportImmediately()`)
1959
+ const pending = new Set(this.pendingChangeTypes);
1960
+ this.pendingChangeTypes.clear();
1931
1961
 
1932
- const modified = this.updateColumns({ updateRows: false });
1962
+ const scrollOnly = pending.has(RF.scroll) && pending.size === 1;
1963
+ if (scrollOnly) {
1964
+ this._updateRows({ newNodesOnly: true });
1965
+ // this.log("_updateViewportImmediately(): scroll only.");
1966
+ } else {
1967
+ this.log("_updateViewportImmediately():", pending);
1968
+ let height = this.listContainerElement.clientHeight;
1969
+ // We cannot get the height for absolute positioned parent, so look at first col
1970
+ // let headerHeight = this.headerElement.clientHeight
1971
+ // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
1972
+ // const headerHeight = this.options.headerHeightPx;
1973
+ const headerHeight = this.headerElement.clientHeight; // May be 0
1974
+ const wantHeight =
1975
+ this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
1976
+
1977
+ if (Math.abs(height - wantHeight) > 1.0) {
1978
+ // this.log("resize", height, wantHeight);
1979
+ this.listContainerElement.style.height = wantHeight + "px";
1980
+ height = wantHeight;
1981
+ }
1982
+ // console.profile(`_updateViewportImmediately()`)
1933
1983
 
1934
- this._updateRows({ newNodesOnly: newNodesOnly && !modified });
1984
+ if (pending.has(RF.clearMarkup)) {
1985
+ this.visit((n) => {
1986
+ n.removeMarkup();
1987
+ });
1988
+ }
1935
1989
 
1936
- // console.profileEnd(`_updateViewportImmediately()`)
1990
+ // let widthModified = false;
1991
+ if (pending.has(RF.header)) {
1992
+ // widthModified = this._updateColumnWidths();
1993
+ this._updateColumnWidths();
1994
+ this._renderHeaderMarkup();
1995
+ }
1996
+ this._updateRows();
1997
+ // console.profileEnd(`_updateViewportImmediately()`)
1998
+ }
1937
1999
 
1938
2000
  if (this.options.connectTopBreadcrumb) {
1939
2001
  let path = this.getTopmostVpNode(true)?.getPath(false, "title", " > ");
@@ -2084,25 +2146,17 @@ export class Wunderbaum {
2084
2146
  }
2085
2147
 
2086
2148
  /**
2087
- * Call fn(node) for all nodes in vertical order, top down (or bottom up).
2149
+ * Call callback(node) for all nodes in vertical order, top down (or bottom up).
2088
2150
  *
2089
- * Note that this considers expansion state, i.e. children of collapsed nodes
2090
- * are skipped.
2151
+ * Note that this considers expansion state, i.e. filtered nodes and children
2152
+ * of collapsed nodes are skipped, unless `includeHidden` is set.
2091
2153
  *
2092
- * Stop iteration, if fn() returns false.<br>
2154
+ * Stop iteration if callback() returns false.<br>
2093
2155
  * Return false if iteration was stopped.
2094
2156
  *
2095
- * @param callback the callback function.
2096
- * Return false to stop iteration, return "skip" to skip this node and children only.
2097
- * @param [options]
2098
- * Defaults:
2099
- * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
2100
2157
  * @returns {boolean} false if iteration was canceled
2101
2158
  */
2102
- visitRows(
2103
- callback: (node: WunderbaumNode) => any,
2104
- options?: VisitRowsOptions
2105
- ): boolean {
2159
+ visitRows(callback: NodeVisitCallback, options?: VisitRowsOptions): boolean {
2106
2160
  if (!this.root.hasChildren()) {
2107
2161
  return false;
2108
2162
  }
@@ -2130,7 +2184,7 @@ export class Wunderbaum {
2130
2184
  nextIdx = siblings.indexOf(node) + siblingOfs;
2131
2185
  util.assert(
2132
2186
  nextIdx >= 0,
2133
- "Could not find " + node + " in parent's children: " + parent
2187
+ `Could not find ${node} in parent's children: ${parent}`
2134
2188
  );
2135
2189
 
2136
2190
  for (i = nextIdx; i < siblings.length; i++) {
@@ -2197,7 +2251,7 @@ export class Wunderbaum {
2197
2251
  * @internal
2198
2252
  */
2199
2253
  protected _visitRowsUp(
2200
- callback: (node: WunderbaumNode) => any,
2254
+ callback: NodeVisitCallback,
2201
2255
  options: VisitRowsOptions
2202
2256
  ): boolean {
2203
2257
  let children,
@@ -2291,8 +2345,10 @@ export class Wunderbaum {
2291
2345
  // `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
2292
2346
  // );
2293
2347
  if (this._disableUpdateCount === 0) {
2294
- // this.changeRedrawRequestPending = true; // make sure, we re-render all markup
2295
- // this.updateViewport();
2348
+ this.logDebug(
2349
+ `enableUpdate(): active again. Re-painting to catch up with ${this._disableUpdateIgnoreCount} ignored update requests...`
2350
+ );
2351
+ this._disableUpdateIgnoreCount = 0;
2296
2352
  this.setModified(ChangeType.any, { immediate: true });
2297
2353
  }
2298
2354
  } else {