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.
@@ -6,8 +6,8 @@
6
6
 
7
7
  /*!
8
8
  * Wunderbaum - util
9
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
10
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
9
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
10
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
11
11
  */
12
12
  /** @module util */
13
13
  /** Readable names for `MouseEvent.button` */
@@ -183,17 +183,19 @@
183
183
  * the element is checked, unchecked, or indeterminate.
184
184
  * For datetime input control a numerical value is assumed, etc.
185
185
  *
186
- * Common use case: store the new user input in the `change` event:
186
+ * Common use case: store the new user input in a `change` event handler:
187
187
  *
188
188
  * ```ts
189
189
  * change: (e) => {
190
+ * const tree = e.tree;
191
+ * const node = e.node;
190
192
  * // Read the value from the input control that triggered the change event:
191
- * let value = e.tree.getValueFromElem(e.element);
192
- * //
193
- * e.node.data[]
193
+ * let value = tree.getValueFromElem(e.element);
194
+ * // and store it to the node model (assuming the column id matches the property name)
195
+ * node.data[e.info.colId] = value;
194
196
  * },
195
197
  * ```
196
- * @param elem `<input>` or `<select>` element Also a parent `span.wb-col` is accepted.
198
+ * @param elem `<input>` or `<select>` element. Also a parent `span.wb-col` is accepted.
197
199
  * @param coerce pass true to convert date/time inputs to `Date`.
198
200
  * @returns the value
199
201
  */
@@ -257,6 +259,23 @@
257
259
  * value is truethy, falsy, or `null`.
258
260
  * For datetime input control a numerical value is assumed, etc.
259
261
  *
262
+ * Common use case: update embedded input controls in a `render` event handler:
263
+ *
264
+ * ```ts
265
+ * render: (e) => {
266
+ * // e.node.log(e.type, e, e.node.data);
267
+ *
268
+ * for (const col of Object.values(e.renderColInfosById)) {
269
+ * switch (col.id) {
270
+ * default:
271
+ * // Assumption: we named column.id === node.data.NAME
272
+ * util.setValueToElem(col.elem, e.node.data[col.id]);
273
+ * break;
274
+ * }
275
+ * }
276
+ * },
277
+ * ```
278
+ *
260
279
  * @param elem `<input>` or `<select>` element Also a parent `span.wb-col` is accepted.
261
280
  * @param value a value that matches the target element.
262
281
  */
@@ -288,7 +307,6 @@
288
307
  case "datetime":
289
308
  case "datetime-local":
290
309
  input.valueAsDate = new Date(value);
291
- // input.valueAsDate = value; // breaks in Edge?
292
310
  break;
293
311
  case "number":
294
312
  case "range":
@@ -300,7 +318,7 @@
300
318
  }
301
319
  break;
302
320
  case "radio":
303
- error("Not implemented");
321
+ error(`Not yet implemented: ${type}`);
304
322
  // const name = input.name;
305
323
  // const checked = input.parentElement!.querySelector(
306
324
  // `input[name="${name}"]:checked`
@@ -327,7 +345,7 @@
327
345
  }
328
346
  }
329
347
  }
330
- /** Show/hide element by setting the `display`style to 'none'. */
348
+ /** Show/hide element by setting the `display` style to 'none'. */
331
349
  function setElemDisplay(elem, flag) {
332
350
  const style = elemFromSelector(elem).style;
333
351
  if (flag) {
@@ -372,7 +390,23 @@
372
390
  * The result also contains a prefix for modifiers if any, for example
373
391
  * `"x"`, `"F2"`, `"Control+Home"`, or `"Shift+clickright"`.
374
392
  * This is especially useful in `switch` statements, to make sure that modifier
375
- * keys are considered and handled correctly.
393
+ * keys are considered and handled correctly:
394
+ * ```ts
395
+ * const eventName = util.eventToString(e);
396
+ * switch (eventName) {
397
+ * case "+":
398
+ * case "Add":
399
+ * ...
400
+ * break;
401
+ * case "Enter":
402
+ * case "End":
403
+ * case "Control+End":
404
+ * case "Meta+ArrowDown":
405
+ * case "PageDown":
406
+ * ...
407
+ * break;
408
+ * }
409
+ * ```
376
410
  */
377
411
  function eventToString(event) {
378
412
  let key = event.key, et = event.type, s = [];
@@ -597,6 +631,21 @@
597
631
  }
598
632
  throw new Error("Cannot convert to Set<string>: " + val);
599
633
  }
634
+ // /** Check if a string is contained in an Array or Set. */
635
+ // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
636
+ // return Array.prototype.includes.call(items, s)
637
+ // }
638
+ // /** Check if an Array or Set has at least one matching entry. */
639
+ // export function hasAnyOf(container: Array<string>|Set<string>, items: Array<string>): boolean {
640
+ // if (Array.isArray(container)) {
641
+ // return container.some(v => )
642
+ // }
643
+ // return container.some(v => {})
644
+ // // const container = toSet(items);
645
+ // // const itemSet = toSet(items);
646
+ // // Array.prototype.includes
647
+ // // throw new Error("Cannot convert to Set<string>: " + val);
648
+ // }
600
649
  /** Return a canonical string representation for an object's type (e.g. 'array', 'number', ...). */
601
650
  function type(obj) {
602
651
  return Object.prototype.toString
@@ -712,28 +761,40 @@
712
761
 
713
762
  /*!
714
763
  * Wunderbaum - types
715
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
716
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
764
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
765
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
766
+ */
767
+ /**
768
+ * Possible values for {@link WunderbaumNode.setModified()} and {@link Wunderbaum.setModified()}.
717
769
  */
718
- /** Possible values for `setModified()`. */
719
770
  var ChangeType;
720
771
  (function (ChangeType) {
721
772
  /** Re-render the whole viewport, headers, and all rows. */
722
773
  ChangeType["any"] = "any";
723
- /** Update current row title, icon, columns, and status. */
774
+ /** A node's title, icon, columns, or status have changed. Update the existing row markup. */
724
775
  ChangeType["data"] = "data";
725
- /** Redraw the header and update the width of all row columns. */
726
- ChangeType["header"] = "header";
727
- /** Re-render the whole current row. */
776
+ /** The `tree.columns` definition has changed beyond simple width adjustments. */
777
+ ChangeType["colStructure"] = "colStructure";
778
+ /** The viewport/window was resized. Adjust layout attributes for all elements. */
779
+ ChangeType["resize"] = "resize";
780
+ /** A node's definition has changed beyond status and data. Re-render the whole row's markup. */
728
781
  ChangeType["row"] = "row";
729
- /** Alias for 'any'. */
782
+ /** Nodes have been added, removed, etc. Update markup. */
730
783
  ChangeType["structure"] = "structure";
731
- /** Update current row's classes, to reflect active, selected, ... */
784
+ /** A node's status has changed. Update current row's classes, to reflect active, selected, ... */
732
785
  ChangeType["status"] = "status";
733
- /** Update the 'top' property of all rows. */
734
- ChangeType["vscroll"] = "vscroll";
786
+ /** Vertical scroll event. Update the 'top' property of all rows. */
787
+ ChangeType["scroll"] = "scroll";
735
788
  })(ChangeType || (ChangeType = {}));
736
- /** Possible values for `setStatus()`. */
789
+ /* Internal use. */
790
+ var RenderFlag;
791
+ (function (RenderFlag) {
792
+ RenderFlag["clearMarkup"] = "clearMarkup";
793
+ RenderFlag["header"] = "header";
794
+ RenderFlag["redraw"] = "redraw";
795
+ RenderFlag["scroll"] = "scroll";
796
+ })(RenderFlag || (RenderFlag = {}));
797
+ /** Possible values for {@link WunderbaumNode.setStatus()}. */
737
798
  var NodeStatusType;
738
799
  (function (NodeStatusType) {
739
800
  NodeStatusType["ok"] = "ok";
@@ -764,8 +825,8 @@
764
825
 
765
826
  /*!
766
827
  * Wunderbaum - wb_extension_base
767
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
768
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
828
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
829
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
769
830
  */
770
831
  class WunderbaumExtension {
771
832
  constructor(tree, id, defaults) {
@@ -1055,8 +1116,8 @@
1055
1116
 
1056
1117
  /*!
1057
1118
  * Wunderbaum - ext-filter
1058
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1059
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1119
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1120
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1060
1121
  */
1061
1122
  const START_MARKER = "\uFFF7";
1062
1123
  const END_MARKER = "\uFFF8";
@@ -1356,8 +1417,8 @@
1356
1417
 
1357
1418
  /*!
1358
1419
  * Wunderbaum - ext-keynav
1359
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1360
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1420
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1421
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1361
1422
  */
1362
1423
  const QUICKSEARCH_DELAY = 500;
1363
1424
  class KeynavExtension extends WunderbaumExtension {
@@ -1696,8 +1757,8 @@
1696
1757
 
1697
1758
  /*!
1698
1759
  * Wunderbaum - ext-logger
1699
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1700
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1760
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1761
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1701
1762
  */
1702
1763
  class LoggerExtension extends WunderbaumExtension {
1703
1764
  constructor(tree) {
@@ -1736,8 +1797,8 @@
1736
1797
 
1737
1798
  /*!
1738
1799
  * Wunderbaum - common
1739
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1740
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1800
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1801
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1741
1802
  */
1742
1803
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1743
1804
  /**
@@ -1859,6 +1920,12 @@
1859
1920
  return reMatch.test(node.title);
1860
1921
  };
1861
1922
  }
1923
+ /** Compare two nodes by title (case-insensitive). */
1924
+ function nodeTitleSorter(a, b) {
1925
+ const x = a.title.toLowerCase();
1926
+ const y = b.title.toLowerCase();
1927
+ return x === y ? 0 : x > y ? 1 : -1;
1928
+ }
1862
1929
  function unflattenSource(source) {
1863
1930
  var _a, _b, _c;
1864
1931
  const { _format, _keyMap, _positional, children } = source;
@@ -1976,8 +2043,8 @@
1976
2043
 
1977
2044
  /*!
1978
2045
  * Wunderbaum - ext-dnd
1979
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1980
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2046
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2047
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1981
2048
  */
1982
2049
  const nodeMimeType = "application/x-wunderbaum-node";
1983
2050
  class DndExtension extends WunderbaumExtension {
@@ -2088,6 +2155,7 @@
2088
2155
  // Only 'before' and 'after':
2089
2156
  return dy > ROW_HEIGHT / 2 ? "after" : "before";
2090
2157
  }
2158
+ // return "over";
2091
2159
  }
2092
2160
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
2093
2161
  autoScroll(event) {
@@ -2162,6 +2230,8 @@
2162
2230
  }
2163
2231
  onDropEvent(e) {
2164
2232
  // const isLink = event.dataTransfer.types.includes("text/uri-list");
2233
+ const srcNode = this.srcNode;
2234
+ const srcTree = srcNode ? srcNode.tree : null;
2165
2235
  const targetNode = Wunderbaum.getNode(e);
2166
2236
  const dndOpts = this.treeOpts.dnd;
2167
2237
  const dt = e.dataTransfer;
@@ -2169,7 +2239,7 @@
2169
2239
  this._leaveNode();
2170
2240
  return;
2171
2241
  }
2172
- if (e.type !== "dragover") {
2242
+ if (!["dragenter", "dragover", "dragleave"].includes(e.type)) {
2173
2243
  this.tree.logDebug("onDropEvent." +
2174
2244
  e.type +
2175
2245
  " targetNode: " +
@@ -2190,9 +2260,24 @@
2190
2260
  this.lastTargetNode = targetNode;
2191
2261
  this.lastEnterStamp = Date.now();
2192
2262
  if (
2193
- // Don't allow void operation ('drop on self')
2194
- (dndOpts.preventVoidMoves && targetNode === this.srcNode) ||
2195
- targetNode.isStatusNode()) {
2263
+ // Don't drop on status node:
2264
+ targetNode.isStatusNode() ||
2265
+ // Prevent dropping nodes from different Wunderbaum trees:
2266
+ (dndOpts.preventForeignNodes && targetNode.tree !== srcTree) ||
2267
+ // Prevent dropping items on unloaded lazy Wunderbaum tree nodes:
2268
+ (dndOpts.preventLazyParents && !targetNode.isLoaded()) ||
2269
+ // Prevent dropping items other than Wunderbaum tree nodes:
2270
+ (dndOpts.preventNonNodes && !srcNode) ||
2271
+ // Prevent dropping nodes on own descendants:
2272
+ (dndOpts.preventRecursion &&
2273
+ srcNode &&
2274
+ srcNode.isAncestorOf(targetNode)) ||
2275
+ // Prevent dropping nodes under same direct parent:
2276
+ (dndOpts.preventSameParent &&
2277
+ srcNode &&
2278
+ targetNode.parent === srcNode.parent) ||
2279
+ // Don't allow void operation ('drop on self'): TODO: should be checke onn move only
2280
+ (dndOpts.preventVoidMoves && targetNode === srcNode)) {
2196
2281
  dt.dropEffect = "none";
2197
2282
  return true; // Prevent drop operation
2198
2283
  }
@@ -2238,9 +2323,11 @@
2238
2323
  else if (e.type === "drop") {
2239
2324
  e.stopPropagation(); // prevent browser from opening links?
2240
2325
  this._leaveNode();
2326
+ const region = this.lastDropRegion;
2241
2327
  targetNode._callEvent("dnd.drop", {
2242
2328
  event: e,
2243
- region: this.lastDropRegion,
2329
+ region: region,
2330
+ defaultDropMode: region === "over" ? "appendChild" : region,
2244
2331
  sourceNode: this.srcNode,
2245
2332
  });
2246
2333
  }
@@ -2249,8 +2336,8 @@
2249
2336
 
2250
2337
  /*!
2251
2338
  * Wunderbaum - drag_observer
2252
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2253
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2339
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2340
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2254
2341
  */
2255
2342
  /**
2256
2343
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2385,8 +2472,8 @@
2385
2472
 
2386
2473
  /*!
2387
2474
  * Wunderbaum - ext-grid
2388
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2389
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2475
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2476
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2390
2477
  */
2391
2478
  class GridExtension extends WunderbaumExtension {
2392
2479
  constructor(tree) {
@@ -2422,8 +2509,8 @@
2422
2509
 
2423
2510
  /*!
2424
2511
  * Wunderbaum - deferred
2425
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2426
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2512
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2513
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2427
2514
  */
2428
2515
  /**
2429
2516
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2475,8 +2562,8 @@
2475
2562
 
2476
2563
  /*!
2477
2564
  * Wunderbaum - wunderbaum_node
2478
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2479
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2565
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2566
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2480
2567
  */
2481
2568
  /** Top-level properties that can be passed with `data`. */
2482
2569
  const NODE_PROPS = new Set([
@@ -2713,9 +2800,9 @@
2713
2800
  * @param [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
2714
2801
  * @returns new node
2715
2802
  */
2716
- addNode(nodeData, mode = "child") {
2803
+ addNode(nodeData, mode = "appendChild") {
2717
2804
  if (mode === "over") {
2718
- mode = "child"; // compatible with drop region
2805
+ mode = "appendChild"; // compatible with drop region
2719
2806
  }
2720
2807
  switch (mode) {
2721
2808
  case "after":
@@ -2724,11 +2811,11 @@
2724
2811
  });
2725
2812
  case "before":
2726
2813
  return this.parent.addChildren(nodeData, { before: this });
2727
- case "firstChild":
2814
+ case "prependChild":
2728
2815
  // Insert before the first child if any
2729
2816
  // let insertBefore = this.children ? this.children[0] : undefined;
2730
2817
  return this.addChildren(nodeData, { before: 0 });
2731
- case "child":
2818
+ case "appendChild":
2732
2819
  return this.addChildren(nodeData);
2733
2820
  }
2734
2821
  assert(false, "Invalid mode: " + mode);
@@ -3123,8 +3210,17 @@
3123
3210
  * an expand operation is currently possible.
3124
3211
  */
3125
3212
  isExpandable(andCollapsed = false) {
3126
- // return !!this.children && (!this.expanded || !andCollapsed);
3127
- return !!(this.children || this.lazy) && (!this.expanded || !andCollapsed);
3213
+ // `false` is never expandable (unoffical)
3214
+ if ((andCollapsed && this.expanded) || this.children === false) {
3215
+ return false;
3216
+ }
3217
+ if (this.children == null) {
3218
+ return this.lazy; // null or undefined can trigger lazy load
3219
+ }
3220
+ if (this.children.length === 0) {
3221
+ return !!this.tree.options.emptyChildListExpandable;
3222
+ }
3223
+ return true;
3128
3224
  }
3129
3225
  /** Return true if this node is currently in edit-title mode. */
3130
3226
  isEditing() {
@@ -3258,7 +3354,7 @@
3258
3354
  tree.logInfo("Redefine columns", source.columns);
3259
3355
  tree.columns = source.columns;
3260
3356
  delete source.columns;
3261
- tree.updateColumns({ calculateCols: false });
3357
+ tree.setModified(ChangeType.colStructure);
3262
3358
  }
3263
3359
  this.addChildren(source.children);
3264
3360
  // Add extra data to `tree.data`
@@ -3270,12 +3366,50 @@
3270
3366
  }
3271
3367
  this._callEvent("load");
3272
3368
  }
3369
+ async _fetchWithOptions(source) {
3370
+ var _a, _b;
3371
+ // Either a URL string or an object with a `.url` property.
3372
+ let url, params, body, options, rest;
3373
+ let fetchOpts = {};
3374
+ if (typeof source === "string") {
3375
+ // source is a plain URL string: assume GET request
3376
+ url = source;
3377
+ fetchOpts.method = "GET";
3378
+ }
3379
+ else if (isPlainObject(source)) {
3380
+ // source is a plain object with `.url` property.
3381
+ ({ url, params, body, options, ...rest } = source);
3382
+ assert(typeof url === "string", `expected source.url as string`);
3383
+ if (isPlainObject(options)) {
3384
+ fetchOpts = options;
3385
+ }
3386
+ if (isPlainObject(body)) {
3387
+ // we also accept 'body' as object...
3388
+ assert(!fetchOpts.body, "options.body should be passed as source.body");
3389
+ fetchOpts.body = JSON.stringify(fetchOpts.body);
3390
+ (_a = fetchOpts.method) !== null && _a !== void 0 ? _a : (fetchOpts.method = "POST"); // set default
3391
+ }
3392
+ if (isPlainObject(params)) {
3393
+ url += "?" + new URLSearchParams(params);
3394
+ (_b = fetchOpts.method) !== null && _b !== void 0 ? _b : (fetchOpts.method = "GET"); // set default
3395
+ }
3396
+ }
3397
+ else {
3398
+ url = ""; // keep linter happy
3399
+ error(`Unsupported source format: ${source}`);
3400
+ }
3401
+ this.setStatus(NodeStatusType.loading);
3402
+ const response = await fetch(url, fetchOpts);
3403
+ if (!response.ok) {
3404
+ error(`GET ${url} returned ${response.status}, ${response}`);
3405
+ }
3406
+ return await response.json();
3407
+ }
3273
3408
  /** Download data from the cloud, then call `.update()`. */
3274
3409
  async load(source) {
3275
3410
  const tree = this.tree;
3276
3411
  const requestId = Date.now();
3277
3412
  const prevParent = this.parent;
3278
- const url = typeof source === "string" ? source : source.url;
3279
3413
  const start = Date.now();
3280
3414
  let elap = 0, elapLoad = 0, elapProcess = 0;
3281
3415
  // Check for overlapping requests
@@ -3286,17 +3420,16 @@
3286
3420
  this._requestId = requestId;
3287
3421
  // const timerLabel = tree.logTime(this + ".load()");
3288
3422
  try {
3423
+ let url = typeof source === "string" ? source : source.url;
3289
3424
  if (!url) {
3425
+ // An array or a plain object (that does NOT contain a `.url` property)
3426
+ // will be treated as native Wunderbaum data
3290
3427
  this._loadSourceObject(source);
3291
3428
  elapProcess = Date.now() - start;
3292
3429
  }
3293
3430
  else {
3294
- this.setStatus(NodeStatusType.loading);
3295
- const response = await fetch(url, { method: "GET" });
3296
- if (!response.ok) {
3297
- error(`GET ${url} returned ${response.status}, ${response}`);
3298
- }
3299
- const data = await response.json();
3431
+ // Either a URL string or an object with a `.url` property.
3432
+ const data = await this._fetchWithOptions(source);
3300
3433
  elapLoad = Date.now() - start;
3301
3434
  if (this._requestId && this._requestId > requestId) {
3302
3435
  this.logWarn(`Ignored load response #${requestId} because #${this._requestId} is pending.`);
@@ -3439,6 +3572,9 @@
3439
3572
  }
3440
3573
  /** Move this node to targetNode. */
3441
3574
  moveTo(targetNode, mode = "appendChild", map) {
3575
+ if (mode === "over") {
3576
+ mode = "appendChild"; // compatible with drop region
3577
+ }
3442
3578
  if (mode === "prependChild") {
3443
3579
  if (targetNode.children && targetNode.children.length) {
3444
3580
  mode = "before";
@@ -3495,7 +3631,7 @@
3495
3631
  targetParent.children.splice(pos + 1, 0, this);
3496
3632
  break;
3497
3633
  default:
3498
- error("Invalid mode " + mode);
3634
+ error(`Invalid mode '${mode}'.`);
3499
3635
  }
3500
3636
  }
3501
3637
  else {
@@ -3522,7 +3658,12 @@
3522
3658
  n.tree = targetNode.tree;
3523
3659
  }, true);
3524
3660
  }
3525
- tree.setModified(ChangeType.structure);
3661
+ // Make sure we update async, because discarding the markup would prevent
3662
+ // DragAndDrop to generate a dragend event on the source node
3663
+ setTimeout(() => {
3664
+ // Even indentation may have changed:
3665
+ tree.setModified(ChangeType.any);
3666
+ }, 0);
3526
3667
  // TODO: fix selection state
3527
3668
  // TODO: fix active state
3528
3669
  }
@@ -3648,7 +3789,7 @@
3648
3789
  icon = iconMap.loading;
3649
3790
  }
3650
3791
  if (icon === false) {
3651
- return null;
3792
+ return null; // explicitly disabled: don't try default icons
3652
3793
  }
3653
3794
  if (typeof icon === "string") ;
3654
3795
  else if (this.statusNodeType) {
@@ -3667,7 +3808,11 @@
3667
3808
  icon = iconMap.doc;
3668
3809
  }
3669
3810
  // this.log("_createIcon: " + icon);
3670
- if (icon.indexOf("<") >= 0) {
3811
+ if (!icon) {
3812
+ iconSpan = document.createElement("i");
3813
+ iconSpan.className = "wb-icon";
3814
+ }
3815
+ else if (icon.indexOf("<") >= 0) {
3671
3816
  // HTML
3672
3817
  iconSpan = elemFromHtml(icon);
3673
3818
  }
@@ -3985,9 +4130,12 @@
3985
4130
  case "data":
3986
4131
  this._render_data(opts);
3987
4132
  break;
3988
- default:
4133
+ case "row":
4134
+ // _rowElem is not yet created (asserted in _render_markup)
3989
4135
  this._render_markup(opts);
3990
4136
  break;
4137
+ default:
4138
+ error(`Invalid change type '${opts.change}'.`);
3991
4139
  }
3992
4140
  }
3993
4141
  /**
@@ -4209,20 +4357,24 @@
4209
4357
  this.setModified();
4210
4358
  }
4211
4359
  /** Set a new icon path or class. */
4212
- setIcon() {
4213
- throw new Error("Not yet implemented");
4214
- // this.setModified();
4360
+ setIcon(icon) {
4361
+ this.icon = icon;
4362
+ this.setModified();
4215
4363
  }
4216
4364
  /** Change node's {@link key} and/or {@link refKey}. */
4217
4365
  setKey(key, refKey) {
4218
4366
  throw new Error("Not yet implemented");
4219
4367
  }
4220
4368
  /**
4221
- * Schedule a render, typically called to update after a status or data change.
4369
+ * Trigger a repaint, typically after a status or data change.
4222
4370
  *
4223
4371
  * `change` defaults to 'data', which handles modifcations of title, icon,
4224
4372
  * and column content. It can be reduced to 'ChangeType.status' if only
4225
4373
  * active/focus/selected state has changed.
4374
+ *
4375
+ * This method will eventually call {@link WunderbaumNode.render()} with
4376
+ * default options, but may be more consistent with the tree's
4377
+ * {@link Wunderbaum.setModified()} API.
4226
4378
  */
4227
4379
  setModified(change = ChangeType.data) {
4228
4380
  assert(change === ChangeType.status || change === ChangeType.data);
@@ -4258,7 +4410,7 @@
4258
4410
  let firstChild = children ? children[0] : null;
4259
4411
  assert(data.statusNodeType);
4260
4412
  assert(!firstChild || !firstChild.isStatusNode());
4261
- statusNode = this.addNode(data, "firstChild");
4413
+ statusNode = this.addNode(data, "prependChild");
4262
4414
  statusNode.match = true;
4263
4415
  tree.setModified(ChangeType.structure);
4264
4416
  return statusNode;
@@ -4324,11 +4476,37 @@
4324
4476
  this.setModified();
4325
4477
  // this.triggerModify("rename"); // TODO
4326
4478
  }
4479
+ _sortChildren(cmp, deep) {
4480
+ const cl = this.children;
4481
+ if (!cl) {
4482
+ return;
4483
+ }
4484
+ cl.sort(cmp);
4485
+ if (deep) {
4486
+ for (let i = 0, l = cl.length; i < l; i++) {
4487
+ if (cl[i].children) {
4488
+ cl[i]._sortChildren(cmp, deep);
4489
+ }
4490
+ }
4491
+ }
4492
+ }
4493
+ /**
4494
+ * Sort child list by title or custom criteria.
4495
+ * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
4496
+ * (defaults to sorting by title).
4497
+ * @param {boolean} deep pass true to sort all descendant nodes recursively
4498
+ */
4499
+ sortChildren(cmp = nodeTitleSorter, deep = false) {
4500
+ this._sortChildren(cmp || nodeTitleSorter, deep);
4501
+ this.tree.setModified(ChangeType.structure);
4502
+ // this.triggerModify("sort"); // TODO
4503
+ }
4327
4504
  /**
4328
4505
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
4329
4506
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
4330
4507
  */
4331
4508
  triggerModifyChild(operation, child, extra) {
4509
+ this.logDebug(`modifyChild(${operation})`, extra, child);
4332
4510
  if (!this.tree.options.modifyChild)
4333
4511
  return;
4334
4512
  if (child && child.parent !== this) {
@@ -4427,8 +4605,8 @@
4427
4605
 
4428
4606
  /*!
4429
4607
  * Wunderbaum - ext-edit
4430
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4431
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
4608
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
4609
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
4432
4610
  */
4433
4611
  // const START_MARKER = "\uFFF7";
4434
4612
  class EditExtension extends WunderbaumExtension {
@@ -4719,12 +4897,12 @@
4719
4897
  *
4720
4898
  * A treegrid control.
4721
4899
  *
4722
- * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
4900
+ * Copyright (c) 2021-2023, Martin Wendt (https://wwWendt.de).
4723
4901
  * https://github.com/mar10/wunderbaum
4724
4902
  *
4725
4903
  * Released under the MIT license.
4726
- * @version v0.2.0
4727
- * @date Tue, 17 Jan 2023 19:26:18 GMT
4904
+ * @version v0.3.0
4905
+ * @date Sat, 27 May 2023 04:56:52 GMT
4728
4906
  */
4729
4907
  class WbSystemRoot extends WunderbaumNode {
4730
4908
  constructor(tree) {
@@ -4753,6 +4931,7 @@
4753
4931
  this.refKeyMap = new Map();
4754
4932
  this.treeRowCount = 0;
4755
4933
  this._disableUpdateCount = 0;
4934
+ this._disableUpdateIgnoreCount = 0;
4756
4935
  /** Currently active node if any. */
4757
4936
  this.activeNode = null;
4758
4937
  /** Current node hat has keyboard focus if any. */
@@ -4763,8 +4942,7 @@
4763
4942
  this.columns = []; // any[] = [];
4764
4943
  this._columnsById = {};
4765
4944
  // Modification Status
4766
- this.changeRedrawRequestPending = false;
4767
- this.changeScrollRequestPending = false;
4945
+ this.pendingChangeTypes = new Set();
4768
4946
  /** Expose some useful methods of the util.ts module as `tree._util`. */
4769
4947
  this._util = util;
4770
4948
  // --- FILTER ---
@@ -4800,6 +4978,7 @@
4800
4978
  showSpinner: false,
4801
4979
  checkbox: false,
4802
4980
  minExpandLevel: 0,
4981
+ emptyChildListExpandable: false,
4803
4982
  updateThrottleWait: 200,
4804
4983
  skeleton: false,
4805
4984
  connectTopBreadcrumb: null,
@@ -4962,11 +5141,11 @@
4962
5141
  // --- Bind listeners
4963
5142
  this.element.addEventListener("scroll", (e) => {
4964
5143
  // this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
4965
- this.setModified(ChangeType.vscroll);
5144
+ this.setModified(ChangeType.scroll);
4966
5145
  });
4967
5146
  this.resizeObserver = new ResizeObserver((entries) => {
4968
- this.setModified(ChangeType.vscroll);
4969
5147
  // this.log("ResizeObserver: Size changed", entries);
5148
+ this.setModified(ChangeType.resize);
4970
5149
  });
4971
5150
  this.resizeObserver.observe(this.element);
4972
5151
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
@@ -5502,7 +5681,7 @@
5502
5681
  this.options[name] = value;
5503
5682
  switch (name) {
5504
5683
  case "checkbox":
5505
- this.setModified(ChangeType.any, { removeMarkup: true });
5684
+ this.setModified(ChangeType.any);
5506
5685
  break;
5507
5686
  case "enabled":
5508
5687
  this.setEnabled(!!value);
@@ -5802,8 +5981,9 @@
5802
5981
  res.region = NodeRegion.title;
5803
5982
  }
5804
5983
  else if (cl.contains("wb-expander")) {
5805
- res.region =
5806
- node.hasChildren() === false ? NodeRegion.prefix : NodeRegion.expander;
5984
+ res.region = node.isExpandable()
5985
+ ? NodeRegion.expander
5986
+ : NodeRegion.prefix;
5807
5987
  }
5808
5988
  else if (cl.contains("wb-checkbox")) {
5809
5989
  res.region = NodeRegion.checkbox;
@@ -5954,7 +6134,7 @@
5954
6134
  // Make sure the topNode is always visible
5955
6135
  this.scrollTo(topNode);
5956
6136
  }
5957
- // this.setModified(ChangeType.vscroll);
6137
+ // this.setModified(ChangeType.scroll);
5958
6138
  }
5959
6139
  }
5960
6140
  /**
@@ -5982,7 +6162,7 @@
5982
6162
  // util.assert(node._rowIdx != null);
5983
6163
  this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
5984
6164
  this.element.scrollLeft = newLeft;
5985
- // this.setModified(ChangeType.vscroll);
6165
+ // this.setModified(ChangeType.scroll);
5986
6166
  }
5987
6167
  /**
5988
6168
  * Set column #colIdx to 'active'.
@@ -6037,34 +6217,40 @@
6037
6217
  // this.log(
6038
6218
  // `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
6039
6219
  // );
6220
+ this._disableUpdateIgnoreCount++;
6040
6221
  return;
6041
6222
  }
6042
6223
  // this.log(`setModified(${change}) node=${node}`);
6043
6224
  if (!(node instanceof WunderbaumNode)) {
6044
6225
  options = node;
6226
+ node = null;
6045
6227
  }
6046
6228
  const immediate = !!getOption(options, "immediate");
6047
- const removeMarkup = !!getOption(options, "removeMarkup");
6048
- if (removeMarkup) {
6049
- this.visit((n) => {
6050
- n.removeMarkup();
6051
- });
6052
- }
6053
- let callUpdate = false;
6229
+ const RF = RenderFlag;
6230
+ const pending = this.pendingChangeTypes;
6054
6231
  switch (change) {
6055
6232
  case ChangeType.any:
6233
+ case ChangeType.colStructure:
6234
+ pending.add(RF.header);
6235
+ pending.add(RF.clearMarkup);
6236
+ pending.add(RF.redraw);
6237
+ pending.add(RF.scroll);
6238
+ break;
6239
+ case ChangeType.resize:
6240
+ // case ChangeType.colWidth:
6241
+ pending.add(RF.header);
6242
+ pending.add(RF.redraw);
6243
+ break;
6056
6244
  case ChangeType.structure:
6057
- case ChangeType.header:
6058
- this.changeRedrawRequestPending = true;
6059
- callUpdate = true;
6245
+ pending.add(RF.redraw);
6060
6246
  break;
6061
- case ChangeType.vscroll:
6062
- this.changeScrollRequestPending = true;
6063
- callUpdate = true;
6247
+ case ChangeType.scroll:
6248
+ pending.add(RF.scroll);
6064
6249
  break;
6065
6250
  case ChangeType.row:
6066
6251
  case ChangeType.data:
6067
6252
  case ChangeType.status:
6253
+ assert(node, `Option '${change}' requires a node.`);
6068
6254
  // Single nodes are immediately updated if already inside the viewport
6069
6255
  // (otherwise we can ignore)
6070
6256
  if (node._rowElem) {
@@ -6072,9 +6258,16 @@
6072
6258
  }
6073
6259
  break;
6074
6260
  default:
6075
- error(`Invalid change type ${change}`);
6261
+ error(`Invalid change type '${change}'.`);
6262
+ }
6263
+ if (change === ChangeType.colStructure) {
6264
+ const isGrid = this.isGrid();
6265
+ this.element.classList.toggle("wb-grid", isGrid);
6266
+ if (!isGrid && this.isCellNav()) {
6267
+ this.setCellNav(false);
6268
+ }
6076
6269
  }
6077
- if (callUpdate) {
6270
+ if (pending.size > 0) {
6078
6271
  if (immediate) {
6079
6272
  this._updateViewportImmediately();
6080
6273
  }
@@ -6146,7 +6339,7 @@
6146
6339
  }
6147
6340
  break;
6148
6341
  default:
6149
- error(`Invalid mode '${mode}'`);
6342
+ error(`Invalid mode '${mode}'.`);
6150
6343
  }
6151
6344
  }
6152
6345
  /** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
@@ -6169,14 +6362,24 @@
6169
6362
  }
6170
6363
  }
6171
6364
  }
6172
- /** Update column headers and width.
6365
+ /**
6366
+ * Sort nodes list by title or custom criteria.
6367
+ * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
6368
+ * (defaults to sorting by title).
6369
+ * @param {boolean} deep pass true to sort all descendant nodes recursively
6370
+ */
6371
+ sortChildren(cmp = nodeTitleSorter, deep = false) {
6372
+ this.root.sortChildren(cmp, deep);
6373
+ }
6374
+ /**
6375
+ * Update column headers and column width.
6173
6376
  * Return true if at least one column width changed.
6174
6377
  */
6175
- updateColumns(options) {
6176
- options = Object.assign({ calculateCols: true, updateRows: true }, options);
6378
+ // _updateColumnWidths(options?: UpdateColumnsOptions): boolean {
6379
+ _updateColumnWidths() {
6380
+ // options = Object.assign({ updateRows: true, renderMarkup: false }, options);
6177
6381
  const defaultMinWidth = 4;
6178
6382
  const vpWidth = this.element.clientWidth;
6179
- const isGrid = this.isGrid();
6180
6383
  // Shorten last column width to avoid h-scrollbar
6181
6384
  const FIX_ADJUST_LAST_COL = 2;
6182
6385
  const columns = this.columns;
@@ -6185,73 +6388,70 @@
6185
6388
  let totalWeight = 0;
6186
6389
  let fixedWidth = 0;
6187
6390
  let modified = false;
6188
- this.element.classList.toggle("wb-grid", isGrid);
6189
- if (!isGrid && this.isCellNav()) {
6190
- this.setCellNav(false);
6391
+ // this.element.classList.toggle("wb-grid", isGrid);
6392
+ // if (!isGrid && this.isCellNav()) {
6393
+ // this.setCellNav(false);
6394
+ // }
6395
+ // if (options.calculateCols) {
6396
+ if (col0.id !== "*") {
6397
+ throw new Error(`First column must have id '*': got '${col0.id}'.`);
6191
6398
  }
6192
- if (options.calculateCols) {
6193
- if (col0.id !== "*") {
6194
- throw new Error(`First column must have id '*': got '${col0.id}'`);
6195
- }
6196
- // Gather width definitions
6197
- this._columnsById = {};
6198
- for (let col of columns) {
6199
- this._columnsById[col.id] = col;
6200
- let cw = col.width;
6201
- if (col.id === "*" && col !== col0) {
6202
- throw new Error(`Column id '*' must be defined only once: '${col.title}'`);
6203
- }
6204
- if (!cw || cw === "*") {
6205
- col._weight = 1.0;
6206
- totalWeight += 1.0;
6399
+ // Gather width definitions
6400
+ this._columnsById = {};
6401
+ for (let col of columns) {
6402
+ this._columnsById[col.id] = col;
6403
+ let cw = col.width;
6404
+ if (col.id === "*" && col !== col0) {
6405
+ throw new Error(`Column id '*' must be defined only once: '${col.title}'.`);
6406
+ }
6407
+ if (!cw || cw === "*") {
6408
+ col._weight = 1.0;
6409
+ totalWeight += 1.0;
6410
+ }
6411
+ else if (typeof cw === "number") {
6412
+ col._weight = cw;
6413
+ totalWeight += cw;
6414
+ }
6415
+ else if (typeof cw === "string" && cw.endsWith("px")) {
6416
+ col._weight = 0;
6417
+ let px = parseFloat(cw.slice(0, -2));
6418
+ if (col._widthPx != px) {
6419
+ modified = true;
6420
+ col._widthPx = px;
6207
6421
  }
6208
- else if (typeof cw === "number") {
6209
- col._weight = cw;
6210
- totalWeight += cw;
6422
+ fixedWidth += px;
6423
+ }
6424
+ else {
6425
+ error(`Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>).`);
6426
+ }
6427
+ }
6428
+ // Share remaining space between non-fixed columns
6429
+ const restPx = Math.max(0, vpWidth - fixedWidth);
6430
+ let ofsPx = 0;
6431
+ for (let col of columns) {
6432
+ let minWidth;
6433
+ if (col._weight) {
6434
+ const cmw = col.minWidth;
6435
+ if (typeof cmw === "number") {
6436
+ minWidth = cmw;
6211
6437
  }
6212
- else if (typeof cw === "string" && cw.endsWith("px")) {
6213
- col._weight = 0;
6214
- let px = parseFloat(cw.slice(0, -2));
6215
- if (col._widthPx != px) {
6216
- modified = true;
6217
- col._widthPx = px;
6218
- }
6219
- fixedWidth += px;
6438
+ else if (typeof cmw === "string" && cmw.endsWith("px")) {
6439
+ minWidth = parseFloat(cmw.slice(0, -2));
6220
6440
  }
6221
6441
  else {
6222
- error(`Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>)`);
6442
+ minWidth = defaultMinWidth;
6223
6443
  }
6224
- }
6225
- // Share remaining space between non-fixed columns
6226
- const restPx = Math.max(0, vpWidth - fixedWidth);
6227
- let ofsPx = 0;
6228
- for (let col of columns) {
6229
- let minWidth;
6230
- if (col._weight) {
6231
- const cmw = col.minWidth;
6232
- if (typeof cmw === "number") {
6233
- minWidth = cmw;
6234
- }
6235
- else if (typeof cmw === "string" && cmw.endsWith("px")) {
6236
- minWidth = parseFloat(cmw.slice(0, -2));
6237
- }
6238
- else {
6239
- minWidth = defaultMinWidth;
6240
- }
6241
- const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6242
- if (col._widthPx != px) {
6243
- modified = true;
6244
- col._widthPx = px;
6245
- }
6444
+ const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6445
+ if (col._widthPx != px) {
6446
+ modified = true;
6447
+ col._widthPx = px;
6246
6448
  }
6247
- col._ofsPx = ofsPx;
6248
- ofsPx += col._widthPx;
6249
6449
  }
6250
- columns[columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6251
- totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
6450
+ col._ofsPx = ofsPx;
6451
+ ofsPx += col._widthPx;
6252
6452
  }
6253
- // if (this.options.fixedCol) {
6254
- // 'position: fixed' requires that the content has the correct size
6453
+ columns[columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6454
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
6255
6455
  const tw = `${totalWidth}px`;
6256
6456
  this.headerElement.style.width = tw;
6257
6457
  this.listContainerElement.style.width = tw;
@@ -6260,12 +6460,14 @@
6260
6460
  // this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
6261
6461
  // console.trace();
6262
6462
  // util.error("BREAK");
6263
- if (modified) {
6264
- this._renderHeaderMarkup();
6265
- if (options.updateRows) {
6266
- this._updateRows();
6267
- }
6268
- }
6463
+ // if (modified) {
6464
+ // this._renderHeaderMarkup();
6465
+ // if (options.renderMarkup) {
6466
+ // this.setModified(ChangeType.header, { removeMarkup: true });
6467
+ // } else if (options.updateRows) {
6468
+ // this._updateRows();
6469
+ // }
6470
+ // }
6269
6471
  return modified;
6270
6472
  }
6271
6473
  /** Create/update header markup from `this.columns` definition.
@@ -6314,7 +6516,7 @@
6314
6516
  * pending async changes if any.
6315
6517
  */
6316
6518
  updatePendingModifications() {
6317
- if (this.changeRedrawRequestPending || this.changeScrollRequestPending) {
6519
+ if (this.pendingChangeTypes.size > 0) {
6318
6520
  this._updateViewportImmediately();
6319
6521
  }
6320
6522
  }
@@ -6329,31 +6531,50 @@
6329
6531
  */
6330
6532
  _updateViewportImmediately() {
6331
6533
  var _a;
6332
- // Shorten container height to avoid v-scrollbar
6333
- const FIX_ADJUST_HEIGHT = 1;
6334
6534
  if (this._disableUpdateCount) {
6335
- this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
6535
+ this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`);
6536
+ this._disableUpdateIgnoreCount++;
6336
6537
  return;
6337
6538
  }
6338
- const newNodesOnly = !this.changeRedrawRequestPending;
6339
- this.changeRedrawRequestPending = false;
6340
- this.changeScrollRequestPending = false;
6341
- let height = this.listContainerElement.clientHeight;
6342
- // We cannot get the height for absolute positioned parent, so look at first col
6343
- // let headerHeight = this.headerElement.clientHeight
6344
- // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
6345
- // const headerHeight = this.options.headerHeightPx;
6346
- const headerHeight = this.headerElement.clientHeight; // May be 0
6347
- const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
6348
- if (Math.abs(height - wantHeight) > 1.0) {
6349
- // this.log("resize", height, wantHeight);
6350
- this.listContainerElement.style.height = wantHeight + "px";
6351
- height = wantHeight;
6352
- }
6353
- // console.profile(`_updateViewportImmediately()`)
6354
- const modified = this.updateColumns({ updateRows: false });
6355
- this._updateRows({ newNodesOnly: newNodesOnly && !modified });
6356
- // console.profileEnd(`_updateViewportImmediately()`)
6539
+ // Shorten container height to avoid v-scrollbar
6540
+ const FIX_ADJUST_HEIGHT = 1;
6541
+ const RF = RenderFlag;
6542
+ const pending = new Set(this.pendingChangeTypes);
6543
+ this.pendingChangeTypes.clear();
6544
+ const scrollOnly = pending.has(RF.scroll) && pending.size === 1;
6545
+ if (scrollOnly) {
6546
+ this._updateRows({ newNodesOnly: true });
6547
+ // this.log("_updateViewportImmediately(): scroll only.");
6548
+ }
6549
+ else {
6550
+ this.log("_updateViewportImmediately():", pending);
6551
+ let height = this.listContainerElement.clientHeight;
6552
+ // We cannot get the height for absolute positioned parent, so look at first col
6553
+ // let headerHeight = this.headerElement.clientHeight
6554
+ // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
6555
+ // const headerHeight = this.options.headerHeightPx;
6556
+ const headerHeight = this.headerElement.clientHeight; // May be 0
6557
+ const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
6558
+ if (Math.abs(height - wantHeight) > 1.0) {
6559
+ // this.log("resize", height, wantHeight);
6560
+ this.listContainerElement.style.height = wantHeight + "px";
6561
+ height = wantHeight;
6562
+ }
6563
+ // console.profile(`_updateViewportImmediately()`)
6564
+ if (pending.has(RF.clearMarkup)) {
6565
+ this.visit((n) => {
6566
+ n.removeMarkup();
6567
+ });
6568
+ }
6569
+ // let widthModified = false;
6570
+ if (pending.has(RF.header)) {
6571
+ // widthModified = this._updateColumnWidths();
6572
+ this._updateColumnWidths();
6573
+ this._renderHeaderMarkup();
6574
+ }
6575
+ this._updateRows();
6576
+ // console.profileEnd(`_updateViewportImmediately()`)
6577
+ }
6357
6578
  if (this.options.connectTopBreadcrumb) {
6358
6579
  let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
6359
6580
  path = path ? path + " >" : "";
@@ -6494,19 +6715,14 @@
6494
6715
  return this.root.visit(callback, false);
6495
6716
  }
6496
6717
  /**
6497
- * Call fn(node) for all nodes in vertical order, top down (or bottom up).
6718
+ * Call callback(node) for all nodes in vertical order, top down (or bottom up).
6498
6719
  *
6499
- * Note that this considers expansion state, i.e. children of collapsed nodes
6500
- * are skipped.
6720
+ * Note that this considers expansion state, i.e. filtered nodes and children
6721
+ * of collapsed nodes are skipped, unless `includeHidden` is set.
6501
6722
  *
6502
- * Stop iteration, if fn() returns false.<br>
6723
+ * Stop iteration if callback() returns false.<br>
6503
6724
  * Return false if iteration was stopped.
6504
6725
  *
6505
- * @param callback the callback function.
6506
- * Return false to stop iteration, return "skip" to skip this node and children only.
6507
- * @param [options]
6508
- * Defaults:
6509
- * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6510
6726
  * @returns {boolean} false if iteration was canceled
6511
6727
  */
6512
6728
  visitRows(callback, options) {
@@ -6524,7 +6740,7 @@
6524
6740
  // visit siblings
6525
6741
  siblings = parent.children;
6526
6742
  nextIdx = siblings.indexOf(node) + siblingOfs;
6527
- assert(nextIdx >= 0, "Could not find " + node + " in parent's children: " + parent);
6743
+ assert(nextIdx >= 0, `Could not find ${node} in parent's children: ${parent}`);
6528
6744
  for (i = nextIdx; i < siblings.length; i++) {
6529
6745
  node = siblings[i];
6530
6746
  if (node === stopNode) {
@@ -6662,8 +6878,8 @@
6662
6878
  // `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
6663
6879
  // );
6664
6880
  if (this._disableUpdateCount === 0) {
6665
- // this.changeRedrawRequestPending = true; // make sure, we re-render all markup
6666
- // this.updateViewport();
6881
+ this.logDebug(`enableUpdate(): active again. Re-painting to catch up with ${this._disableUpdateIgnoreCount} ignored update requests...`);
6882
+ this._disableUpdateIgnoreCount = 0;
6667
6883
  this.setModified(ChangeType.any, { immediate: true });
6668
6884
  }
6669
6885
  }
@@ -6718,7 +6934,7 @@
6718
6934
  }
6719
6935
  Wunderbaum.sequence = 0;
6720
6936
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6721
- Wunderbaum.version = "v0.2.0"; // Set to semver by 'grunt release'
6937
+ Wunderbaum.version = "v0.3.0"; // Set to semver by 'grunt release'
6722
6938
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6723
6939
  Wunderbaum.util = util;
6724
6940