wunderbaum 0.0.1 → 0.0.2

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/wb_node.ts CHANGED
@@ -236,7 +236,7 @@ export class WunderbaumNode {
236
236
  // this.fixSelection3FromEndNodes();
237
237
  // }
238
238
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
239
- this.tree.setModified(ChangeType.structure, this);
239
+ this.tree.setModified(ChangeType.structure);
240
240
  return nodeList[0];
241
241
  } finally {
242
242
  this.tree.enableUpdate(true);
@@ -784,7 +784,7 @@ export class WunderbaumNode {
784
784
 
785
785
  if (wasExpanded) {
786
786
  this.expanded = true;
787
- this.tree.updateViewport();
787
+ this.tree.setModified(ChangeType.structure);
788
788
  } else {
789
789
  this.render(); // Fix expander icon to 'loaded'
790
790
  }
@@ -956,7 +956,7 @@ export class WunderbaumNode {
956
956
  }, true);
957
957
  }
958
958
 
959
- tree.updateViewport();
959
+ tree.setModified(ChangeType.structure);
960
960
  // TODO: fix selection state
961
961
  // TODO: fix active state
962
962
  }
@@ -1035,7 +1035,7 @@ export class WunderbaumNode {
1035
1035
  if (!this.isRootNode()) {
1036
1036
  this.expanded = false;
1037
1037
  }
1038
- this.tree.updateViewport();
1038
+ this.tree.setModified(ChangeType.structure);
1039
1039
  }
1040
1040
 
1041
1041
  /** Remove all HTML markup from the DOM. */
@@ -1194,7 +1194,7 @@ export class WunderbaumNode {
1194
1194
  ofsTitlePx += ICON_WIDTH;
1195
1195
  }
1196
1196
 
1197
- if (level > treeOptions.minExpandLevel) {
1197
+ if (treeOptions.minExpandLevel && level > treeOptions.minExpandLevel) {
1198
1198
  expanderSpan = document.createElement("i");
1199
1199
  nodeElem.appendChild(expanderSpan);
1200
1200
  ofsTitlePx += ICON_WIDTH;
@@ -1214,7 +1214,7 @@ export class WunderbaumNode {
1214
1214
  // Store the width of leading icons with the node, so we can calculate
1215
1215
  // the width of the embedded title span later
1216
1216
  (<any>nodeElem)._ofsTitlePx = ofsTitlePx;
1217
- if (tree.options.dnd.dragStart) {
1217
+ if (tree.options.dnd!.dragStart) {
1218
1218
  nodeElem.draggable = true;
1219
1219
  }
1220
1220
 
@@ -1290,10 +1290,11 @@ export class WunderbaumNode {
1290
1290
 
1291
1291
  if (this.titleWithHighlight) {
1292
1292
  titleSpan.innerHTML = this.titleWithHighlight;
1293
- } else if (tree.options.escapeTitles) {
1294
- titleSpan.textContent = this.title;
1295
1293
  } else {
1296
- titleSpan.innerHTML = this.title;
1294
+ // } else if (tree.options.escapeTitles) {
1295
+ titleSpan.textContent = this.title;
1296
+ // } else {
1297
+ // titleSpan.innerHTML = this.title;
1297
1298
  }
1298
1299
  // Set the width of the title span, so overflow ellipsis work
1299
1300
  if (!treeOptions.skeleton) {
@@ -1342,7 +1343,7 @@ export class WunderbaumNode {
1342
1343
  this.expanded = false;
1343
1344
  this.lazy = true;
1344
1345
  this.children = null;
1345
- this.tree.updateViewport();
1346
+ this.tree.setModified(ChangeType.structure);
1346
1347
  }
1347
1348
 
1348
1349
  /** Convert node (or whole branch) into a plain object.
@@ -1482,7 +1483,7 @@ export class WunderbaumNode {
1482
1483
  }) === false
1483
1484
  ) {
1484
1485
  tree.activeNode = null;
1485
- prev?.setDirty(ChangeType.status);
1486
+ prev?.setModified();
1486
1487
  return;
1487
1488
  }
1488
1489
  }
@@ -1493,8 +1494,8 @@ export class WunderbaumNode {
1493
1494
 
1494
1495
  if (prev !== this) {
1495
1496
  tree.activeNode = this;
1496
- prev?.setDirty(ChangeType.status);
1497
- this.setDirty(ChangeType.status);
1497
+ prev?.setModified();
1498
+ this.setModified();
1498
1499
  }
1499
1500
  if (
1500
1501
  options &&
@@ -1510,16 +1511,9 @@ export class WunderbaumNode {
1510
1511
  this.scrollIntoView();
1511
1512
  }
1512
1513
 
1513
- setDirty(type: ChangeType) {
1514
- if (this.tree._disableUpdate) {
1515
- return;
1516
- }
1517
- if (type === ChangeType.structure) {
1518
- this.tree.updateViewport();
1519
- } else if (this._rowElem) {
1520
- // otherwise not in viewport, so no need to render
1521
- this.render();
1522
- }
1514
+ setModified(change: ChangeType = ChangeType.status) {
1515
+ util.assert(change === ChangeType.status);
1516
+ this.tree.setModified(ChangeType.row, this);
1523
1517
  }
1524
1518
 
1525
1519
  async setExpanded(flag: boolean = true, options?: any) {
@@ -1537,7 +1531,7 @@ export class WunderbaumNode {
1537
1531
  await this.loadLazy();
1538
1532
  }
1539
1533
  this.expanded = flag;
1540
- this.setDirty(ChangeType.structure);
1534
+ this.tree.setModified(ChangeType.structure);
1541
1535
  }
1542
1536
 
1543
1537
  setIcon() {
@@ -1548,8 +1542,8 @@ export class WunderbaumNode {
1548
1542
  setFocus(flag: boolean = true, options?: any) {
1549
1543
  const prev = this.tree.focusNode;
1550
1544
  this.tree.focusNode = this;
1551
- prev?.setDirty(ChangeType.status);
1552
- this.setDirty(ChangeType.status);
1545
+ prev?.setModified();
1546
+ this.setModified();
1553
1547
  }
1554
1548
 
1555
1549
  setSelected(flag: boolean = true, options?: any) {
@@ -1558,7 +1552,7 @@ export class WunderbaumNode {
1558
1552
  this._callEvent("select", { flag: flag });
1559
1553
  }
1560
1554
  this.selected = !!flag;
1561
- this.setDirty(ChangeType.status);
1555
+ this.setModified();
1562
1556
  }
1563
1557
 
1564
1558
  /** Show node status (ok, loading, error, noData) using styles and a dummy child node.
@@ -1649,13 +1643,13 @@ export class WunderbaumNode {
1649
1643
  default:
1650
1644
  util.error("invalid node status " + status);
1651
1645
  }
1652
- tree.updateViewport();
1646
+ tree.setModified(ChangeType.structure);
1653
1647
  return statusNode;
1654
1648
  }
1655
1649
 
1656
1650
  setTitle(title: string): void {
1657
1651
  this.title = title;
1658
- this.setDirty(ChangeType.status);
1652
+ this.setModified();
1659
1653
  // this.triggerModify("rename"); // TODO
1660
1654
  }
1661
1655
 
package/src/wb_options.ts CHANGED
@@ -7,8 +7,10 @@
7
7
  import {
8
8
  BoolOptionResolver,
9
9
  NavigationModeOption,
10
- WbEventType,
10
+ WbNodeEventType,
11
+ WbTreeEventType,
11
12
  } from "./common";
13
+ import { DndOptionsType } from "./wb_ext_dnd";
12
14
 
13
15
  export interface WbNodeData {
14
16
  title: string;
@@ -16,7 +18,7 @@ export interface WbNodeData {
16
18
  refKey?: string;
17
19
  expanded?: boolean;
18
20
  selected?: boolean;
19
- checkbox?: boolean | "radio" | BoolOptionResolver;
21
+ checkbox?: boolean | string;
20
22
  children?: Array<WbNodeData>;
21
23
  // ...any?: Any;
22
24
  }
@@ -62,56 +64,167 @@ export interface WunderbaumOptions {
62
64
  *
63
65
  * Default: `{}`.
64
66
  */
65
- types: any; //[key: string]: any;
67
+ types?: any; //[key: string]: any;
66
68
  /**
67
69
  * A list of maps that define column headers. If this option is set,
68
- * Wunderbaum becomes a tree-grid control instead of a plain tree.
70
+ * Wunderbaum becomes a treegrid control instead of a plain tree.
69
71
  * Column definitions can be passed as tree option, or be part of a `source`
70
72
  * response.
71
73
  * Default: `[]` meaning this is a plain tree.
72
74
  */
73
75
  columns?: Array<any>;
74
76
  /**
75
- *
77
+ * If true, add a `wb-skeleton` class to all nodes, that will result in a
78
+ * 'glow' effect. Typically used with initial dummy nodes, while loading the
79
+ * real data.
76
80
  * Default: false.
77
81
  */
78
- skeleton: false;
82
+ skeleton?: false;
79
83
  /**
80
- *
81
- * Default: false.
84
+ * Translation map for some system messages.
82
85
  */
83
- strings: any; //[key: string] string;
86
+ strings?: any; //[key: string] string;
84
87
  /**
88
+ * 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
89
+ * Default: 3 (4 in local debug environment)
85
90
  */
86
- debugLevel: 3;
87
- minExpandLevel: 0;
88
- escapeTitles: true;
89
- headerHeightPx: 22;
90
- autoCollapse: false;
91
- // --- Extensions ---
92
- dnd: any; // = {};
93
- filter: any; // = {};
91
+ debugLevel: number;
94
92
  /**
95
- * A list of maps that define column headers. If this option is set,
96
- * Wunderbaum becomes a tree grid control instead of a plain tree.
97
- * Column definitions can be passed as tree option, or be part of a `source`
98
- * response.
93
+ * Number of levels that are forced to be expanded, and have no expander icon.
94
+ * Default: 0
95
+ */
96
+ minExpandLevel?: number;
97
+ // escapeTitles: boolean;
98
+ /**
99
+ * Height of the header row div.
100
+ * Default: 22
101
+ */
102
+ headerHeightPx: number;
103
+ /**
104
+ * Height of a node row div.
105
+ * Default: 22
106
+ */
107
+ rowHeightPx?: number;
108
+ /**
109
+ * Collapse siblings when a node is expanded.
110
+ * Default: false
111
+ */
112
+ autoCollapse?: boolean;
113
+ /**
114
+ * Default: NavigationModeOption.startRow
99
115
  */
100
116
  navigationMode?: NavigationModeOption;
117
+ /**
118
+ * Show/hide header (pass bool or string)
119
+ */
120
+ header?: boolean | string | null;
121
+ /**
122
+ *
123
+ */
124
+ showSpinner?: boolean;
125
+ /**
126
+ * Default: true
127
+ */
128
+ checkbox?: boolean | "radio" | BoolOptionResolver;
129
+ /**
130
+ * Default: 200
131
+ */
132
+ updateThrottleWait?: number;
133
+ // --- KeyNav ---
134
+ /**
135
+ * Default: true
136
+ */
137
+ quicksearch?: boolean;
138
+
139
+ // --- Extensions ---
140
+ dnd?: DndOptionsType; // = {};
141
+ filter: any; // = {};
142
+ grid: any; // = {};
101
143
  // --- Events ---
102
144
  /**
103
145
  * Called after initial data was loaded and tree markup was rendered.
146
+ * Check `e.error` for status.
104
147
  * @category Callback
105
148
  */
106
- init?: (e: WbEventType) => void;
149
+ init?: (e: WbTreeEventType) => void;
107
150
  /**
108
- * Called after data was loaded from local storage.
151
+ *
152
+ * @category Callback
153
+ */
154
+ update?: (e: WbTreeEventType) => void;
155
+ /**
156
+ *
157
+ * @category Callback
158
+ */
159
+ activate?: (e: WbNodeEventType) => void;
160
+ /**
161
+ *
162
+ * @category Callback
163
+ */
164
+ deactivate?: (e: WbNodeEventType) => void;
165
+ /**
166
+ *
167
+ * @category Callback
168
+ */
169
+ change?: (e: WbNodeEventType) => void;
170
+ /**
171
+ *
172
+ * @category Callback
173
+ */
174
+ click?: (e: WbTreeEventType) => void;
175
+ /**
176
+ *
177
+ * @category Callback
178
+ */
179
+ discard?: (e: WbNodeEventType) => void;
180
+ /**
181
+ *
182
+ * @category Callback
183
+ */
184
+ error?: (e: WbTreeEventType) => void;
185
+ /**
186
+ *
187
+ * @category Callback
188
+ */
189
+ enhanceTitle?: (e: WbNodeEventType) => void;
190
+ /**
191
+ *
192
+ * Check `e.flag` for status.
109
193
  * @category Callback
110
194
  */
111
- update?: (e: WbEventType) => void;
195
+ focus?: (e: WbTreeEventType) => void;
196
+ /**
197
+ *
198
+ * @category Callback
199
+ */
200
+ keydown?: (e: WbNodeEventType) => void;
112
201
  /**
113
202
  * Called after data was loaded from local storage.
203
+ */
204
+ load?: (e: WbNodeEventType) => void;
205
+ /**
206
+ * @category Callback
207
+ */
208
+ modifyChild?: (e: WbNodeEventType) => void;
209
+ /**
210
+ *
211
+ * @category Callback
212
+ */
213
+ receive?: (e: WbNodeEventType) => void;
214
+ /**
215
+ *
216
+ * @category Callback
217
+ */
218
+ render?: (e: WbNodeEventType) => void;
219
+ /**
220
+ *
221
+ * @category Callback
222
+ */
223
+ renderStatusNode?: (e: WbNodeEventType) => void;
224
+ /**
225
+ *
226
+ * Check `e.flag` for status.
114
227
  * @category Callback
115
228
  */
116
- modifyChild?: (e: WbEventType) => void;
229
+ select?: (e: WbNodeEventType) => void;
117
230
  }
@@ -177,7 +177,7 @@ div.wunderbaum {
177
177
  span.wb-col {
178
178
  position: absolute;
179
179
  display: inline-block;
180
- text-overflow: ellipsis;
180
+ // text-overflow: ellipsis;
181
181
  height: $row-inner-height;
182
182
  line-height: $row-inner-height;
183
183
  padding: 0 $col-padding-x;
@@ -185,6 +185,25 @@ div.wunderbaum {
185
185
  &:last-of-type {
186
186
  border-right: none;
187
187
  }
188
+ overflow: visible; // allow resizer to overlap next col
189
+
190
+ span.wb-col-title {
191
+ width: 100%;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ }
195
+ span.wb-col-resizer {
196
+ position: absolute;
197
+ top: 0;
198
+ // left: auto;
199
+ right: -1px;
200
+ width: 3px;
201
+ // float: right;
202
+ border: none;
203
+ border-right: 2px solid $border-color;
204
+ height: 100%;
205
+ cursor: col-resize;
206
+ }
188
207
  }
189
208
 
190
209
  span.wb-node {
package/src/wunderbaum.ts CHANGED
@@ -16,6 +16,7 @@ import { FilterExtension } from "./wb_ext_filter";
16
16
  import { KeynavExtension } from "./wb_ext_keynav";
17
17
  import { LoggerExtension } from "./wb_ext_logger";
18
18
  import { DndExtension } from "./wb_ext_dnd";
19
+ import { GridExtension } from "./wb_ext_grid";
19
20
  import { ExtensionsDict, WunderbaumExtension } from "./wb_extension_base";
20
21
 
21
22
  import {
@@ -40,7 +41,7 @@ import { WunderbaumOptions } from "./wb_options";
40
41
 
41
42
  // const class_prefix = "wb-";
42
43
  // const node_props: string[] = ["title", "key", "refKey"];
43
- const MAX_CHANGED_NODES = 10;
44
+ // const MAX_CHANGED_NODES = 10;
44
45
 
45
46
  /**
46
47
  * A persistent plain object or array.
@@ -94,6 +95,7 @@ export class Wunderbaum {
94
95
  protected changedSince = 0;
95
96
  protected changes = new Set<ChangeType>();
96
97
  protected changedNodes = new Set<WunderbaumNode>();
98
+ protected changeRedrawPending = false;
97
99
 
98
100
  // --- FILTER ---
99
101
  public filterMode: FilterModeType = null;
@@ -125,7 +127,7 @@ export class Wunderbaum {
125
127
  rowHeightPx: ROW_HEIGHT,
126
128
  columns: null,
127
129
  types: null,
128
- escapeTitles: true,
130
+ // escapeTitles: true,
129
131
  showSpinner: false,
130
132
  checkbox: true,
131
133
  minExpandLevel: 0,
@@ -185,6 +187,7 @@ export class Wunderbaum {
185
187
  this._registerExtension(new EditExtension(this));
186
188
  this._registerExtension(new FilterExtension(this));
187
189
  this._registerExtension(new DndExtension(this));
190
+ this._registerExtension(new GridExtension(this));
188
191
  this._registerExtension(new LoggerExtension(this));
189
192
 
190
193
  // --- Evaluate options
@@ -313,10 +316,8 @@ export class Wunderbaum {
313
316
  .finally(() => {
314
317
  this.element.querySelector("progress.spinner")?.remove();
315
318
  this.element.classList.remove("wb-initializing");
316
- // this.updateViewport();
317
319
  });
318
320
  } else {
319
- // this.updateViewport();
320
321
  readyDeferred.resolve();
321
322
  }
322
323
 
@@ -328,14 +329,11 @@ export class Wunderbaum {
328
329
 
329
330
  // --- Bind listeners
330
331
  this.scrollContainer.addEventListener("scroll", (e: Event) => {
331
- this.updateViewport();
332
+ this.setModified(ChangeType.vscroll);
332
333
  });
333
334
 
334
- // window.addEventListener("resize", (e: Event) => {
335
- // this.updateViewport();
336
- // });
337
335
  this.resizeObserver = new ResizeObserver((entries) => {
338
- this.updateViewport();
336
+ this.setModified(ChangeType.vscroll);
339
337
  console.log("ResizeObserver: Size changed", entries);
340
338
  });
341
339
  this.resizeObserver.observe(this.element);
@@ -845,7 +843,7 @@ export class Wunderbaum {
845
843
  // public cellNavMode = false;
846
844
  // public lastQuicksearchTime = 0;
847
845
  // public lastQuicksearchTerm = "";
848
- this.updateViewport();
846
+ this.setModified(ChangeType.structure);
849
847
  }
850
848
 
851
849
  /**
@@ -1157,9 +1155,11 @@ export class Wunderbaum {
1157
1155
  static getEventInfo(event: Event) {
1158
1156
  let target = <Element>event.target,
1159
1157
  cl = target.classList,
1160
- parentCol = target.closest(".wb-col"),
1158
+ parentCol = target.closest("span.wb-col") as HTMLSpanElement,
1161
1159
  node = Wunderbaum.getNode(target),
1160
+ tree = node ? node.tree : Wunderbaum.getTree(event),
1162
1161
  res = {
1162
+ tree: tree,
1163
1163
  node: node,
1164
1164
  region: NodeRegion.unknown,
1165
1165
  colDef: undefined,
@@ -1189,13 +1189,15 @@ export class Wunderbaum {
1189
1189
  res.colIdx = idx;
1190
1190
  } else {
1191
1191
  // Somewhere near the title
1192
- console.warn("getEventInfo(): not found", event, res);
1192
+ if (event.type !== "mousemove") {
1193
+ console.warn("getEventInfo(): not found", event, res);
1194
+ }
1193
1195
  return res;
1194
1196
  }
1195
1197
  if (res.colIdx === -1) {
1196
1198
  res.colIdx = 0;
1197
1199
  }
1198
- res.colDef = node!.tree.columns[res.colIdx];
1200
+ res.colDef = tree?.columns[res.colIdx];
1199
1201
  res.colDef != null ? (res.colId = (<any>res.colDef).id) : 0;
1200
1202
  // this.log("Event", event, res);
1201
1203
  return res;
@@ -1298,6 +1300,7 @@ export class Wunderbaum {
1298
1300
  let start = opts?.startIdx;
1299
1301
  let end = opts?.endIdx;
1300
1302
  const obsoleteViewNodes = this.viewNodes;
1303
+ const newNodesOnly = !!util.getOption(opts, "newNodesOnly");
1301
1304
 
1302
1305
  this.viewNodes = new Set();
1303
1306
  let viewNodes = this.viewNodes;
@@ -1322,9 +1325,10 @@ export class Wunderbaum {
1322
1325
  if (idx < start || idx > end) {
1323
1326
  node._callEvent("discard");
1324
1327
  node.removeMarkup();
1325
- } else {
1326
- // if (!node._rowElem || prevIdx != idx) {
1328
+ } else if (!node._rowElem || !newNodesOnly) {
1327
1329
  node.render({ top: top });
1330
+ // }else{
1331
+ // node.log("ignrored render")
1328
1332
  }
1329
1333
  idx++;
1330
1334
  top += height;
@@ -1352,21 +1356,25 @@ export class Wunderbaum {
1352
1356
  );
1353
1357
 
1354
1358
  for (let i = 0; i < this.columns.length; i++) {
1355
- let col = this.columns[i];
1356
- let colElem = <HTMLElement>headerRow.children[i];
1359
+ const col = this.columns[i];
1360
+ const colElem = <HTMLElement>headerRow.children[i];
1361
+
1357
1362
  colElem.style.left = col._ofsPx + "px";
1358
1363
  colElem.style.width = col._widthPx + "px";
1359
- colElem.textContent = col.title || col.id;
1364
+ // colElem.textContent = col.title || col.id;
1365
+ const title = util.escapeHtml(col.title || col.id);
1366
+ colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
1367
+ // colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
1360
1368
  }
1361
1369
  }
1362
1370
 
1363
1371
  /**
1372
+ * Make sure that this node is scrolled into the viewport.
1364
1373
  *
1365
1374
  * @param {boolean | PlainObject} [effects=false] animation options.
1366
1375
  * @param {object} [options=null] {topNode: null, effects: ..., parent: ...}
1367
1376
  * this node will remain visible in
1368
1377
  * any case, even if `this` is outside the scroll pane.
1369
- * Make sure that a node is scrolled into the viewport.
1370
1378
  */
1371
1379
  scrollTo(opts: any) {
1372
1380
  const MARGIN = 1;
@@ -1388,10 +1396,13 @@ export class Wunderbaum {
1388
1396
  // Node is above viewport
1389
1397
  newTop = nodeOfs + MARGIN;
1390
1398
  }
1391
- this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
1392
1399
  if (newTop != null) {
1400
+ this.log(
1401
+ "scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop,
1402
+ height
1403
+ );
1393
1404
  this.scrollContainer.scrollTop = newTop;
1394
- this.updateViewport();
1405
+ this.setModified(ChangeType.vscroll);
1395
1406
  }
1396
1407
  }
1397
1408
 
@@ -1456,6 +1467,9 @@ export class Wunderbaum {
1456
1467
  setModified(change: ChangeType, options?: any): void;
1457
1468
 
1458
1469
  /** */
1470
+ setModified(change: ChangeType, node: WunderbaumNode, options?: any): void;
1471
+
1472
+ /* */
1459
1473
  setModified(
1460
1474
  change: ChangeType,
1461
1475
  node?: WunderbaumNode | any,
@@ -1464,20 +1478,47 @@ export class Wunderbaum {
1464
1478
  if (!(node instanceof WunderbaumNode)) {
1465
1479
  options = node;
1466
1480
  }
1467
- if (!this.changedSince) {
1468
- this.changedSince = Date.now();
1469
- }
1470
- this.changes.add(change);
1471
- if (change === ChangeType.structure) {
1472
- this.changedNodes.clear();
1473
- } else if (node && !this.changes.has(ChangeType.structure)) {
1474
- if (this.changedNodes.size < MAX_CHANGED_NODES) {
1475
- this.changedNodes.add(node);
1476
- } else {
1477
- this.changes.add(ChangeType.structure);
1478
- this.changedNodes.clear();
1479
- }
1481
+ if (this._disableUpdate) {
1482
+ return;
1480
1483
  }
1484
+ const immediate = !!util.getOption(options, "immediate");
1485
+
1486
+ switch (change) {
1487
+ case ChangeType.any:
1488
+ case ChangeType.structure:
1489
+ case ChangeType.header:
1490
+ this.changeRedrawPending = true;
1491
+ this.updateViewport(immediate);
1492
+ break;
1493
+ case ChangeType.vscroll:
1494
+ this.updateViewport(immediate);
1495
+ break;
1496
+ case ChangeType.row:
1497
+ case ChangeType.status:
1498
+ // Single nodes are immedialtely updated if already inside the viewport
1499
+ // (otherwise we can ignore)
1500
+ if (node._rowElem) {
1501
+ node.render();
1502
+ }
1503
+ break;
1504
+ default:
1505
+ util.error(`Invalid change type ${change}`);
1506
+ }
1507
+
1508
+ // if (!this.changedSince) {
1509
+ // this.changedSince = Date.now();
1510
+ // }
1511
+ // this.changes.add(change);
1512
+ // if (change === ChangeType.structure) {
1513
+ // this.changedNodes.clear();
1514
+ // } else if (node && !this.changes.has(ChangeType.structure)) {
1515
+ // if (this.changedNodes.size < MAX_CHANGED_NODES) {
1516
+ // this.changedNodes.add(node);
1517
+ // } else {
1518
+ // this.changes.add(ChangeType.structure);
1519
+ // this.changedNodes.clear();
1520
+ // }
1521
+ // }
1481
1522
  // this.log("setModified(" + change + ")", node);
1482
1523
  }
1483
1524
 
@@ -1562,13 +1603,16 @@ export class Wunderbaum {
1562
1603
  if (this._disableUpdate) {
1563
1604
  return;
1564
1605
  }
1606
+ const newNodesOnly = !this.changeRedrawPending;
1607
+ this.changeRedrawPending = false;
1608
+
1565
1609
  let height = this.scrollContainer.clientHeight;
1566
- // We cannot get the height for abolut positioned parent, so look at first col
1610
+ // We cannot get the height for absolut positioned parent, so look at first col
1567
1611
  // let headerHeight = this.headerElement.clientHeight
1568
1612
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
1569
1613
  const headerHeight = this.options.headerHeightPx;
1570
- let wantHeight = this.element.clientHeight - headerHeight;
1571
- let ofs = this.scrollContainer.scrollTop;
1614
+ const wantHeight = this.element.clientHeight - headerHeight;
1615
+ const ofs = this.scrollContainer.scrollTop;
1572
1616
 
1573
1617
  if (Math.abs(height - wantHeight) > 1.0) {
1574
1618
  // this.log("resize", height, wantHeight);
@@ -1580,6 +1624,7 @@ export class Wunderbaum {
1580
1624
  this.render({
1581
1625
  startIdx: Math.max(0, ofs / ROW_HEIGHT - RENDER_MAX_PREFETCH),
1582
1626
  endIdx: Math.max(0, (ofs + height) / ROW_HEIGHT + RENDER_MAX_PREFETCH),
1627
+ newNodesOnly: newNodesOnly,
1583
1628
  });
1584
1629
  this._callEvent("update");
1585
1630
  }