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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Wunderbaum - util
3
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
3
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
4
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  /** @module util */
7
7
  /** Readable names for `MouseEvent.button` */
@@ -177,17 +177,19 @@ function extractHtmlText(s) {
177
177
  * the element is checked, unchecked, or indeterminate.
178
178
  * For datetime input control a numerical value is assumed, etc.
179
179
  *
180
- * Common use case: store the new user input in the `change` event:
180
+ * Common use case: store the new user input in a `change` event handler:
181
181
  *
182
182
  * ```ts
183
183
  * change: (e) => {
184
+ * const tree = e.tree;
185
+ * const node = e.node;
184
186
  * // Read the value from the input control that triggered the change event:
185
- * let value = e.tree.getValueFromElem(e.element);
186
- * //
187
- * e.node.data[]
187
+ * let value = tree.getValueFromElem(e.element);
188
+ * // and store it to the node model (assuming the column id matches the property name)
189
+ * node.data[e.info.colId] = value;
188
190
  * },
189
191
  * ```
190
- * @param elem `<input>` or `<select>` element Also a parent `span.wb-col` is accepted.
192
+ * @param elem `<input>` or `<select>` element. Also a parent `span.wb-col` is accepted.
191
193
  * @param coerce pass true to convert date/time inputs to `Date`.
192
194
  * @returns the value
193
195
  */
@@ -251,6 +253,23 @@ function getValueFromElem(elem, coerce = false) {
251
253
  * value is truethy, falsy, or `null`.
252
254
  * For datetime input control a numerical value is assumed, etc.
253
255
  *
256
+ * Common use case: update embedded input controls in a `render` event handler:
257
+ *
258
+ * ```ts
259
+ * render: (e) => {
260
+ * // e.node.log(e.type, e, e.node.data);
261
+ *
262
+ * for (const col of Object.values(e.renderColInfosById)) {
263
+ * switch (col.id) {
264
+ * default:
265
+ * // Assumption: we named column.id === node.data.NAME
266
+ * util.setValueToElem(col.elem, e.node.data[col.id]);
267
+ * break;
268
+ * }
269
+ * }
270
+ * },
271
+ * ```
272
+ *
254
273
  * @param elem `<input>` or `<select>` element Also a parent `span.wb-col` is accepted.
255
274
  * @param value a value that matches the target element.
256
275
  */
@@ -282,7 +301,6 @@ function setValueToElem(elem, value) {
282
301
  case "datetime":
283
302
  case "datetime-local":
284
303
  input.valueAsDate = new Date(value);
285
- // input.valueAsDate = value; // breaks in Edge?
286
304
  break;
287
305
  case "number":
288
306
  case "range":
@@ -294,7 +312,7 @@ function setValueToElem(elem, value) {
294
312
  }
295
313
  break;
296
314
  case "radio":
297
- error("Not implemented");
315
+ error(`Not yet implemented: ${type}`);
298
316
  // const name = input.name;
299
317
  // const checked = input.parentElement!.querySelector(
300
318
  // `input[name="${name}"]:checked`
@@ -321,7 +339,7 @@ function setValueToElem(elem, value) {
321
339
  }
322
340
  }
323
341
  }
324
- /** Show/hide element by setting the `display`style to 'none'. */
342
+ /** Show/hide element by setting the `display` style to 'none'. */
325
343
  function setElemDisplay(elem, flag) {
326
344
  const style = elemFromSelector(elem).style;
327
345
  if (flag) {
@@ -366,7 +384,23 @@ function eventTargetFromSelector(obj) {
366
384
  * The result also contains a prefix for modifiers if any, for example
367
385
  * `"x"`, `"F2"`, `"Control+Home"`, or `"Shift+clickright"`.
368
386
  * This is especially useful in `switch` statements, to make sure that modifier
369
- * keys are considered and handled correctly.
387
+ * keys are considered and handled correctly:
388
+ * ```ts
389
+ * const eventName = util.eventToString(e);
390
+ * switch (eventName) {
391
+ * case "+":
392
+ * case "Add":
393
+ * ...
394
+ * break;
395
+ * case "Enter":
396
+ * case "End":
397
+ * case "Control+End":
398
+ * case "Meta+ArrowDown":
399
+ * case "PageDown":
400
+ * ...
401
+ * break;
402
+ * }
403
+ * ```
370
404
  */
371
405
  function eventToString(event) {
372
406
  let key = event.key, et = event.type, s = [];
@@ -591,6 +625,21 @@ function toSet(val) {
591
625
  }
592
626
  throw new Error("Cannot convert to Set<string>: " + val);
593
627
  }
628
+ // /** Check if a string is contained in an Array or Set. */
629
+ // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
630
+ // return Array.prototype.includes.call(items, s)
631
+ // }
632
+ // /** Check if an Array or Set has at least one matching entry. */
633
+ // export function hasAnyOf(container: Array<string>|Set<string>, items: Array<string>): boolean {
634
+ // if (Array.isArray(container)) {
635
+ // return container.some(v => )
636
+ // }
637
+ // return container.some(v => {})
638
+ // // const container = toSet(items);
639
+ // // const itemSet = toSet(items);
640
+ // // Array.prototype.includes
641
+ // // throw new Error("Cannot convert to Set<string>: " + val);
642
+ // }
594
643
  /** Return a canonical string representation for an object's type (e.g. 'array', 'number', ...). */
595
644
  function type(obj) {
596
645
  return Object.prototype.toString
@@ -706,28 +755,40 @@ var util = /*#__PURE__*/Object.freeze({
706
755
 
707
756
  /*!
708
757
  * Wunderbaum - types
709
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
710
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
758
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
759
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
760
+ */
761
+ /**
762
+ * Possible values for {@link WunderbaumNode.setModified()} and {@link Wunderbaum.setModified()}.
711
763
  */
712
- /** Possible values for `setModified()`. */
713
764
  var ChangeType;
714
765
  (function (ChangeType) {
715
766
  /** Re-render the whole viewport, headers, and all rows. */
716
767
  ChangeType["any"] = "any";
717
- /** Update current row title, icon, columns, and status. */
768
+ /** A node's title, icon, columns, or status have changed. Update the existing row markup. */
718
769
  ChangeType["data"] = "data";
719
- /** Redraw the header and update the width of all row columns. */
720
- ChangeType["header"] = "header";
721
- /** Re-render the whole current row. */
770
+ /** The `tree.columns` definition has changed beyond simple width adjustments. */
771
+ ChangeType["colStructure"] = "colStructure";
772
+ /** The viewport/window was resized. Adjust layout attributes for all elements. */
773
+ ChangeType["resize"] = "resize";
774
+ /** A node's definition has changed beyond status and data. Re-render the whole row's markup. */
722
775
  ChangeType["row"] = "row";
723
- /** Alias for 'any'. */
776
+ /** Nodes have been added, removed, etc. Update markup. */
724
777
  ChangeType["structure"] = "structure";
725
- /** Update current row's classes, to reflect active, selected, ... */
778
+ /** A node's status has changed. Update current row's classes, to reflect active, selected, ... */
726
779
  ChangeType["status"] = "status";
727
- /** Update the 'top' property of all rows. */
728
- ChangeType["vscroll"] = "vscroll";
780
+ /** Vertical scroll event. Update the 'top' property of all rows. */
781
+ ChangeType["scroll"] = "scroll";
729
782
  })(ChangeType || (ChangeType = {}));
730
- /** Possible values for `setStatus()`. */
783
+ /* Internal use. */
784
+ var RenderFlag;
785
+ (function (RenderFlag) {
786
+ RenderFlag["clearMarkup"] = "clearMarkup";
787
+ RenderFlag["header"] = "header";
788
+ RenderFlag["redraw"] = "redraw";
789
+ RenderFlag["scroll"] = "scroll";
790
+ })(RenderFlag || (RenderFlag = {}));
791
+ /** Possible values for {@link WunderbaumNode.setStatus()}. */
731
792
  var NodeStatusType;
732
793
  (function (NodeStatusType) {
733
794
  NodeStatusType["ok"] = "ok";
@@ -758,8 +819,8 @@ var NavModeEnum;
758
819
 
759
820
  /*!
760
821
  * Wunderbaum - wb_extension_base
761
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
762
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
822
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
823
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
763
824
  */
764
825
  class WunderbaumExtension {
765
826
  constructor(tree, id, defaults) {
@@ -1049,8 +1110,8 @@ function debounce(func, wait = 0, options = {}) {
1049
1110
 
1050
1111
  /*!
1051
1112
  * Wunderbaum - ext-filter
1052
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1053
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1113
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1114
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1054
1115
  */
1055
1116
  const START_MARKER = "\uFFF7";
1056
1117
  const END_MARKER = "\uFFF8";
@@ -1350,8 +1411,8 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1350
1411
 
1351
1412
  /*!
1352
1413
  * Wunderbaum - ext-keynav
1353
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1354
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1414
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1415
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1355
1416
  */
1356
1417
  const QUICKSEARCH_DELAY = 500;
1357
1418
  class KeynavExtension extends WunderbaumExtension {
@@ -1690,8 +1751,8 @@ class KeynavExtension extends WunderbaumExtension {
1690
1751
 
1691
1752
  /*!
1692
1753
  * Wunderbaum - ext-logger
1693
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1694
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1754
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1755
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1695
1756
  */
1696
1757
  class LoggerExtension extends WunderbaumExtension {
1697
1758
  constructor(tree) {
@@ -1730,8 +1791,8 @@ class LoggerExtension extends WunderbaumExtension {
1730
1791
 
1731
1792
  /*!
1732
1793
  * Wunderbaum - common
1733
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1734
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
1794
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1795
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1735
1796
  */
1736
1797
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1737
1798
  /**
@@ -1853,6 +1914,12 @@ function makeNodeTitleStartMatcher(s) {
1853
1914
  return reMatch.test(node.title);
1854
1915
  };
1855
1916
  }
1917
+ /** Compare two nodes by title (case-insensitive). */
1918
+ function nodeTitleSorter(a, b) {
1919
+ const x = a.title.toLowerCase();
1920
+ const y = b.title.toLowerCase();
1921
+ return x === y ? 0 : x > y ? 1 : -1;
1922
+ }
1856
1923
  function unflattenSource(source) {
1857
1924
  var _a, _b, _c;
1858
1925
  const { _format, _keyMap, _positional, children } = source;
@@ -1970,8 +2037,8 @@ function inflateSourceData(source) {
1970
2037
 
1971
2038
  /*!
1972
2039
  * Wunderbaum - ext-dnd
1973
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1974
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2040
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2041
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
1975
2042
  */
1976
2043
  const nodeMimeType = "application/x-wunderbaum-node";
1977
2044
  class DndExtension extends WunderbaumExtension {
@@ -2082,6 +2149,7 @@ class DndExtension extends WunderbaumExtension {
2082
2149
  // Only 'before' and 'after':
2083
2150
  return dy > ROW_HEIGHT / 2 ? "after" : "before";
2084
2151
  }
2152
+ // return "over";
2085
2153
  }
2086
2154
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
2087
2155
  autoScroll(event) {
@@ -2156,6 +2224,8 @@ class DndExtension extends WunderbaumExtension {
2156
2224
  }
2157
2225
  onDropEvent(e) {
2158
2226
  // const isLink = event.dataTransfer.types.includes("text/uri-list");
2227
+ const srcNode = this.srcNode;
2228
+ const srcTree = srcNode ? srcNode.tree : null;
2159
2229
  const targetNode = Wunderbaum.getNode(e);
2160
2230
  const dndOpts = this.treeOpts.dnd;
2161
2231
  const dt = e.dataTransfer;
@@ -2163,7 +2233,7 @@ class DndExtension extends WunderbaumExtension {
2163
2233
  this._leaveNode();
2164
2234
  return;
2165
2235
  }
2166
- if (e.type !== "dragover") {
2236
+ if (!["dragenter", "dragover", "dragleave"].includes(e.type)) {
2167
2237
  this.tree.logDebug("onDropEvent." +
2168
2238
  e.type +
2169
2239
  " targetNode: " +
@@ -2184,9 +2254,24 @@ class DndExtension extends WunderbaumExtension {
2184
2254
  this.lastTargetNode = targetNode;
2185
2255
  this.lastEnterStamp = Date.now();
2186
2256
  if (
2187
- // Don't allow void operation ('drop on self')
2188
- (dndOpts.preventVoidMoves && targetNode === this.srcNode) ||
2189
- targetNode.isStatusNode()) {
2257
+ // Don't drop on status node:
2258
+ targetNode.isStatusNode() ||
2259
+ // Prevent dropping nodes from different Wunderbaum trees:
2260
+ (dndOpts.preventForeignNodes && targetNode.tree !== srcTree) ||
2261
+ // Prevent dropping items on unloaded lazy Wunderbaum tree nodes:
2262
+ (dndOpts.preventLazyParents && !targetNode.isLoaded()) ||
2263
+ // Prevent dropping items other than Wunderbaum tree nodes:
2264
+ (dndOpts.preventNonNodes && !srcNode) ||
2265
+ // Prevent dropping nodes on own descendants:
2266
+ (dndOpts.preventRecursion &&
2267
+ srcNode &&
2268
+ srcNode.isAncestorOf(targetNode)) ||
2269
+ // Prevent dropping nodes under same direct parent:
2270
+ (dndOpts.preventSameParent &&
2271
+ srcNode &&
2272
+ targetNode.parent === srcNode.parent) ||
2273
+ // Don't allow void operation ('drop on self'): TODO: should be checke onn move only
2274
+ (dndOpts.preventVoidMoves && targetNode === srcNode)) {
2190
2275
  dt.dropEffect = "none";
2191
2276
  return true; // Prevent drop operation
2192
2277
  }
@@ -2232,9 +2317,11 @@ class DndExtension extends WunderbaumExtension {
2232
2317
  else if (e.type === "drop") {
2233
2318
  e.stopPropagation(); // prevent browser from opening links?
2234
2319
  this._leaveNode();
2320
+ const region = this.lastDropRegion;
2235
2321
  targetNode._callEvent("dnd.drop", {
2236
2322
  event: e,
2237
- region: this.lastDropRegion,
2323
+ region: region,
2324
+ defaultDropMode: region === "over" ? "appendChild" : region,
2238
2325
  sourceNode: this.srcNode,
2239
2326
  });
2240
2327
  }
@@ -2243,8 +2330,8 @@ class DndExtension extends WunderbaumExtension {
2243
2330
 
2244
2331
  /*!
2245
2332
  * Wunderbaum - drag_observer
2246
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2247
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2333
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2334
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2248
2335
  */
2249
2336
  /**
2250
2337
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2379,8 +2466,8 @@ class DragObserver {
2379
2466
 
2380
2467
  /*!
2381
2468
  * Wunderbaum - ext-grid
2382
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2383
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2469
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2470
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2384
2471
  */
2385
2472
  class GridExtension extends WunderbaumExtension {
2386
2473
  constructor(tree) {
@@ -2416,8 +2503,8 @@ class GridExtension extends WunderbaumExtension {
2416
2503
 
2417
2504
  /*!
2418
2505
  * Wunderbaum - deferred
2419
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2420
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2506
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2507
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2421
2508
  */
2422
2509
  /**
2423
2510
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2469,8 +2556,8 @@ class Deferred {
2469
2556
 
2470
2557
  /*!
2471
2558
  * Wunderbaum - wunderbaum_node
2472
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2473
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
2559
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2560
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
2474
2561
  */
2475
2562
  /** Top-level properties that can be passed with `data`. */
2476
2563
  const NODE_PROPS = new Set([
@@ -2707,9 +2794,9 @@ class WunderbaumNode {
2707
2794
  * @param [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
2708
2795
  * @returns new node
2709
2796
  */
2710
- addNode(nodeData, mode = "child") {
2797
+ addNode(nodeData, mode = "appendChild") {
2711
2798
  if (mode === "over") {
2712
- mode = "child"; // compatible with drop region
2799
+ mode = "appendChild"; // compatible with drop region
2713
2800
  }
2714
2801
  switch (mode) {
2715
2802
  case "after":
@@ -2718,11 +2805,11 @@ class WunderbaumNode {
2718
2805
  });
2719
2806
  case "before":
2720
2807
  return this.parent.addChildren(nodeData, { before: this });
2721
- case "firstChild":
2808
+ case "prependChild":
2722
2809
  // Insert before the first child if any
2723
2810
  // let insertBefore = this.children ? this.children[0] : undefined;
2724
2811
  return this.addChildren(nodeData, { before: 0 });
2725
- case "child":
2812
+ case "appendChild":
2726
2813
  return this.addChildren(nodeData);
2727
2814
  }
2728
2815
  assert(false, "Invalid mode: " + mode);
@@ -3117,8 +3204,17 @@ class WunderbaumNode {
3117
3204
  * an expand operation is currently possible.
3118
3205
  */
3119
3206
  isExpandable(andCollapsed = false) {
3120
- // return !!this.children && (!this.expanded || !andCollapsed);
3121
- return !!(this.children || this.lazy) && (!this.expanded || !andCollapsed);
3207
+ // `false` is never expandable (unoffical)
3208
+ if ((andCollapsed && this.expanded) || this.children === false) {
3209
+ return false;
3210
+ }
3211
+ if (this.children == null) {
3212
+ return this.lazy; // null or undefined can trigger lazy load
3213
+ }
3214
+ if (this.children.length === 0) {
3215
+ return !!this.tree.options.emptyChildListExpandable;
3216
+ }
3217
+ return true;
3122
3218
  }
3123
3219
  /** Return true if this node is currently in edit-title mode. */
3124
3220
  isEditing() {
@@ -3252,7 +3348,7 @@ class WunderbaumNode {
3252
3348
  tree.logInfo("Redefine columns", source.columns);
3253
3349
  tree.columns = source.columns;
3254
3350
  delete source.columns;
3255
- tree.updateColumns({ calculateCols: false });
3351
+ tree.setModified(ChangeType.colStructure);
3256
3352
  }
3257
3353
  this.addChildren(source.children);
3258
3354
  // Add extra data to `tree.data`
@@ -3264,12 +3360,50 @@ class WunderbaumNode {
3264
3360
  }
3265
3361
  this._callEvent("load");
3266
3362
  }
3363
+ async _fetchWithOptions(source) {
3364
+ var _a, _b;
3365
+ // Either a URL string or an object with a `.url` property.
3366
+ let url, params, body, options, rest;
3367
+ let fetchOpts = {};
3368
+ if (typeof source === "string") {
3369
+ // source is a plain URL string: assume GET request
3370
+ url = source;
3371
+ fetchOpts.method = "GET";
3372
+ }
3373
+ else if (isPlainObject(source)) {
3374
+ // source is a plain object with `.url` property.
3375
+ ({ url, params, body, options, ...rest } = source);
3376
+ assert(typeof url === "string", `expected source.url as string`);
3377
+ if (isPlainObject(options)) {
3378
+ fetchOpts = options;
3379
+ }
3380
+ if (isPlainObject(body)) {
3381
+ // we also accept 'body' as object...
3382
+ assert(!fetchOpts.body, "options.body should be passed as source.body");
3383
+ fetchOpts.body = JSON.stringify(fetchOpts.body);
3384
+ (_a = fetchOpts.method) !== null && _a !== void 0 ? _a : (fetchOpts.method = "POST"); // set default
3385
+ }
3386
+ if (isPlainObject(params)) {
3387
+ url += "?" + new URLSearchParams(params);
3388
+ (_b = fetchOpts.method) !== null && _b !== void 0 ? _b : (fetchOpts.method = "GET"); // set default
3389
+ }
3390
+ }
3391
+ else {
3392
+ url = ""; // keep linter happy
3393
+ error(`Unsupported source format: ${source}`);
3394
+ }
3395
+ this.setStatus(NodeStatusType.loading);
3396
+ const response = await fetch(url, fetchOpts);
3397
+ if (!response.ok) {
3398
+ error(`GET ${url} returned ${response.status}, ${response}`);
3399
+ }
3400
+ return await response.json();
3401
+ }
3267
3402
  /** Download data from the cloud, then call `.update()`. */
3268
3403
  async load(source) {
3269
3404
  const tree = this.tree;
3270
3405
  const requestId = Date.now();
3271
3406
  const prevParent = this.parent;
3272
- const url = typeof source === "string" ? source : source.url;
3273
3407
  const start = Date.now();
3274
3408
  let elap = 0, elapLoad = 0, elapProcess = 0;
3275
3409
  // Check for overlapping requests
@@ -3280,17 +3414,16 @@ class WunderbaumNode {
3280
3414
  this._requestId = requestId;
3281
3415
  // const timerLabel = tree.logTime(this + ".load()");
3282
3416
  try {
3417
+ let url = typeof source === "string" ? source : source.url;
3283
3418
  if (!url) {
3419
+ // An array or a plain object (that does NOT contain a `.url` property)
3420
+ // will be treated as native Wunderbaum data
3284
3421
  this._loadSourceObject(source);
3285
3422
  elapProcess = Date.now() - start;
3286
3423
  }
3287
3424
  else {
3288
- this.setStatus(NodeStatusType.loading);
3289
- const response = await fetch(url, { method: "GET" });
3290
- if (!response.ok) {
3291
- error(`GET ${url} returned ${response.status}, ${response}`);
3292
- }
3293
- const data = await response.json();
3425
+ // Either a URL string or an object with a `.url` property.
3426
+ const data = await this._fetchWithOptions(source);
3294
3427
  elapLoad = Date.now() - start;
3295
3428
  if (this._requestId && this._requestId > requestId) {
3296
3429
  this.logWarn(`Ignored load response #${requestId} because #${this._requestId} is pending.`);
@@ -3433,6 +3566,9 @@ class WunderbaumNode {
3433
3566
  }
3434
3567
  /** Move this node to targetNode. */
3435
3568
  moveTo(targetNode, mode = "appendChild", map) {
3569
+ if (mode === "over") {
3570
+ mode = "appendChild"; // compatible with drop region
3571
+ }
3436
3572
  if (mode === "prependChild") {
3437
3573
  if (targetNode.children && targetNode.children.length) {
3438
3574
  mode = "before";
@@ -3489,7 +3625,7 @@ class WunderbaumNode {
3489
3625
  targetParent.children.splice(pos + 1, 0, this);
3490
3626
  break;
3491
3627
  default:
3492
- error("Invalid mode " + mode);
3628
+ error(`Invalid mode '${mode}'.`);
3493
3629
  }
3494
3630
  }
3495
3631
  else {
@@ -3516,7 +3652,12 @@ class WunderbaumNode {
3516
3652
  n.tree = targetNode.tree;
3517
3653
  }, true);
3518
3654
  }
3519
- tree.setModified(ChangeType.structure);
3655
+ // Make sure we update async, because discarding the markup would prevent
3656
+ // DragAndDrop to generate a dragend event on the source node
3657
+ setTimeout(() => {
3658
+ // Even indentation may have changed:
3659
+ tree.setModified(ChangeType.any);
3660
+ }, 0);
3520
3661
  // TODO: fix selection state
3521
3662
  // TODO: fix active state
3522
3663
  }
@@ -3642,7 +3783,7 @@ class WunderbaumNode {
3642
3783
  icon = iconMap.loading;
3643
3784
  }
3644
3785
  if (icon === false) {
3645
- return null;
3786
+ return null; // explicitly disabled: don't try default icons
3646
3787
  }
3647
3788
  if (typeof icon === "string") ;
3648
3789
  else if (this.statusNodeType) {
@@ -3661,7 +3802,11 @@ class WunderbaumNode {
3661
3802
  icon = iconMap.doc;
3662
3803
  }
3663
3804
  // this.log("_createIcon: " + icon);
3664
- if (icon.indexOf("<") >= 0) {
3805
+ if (!icon) {
3806
+ iconSpan = document.createElement("i");
3807
+ iconSpan.className = "wb-icon";
3808
+ }
3809
+ else if (icon.indexOf("<") >= 0) {
3665
3810
  // HTML
3666
3811
  iconSpan = elemFromHtml(icon);
3667
3812
  }
@@ -3979,9 +4124,12 @@ class WunderbaumNode {
3979
4124
  case "data":
3980
4125
  this._render_data(opts);
3981
4126
  break;
3982
- default:
4127
+ case "row":
4128
+ // _rowElem is not yet created (asserted in _render_markup)
3983
4129
  this._render_markup(opts);
3984
4130
  break;
4131
+ default:
4132
+ error(`Invalid change type '${opts.change}'.`);
3985
4133
  }
3986
4134
  }
3987
4135
  /**
@@ -4203,20 +4351,24 @@ class WunderbaumNode {
4203
4351
  this.setModified();
4204
4352
  }
4205
4353
  /** Set a new icon path or class. */
4206
- setIcon() {
4207
- throw new Error("Not yet implemented");
4208
- // this.setModified();
4354
+ setIcon(icon) {
4355
+ this.icon = icon;
4356
+ this.setModified();
4209
4357
  }
4210
4358
  /** Change node's {@link key} and/or {@link refKey}. */
4211
4359
  setKey(key, refKey) {
4212
4360
  throw new Error("Not yet implemented");
4213
4361
  }
4214
4362
  /**
4215
- * Schedule a render, typically called to update after a status or data change.
4363
+ * Trigger a repaint, typically after a status or data change.
4216
4364
  *
4217
4365
  * `change` defaults to 'data', which handles modifcations of title, icon,
4218
4366
  * and column content. It can be reduced to 'ChangeType.status' if only
4219
4367
  * active/focus/selected state has changed.
4368
+ *
4369
+ * This method will eventually call {@link WunderbaumNode.render()} with
4370
+ * default options, but may be more consistent with the tree's
4371
+ * {@link Wunderbaum.setModified()} API.
4220
4372
  */
4221
4373
  setModified(change = ChangeType.data) {
4222
4374
  assert(change === ChangeType.status || change === ChangeType.data);
@@ -4252,7 +4404,7 @@ class WunderbaumNode {
4252
4404
  let firstChild = children ? children[0] : null;
4253
4405
  assert(data.statusNodeType);
4254
4406
  assert(!firstChild || !firstChild.isStatusNode());
4255
- statusNode = this.addNode(data, "firstChild");
4407
+ statusNode = this.addNode(data, "prependChild");
4256
4408
  statusNode.match = true;
4257
4409
  tree.setModified(ChangeType.structure);
4258
4410
  return statusNode;
@@ -4318,11 +4470,37 @@ class WunderbaumNode {
4318
4470
  this.setModified();
4319
4471
  // this.triggerModify("rename"); // TODO
4320
4472
  }
4473
+ _sortChildren(cmp, deep) {
4474
+ const cl = this.children;
4475
+ if (!cl) {
4476
+ return;
4477
+ }
4478
+ cl.sort(cmp);
4479
+ if (deep) {
4480
+ for (let i = 0, l = cl.length; i < l; i++) {
4481
+ if (cl[i].children) {
4482
+ cl[i]._sortChildren(cmp, deep);
4483
+ }
4484
+ }
4485
+ }
4486
+ }
4487
+ /**
4488
+ * Sort child list by title or custom criteria.
4489
+ * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
4490
+ * (defaults to sorting by title).
4491
+ * @param {boolean} deep pass true to sort all descendant nodes recursively
4492
+ */
4493
+ sortChildren(cmp = nodeTitleSorter, deep = false) {
4494
+ this._sortChildren(cmp || nodeTitleSorter, deep);
4495
+ this.tree.setModified(ChangeType.structure);
4496
+ // this.triggerModify("sort"); // TODO
4497
+ }
4321
4498
  /**
4322
4499
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
4323
4500
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
4324
4501
  */
4325
4502
  triggerModifyChild(operation, child, extra) {
4503
+ this.logDebug(`modifyChild(${operation})`, extra, child);
4326
4504
  if (!this.tree.options.modifyChild)
4327
4505
  return;
4328
4506
  if (child && child.parent !== this) {
@@ -4421,8 +4599,8 @@ WunderbaumNode.sequence = 0;
4421
4599
 
4422
4600
  /*!
4423
4601
  * Wunderbaum - ext-edit
4424
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4425
- * v0.2.0, Tue, 17 Jan 2023 19:26:18 GMT (https://github.com/mar10/wunderbaum)
4602
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
4603
+ * v0.3.0, Sat, 27 May 2023 04:56:52 GMT (https://github.com/mar10/wunderbaum)
4426
4604
  */
4427
4605
  // const START_MARKER = "\uFFF7";
4428
4606
  class EditExtension extends WunderbaumExtension {
@@ -4713,12 +4891,12 @@ class EditExtension extends WunderbaumExtension {
4713
4891
  *
4714
4892
  * A treegrid control.
4715
4893
  *
4716
- * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
4894
+ * Copyright (c) 2021-2023, Martin Wendt (https://wwWendt.de).
4717
4895
  * https://github.com/mar10/wunderbaum
4718
4896
  *
4719
4897
  * Released under the MIT license.
4720
- * @version v0.2.0
4721
- * @date Tue, 17 Jan 2023 19:26:18 GMT
4898
+ * @version v0.3.0
4899
+ * @date Sat, 27 May 2023 04:56:52 GMT
4722
4900
  */
4723
4901
  class WbSystemRoot extends WunderbaumNode {
4724
4902
  constructor(tree) {
@@ -4747,6 +4925,7 @@ class Wunderbaum {
4747
4925
  this.refKeyMap = new Map();
4748
4926
  this.treeRowCount = 0;
4749
4927
  this._disableUpdateCount = 0;
4928
+ this._disableUpdateIgnoreCount = 0;
4750
4929
  /** Currently active node if any. */
4751
4930
  this.activeNode = null;
4752
4931
  /** Current node hat has keyboard focus if any. */
@@ -4757,8 +4936,7 @@ class Wunderbaum {
4757
4936
  this.columns = []; // any[] = [];
4758
4937
  this._columnsById = {};
4759
4938
  // Modification Status
4760
- this.changeRedrawRequestPending = false;
4761
- this.changeScrollRequestPending = false;
4939
+ this.pendingChangeTypes = new Set();
4762
4940
  /** Expose some useful methods of the util.ts module as `tree._util`. */
4763
4941
  this._util = util;
4764
4942
  // --- FILTER ---
@@ -4794,6 +4972,7 @@ class Wunderbaum {
4794
4972
  showSpinner: false,
4795
4973
  checkbox: false,
4796
4974
  minExpandLevel: 0,
4975
+ emptyChildListExpandable: false,
4797
4976
  updateThrottleWait: 200,
4798
4977
  skeleton: false,
4799
4978
  connectTopBreadcrumb: null,
@@ -4956,11 +5135,11 @@ class Wunderbaum {
4956
5135
  // --- Bind listeners
4957
5136
  this.element.addEventListener("scroll", (e) => {
4958
5137
  // this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
4959
- this.setModified(ChangeType.vscroll);
5138
+ this.setModified(ChangeType.scroll);
4960
5139
  });
4961
5140
  this.resizeObserver = new ResizeObserver((entries) => {
4962
- this.setModified(ChangeType.vscroll);
4963
5141
  // this.log("ResizeObserver: Size changed", entries);
5142
+ this.setModified(ChangeType.resize);
4964
5143
  });
4965
5144
  this.resizeObserver.observe(this.element);
4966
5145
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
@@ -5496,7 +5675,7 @@ class Wunderbaum {
5496
5675
  this.options[name] = value;
5497
5676
  switch (name) {
5498
5677
  case "checkbox":
5499
- this.setModified(ChangeType.any, { removeMarkup: true });
5678
+ this.setModified(ChangeType.any);
5500
5679
  break;
5501
5680
  case "enabled":
5502
5681
  this.setEnabled(!!value);
@@ -5796,8 +5975,9 @@ class Wunderbaum {
5796
5975
  res.region = NodeRegion.title;
5797
5976
  }
5798
5977
  else if (cl.contains("wb-expander")) {
5799
- res.region =
5800
- node.hasChildren() === false ? NodeRegion.prefix : NodeRegion.expander;
5978
+ res.region = node.isExpandable()
5979
+ ? NodeRegion.expander
5980
+ : NodeRegion.prefix;
5801
5981
  }
5802
5982
  else if (cl.contains("wb-checkbox")) {
5803
5983
  res.region = NodeRegion.checkbox;
@@ -5948,7 +6128,7 @@ class Wunderbaum {
5948
6128
  // Make sure the topNode is always visible
5949
6129
  this.scrollTo(topNode);
5950
6130
  }
5951
- // this.setModified(ChangeType.vscroll);
6131
+ // this.setModified(ChangeType.scroll);
5952
6132
  }
5953
6133
  }
5954
6134
  /**
@@ -5976,7 +6156,7 @@ class Wunderbaum {
5976
6156
  // util.assert(node._rowIdx != null);
5977
6157
  this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
5978
6158
  this.element.scrollLeft = newLeft;
5979
- // this.setModified(ChangeType.vscroll);
6159
+ // this.setModified(ChangeType.scroll);
5980
6160
  }
5981
6161
  /**
5982
6162
  * Set column #colIdx to 'active'.
@@ -6031,34 +6211,40 @@ class Wunderbaum {
6031
6211
  // this.log(
6032
6212
  // `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
6033
6213
  // );
6214
+ this._disableUpdateIgnoreCount++;
6034
6215
  return;
6035
6216
  }
6036
6217
  // this.log(`setModified(${change}) node=${node}`);
6037
6218
  if (!(node instanceof WunderbaumNode)) {
6038
6219
  options = node;
6220
+ node = null;
6039
6221
  }
6040
6222
  const immediate = !!getOption(options, "immediate");
6041
- const removeMarkup = !!getOption(options, "removeMarkup");
6042
- if (removeMarkup) {
6043
- this.visit((n) => {
6044
- n.removeMarkup();
6045
- });
6046
- }
6047
- let callUpdate = false;
6223
+ const RF = RenderFlag;
6224
+ const pending = this.pendingChangeTypes;
6048
6225
  switch (change) {
6049
6226
  case ChangeType.any:
6227
+ case ChangeType.colStructure:
6228
+ pending.add(RF.header);
6229
+ pending.add(RF.clearMarkup);
6230
+ pending.add(RF.redraw);
6231
+ pending.add(RF.scroll);
6232
+ break;
6233
+ case ChangeType.resize:
6234
+ // case ChangeType.colWidth:
6235
+ pending.add(RF.header);
6236
+ pending.add(RF.redraw);
6237
+ break;
6050
6238
  case ChangeType.structure:
6051
- case ChangeType.header:
6052
- this.changeRedrawRequestPending = true;
6053
- callUpdate = true;
6239
+ pending.add(RF.redraw);
6054
6240
  break;
6055
- case ChangeType.vscroll:
6056
- this.changeScrollRequestPending = true;
6057
- callUpdate = true;
6241
+ case ChangeType.scroll:
6242
+ pending.add(RF.scroll);
6058
6243
  break;
6059
6244
  case ChangeType.row:
6060
6245
  case ChangeType.data:
6061
6246
  case ChangeType.status:
6247
+ assert(node, `Option '${change}' requires a node.`);
6062
6248
  // Single nodes are immediately updated if already inside the viewport
6063
6249
  // (otherwise we can ignore)
6064
6250
  if (node._rowElem) {
@@ -6066,9 +6252,16 @@ class Wunderbaum {
6066
6252
  }
6067
6253
  break;
6068
6254
  default:
6069
- error(`Invalid change type ${change}`);
6255
+ error(`Invalid change type '${change}'.`);
6256
+ }
6257
+ if (change === ChangeType.colStructure) {
6258
+ const isGrid = this.isGrid();
6259
+ this.element.classList.toggle("wb-grid", isGrid);
6260
+ if (!isGrid && this.isCellNav()) {
6261
+ this.setCellNav(false);
6262
+ }
6070
6263
  }
6071
- if (callUpdate) {
6264
+ if (pending.size > 0) {
6072
6265
  if (immediate) {
6073
6266
  this._updateViewportImmediately();
6074
6267
  }
@@ -6140,7 +6333,7 @@ class Wunderbaum {
6140
6333
  }
6141
6334
  break;
6142
6335
  default:
6143
- error(`Invalid mode '${mode}'`);
6336
+ error(`Invalid mode '${mode}'.`);
6144
6337
  }
6145
6338
  }
6146
6339
  /** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
@@ -6163,14 +6356,24 @@ class Wunderbaum {
6163
6356
  }
6164
6357
  }
6165
6358
  }
6166
- /** Update column headers and width.
6359
+ /**
6360
+ * Sort nodes list by title or custom criteria.
6361
+ * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
6362
+ * (defaults to sorting by title).
6363
+ * @param {boolean} deep pass true to sort all descendant nodes recursively
6364
+ */
6365
+ sortChildren(cmp = nodeTitleSorter, deep = false) {
6366
+ this.root.sortChildren(cmp, deep);
6367
+ }
6368
+ /**
6369
+ * Update column headers and column width.
6167
6370
  * Return true if at least one column width changed.
6168
6371
  */
6169
- updateColumns(options) {
6170
- options = Object.assign({ calculateCols: true, updateRows: true }, options);
6372
+ // _updateColumnWidths(options?: UpdateColumnsOptions): boolean {
6373
+ _updateColumnWidths() {
6374
+ // options = Object.assign({ updateRows: true, renderMarkup: false }, options);
6171
6375
  const defaultMinWidth = 4;
6172
6376
  const vpWidth = this.element.clientWidth;
6173
- const isGrid = this.isGrid();
6174
6377
  // Shorten last column width to avoid h-scrollbar
6175
6378
  const FIX_ADJUST_LAST_COL = 2;
6176
6379
  const columns = this.columns;
@@ -6179,73 +6382,70 @@ class Wunderbaum {
6179
6382
  let totalWeight = 0;
6180
6383
  let fixedWidth = 0;
6181
6384
  let modified = false;
6182
- this.element.classList.toggle("wb-grid", isGrid);
6183
- if (!isGrid && this.isCellNav()) {
6184
- this.setCellNav(false);
6385
+ // this.element.classList.toggle("wb-grid", isGrid);
6386
+ // if (!isGrid && this.isCellNav()) {
6387
+ // this.setCellNav(false);
6388
+ // }
6389
+ // if (options.calculateCols) {
6390
+ if (col0.id !== "*") {
6391
+ throw new Error(`First column must have id '*': got '${col0.id}'.`);
6185
6392
  }
6186
- if (options.calculateCols) {
6187
- if (col0.id !== "*") {
6188
- throw new Error(`First column must have id '*': got '${col0.id}'`);
6189
- }
6190
- // Gather width definitions
6191
- this._columnsById = {};
6192
- for (let col of columns) {
6193
- this._columnsById[col.id] = col;
6194
- let cw = col.width;
6195
- if (col.id === "*" && col !== col0) {
6196
- throw new Error(`Column id '*' must be defined only once: '${col.title}'`);
6197
- }
6198
- if (!cw || cw === "*") {
6199
- col._weight = 1.0;
6200
- totalWeight += 1.0;
6393
+ // Gather width definitions
6394
+ this._columnsById = {};
6395
+ for (let col of columns) {
6396
+ this._columnsById[col.id] = col;
6397
+ let cw = col.width;
6398
+ if (col.id === "*" && col !== col0) {
6399
+ throw new Error(`Column id '*' must be defined only once: '${col.title}'.`);
6400
+ }
6401
+ if (!cw || cw === "*") {
6402
+ col._weight = 1.0;
6403
+ totalWeight += 1.0;
6404
+ }
6405
+ else if (typeof cw === "number") {
6406
+ col._weight = cw;
6407
+ totalWeight += cw;
6408
+ }
6409
+ else if (typeof cw === "string" && cw.endsWith("px")) {
6410
+ col._weight = 0;
6411
+ let px = parseFloat(cw.slice(0, -2));
6412
+ if (col._widthPx != px) {
6413
+ modified = true;
6414
+ col._widthPx = px;
6201
6415
  }
6202
- else if (typeof cw === "number") {
6203
- col._weight = cw;
6204
- totalWeight += cw;
6416
+ fixedWidth += px;
6417
+ }
6418
+ else {
6419
+ error(`Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>).`);
6420
+ }
6421
+ }
6422
+ // Share remaining space between non-fixed columns
6423
+ const restPx = Math.max(0, vpWidth - fixedWidth);
6424
+ let ofsPx = 0;
6425
+ for (let col of columns) {
6426
+ let minWidth;
6427
+ if (col._weight) {
6428
+ const cmw = col.minWidth;
6429
+ if (typeof cmw === "number") {
6430
+ minWidth = cmw;
6205
6431
  }
6206
- else if (typeof cw === "string" && cw.endsWith("px")) {
6207
- col._weight = 0;
6208
- let px = parseFloat(cw.slice(0, -2));
6209
- if (col._widthPx != px) {
6210
- modified = true;
6211
- col._widthPx = px;
6212
- }
6213
- fixedWidth += px;
6432
+ else if (typeof cmw === "string" && cmw.endsWith("px")) {
6433
+ minWidth = parseFloat(cmw.slice(0, -2));
6214
6434
  }
6215
6435
  else {
6216
- error(`Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>)`);
6436
+ minWidth = defaultMinWidth;
6217
6437
  }
6218
- }
6219
- // Share remaining space between non-fixed columns
6220
- const restPx = Math.max(0, vpWidth - fixedWidth);
6221
- let ofsPx = 0;
6222
- for (let col of columns) {
6223
- let minWidth;
6224
- if (col._weight) {
6225
- const cmw = col.minWidth;
6226
- if (typeof cmw === "number") {
6227
- minWidth = cmw;
6228
- }
6229
- else if (typeof cmw === "string" && cmw.endsWith("px")) {
6230
- minWidth = parseFloat(cmw.slice(0, -2));
6231
- }
6232
- else {
6233
- minWidth = defaultMinWidth;
6234
- }
6235
- const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6236
- if (col._widthPx != px) {
6237
- modified = true;
6238
- col._widthPx = px;
6239
- }
6438
+ const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6439
+ if (col._widthPx != px) {
6440
+ modified = true;
6441
+ col._widthPx = px;
6240
6442
  }
6241
- col._ofsPx = ofsPx;
6242
- ofsPx += col._widthPx;
6243
6443
  }
6244
- columns[columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6245
- totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
6444
+ col._ofsPx = ofsPx;
6445
+ ofsPx += col._widthPx;
6246
6446
  }
6247
- // if (this.options.fixedCol) {
6248
- // 'position: fixed' requires that the content has the correct size
6447
+ columns[columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6448
+ totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
6249
6449
  const tw = `${totalWidth}px`;
6250
6450
  this.headerElement.style.width = tw;
6251
6451
  this.listContainerElement.style.width = tw;
@@ -6254,12 +6454,14 @@ class Wunderbaum {
6254
6454
  // this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
6255
6455
  // console.trace();
6256
6456
  // util.error("BREAK");
6257
- if (modified) {
6258
- this._renderHeaderMarkup();
6259
- if (options.updateRows) {
6260
- this._updateRows();
6261
- }
6262
- }
6457
+ // if (modified) {
6458
+ // this._renderHeaderMarkup();
6459
+ // if (options.renderMarkup) {
6460
+ // this.setModified(ChangeType.header, { removeMarkup: true });
6461
+ // } else if (options.updateRows) {
6462
+ // this._updateRows();
6463
+ // }
6464
+ // }
6263
6465
  return modified;
6264
6466
  }
6265
6467
  /** Create/update header markup from `this.columns` definition.
@@ -6308,7 +6510,7 @@ class Wunderbaum {
6308
6510
  * pending async changes if any.
6309
6511
  */
6310
6512
  updatePendingModifications() {
6311
- if (this.changeRedrawRequestPending || this.changeScrollRequestPending) {
6513
+ if (this.pendingChangeTypes.size > 0) {
6312
6514
  this._updateViewportImmediately();
6313
6515
  }
6314
6516
  }
@@ -6323,31 +6525,50 @@ class Wunderbaum {
6323
6525
  */
6324
6526
  _updateViewportImmediately() {
6325
6527
  var _a;
6326
- // Shorten container height to avoid v-scrollbar
6327
- const FIX_ADJUST_HEIGHT = 1;
6328
6528
  if (this._disableUpdateCount) {
6329
- this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
6529
+ this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`);
6530
+ this._disableUpdateIgnoreCount++;
6330
6531
  return;
6331
6532
  }
6332
- const newNodesOnly = !this.changeRedrawRequestPending;
6333
- this.changeRedrawRequestPending = false;
6334
- this.changeScrollRequestPending = false;
6335
- let height = this.listContainerElement.clientHeight;
6336
- // We cannot get the height for absolute positioned parent, so look at first col
6337
- // let headerHeight = this.headerElement.clientHeight
6338
- // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
6339
- // const headerHeight = this.options.headerHeightPx;
6340
- const headerHeight = this.headerElement.clientHeight; // May be 0
6341
- const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
6342
- if (Math.abs(height - wantHeight) > 1.0) {
6343
- // this.log("resize", height, wantHeight);
6344
- this.listContainerElement.style.height = wantHeight + "px";
6345
- height = wantHeight;
6346
- }
6347
- // console.profile(`_updateViewportImmediately()`)
6348
- const modified = this.updateColumns({ updateRows: false });
6349
- this._updateRows({ newNodesOnly: newNodesOnly && !modified });
6350
- // console.profileEnd(`_updateViewportImmediately()`)
6533
+ // Shorten container height to avoid v-scrollbar
6534
+ const FIX_ADJUST_HEIGHT = 1;
6535
+ const RF = RenderFlag;
6536
+ const pending = new Set(this.pendingChangeTypes);
6537
+ this.pendingChangeTypes.clear();
6538
+ const scrollOnly = pending.has(RF.scroll) && pending.size === 1;
6539
+ if (scrollOnly) {
6540
+ this._updateRows({ newNodesOnly: true });
6541
+ // this.log("_updateViewportImmediately(): scroll only.");
6542
+ }
6543
+ else {
6544
+ this.log("_updateViewportImmediately():", pending);
6545
+ let height = this.listContainerElement.clientHeight;
6546
+ // We cannot get the height for absolute positioned parent, so look at first col
6547
+ // let headerHeight = this.headerElement.clientHeight
6548
+ // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
6549
+ // const headerHeight = this.options.headerHeightPx;
6550
+ const headerHeight = this.headerElement.clientHeight; // May be 0
6551
+ const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
6552
+ if (Math.abs(height - wantHeight) > 1.0) {
6553
+ // this.log("resize", height, wantHeight);
6554
+ this.listContainerElement.style.height = wantHeight + "px";
6555
+ height = wantHeight;
6556
+ }
6557
+ // console.profile(`_updateViewportImmediately()`)
6558
+ if (pending.has(RF.clearMarkup)) {
6559
+ this.visit((n) => {
6560
+ n.removeMarkup();
6561
+ });
6562
+ }
6563
+ // let widthModified = false;
6564
+ if (pending.has(RF.header)) {
6565
+ // widthModified = this._updateColumnWidths();
6566
+ this._updateColumnWidths();
6567
+ this._renderHeaderMarkup();
6568
+ }
6569
+ this._updateRows();
6570
+ // console.profileEnd(`_updateViewportImmediately()`)
6571
+ }
6351
6572
  if (this.options.connectTopBreadcrumb) {
6352
6573
  let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
6353
6574
  path = path ? path + " >" : "";
@@ -6488,19 +6709,14 @@ class Wunderbaum {
6488
6709
  return this.root.visit(callback, false);
6489
6710
  }
6490
6711
  /**
6491
- * Call fn(node) for all nodes in vertical order, top down (or bottom up).
6712
+ * Call callback(node) for all nodes in vertical order, top down (or bottom up).
6492
6713
  *
6493
- * Note that this considers expansion state, i.e. children of collapsed nodes
6494
- * are skipped.
6714
+ * Note that this considers expansion state, i.e. filtered nodes and children
6715
+ * of collapsed nodes are skipped, unless `includeHidden` is set.
6495
6716
  *
6496
- * Stop iteration, if fn() returns false.<br>
6717
+ * Stop iteration if callback() returns false.<br>
6497
6718
  * Return false if iteration was stopped.
6498
6719
  *
6499
- * @param callback the callback function.
6500
- * Return false to stop iteration, return "skip" to skip this node and children only.
6501
- * @param [options]
6502
- * Defaults:
6503
- * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6504
6720
  * @returns {boolean} false if iteration was canceled
6505
6721
  */
6506
6722
  visitRows(callback, options) {
@@ -6518,7 +6734,7 @@ class Wunderbaum {
6518
6734
  // visit siblings
6519
6735
  siblings = parent.children;
6520
6736
  nextIdx = siblings.indexOf(node) + siblingOfs;
6521
- assert(nextIdx >= 0, "Could not find " + node + " in parent's children: " + parent);
6737
+ assert(nextIdx >= 0, `Could not find ${node} in parent's children: ${parent}`);
6522
6738
  for (i = nextIdx; i < siblings.length; i++) {
6523
6739
  node = siblings[i];
6524
6740
  if (node === stopNode) {
@@ -6656,8 +6872,8 @@ class Wunderbaum {
6656
6872
  // `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
6657
6873
  // );
6658
6874
  if (this._disableUpdateCount === 0) {
6659
- // this.changeRedrawRequestPending = true; // make sure, we re-render all markup
6660
- // this.updateViewport();
6875
+ this.logDebug(`enableUpdate(): active again. Re-painting to catch up with ${this._disableUpdateIgnoreCount} ignored update requests...`);
6876
+ this._disableUpdateIgnoreCount = 0;
6661
6877
  this.setModified(ChangeType.any, { immediate: true });
6662
6878
  }
6663
6879
  }
@@ -6712,7 +6928,7 @@ class Wunderbaum {
6712
6928
  }
6713
6929
  Wunderbaum.sequence = 0;
6714
6930
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6715
- Wunderbaum.version = "v0.2.0"; // Set to semver by 'grunt release'
6931
+ Wunderbaum.version = "v0.3.0"; // Set to semver by 'grunt release'
6716
6932
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6717
6933
  Wunderbaum.util = util;
6718
6934