wunderbaum 0.1.1 → 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.1.1, Sun, 27 Nov 2022 07:35:11 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.1.1, Sun, 27 Nov 2022 07:35:11 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.1.1, Sun, 27 Nov 2022 07:35:11 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.1.1, Sun, 27 Nov 2022 07:35:11 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";
@@ -1234,16 +1295,12 @@ class FilterExtension extends WunderbaumExtension {
1234
1295
  }
1235
1296
  /**
1236
1297
  * [ext-filter] Dim or hide nodes.
1237
- *
1238
- * @param {boolean} [options={autoExpand: false, leavesOnly: false}]
1239
1298
  */
1240
1299
  filterNodes(filter, options) {
1241
1300
  return this._applyFilterNoUpdate(filter, false, options);
1242
1301
  }
1243
1302
  /**
1244
1303
  * [ext-filter] Dim or hide whole branches.
1245
- *
1246
- * @param {boolean} [options={autoExpand: false}]
1247
1304
  */
1248
1305
  filterBranches(filter, options) {
1249
1306
  return this._applyFilterNoUpdate(filter, true, options);
@@ -1354,8 +1411,8 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1354
1411
 
1355
1412
  /*!
1356
1413
  * Wunderbaum - ext-keynav
1357
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1358
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
1359
1416
  */
1360
1417
  const QUICKSEARCH_DELAY = 500;
1361
1418
  class KeynavExtension extends WunderbaumExtension {
@@ -1382,11 +1439,10 @@ class KeynavExtension extends WunderbaumExtension {
1382
1439
  var _a;
1383
1440
  const ace = (_a = this.tree
1384
1441
  .getActiveColElem()) === null || _a === void 0 ? void 0 : _a.querySelector("input:focus,select:focus");
1385
- console.log(`_isCurInputFocused`, ace);
1386
1442
  return !!ace;
1387
1443
  }
1388
1444
  onKeyEvent(data) {
1389
- const event = data.event, tree = this.tree, opts = data.options, activate = !event.ctrlKey || opts.autoActivate, curInput = this._getEmbeddedInputElem(event.target), navModeOption = opts.navigationModeOption;
1445
+ const event = data.event, tree = this.tree, opts = data.options, activate = !event.ctrlKey || opts.autoActivate, curInput = this._getEmbeddedInputElem(event.target), inputHasFocus = curInput && this._isCurInputFocused(), navModeOption = opts.navigationModeOption;
1390
1446
  // isCellEditMode = tree.navMode === NavigationMode.cellEdit;
1391
1447
  let focusNode, eventName = eventToString(event), node = data.node, handled = true;
1392
1448
  // tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
@@ -1423,6 +1479,22 @@ class KeynavExtension extends WunderbaumExtension {
1423
1479
  // -----------------------------------------------------------------------
1424
1480
  // --- Row Mode ---
1425
1481
  // -----------------------------------------------------------------------
1482
+ if (inputHasFocus) {
1483
+ // If editing an embedded input control, let the control handle all
1484
+ // keys. Only Enter and Escape should apply / discard, but keep the
1485
+ // keyboard focus.
1486
+ switch (eventName) {
1487
+ case "Enter":
1488
+ curInput.blur();
1489
+ tree.setFocus();
1490
+ break;
1491
+ case "Escape":
1492
+ node.render();
1493
+ tree.setFocus();
1494
+ break;
1495
+ }
1496
+ return;
1497
+ }
1426
1498
  // --- Quick-Search
1427
1499
  if (opts.quicksearch &&
1428
1500
  eventName.length === 1 &&
@@ -1524,6 +1596,11 @@ class KeynavExtension extends WunderbaumExtension {
1524
1596
  if (eventName === "Escape") {
1525
1597
  // Discard changes
1526
1598
  node.render();
1599
+ // Keep cell-nav mode
1600
+ node.logDebug(`Reset focused input`);
1601
+ tree.setFocus();
1602
+ tree.setColumn(tree.activeColIdx);
1603
+ return;
1527
1604
  // } else if (!INPUT_BREAKOUT_KEYS.has(eventName)) {
1528
1605
  }
1529
1606
  else if (eventName !== "Enter") {
@@ -1598,8 +1675,11 @@ class KeynavExtension extends WunderbaumExtension {
1598
1675
  break;
1599
1676
  case "Escape":
1600
1677
  tree.setFocus(); // Blur prev. input if any
1678
+ node.log(`keynav: focus tree...`);
1601
1679
  if (tree.isCellNav() && navModeOption !== NavModeEnum.cell) {
1680
+ node.log(`keynav: setCellNav(false)`);
1602
1681
  tree.setCellNav(false); // row-nav mode
1682
+ tree.setFocus(); //
1603
1683
  handled = true;
1604
1684
  }
1605
1685
  break;
@@ -1671,8 +1751,8 @@ class KeynavExtension extends WunderbaumExtension {
1671
1751
 
1672
1752
  /*!
1673
1753
  * Wunderbaum - ext-logger
1674
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1675
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
1676
1756
  */
1677
1757
  class LoggerExtension extends WunderbaumExtension {
1678
1758
  constructor(tree) {
@@ -1711,8 +1791,8 @@ class LoggerExtension extends WunderbaumExtension {
1711
1791
 
1712
1792
  /*!
1713
1793
  * Wunderbaum - common
1714
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1715
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
1716
1796
  */
1717
1797
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
1718
1798
  /**
@@ -1834,6 +1914,12 @@ function makeNodeTitleStartMatcher(s) {
1834
1914
  return reMatch.test(node.title);
1835
1915
  };
1836
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
+ }
1837
1923
  function unflattenSource(source) {
1838
1924
  var _a, _b, _c;
1839
1925
  const { _format, _keyMap, _positional, children } = source;
@@ -1951,8 +2037,8 @@ function inflateSourceData(source) {
1951
2037
 
1952
2038
  /*!
1953
2039
  * Wunderbaum - ext-dnd
1954
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1955
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
1956
2042
  */
1957
2043
  const nodeMimeType = "application/x-wunderbaum-node";
1958
2044
  class DndExtension extends WunderbaumExtension {
@@ -2063,6 +2149,7 @@ class DndExtension extends WunderbaumExtension {
2063
2149
  // Only 'before' and 'after':
2064
2150
  return dy > ROW_HEIGHT / 2 ? "after" : "before";
2065
2151
  }
2152
+ // return "over";
2066
2153
  }
2067
2154
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
2068
2155
  autoScroll(event) {
@@ -2137,6 +2224,8 @@ class DndExtension extends WunderbaumExtension {
2137
2224
  }
2138
2225
  onDropEvent(e) {
2139
2226
  // const isLink = event.dataTransfer.types.includes("text/uri-list");
2227
+ const srcNode = this.srcNode;
2228
+ const srcTree = srcNode ? srcNode.tree : null;
2140
2229
  const targetNode = Wunderbaum.getNode(e);
2141
2230
  const dndOpts = this.treeOpts.dnd;
2142
2231
  const dt = e.dataTransfer;
@@ -2144,7 +2233,7 @@ class DndExtension extends WunderbaumExtension {
2144
2233
  this._leaveNode();
2145
2234
  return;
2146
2235
  }
2147
- if (e.type !== "dragover") {
2236
+ if (!["dragenter", "dragover", "dragleave"].includes(e.type)) {
2148
2237
  this.tree.logDebug("onDropEvent." +
2149
2238
  e.type +
2150
2239
  " targetNode: " +
@@ -2165,9 +2254,24 @@ class DndExtension extends WunderbaumExtension {
2165
2254
  this.lastTargetNode = targetNode;
2166
2255
  this.lastEnterStamp = Date.now();
2167
2256
  if (
2168
- // Don't allow void operation ('drop on self')
2169
- (dndOpts.preventVoidMoves && targetNode === this.srcNode) ||
2170
- 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)) {
2171
2275
  dt.dropEffect = "none";
2172
2276
  return true; // Prevent drop operation
2173
2277
  }
@@ -2213,9 +2317,11 @@ class DndExtension extends WunderbaumExtension {
2213
2317
  else if (e.type === "drop") {
2214
2318
  e.stopPropagation(); // prevent browser from opening links?
2215
2319
  this._leaveNode();
2320
+ const region = this.lastDropRegion;
2216
2321
  targetNode._callEvent("dnd.drop", {
2217
2322
  event: e,
2218
- region: this.lastDropRegion,
2323
+ region: region,
2324
+ defaultDropMode: region === "over" ? "appendChild" : region,
2219
2325
  sourceNode: this.srcNode,
2220
2326
  });
2221
2327
  }
@@ -2224,8 +2330,8 @@ class DndExtension extends WunderbaumExtension {
2224
2330
 
2225
2331
  /*!
2226
2332
  * Wunderbaum - drag_observer
2227
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2228
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
2229
2335
  */
2230
2336
  /**
2231
2337
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2360,8 +2466,8 @@ class DragObserver {
2360
2466
 
2361
2467
  /*!
2362
2468
  * Wunderbaum - ext-grid
2363
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2364
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
2365
2471
  */
2366
2472
  class GridExtension extends WunderbaumExtension {
2367
2473
  constructor(tree) {
@@ -2397,8 +2503,8 @@ class GridExtension extends WunderbaumExtension {
2397
2503
 
2398
2504
  /*!
2399
2505
  * Wunderbaum - deferred
2400
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2401
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
2402
2508
  */
2403
2509
  /**
2404
2510
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2450,8 +2556,8 @@ class Deferred {
2450
2556
 
2451
2557
  /*!
2452
2558
  * Wunderbaum - wunderbaum_node
2453
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2454
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
2455
2561
  */
2456
2562
  /** Top-level properties that can be passed with `data`. */
2457
2563
  const NODE_PROPS = new Set([
@@ -2688,9 +2794,9 @@ class WunderbaumNode {
2688
2794
  * @param [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
2689
2795
  * @returns new node
2690
2796
  */
2691
- addNode(nodeData, mode = "child") {
2797
+ addNode(nodeData, mode = "appendChild") {
2692
2798
  if (mode === "over") {
2693
- mode = "child"; // compatible with drop region
2799
+ mode = "appendChild"; // compatible with drop region
2694
2800
  }
2695
2801
  switch (mode) {
2696
2802
  case "after":
@@ -2699,11 +2805,11 @@ class WunderbaumNode {
2699
2805
  });
2700
2806
  case "before":
2701
2807
  return this.parent.addChildren(nodeData, { before: this });
2702
- case "firstChild":
2808
+ case "prependChild":
2703
2809
  // Insert before the first child if any
2704
2810
  // let insertBefore = this.children ? this.children[0] : undefined;
2705
2811
  return this.addChildren(nodeData, { before: 0 });
2706
- case "child":
2812
+ case "appendChild":
2707
2813
  return this.addChildren(nodeData);
2708
2814
  }
2709
2815
  assert(false, "Invalid mode: " + mode);
@@ -3098,8 +3204,17 @@ class WunderbaumNode {
3098
3204
  * an expand operation is currently possible.
3099
3205
  */
3100
3206
  isExpandable(andCollapsed = false) {
3101
- // return !!this.children && (!this.expanded || !andCollapsed);
3102
- 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;
3103
3218
  }
3104
3219
  /** Return true if this node is currently in edit-title mode. */
3105
3220
  isEditing() {
@@ -3233,7 +3348,7 @@ class WunderbaumNode {
3233
3348
  tree.logInfo("Redefine columns", source.columns);
3234
3349
  tree.columns = source.columns;
3235
3350
  delete source.columns;
3236
- tree.updateColumns({ calculateCols: false });
3351
+ tree.setModified(ChangeType.colStructure);
3237
3352
  }
3238
3353
  this.addChildren(source.children);
3239
3354
  // Add extra data to `tree.data`
@@ -3245,12 +3360,50 @@ class WunderbaumNode {
3245
3360
  }
3246
3361
  this._callEvent("load");
3247
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
+ }
3248
3402
  /** Download data from the cloud, then call `.update()`. */
3249
3403
  async load(source) {
3250
3404
  const tree = this.tree;
3251
3405
  const requestId = Date.now();
3252
3406
  const prevParent = this.parent;
3253
- const url = typeof source === "string" ? source : source.url;
3254
3407
  const start = Date.now();
3255
3408
  let elap = 0, elapLoad = 0, elapProcess = 0;
3256
3409
  // Check for overlapping requests
@@ -3261,17 +3414,16 @@ class WunderbaumNode {
3261
3414
  this._requestId = requestId;
3262
3415
  // const timerLabel = tree.logTime(this + ".load()");
3263
3416
  try {
3417
+ let url = typeof source === "string" ? source : source.url;
3264
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
3265
3421
  this._loadSourceObject(source);
3266
3422
  elapProcess = Date.now() - start;
3267
3423
  }
3268
3424
  else {
3269
- this.setStatus(NodeStatusType.loading);
3270
- const response = await fetch(url, { method: "GET" });
3271
- if (!response.ok) {
3272
- error(`GET ${url} returned ${response.status}, ${response}`);
3273
- }
3274
- const data = await response.json();
3425
+ // Either a URL string or an object with a `.url` property.
3426
+ const data = await this._fetchWithOptions(source);
3275
3427
  elapLoad = Date.now() - start;
3276
3428
  if (this._requestId && this._requestId > requestId) {
3277
3429
  this.logWarn(`Ignored load response #${requestId} because #${this._requestId} is pending.`);
@@ -3414,6 +3566,9 @@ class WunderbaumNode {
3414
3566
  }
3415
3567
  /** Move this node to targetNode. */
3416
3568
  moveTo(targetNode, mode = "appendChild", map) {
3569
+ if (mode === "over") {
3570
+ mode = "appendChild"; // compatible with drop region
3571
+ }
3417
3572
  if (mode === "prependChild") {
3418
3573
  if (targetNode.children && targetNode.children.length) {
3419
3574
  mode = "before";
@@ -3470,7 +3625,7 @@ class WunderbaumNode {
3470
3625
  targetParent.children.splice(pos + 1, 0, this);
3471
3626
  break;
3472
3627
  default:
3473
- error("Invalid mode " + mode);
3628
+ error(`Invalid mode '${mode}'.`);
3474
3629
  }
3475
3630
  }
3476
3631
  else {
@@ -3497,7 +3652,12 @@ class WunderbaumNode {
3497
3652
  n.tree = targetNode.tree;
3498
3653
  }, true);
3499
3654
  }
3500
- 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);
3501
3661
  // TODO: fix selection state
3502
3662
  // TODO: fix active state
3503
3663
  }
@@ -3623,7 +3783,7 @@ class WunderbaumNode {
3623
3783
  icon = iconMap.loading;
3624
3784
  }
3625
3785
  if (icon === false) {
3626
- return null;
3786
+ return null; // explicitly disabled: don't try default icons
3627
3787
  }
3628
3788
  if (typeof icon === "string") ;
3629
3789
  else if (this.statusNodeType) {
@@ -3642,7 +3802,11 @@ class WunderbaumNode {
3642
3802
  icon = iconMap.doc;
3643
3803
  }
3644
3804
  // this.log("_createIcon: " + icon);
3645
- if (icon.indexOf("<") >= 0) {
3805
+ if (!icon) {
3806
+ iconSpan = document.createElement("i");
3807
+ iconSpan.className = "wb-icon";
3808
+ }
3809
+ else if (icon.indexOf("<") >= 0) {
3646
3810
  // HTML
3647
3811
  iconSpan = elemFromHtml(icon);
3648
3812
  }
@@ -3960,9 +4124,12 @@ class WunderbaumNode {
3960
4124
  case "data":
3961
4125
  this._render_data(opts);
3962
4126
  break;
3963
- default:
4127
+ case "row":
4128
+ // _rowElem is not yet created (asserted in _render_markup)
3964
4129
  this._render_markup(opts);
3965
4130
  break;
4131
+ default:
4132
+ error(`Invalid change type '${opts.change}'.`);
3966
4133
  }
3967
4134
  }
3968
4135
  /**
@@ -4184,20 +4351,24 @@ class WunderbaumNode {
4184
4351
  this.setModified();
4185
4352
  }
4186
4353
  /** Set a new icon path or class. */
4187
- setIcon() {
4188
- throw new Error("Not yet implemented");
4189
- // this.setModified();
4354
+ setIcon(icon) {
4355
+ this.icon = icon;
4356
+ this.setModified();
4190
4357
  }
4191
4358
  /** Change node's {@link key} and/or {@link refKey}. */
4192
4359
  setKey(key, refKey) {
4193
4360
  throw new Error("Not yet implemented");
4194
4361
  }
4195
4362
  /**
4196
- * Schedule a render, typically called to update after a status or data change.
4363
+ * Trigger a repaint, typically after a status or data change.
4197
4364
  *
4198
4365
  * `change` defaults to 'data', which handles modifcations of title, icon,
4199
4366
  * and column content. It can be reduced to 'ChangeType.status' if only
4200
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.
4201
4372
  */
4202
4373
  setModified(change = ChangeType.data) {
4203
4374
  assert(change === ChangeType.status || change === ChangeType.data);
@@ -4233,7 +4404,7 @@ class WunderbaumNode {
4233
4404
  let firstChild = children ? children[0] : null;
4234
4405
  assert(data.statusNodeType);
4235
4406
  assert(!firstChild || !firstChild.isStatusNode());
4236
- statusNode = this.addNode(data, "firstChild");
4407
+ statusNode = this.addNode(data, "prependChild");
4237
4408
  statusNode.match = true;
4238
4409
  tree.setModified(ChangeType.structure);
4239
4410
  return statusNode;
@@ -4299,11 +4470,37 @@ class WunderbaumNode {
4299
4470
  this.setModified();
4300
4471
  // this.triggerModify("rename"); // TODO
4301
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
+ }
4302
4498
  /**
4303
4499
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
4304
4500
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
4305
4501
  */
4306
4502
  triggerModifyChild(operation, child, extra) {
4503
+ this.logDebug(`modifyChild(${operation})`, extra, child);
4307
4504
  if (!this.tree.options.modifyChild)
4308
4505
  return;
4309
4506
  if (child && child.parent !== this) {
@@ -4402,8 +4599,8 @@ WunderbaumNode.sequence = 0;
4402
4599
 
4403
4600
  /*!
4404
4601
  * Wunderbaum - ext-edit
4405
- * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4406
- * v0.1.1, Sun, 27 Nov 2022 07:35:11 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)
4407
4604
  */
4408
4605
  // const START_MARKER = "\uFFF7";
4409
4606
  class EditExtension extends WunderbaumExtension {
@@ -4494,7 +4691,7 @@ class EditExtension extends WunderbaumExtension {
4494
4691
  const trigger = this.getPluginOption("trigger");
4495
4692
  // const inputElem =
4496
4693
  // event.target && event.target.closest("input,[contenteditable]");
4497
- // tree.logDebug(`_preprocessKeyEvent: ${eventName}`);
4694
+ // tree.logDebug(`_preprocessKeyEvent: ${eventName}, editing:${this.isEditingTitle()}`);
4498
4695
  // --- Title editing: apply/discard ---
4499
4696
  // if (inputElem) {
4500
4697
  if (this.isEditingTitle()) {
@@ -4694,12 +4891,12 @@ class EditExtension extends WunderbaumExtension {
4694
4891
  *
4695
4892
  * A treegrid control.
4696
4893
  *
4697
- * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
4894
+ * Copyright (c) 2021-2023, Martin Wendt (https://wwWendt.de).
4698
4895
  * https://github.com/mar10/wunderbaum
4699
4896
  *
4700
4897
  * Released under the MIT license.
4701
- * @version v0.1.1
4702
- * @date Sun, 27 Nov 2022 07:35:11 GMT
4898
+ * @version v0.3.0
4899
+ * @date Sat, 27 May 2023 04:56:52 GMT
4703
4900
  */
4704
4901
  class WbSystemRoot extends WunderbaumNode {
4705
4902
  constructor(tree) {
@@ -4728,6 +4925,7 @@ class Wunderbaum {
4728
4925
  this.refKeyMap = new Map();
4729
4926
  this.treeRowCount = 0;
4730
4927
  this._disableUpdateCount = 0;
4928
+ this._disableUpdateIgnoreCount = 0;
4731
4929
  /** Currently active node if any. */
4732
4930
  this.activeNode = null;
4733
4931
  /** Current node hat has keyboard focus if any. */
@@ -4738,8 +4936,7 @@ class Wunderbaum {
4738
4936
  this.columns = []; // any[] = [];
4739
4937
  this._columnsById = {};
4740
4938
  // Modification Status
4741
- this.changeRedrawRequestPending = false;
4742
- this.changeScrollRequestPending = false;
4939
+ this.pendingChangeTypes = new Set();
4743
4940
  /** Expose some useful methods of the util.ts module as `tree._util`. */
4744
4941
  this._util = util;
4745
4942
  // --- FILTER ---
@@ -4775,6 +4972,7 @@ class Wunderbaum {
4775
4972
  showSpinner: false,
4776
4973
  checkbox: false,
4777
4974
  minExpandLevel: 0,
4975
+ emptyChildListExpandable: false,
4778
4976
  updateThrottleWait: 200,
4779
4977
  skeleton: false,
4780
4978
  connectTopBreadcrumb: null,
@@ -4937,11 +5135,11 @@ class Wunderbaum {
4937
5135
  // --- Bind listeners
4938
5136
  this.element.addEventListener("scroll", (e) => {
4939
5137
  // this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
4940
- this.setModified(ChangeType.vscroll);
5138
+ this.setModified(ChangeType.scroll);
4941
5139
  });
4942
5140
  this.resizeObserver = new ResizeObserver((entries) => {
4943
- this.setModified(ChangeType.vscroll);
4944
5141
  // this.log("ResizeObserver: Size changed", entries);
5142
+ this.setModified(ChangeType.resize);
4945
5143
  });
4946
5144
  this.resizeObserver.observe(this.element);
4947
5145
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
@@ -4986,6 +5184,7 @@ class Wunderbaum {
4986
5184
  return false;
4987
5185
  }
4988
5186
  if (node && info.colIdx === 0 && node.isExpandable()) {
5187
+ this._callMethod("edit._stopEditTitle");
4989
5188
  node.setExpanded(!node.isExpanded());
4990
5189
  }
4991
5190
  });
@@ -5002,7 +5201,16 @@ class Wunderbaum {
5002
5201
  });
5003
5202
  onEvent(this.element, "focusin focusout", (e) => {
5004
5203
  const flag = e.type === "focusin";
5204
+ const targetNode = Wunderbaum.getNode(e);
5005
5205
  this._callEvent("focus", { flag: flag, event: e });
5206
+ if (flag && this.isRowNav() && !this.isEditing()) {
5207
+ if (opts.navigationModeOption === NavModeEnum.row) {
5208
+ targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
5209
+ }
5210
+ else {
5211
+ this.setCellNav();
5212
+ }
5213
+ }
5006
5214
  if (!flag) {
5007
5215
  this._callMethod("edit._stopEditTitle", true, {
5008
5216
  event: e,
@@ -5467,7 +5675,7 @@ class Wunderbaum {
5467
5675
  this.options[name] = value;
5468
5676
  switch (name) {
5469
5677
  case "checkbox":
5470
- this.setModified(ChangeType.any, { removeMarkup: true });
5678
+ this.setModified(ChangeType.any);
5471
5679
  break;
5472
5680
  case "enabled":
5473
5681
  this.setEnabled(!!value);
@@ -5767,8 +5975,9 @@ class Wunderbaum {
5767
5975
  res.region = NodeRegion.title;
5768
5976
  }
5769
5977
  else if (cl.contains("wb-expander")) {
5770
- res.region =
5771
- node.hasChildren() === false ? NodeRegion.prefix : NodeRegion.expander;
5978
+ res.region = node.isExpandable()
5979
+ ? NodeRegion.expander
5980
+ : NodeRegion.prefix;
5772
5981
  }
5773
5982
  else if (cl.contains("wb-checkbox")) {
5774
5983
  res.region = NodeRegion.checkbox;
@@ -5919,7 +6128,7 @@ class Wunderbaum {
5919
6128
  // Make sure the topNode is always visible
5920
6129
  this.scrollTo(topNode);
5921
6130
  }
5922
- // this.setModified(ChangeType.vscroll);
6131
+ // this.setModified(ChangeType.scroll);
5923
6132
  }
5924
6133
  }
5925
6134
  /**
@@ -5947,7 +6156,7 @@ class Wunderbaum {
5947
6156
  // util.assert(node._rowIdx != null);
5948
6157
  this.log(`scrollToHorz(${this.activeColIdx}): ${colLeft}..${colRight}, fixedOfs=${fixedWidth}, vpWidth=${vpWidth}, curLeft=${scrollLeft} -> ${newLeft}`);
5949
6158
  this.element.scrollLeft = newLeft;
5950
- // this.setModified(ChangeType.vscroll);
6159
+ // this.setModified(ChangeType.scroll);
5951
6160
  }
5952
6161
  /**
5953
6162
  * Set column #colIdx to 'active'.
@@ -6002,34 +6211,40 @@ class Wunderbaum {
6002
6211
  // this.log(
6003
6212
  // `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
6004
6213
  // );
6214
+ this._disableUpdateIgnoreCount++;
6005
6215
  return;
6006
6216
  }
6007
6217
  // this.log(`setModified(${change}) node=${node}`);
6008
6218
  if (!(node instanceof WunderbaumNode)) {
6009
6219
  options = node;
6220
+ node = null;
6010
6221
  }
6011
6222
  const immediate = !!getOption(options, "immediate");
6012
- const removeMarkup = !!getOption(options, "removeMarkup");
6013
- if (removeMarkup) {
6014
- this.visit((n) => {
6015
- n.removeMarkup();
6016
- });
6017
- }
6018
- let callUpdate = false;
6223
+ const RF = RenderFlag;
6224
+ const pending = this.pendingChangeTypes;
6019
6225
  switch (change) {
6020
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;
6021
6238
  case ChangeType.structure:
6022
- case ChangeType.header:
6023
- this.changeRedrawRequestPending = true;
6024
- callUpdate = true;
6239
+ pending.add(RF.redraw);
6025
6240
  break;
6026
- case ChangeType.vscroll:
6027
- this.changeScrollRequestPending = true;
6028
- callUpdate = true;
6241
+ case ChangeType.scroll:
6242
+ pending.add(RF.scroll);
6029
6243
  break;
6030
6244
  case ChangeType.row:
6031
6245
  case ChangeType.data:
6032
6246
  case ChangeType.status:
6247
+ assert(node, `Option '${change}' requires a node.`);
6033
6248
  // Single nodes are immediately updated if already inside the viewport
6034
6249
  // (otherwise we can ignore)
6035
6250
  if (node._rowElem) {
@@ -6037,9 +6252,16 @@ class Wunderbaum {
6037
6252
  }
6038
6253
  break;
6039
6254
  default:
6040
- 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
+ }
6041
6263
  }
6042
- if (callUpdate) {
6264
+ if (pending.size > 0) {
6043
6265
  if (immediate) {
6044
6266
  this._updateViewportImmediately();
6045
6267
  }
@@ -6111,7 +6333,7 @@ class Wunderbaum {
6111
6333
  }
6112
6334
  break;
6113
6335
  default:
6114
- error(`Invalid mode '${mode}'`);
6336
+ error(`Invalid mode '${mode}'.`);
6115
6337
  }
6116
6338
  }
6117
6339
  /** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
@@ -6134,81 +6356,96 @@ class Wunderbaum {
6134
6356
  }
6135
6357
  }
6136
6358
  }
6137
- /** 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.
6138
6370
  * Return true if at least one column width changed.
6139
6371
  */
6140
- updateColumns(options) {
6141
- options = Object.assign({ calculateCols: true, updateRows: true }, options);
6372
+ // _updateColumnWidths(options?: UpdateColumnsOptions): boolean {
6373
+ _updateColumnWidths() {
6374
+ // options = Object.assign({ updateRows: true, renderMarkup: false }, options);
6142
6375
  const defaultMinWidth = 4;
6143
6376
  const vpWidth = this.element.clientWidth;
6144
- const isGrid = this.isGrid();
6145
6377
  // Shorten last column width to avoid h-scrollbar
6146
6378
  const FIX_ADJUST_LAST_COL = 2;
6379
+ const columns = this.columns;
6380
+ const col0 = columns[0];
6147
6381
  let totalWidth = 0;
6148
6382
  let totalWeight = 0;
6149
6383
  let fixedWidth = 0;
6150
6384
  let modified = false;
6151
- this.element.classList.toggle("wb-grid", isGrid);
6152
- if (!isGrid && this.isCellNav()) {
6153
- this.setCellNav(false);
6154
- }
6155
- if (options.calculateCols) {
6156
- // Gather width definitions
6157
- this._columnsById = {};
6158
- for (let col of this.columns) {
6159
- this._columnsById[col.id] = col;
6160
- let cw = col.width;
6161
- if (!cw || cw === "*") {
6162
- col._weight = 1.0;
6163
- totalWeight += 1.0;
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}'.`);
6392
+ }
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;
6164
6415
  }
6165
- else if (typeof cw === "number") {
6166
- col._weight = cw;
6167
- 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;
6168
6431
  }
6169
- else if (typeof cw === "string" && cw.endsWith("px")) {
6170
- col._weight = 0;
6171
- let px = parseFloat(cw.slice(0, -2));
6172
- if (col._widthPx != px) {
6173
- modified = true;
6174
- col._widthPx = px;
6175
- }
6176
- fixedWidth += px;
6432
+ else if (typeof cmw === "string" && cmw.endsWith("px")) {
6433
+ minWidth = parseFloat(cmw.slice(0, -2));
6177
6434
  }
6178
6435
  else {
6179
- error(`Invalid column width: ${cw} (expected string ending with 'px' or number, e.g. "<num>px" or <int>)`);
6436
+ minWidth = defaultMinWidth;
6180
6437
  }
6181
- }
6182
- // Share remaining space between non-fixed columns
6183
- const restPx = Math.max(0, vpWidth - fixedWidth);
6184
- let ofsPx = 0;
6185
- for (let col of this.columns) {
6186
- let minWidth;
6187
- if (col._weight) {
6188
- const cmw = col.minWidth;
6189
- if (typeof cmw === "number") {
6190
- minWidth = cmw;
6191
- }
6192
- else if (typeof cmw === "string" && cmw.endsWith("px")) {
6193
- minWidth = parseFloat(cmw.slice(0, -2));
6194
- }
6195
- else {
6196
- minWidth = defaultMinWidth;
6197
- }
6198
- const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6199
- if (col._widthPx != px) {
6200
- modified = true;
6201
- col._widthPx = px;
6202
- }
6438
+ const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
6439
+ if (col._widthPx != px) {
6440
+ modified = true;
6441
+ col._widthPx = px;
6203
6442
  }
6204
- col._ofsPx = ofsPx;
6205
- ofsPx += col._widthPx;
6206
6443
  }
6207
- this.columns[this.columns.length - 1]._widthPx -= FIX_ADJUST_LAST_COL;
6208
- totalWidth = ofsPx - FIX_ADJUST_LAST_COL;
6444
+ col._ofsPx = ofsPx;
6445
+ ofsPx += col._widthPx;
6209
6446
  }
6210
- // if (this.options.fixedCol) {
6211
- // '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;
6212
6449
  const tw = `${totalWidth}px`;
6213
6450
  this.headerElement.style.width = tw;
6214
6451
  this.listContainerElement.style.width = tw;
@@ -6217,12 +6454,14 @@ class Wunderbaum {
6217
6454
  // this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
6218
6455
  // console.trace();
6219
6456
  // util.error("BREAK");
6220
- if (modified) {
6221
- this._renderHeaderMarkup();
6222
- if (options.updateRows) {
6223
- this._updateRows();
6224
- }
6225
- }
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
+ // }
6226
6465
  return modified;
6227
6466
  }
6228
6467
  /** Create/update header markup from `this.columns` definition.
@@ -6271,7 +6510,7 @@ class Wunderbaum {
6271
6510
  * pending async changes if any.
6272
6511
  */
6273
6512
  updatePendingModifications() {
6274
- if (this.changeRedrawRequestPending || this.changeScrollRequestPending) {
6513
+ if (this.pendingChangeTypes.size > 0) {
6275
6514
  this._updateViewportImmediately();
6276
6515
  }
6277
6516
  }
@@ -6286,31 +6525,50 @@ class Wunderbaum {
6286
6525
  */
6287
6526
  _updateViewportImmediately() {
6288
6527
  var _a;
6289
- // Shorten container height to avoid v-scrollbar
6290
- const FIX_ADJUST_HEIGHT = 1;
6291
6528
  if (this._disableUpdateCount) {
6292
- this.log(`IGNORED _updateViewportImmediately() disable level: ${this._disableUpdateCount}`);
6529
+ this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`);
6530
+ this._disableUpdateIgnoreCount++;
6293
6531
  return;
6294
6532
  }
6295
- const newNodesOnly = !this.changeRedrawRequestPending;
6296
- this.changeRedrawRequestPending = false;
6297
- this.changeScrollRequestPending = false;
6298
- let height = this.listContainerElement.clientHeight;
6299
- // We cannot get the height for absolute positioned parent, so look at first col
6300
- // let headerHeight = this.headerElement.clientHeight
6301
- // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
6302
- // const headerHeight = this.options.headerHeightPx;
6303
- const headerHeight = this.headerElement.clientHeight; // May be 0
6304
- const wantHeight = this.element.clientHeight - headerHeight - FIX_ADJUST_HEIGHT;
6305
- if (Math.abs(height - wantHeight) > 1.0) {
6306
- // this.log("resize", height, wantHeight);
6307
- this.listContainerElement.style.height = wantHeight + "px";
6308
- height = wantHeight;
6309
- }
6310
- // console.profile(`_updateViewportImmediately()`)
6311
- const modified = this.updateColumns({ updateRows: false });
6312
- this._updateRows({ newNodesOnly: newNodesOnly && !modified });
6313
- // 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
+ }
6314
6572
  if (this.options.connectTopBreadcrumb) {
6315
6573
  let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
6316
6574
  path = path ? path + " >" : "";
@@ -6451,19 +6709,14 @@ class Wunderbaum {
6451
6709
  return this.root.visit(callback, false);
6452
6710
  }
6453
6711
  /**
6454
- * 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).
6455
6713
  *
6456
- * Note that this considers expansion state, i.e. children of collapsed nodes
6457
- * 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.
6458
6716
  *
6459
- * Stop iteration, if fn() returns false.<br>
6717
+ * Stop iteration if callback() returns false.<br>
6460
6718
  * Return false if iteration was stopped.
6461
6719
  *
6462
- * @param callback the callback function.
6463
- * Return false to stop iteration, return "skip" to skip this node and children only.
6464
- * @param [options]
6465
- * Defaults:
6466
- * {start: First tree node, reverse: false, includeSelf: true, includeHidden: false, wrap: false}
6467
6720
  * @returns {boolean} false if iteration was canceled
6468
6721
  */
6469
6722
  visitRows(callback, options) {
@@ -6481,7 +6734,7 @@ class Wunderbaum {
6481
6734
  // visit siblings
6482
6735
  siblings = parent.children;
6483
6736
  nextIdx = siblings.indexOf(node) + siblingOfs;
6484
- 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}`);
6485
6738
  for (i = nextIdx; i < siblings.length; i++) {
6486
6739
  node = siblings[i];
6487
6740
  if (node === stopNode) {
@@ -6619,8 +6872,8 @@ class Wunderbaum {
6619
6872
  // `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
6620
6873
  // );
6621
6874
  if (this._disableUpdateCount === 0) {
6622
- // this.changeRedrawRequestPending = true; // make sure, we re-render all markup
6623
- // this.updateViewport();
6875
+ this.logDebug(`enableUpdate(): active again. Re-painting to catch up with ${this._disableUpdateIgnoreCount} ignored update requests...`);
6876
+ this._disableUpdateIgnoreCount = 0;
6624
6877
  this.setModified(ChangeType.any, { immediate: true });
6625
6878
  }
6626
6879
  }
@@ -6636,6 +6889,18 @@ class Wunderbaum {
6636
6889
  /* ---------------------------------------------------------------------------
6637
6890
  * FILTER
6638
6891
  * -------------------------------------------------------------------------*/
6892
+ /**
6893
+ * [ext-filter] Dim or hide nodes.
6894
+ */
6895
+ filterNodes(filter, options) {
6896
+ return this.extensions.filter.filterNodes(filter, options);
6897
+ }
6898
+ /**
6899
+ * [ext-filter] Dim or hide whole branches.
6900
+ */
6901
+ filterBranches(filter, options) {
6902
+ return this.extensions.filter.filterBranches(filter, options);
6903
+ }
6639
6904
  /**
6640
6905
  * [ext-filter] Reset the filter.
6641
6906
  *
@@ -6663,7 +6928,7 @@ class Wunderbaum {
6663
6928
  }
6664
6929
  Wunderbaum.sequence = 0;
6665
6930
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
6666
- Wunderbaum.version = "v0.1.1"; // Set to semver by 'grunt release'
6931
+ Wunderbaum.version = "v0.3.0"; // Set to semver by 'grunt release'
6667
6932
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
6668
6933
  Wunderbaum.util = util;
6669
6934