wunderbaum 0.12.0 → 0.13.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.
@@ -5,6 +5,11 @@
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
7
  /*!
8
+ * Wunderbaum - debounce.ts
9
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
10
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
11
+ */
12
+ /*
8
13
  * debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
9
14
  * MIT License: https://raw.githubusercontent.com/lodash/lodash/4.17.21-npm/LICENSE
10
15
  * Modified for TypeScript type annotations.
@@ -293,8 +298,8 @@
293
298
 
294
299
  /*!
295
300
  * Wunderbaum - util
296
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
297
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
301
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
302
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
298
303
  */
299
304
  /** @module util */
300
305
  /** Readable names for `MouseEvent.button` */
@@ -677,18 +682,6 @@
677
682
  }
678
683
  return obj;
679
684
  }
680
- // /** Return a EventTarget from selector or cast an existing element. */
681
- // export function eventTargetFromSelector(
682
- // obj: string | EventTarget
683
- // ): EventTarget | null {
684
- // if (!obj) {
685
- // return null;
686
- // }
687
- // if (typeof obj === "string") {
688
- // return document.querySelector(obj) as EventTarget;
689
- // }
690
- // return obj as EventTarget;
691
- // }
692
685
  /**
693
686
  * Return a canonical descriptive string for a keyboard or mouse event.
694
687
  *
@@ -1147,8 +1140,8 @@
1147
1140
 
1148
1141
  /*!
1149
1142
  * Wunderbaum - types
1150
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1151
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
1143
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
1144
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
1152
1145
  */
1153
1146
  /**
1154
1147
  * Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
@@ -1172,7 +1165,7 @@
1172
1165
  /** Vertical scroll event. Update the 'top' property of all rows. */
1173
1166
  ChangeType["scroll"] = "scroll";
1174
1167
  })(ChangeType || (ChangeType = {}));
1175
- /* Internal use. */
1168
+ /** @internal */
1176
1169
  var RenderFlag;
1177
1170
  (function (RenderFlag) {
1178
1171
  RenderFlag["clearMarkup"] = "clearMarkup";
@@ -1203,16 +1196,20 @@
1203
1196
  /** Initial navigation mode and possible transition. */
1204
1197
  var NavModeEnum;
1205
1198
  (function (NavModeEnum) {
1199
+ /** Start with row mode, but allow cell-nav mode */
1206
1200
  NavModeEnum["startRow"] = "startRow";
1201
+ /** Cell-nav mode only */
1207
1202
  NavModeEnum["cell"] = "cell";
1203
+ /** Start in cell-nav mode, but allow row mode */
1208
1204
  NavModeEnum["startCell"] = "startCell";
1205
+ /** Row mode only */
1209
1206
  NavModeEnum["row"] = "row";
1210
1207
  })(NavModeEnum || (NavModeEnum = {}));
1211
1208
 
1212
1209
  /*!
1213
1210
  * Wunderbaum - wb_extension_base
1214
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1215
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
1211
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
1212
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
1216
1213
  */
1217
1214
  class WunderbaumExtension {
1218
1215
  constructor(tree, id, defaults) {
@@ -1270,8 +1267,8 @@
1270
1267
 
1271
1268
  /*!
1272
1269
  * Wunderbaum - ext-filter
1273
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1274
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
1270
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
1271
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
1275
1272
  */
1276
1273
  const START_MARKER = "\uFFF7";
1277
1274
  const END_MARKER = "\uFFF8";
@@ -1283,7 +1280,7 @@
1283
1280
  autoApply: true, // Re-apply last filter if lazy data is loaded
1284
1281
  autoExpand: false, // Expand all branches that contain matches while filtered
1285
1282
  matchBranch: false, // Whether to implicitly match all children of matched nodes
1286
- connectInput: null, // Element or selector of an input control for filter query strings
1283
+ connect: null, // Element or selector of an input control for filter query strings
1287
1284
  fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
1288
1285
  hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
1289
1286
  highlight: true, // Highlight matches by wrapping inside <mark> tags
@@ -1291,36 +1288,117 @@
1291
1288
  mode: "dim", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
1292
1289
  noData: true, // Display a 'no data' status node if result is empty
1293
1290
  });
1291
+ this.queryInput = null;
1292
+ this.prevButton = null;
1293
+ this.nextButton = null;
1294
+ this.modeButton = null;
1295
+ this.matchInfoElem = null;
1294
1296
  this.lastFilterArgs = null;
1295
1297
  }
1296
1298
  init() {
1297
1299
  super.init();
1298
- const connectInput = this.getPluginOption("connectInput");
1299
- if (connectInput) {
1300
- this.queryInput = elemFromSelector(connectInput);
1301
- assert(this.queryInput, `Invalid 'filter.connectInput' option: ${connectInput}.`);
1302
- onEvent(this.queryInput, "input", debounce((e) => {
1303
- // this.tree.log("query", e);
1304
- this.filterNodes(this.queryInput.value.trim(), {});
1305
- }, 700));
1300
+ const connect = this.getPluginOption("connect");
1301
+ if (connect) {
1302
+ this._connectControls();
1306
1303
  }
1307
1304
  }
1308
1305
  setPluginOption(name, value) {
1309
- // alert("filter opt=" + name + ", " + value)
1310
1306
  super.setPluginOption(name, value);
1311
1307
  switch (name) {
1312
1308
  case "mode":
1313
- this.tree.filterMode = value === "hide" ? "hide" : "dim";
1309
+ this.tree.filterMode =
1310
+ value === "hide" ? "hide" : value === "mark" ? "mark" : "dim";
1314
1311
  this.tree.updateFilter();
1315
1312
  break;
1316
1313
  }
1317
1314
  }
1315
+ _updatedConnectedControls() {
1316
+ var _a;
1317
+ const filterActive = this.tree.filterMode !== null;
1318
+ const activeNode = this.tree.getActiveNode();
1319
+ const matchCount = filterActive ? this.countMatches() : 0;
1320
+ const strings = this.treeOpts.strings;
1321
+ let matchIdx = "?";
1322
+ if (this.matchInfoElem) {
1323
+ if (filterActive) {
1324
+ let info;
1325
+ if (matchCount === 0) {
1326
+ info = strings.noMatch;
1327
+ }
1328
+ else if (activeNode && activeNode.match >= 1) {
1329
+ matchIdx = (_a = activeNode.match) !== null && _a !== void 0 ? _a : "?";
1330
+ info = strings.matchIndex;
1331
+ }
1332
+ else {
1333
+ info = strings.queryResult;
1334
+ }
1335
+ info = info
1336
+ .replace("${count}", this.tree.count().toLocaleString())
1337
+ .replace("${match}", "" + matchIdx)
1338
+ .replace("${matches}", matchCount.toLocaleString());
1339
+ this.matchInfoElem.textContent = info;
1340
+ }
1341
+ else {
1342
+ this.matchInfoElem.textContent = "";
1343
+ }
1344
+ }
1345
+ if (this.nextButton instanceof HTMLButtonElement) {
1346
+ this.nextButton.disabled = !matchCount;
1347
+ }
1348
+ if (this.prevButton instanceof HTMLButtonElement) {
1349
+ this.prevButton.disabled = !matchCount;
1350
+ }
1351
+ if (this.modeButton) {
1352
+ this.modeButton.disabled = !filterActive;
1353
+ this.modeButton.classList.toggle("wb-filter-hide", this.tree.filterMode === "hide");
1354
+ }
1355
+ }
1356
+ _connectControls() {
1357
+ const tree = this.tree;
1358
+ const connect = this.getPluginOption("connect");
1359
+ if (!connect) {
1360
+ return;
1361
+ }
1362
+ this.queryInput = elemFromSelector(connect.inputElem);
1363
+ if (!this.queryInput) {
1364
+ throw new Error(`Invalid 'filter.connect' option: ${connect.inputElem}.`);
1365
+ }
1366
+ this.prevButton = elemFromSelector(connect.prevButton);
1367
+ this.nextButton = elemFromSelector(connect.nextButton);
1368
+ this.modeButton = elemFromSelector(connect.modeButton);
1369
+ this.matchInfoElem = elemFromSelector(connect.matchInfoElem);
1370
+ if (this.prevButton) {
1371
+ onEvent(this.prevButton, "click", () => {
1372
+ tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "prevMatch");
1373
+ this._updatedConnectedControls();
1374
+ });
1375
+ }
1376
+ if (this.nextButton) {
1377
+ onEvent(this.nextButton, "click", () => {
1378
+ tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "nextMatch");
1379
+ this._updatedConnectedControls();
1380
+ });
1381
+ }
1382
+ if (this.modeButton) {
1383
+ onEvent(this.modeButton, "click", (e) => {
1384
+ if (!this.tree.filterMode) {
1385
+ return;
1386
+ }
1387
+ this.setPluginOption("mode", tree.filterMode === "dim" ? "hide" : "dim");
1388
+ });
1389
+ }
1390
+ onEvent(this.queryInput, "input", debounce((e) => {
1391
+ this.filterNodes(this.queryInput.value.trim(), {});
1392
+ }, 700));
1393
+ this._updatedConnectedControls();
1394
+ }
1318
1395
  _applyFilterNoUpdate(filter, _opts) {
1319
1396
  return this.tree.runWithDeferredUpdate(() => {
1320
1397
  return this._applyFilterImpl(filter, _opts);
1321
1398
  });
1322
1399
  }
1323
1400
  _applyFilterImpl(filter, _opts) {
1401
+ var _a;
1324
1402
  let //temp,
1325
1403
  count = 0;
1326
1404
  const start = Date.now();
@@ -1402,11 +1480,11 @@
1402
1480
  return !!res;
1403
1481
  };
1404
1482
  }
1405
- tree.filterMode = opts.mode;
1406
- // eslint-disable-next-line prefer-rest-params, prefer-spread
1483
+ tree.filterMode = (_a = opts.mode) !== null && _a !== void 0 ? _a : "dim";
1484
+ // eslint-disable-next-line prefer-rest-params
1407
1485
  this.lastFilterArgs = arguments;
1408
1486
  tree.element.classList.toggle("wb-ext-filter-hide", !!hideMode);
1409
- tree.element.classList.toggle("wb-ext-filter-dim", !hideMode);
1487
+ tree.element.classList.toggle("wb-ext-filter-dim", opts.mode === "dim");
1410
1488
  tree.element.classList.toggle("wb-ext-filter-hide-expanders", !!opts.hideExpanders);
1411
1489
  // Reset current filter
1412
1490
  tree.root.subMatchCount = 0;
@@ -1415,10 +1493,6 @@
1415
1493
  delete node.titleWithHighlight;
1416
1494
  node.subMatchCount = 0;
1417
1495
  });
1418
- // statusNode = tree.root.findDirectChild(KEY_NODATA);
1419
- // if (statusNode) {
1420
- // statusNode.remove();
1421
- // }
1422
1496
  tree.setStatus(NodeStatusType.ok);
1423
1497
  // Adjust node.hide, .match, and .subMatchCount properties
1424
1498
  treeOpts.autoCollapse = false; // #528
@@ -1429,7 +1503,7 @@
1429
1503
  let res = filter(node);
1430
1504
  if (res === "skip") {
1431
1505
  node.visit(function (c) {
1432
- c.match = false;
1506
+ c.match = undefined;
1433
1507
  }, true);
1434
1508
  return "skip";
1435
1509
  }
@@ -1440,7 +1514,7 @@
1440
1514
  }
1441
1515
  if (res) {
1442
1516
  count++;
1443
- node.match = true;
1517
+ node.match = count;
1444
1518
  node.visitParents((p) => {
1445
1519
  if (p !== node) {
1446
1520
  p.subMatchCount += 1;
@@ -1467,6 +1541,7 @@
1467
1541
  }
1468
1542
  // Redraw whole tree
1469
1543
  tree.logDebug(`Filter '${filter}' found ${count} nodes in ${Date.now() - start} ms.`);
1544
+ this._updatedConnectedControls();
1470
1545
  return count;
1471
1546
  }
1472
1547
  /**
@@ -1511,34 +1586,22 @@
1511
1586
  else {
1512
1587
  tree.logWarn("updateFilter(): no filter active.");
1513
1588
  }
1589
+ this._updatedConnectedControls();
1514
1590
  }
1515
1591
  /**
1516
1592
  * [ext-filter] Reset the filter.
1517
1593
  */
1518
1594
  clearFilter() {
1519
1595
  const tree = this.tree;
1520
- // statusNode = tree.root.findDirectChild(KEY_NODATA),
1521
- // escapeTitles = tree.options.escapeTitles;
1522
1596
  tree.enableUpdate(false);
1523
- // if (statusNode) {
1524
- // statusNode.remove();
1525
- // }
1526
1597
  tree.setStatus(NodeStatusType.ok);
1527
1598
  // we also counted root node's subMatchCount
1528
1599
  delete tree.root.match;
1529
1600
  delete tree.root.subMatchCount;
1530
1601
  tree.visit((node) => {
1531
- // if (node.match && node._rowElem) {
1532
- // let titleElem = node._rowElem.querySelector("span.wb-title")!;
1533
- // node._callEvent("enhanceTitle", { titleElem: titleElem });
1534
- // }
1535
1602
  delete node.match;
1536
1603
  delete node.subMatchCount;
1537
1604
  delete node.titleWithHighlight;
1538
- // if (node.subMatchBadge) {
1539
- // node.subMatchBadge.remove();
1540
- // delete node.subMatchBadge;
1541
- // }
1542
1605
  if (node._filterAutoExpanded && node.expanded) {
1543
1606
  node.setExpanded(false, {
1544
1607
  noAnimation: true,
@@ -1552,7 +1615,7 @@
1552
1615
  tree.element.classList.remove(
1553
1616
  // "wb-ext-filter",
1554
1617
  "wb-ext-filter-dim", "wb-ext-filter-hide");
1555
- // tree._callHook("treeStructureChanged", this, "clearFilter");
1618
+ this._updatedConnectedControls();
1556
1619
  tree.enableUpdate(true);
1557
1620
  }
1558
1621
  }
@@ -1595,8 +1658,8 @@
1595
1658
 
1596
1659
  /*!
1597
1660
  * Wunderbaum - ext-keynav
1598
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1599
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
1661
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
1662
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
1600
1663
  */
1601
1664
  const QUICKSEARCH_DELAY = 500;
1602
1665
  class KeynavExtension extends WunderbaumExtension {
@@ -1959,8 +2022,8 @@
1959
2022
 
1960
2023
  /*!
1961
2024
  * Wunderbaum - ext-logger
1962
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1963
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
2025
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
2026
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
1964
2027
  */
1965
2028
  class LoggerExtension extends WunderbaumExtension {
1966
2029
  constructor(tree) {
@@ -2001,8 +2064,8 @@
2001
2064
 
2002
2065
  /*!
2003
2066
  * Wunderbaum - ext-dnd
2004
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2005
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
2067
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
2068
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
2006
2069
  */
2007
2070
  const nodeMimeType = "application/x-wunderbaum-node";
2008
2071
  class DndExtension extends WunderbaumExtension {
@@ -2222,7 +2285,7 @@
2222
2285
  viewportY >= height - sensitivity) {
2223
2286
  // Mouse in bottom 20px area: scroll down
2224
2287
  // sp.scrollTop = scrollTop + dndOpts.scrollSpeed;
2225
- this.currentScrollDir = +1;
2288
+ this.currentScrollDir = 1;
2226
2289
  }
2227
2290
  if (this.currentScrollDir) {
2228
2291
  this.applyScrollDirThrottled();
@@ -2451,8 +2514,8 @@
2451
2514
 
2452
2515
  /*!
2453
2516
  * Wunderbaum - drag_observer
2454
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2455
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
2517
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
2518
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
2456
2519
  */
2457
2520
  /**
2458
2521
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2600,8 +2663,8 @@
2600
2663
 
2601
2664
  /*!
2602
2665
  * Wunderbaum - common
2603
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2604
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
2666
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
2667
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
2605
2668
  */
2606
2669
  const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
2607
2670
  /**
@@ -2628,8 +2691,11 @@
2628
2691
  // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2629
2692
  // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2630
2693
  /**
2631
- * Default node icons.
2632
- * Requires bootstrap icons https://icons.getbootstrap.com
2694
+ * Default node icons for icon libraries
2695
+ *
2696
+ * - 'bootstrap': {@link https://icons.getbootstrap.com}
2697
+ * - 'fontawesome6' {@link https://fontawesome.com/icons}
2698
+ *
2633
2699
  */
2634
2700
  const iconMaps = {
2635
2701
  bootstrap: {
@@ -2711,29 +2777,20 @@
2711
2777
  // "Escape",
2712
2778
  // ]);
2713
2779
  /** Map `KeyEvent.key` to navigation action. */
2714
- const KEY_TO_ACTION_DICT = {
2715
- " ": "toggleSelect",
2716
- "+": "expand",
2717
- Add: "expand",
2780
+ const KEY_TO_NAVIGATION_MAP = {
2718
2781
  ArrowDown: "down",
2719
2782
  ArrowLeft: "left",
2720
2783
  ArrowRight: "right",
2721
2784
  ArrowUp: "up",
2722
2785
  Backspace: "parent",
2723
- "/": "collapseAll",
2724
- Divide: "collapseAll",
2725
2786
  End: "lastCol",
2726
2787
  Home: "firstCol",
2727
2788
  "Control+End": "last",
2728
2789
  "Control+Home": "first",
2729
2790
  "Meta+ArrowDown": "last", // macOs
2730
2791
  "Meta+ArrowUp": "first", // macOs
2731
- "*": "expandAll",
2732
- Multiply: "expandAll",
2733
2792
  PageDown: "pageDown",
2734
2793
  PageUp: "pageUp",
2735
- "-": "collapse",
2736
- Subtract: "collapse",
2737
2794
  };
2738
2795
  /** Return a callback that returns true if the node title matches the string
2739
2796
  * or regular expression.
@@ -2770,10 +2827,12 @@
2770
2827
  /**
2771
2828
  * Convert 'flat' to 'nested' format.
2772
2829
  *
2773
- * Flat node entry format:
2774
- * [PARENT_ID, [POSITIONAL_ARGS]]
2775
- * or
2776
- * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2830
+ * Flat node entry format:
2831
+ * [PARENT_IDX, {KEY_VALUE_ARGS}]
2832
+ * or, if N _positional re defined:
2833
+ * [PARENT_IDX, POSITIONAL_ARG_1, POSITIONAL_ARG_2, ..., POSITIONAL_ARG_N]
2834
+ * Even if _positional additional are defined, KEY_VALUE_ARGS can be appended:
2835
+ * [PARENT_IDX, POSITIONAL_ARG_1, ..., {KEY_VALUE_ARGS}]
2777
2836
  *
2778
2837
  * 1. Parent-referencing list is converted to a list of nested dicts with
2779
2838
  * optional `children` properties.
@@ -2782,10 +2841,11 @@
2782
2841
  function unflattenSource(source) {
2783
2842
  var _a, _b, _c;
2784
2843
  const { _format, _keyMap = {}, _positional = [], children } = source;
2844
+ const _positionalCount = _positional.length;
2785
2845
  if (_format !== "flat") {
2786
2846
  throw new Error(`Expected source._format: "flat", but got ${_format}`);
2787
2847
  }
2788
- if (_positional && _positional.includes("children")) {
2848
+ if (_positionalCount && _positional.includes("children")) {
2789
2849
  throw new Error(`source._positional must not include "children": ${_positional}`);
2790
2850
  }
2791
2851
  let longToShort = _keyMap;
@@ -2799,7 +2859,7 @@
2799
2859
  longToShort[value] = key;
2800
2860
  }
2801
2861
  }
2802
- const positionalShort = _positional.map((e) => longToShort[e]);
2862
+ const positionalShort = _positional.map((e) => { var _a; return (_a = longToShort[e]) !== null && _a !== void 0 ? _a : e; });
2803
2863
  const newChildren = [];
2804
2864
  const keyToNodeMap = {};
2805
2865
  const indexToNodeMap = {};
@@ -2809,19 +2869,32 @@
2809
2869
  // Node entry format:
2810
2870
  // [PARENT_ID, [POSITIONAL_ARGS]]
2811
2871
  // or
2812
- // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2813
- const [parentId, args, kwargs = {}] = nodeTuple;
2872
+ // [PARENT_ID, POSITIONAL_ARG_1, POSITIONAL_ARG_2, ..., {KEY_VALUE_ARGS}]
2873
+ let kwargs;
2874
+ const [parentId, ...args] = nodeTuple;
2875
+ if (args.length === _positionalCount) {
2876
+ kwargs = {};
2877
+ }
2878
+ else if (args.length === _positionalCount + 1) {
2879
+ kwargs = args.pop();
2880
+ if (typeof kwargs !== "object") {
2881
+ throw new Error(`unflattenSource: Expected dict as last tuple element: ${nodeTuple}`);
2882
+ }
2883
+ }
2884
+ else {
2885
+ throw new Error(`unflattenSource: unexpected tuple length: ${nodeTuple}`);
2886
+ }
2814
2887
  // Free up some memory as we go
2815
2888
  nodeTuple[1] = null;
2816
2889
  if (nodeTuple[2] != null) {
2817
2890
  nodeTuple[2] = null;
2818
2891
  }
2819
- // console.log("flatten", parentId, args, kwargs)
2820
2892
  // We keep `kwargs` as our new node definition. Then we add all positional
2821
2893
  // values to this object:
2822
2894
  args.forEach((val, positionalIdx) => {
2823
2895
  kwargs[positionalShort[positionalIdx]] = val;
2824
2896
  });
2897
+ args.length = 0;
2825
2898
  // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2826
2899
  // index of the source.children list. If PARENT_ID is a string, we search
2827
2900
  // a parent with node.key of this value.
@@ -2940,8 +3013,8 @@
2940
3013
 
2941
3014
  /*!
2942
3015
  * Wunderbaum - ext-grid
2943
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2944
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
3016
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
3017
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
2945
3018
  */
2946
3019
  class GridExtension extends WunderbaumExtension {
2947
3020
  constructor(tree) {
@@ -3031,8 +3104,8 @@
3031
3104
 
3032
3105
  /*!
3033
3106
  * Wunderbaum - deferred
3034
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3035
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
3107
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
3108
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
3036
3109
  */
3037
3110
  /**
3038
3111
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -3084,8 +3157,8 @@
3084
3157
 
3085
3158
  /*!
3086
3159
  * Wunderbaum - wunderbaum_node
3087
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3088
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
3160
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
3161
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
3089
3162
  */
3090
3163
  /** WunderbaumNode properties that can be passed with source data.
3091
3164
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3867,7 +3940,7 @@
3867
3940
  isParentOf(other) {
3868
3941
  return other && other.parent === this;
3869
3942
  }
3870
- /** (experimental) Return true if this node is partially loaded. */
3943
+ /** Return true if this node is partially loaded. @experimental */
3871
3944
  isPartload() {
3872
3945
  return !!this._partload;
3873
3946
  }
@@ -4325,10 +4398,11 @@
4325
4398
  * @param options
4326
4399
  */
4327
4400
  async navigate(where, options) {
4401
+ var _a;
4328
4402
  // Allow to pass 'ArrowLeft' instead of 'left'
4329
- where = KEY_TO_ACTION_DICT[where] || where;
4403
+ const navType = ((_a = KEY_TO_NAVIGATION_MAP[where]) !== null && _a !== void 0 ? _a : where);
4330
4404
  // Otherwise activate or focus the related node
4331
- const node = this.findRelatedNode(where);
4405
+ const node = this.findRelatedNode(navType);
4332
4406
  if (!node) {
4333
4407
  this.logWarn(`Could not find related node '${where}'.`);
4334
4408
  return Promise.resolve(this);
@@ -4425,86 +4499,17 @@
4425
4499
  renderColInfosById: renderColInfosById,
4426
4500
  };
4427
4501
  }
4428
- _createIcon(iconMap, parentElem, replaceChild, showLoading) {
4429
- let iconSpan;
4430
- let icon = this.getOption("icon");
4431
- if (this._errorInfo) {
4432
- icon = iconMap.error;
4433
- }
4434
- else if (this._isLoading && showLoading) {
4435
- // Status nodes, or nodes without expander (< minExpandLevel) should
4436
- // display the 'loading' status with the i.wb-icon span
4437
- icon = iconMap.loading;
4438
- }
4439
- if (icon === false) {
4440
- return null; // explicitly disabled: don't try default icons
4441
- }
4442
- if (typeof icon === "string") ;
4443
- else if (this.statusNodeType) {
4444
- icon = iconMap[this.statusNodeType];
4445
- }
4446
- else if (this.expanded) {
4447
- icon = iconMap.folderOpen;
4448
- }
4449
- else if (this.children) {
4450
- icon = iconMap.folder;
4451
- }
4452
- else if (this.lazy) {
4453
- icon = iconMap.folderLazy;
4454
- }
4455
- else {
4456
- icon = iconMap.doc;
4457
- }
4458
- // this.log("_createIcon: " + icon);
4459
- if (!icon) {
4460
- iconSpan = document.createElement("i");
4461
- iconSpan.className = "wb-icon";
4462
- }
4463
- else if (icon.indexOf("<") >= 0) {
4464
- // HTML
4465
- iconSpan = elemFromHtml(icon);
4466
- }
4467
- else if (TEST_IMG.test(icon)) {
4468
- // Image URL
4469
- iconSpan = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
4470
- }
4471
- else {
4472
- // Class name
4473
- iconSpan = document.createElement("i");
4474
- iconSpan.className = "wb-icon " + icon;
4475
- }
4476
- if (replaceChild) {
4477
- parentElem.replaceChild(iconSpan, replaceChild);
4478
- }
4479
- else {
4480
- parentElem.appendChild(iconSpan);
4481
- }
4482
- // Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
4483
- const cbRes = this._callEvent("iconBadge", { iconSpan: iconSpan });
4484
- let badge = null;
4485
- if (cbRes != null && cbRes !== false) {
4486
- let classes = "";
4487
- let tooltip = "";
4488
- if (isPlainObject(cbRes)) {
4489
- badge = "" + cbRes.badge;
4490
- classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
4491
- tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
4492
- }
4493
- else if (typeof cbRes === "number") {
4494
- badge = "" + cbRes;
4502
+ _createIcon(parentElem, replaceChild, showLoading) {
4503
+ const iconElem = this.tree._createNodeIcon(this, showLoading, true);
4504
+ if (iconElem) {
4505
+ if (replaceChild) {
4506
+ parentElem.replaceChild(iconElem, replaceChild);
4495
4507
  }
4496
4508
  else {
4497
- badge = cbRes; // string or HTMLSpanElement
4498
- }
4499
- if (typeof badge === "string") {
4500
- badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
4501
- }
4502
- if (badge) {
4503
- iconSpan.append(badge);
4509
+ parentElem.appendChild(iconElem);
4504
4510
  }
4505
4511
  }
4506
- // this.log("_createIcon: ", iconSpan);
4507
- return iconSpan;
4512
+ return iconElem;
4508
4513
  }
4509
4514
  /**
4510
4515
  * Create a whole new `<div class="wb-row">` element.
@@ -4559,7 +4564,7 @@
4559
4564
  }
4560
4565
  // Render the icon (show a 'loading' icon if we do not have an expander that
4561
4566
  // we would prefer).
4562
- const iconSpan = this._createIcon(tree.iconMap, nodeElem, null, !expanderSpan);
4567
+ const iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
4563
4568
  if (iconSpan) {
4564
4569
  ofsTitlePx += ICON_WIDTH;
4565
4570
  }
@@ -4791,7 +4796,7 @@
4791
4796
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
4792
4797
  const iconSpan = nodeElem.querySelector("i.wb-icon");
4793
4798
  if (iconSpan) {
4794
- this._createIcon(tree.iconMap, nodeElem, iconSpan, !expanderSpan);
4799
+ this._createIcon(nodeElem, iconSpan, !expanderSpan);
4795
4800
  }
4796
4801
  }
4797
4802
  // Adjust column width
@@ -5169,7 +5174,8 @@
5169
5174
  case undefined:
5170
5175
  changed = this.selected || !this._partsel;
5171
5176
  this.selected = false;
5172
- this._partsel = true;
5177
+ // #110: end nodess cannot have a `_partsel` flag
5178
+ this._partsel = this.hasChildren() ? true : false;
5173
5179
  break;
5174
5180
  default:
5175
5181
  error(`Invalid state: ${state}`);
@@ -5339,7 +5345,7 @@
5339
5345
  assert(data.statusNodeType, "Not a status node");
5340
5346
  assert(!firstChild || !firstChild.isStatusNode(), "Child must not be a status node");
5341
5347
  statusNode = this.addNode(data, "prependChild");
5342
- statusNode.match = true;
5348
+ statusNode.match = -1; // Mark as 'match' to avoid hiding
5343
5349
  tree.update(ChangeType.structure);
5344
5350
  return statusNode;
5345
5351
  };
@@ -5630,8 +5636,8 @@
5630
5636
 
5631
5637
  /*!
5632
5638
  * Wunderbaum - ext-edit
5633
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
5634
- * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
5639
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
5640
+ * v0.13.0, Sat, 08 Mar 2025 14:16:31 GMT (https://github.com/mar10/wunderbaum)
5635
5641
  */
5636
5642
  // const START_MARKER = "\uFFF7";
5637
5643
  class EditExtension extends WunderbaumExtension {
@@ -5950,7 +5956,7 @@
5950
5956
  newNode.setClass("wb-edit-new");
5951
5957
  this.relatedNode = node;
5952
5958
  // Don't filter new nodes:
5953
- newNode.match = true;
5959
+ newNode.match = -1;
5954
5960
  newNode.makeVisible({ noAnimation: true }).then(() => {
5955
5961
  this.startEditTitle(newNode);
5956
5962
  });
@@ -5962,12 +5968,12 @@
5962
5968
  *
5963
5969
  * A treegrid control.
5964
5970
  *
5965
- * Copyright (c) 2021-2024, Martin Wendt (https://wwWendt.de).
5971
+ * Copyright (c) 2021-2025, Martin Wendt (https://wwWendt.de).
5966
5972
  * https://github.com/mar10/wunderbaum
5967
5973
  *
5968
5974
  * Released under the MIT license.
5969
- * @version v0.12.0
5970
- * @date Sun, 12 Jan 2025 10:51:41 GMT
5975
+ * @version v0.13.0
5976
+ * @date Sat, 08 Mar 2025 14:16:31 GMT
5971
5977
  */
5972
5978
  // import "./wunderbaum.scss";
5973
5979
  class WbSystemRoot extends WunderbaumNode {
@@ -5988,7 +5994,7 @@
5988
5994
  */
5989
5995
  class Wunderbaum {
5990
5996
  /** Currently active node if any.
5991
- * Use @link {WunderbaumNode.setActive|setActive} to modify.
5997
+ * Use {@link WunderbaumNode.setActive|setActive} to modify.
5992
5998
  */
5993
5999
  get activeNode() {
5994
6000
  var _a;
@@ -5996,7 +6002,7 @@
5996
6002
  return ((_a = this._activeNode) === null || _a === void 0 ? void 0 : _a.tree) ? this._activeNode : null;
5997
6003
  }
5998
6004
  /** Current node hat has keyboard focus if any.
5999
- * Use @link {WunderbaumNode.setFocus|setFocus()} to modify.
6005
+ * Use {@link WunderbaumNode.setFocus|setFocus()} to modify.
6000
6006
  */
6001
6007
  get focusNode() {
6002
6008
  var _a;
@@ -6028,6 +6034,9 @@
6028
6034
  // --- SELECT ---
6029
6035
  // /** @internal */
6030
6036
  // public selectRangeAnchor: WunderbaumNode | null = null;
6037
+ // --- BREADCRUMB ---
6038
+ /** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
6039
+ this.breadcrumb = null;
6031
6040
  // --- FILTER ---
6032
6041
  /** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
6033
6042
  this.filterMode = null;
@@ -6062,10 +6071,10 @@
6062
6071
  emptyChildListExpandable: false,
6063
6072
  // updateThrottleWait: 200,
6064
6073
  skeleton: false,
6065
- connectTopBreadcrumb: null, // HTMLElement that receives the top nodes breadcrumb
6074
+ connectTopBreadcrumb: null,
6066
6075
  selectMode: "multi", // SelectModeType
6067
6076
  // --- KeyNav ---
6068
- navigationModeOption: null, // NavModeEnum.startRow,
6077
+ navigationModeOption: null, // NavModeEnum,
6069
6078
  quicksearch: true,
6070
6079
  // --- Events ---
6071
6080
  iconBadge: null,
@@ -6077,8 +6086,11 @@
6077
6086
  strings: {
6078
6087
  loadError: "Error",
6079
6088
  loading: "Loading...",
6080
- // loading: "Loading&hellip;",
6081
6089
  noData: "No data",
6090
+ breadcrumbDelimiter: " » ",
6091
+ queryResult: "Found ${matches} of ${count}",
6092
+ noMatch: "No results",
6093
+ matchIndex: "${match} of ${matches}",
6082
6094
  },
6083
6095
  }, options));
6084
6096
  const readyDeferred = new Deferred();
@@ -6146,7 +6158,7 @@
6146
6158
  const wantHeader = opts.header == null ? this.columns.length > 1 : !!opts.header;
6147
6159
  if (this.headerElement) {
6148
6160
  // User existing header markup to define `this.columns`
6149
- assert(!this.columns, "`opts.columns` must not be set if markup already contains a header");
6161
+ assert(!this.columns, "`opts.columns` must not be set if table markup already contains a header");
6150
6162
  this.columns = [];
6151
6163
  const rowElement = this.headerElement.querySelector("div.wb-row");
6152
6164
  for (const colDiv of rowElement.querySelectorAll("div")) {
@@ -6184,6 +6196,19 @@
6184
6196
  this.headerElement =
6185
6197
  this.element.querySelector("div.wb-header");
6186
6198
  this.element.classList.toggle("wb-grid", this.columns.length > 1);
6199
+ if (this.options.connectTopBreadcrumb) {
6200
+ this.breadcrumb = elemFromSelector(this.options.connectTopBreadcrumb);
6201
+ assert(!this.breadcrumb || this.breadcrumb.innerHTML != null, `Invalid 'connectTopBreadcrumb' option: ${this.breadcrumb}.`);
6202
+ this.breadcrumb.addEventListener("click", (e) => {
6203
+ // const node = Wunderbaum.getNode(e)!;
6204
+ const elem = e.target;
6205
+ if (elem && elem.matches("a.wb-breadcrumb")) {
6206
+ const node = this.keyMap.get(elem.dataset.key);
6207
+ node === null || node === void 0 ? void 0 : node.setActive();
6208
+ e.preventDefault();
6209
+ }
6210
+ });
6211
+ }
6187
6212
  this._initExtensions();
6188
6213
  // --- apply initial options
6189
6214
  ["enabled", "fixedCol"].forEach((optName) => {
@@ -6194,8 +6219,7 @@
6194
6219
  // --- Load initial data
6195
6220
  if (opts.source) {
6196
6221
  if (opts.showSpinner) {
6197
- this.nodeListElement.innerHTML =
6198
- "<progress class='spinner'>loading...</progress>";
6222
+ this.nodeListElement.innerHTML = `<progress class='spinner'>${opts.strings.loading}</progress>`;
6199
6223
  }
6200
6224
  this.load(opts.source)
6201
6225
  .then(() => {
@@ -6548,7 +6572,10 @@
6548
6572
  });
6549
6573
  return node;
6550
6574
  }
6551
- /** Return the topmost visible node in the viewport. */
6575
+ /** Return the topmost visible node in the viewport.
6576
+ * @param complete If `false`, the node is considered visible if at least one
6577
+ * pixel is visible.
6578
+ */
6552
6579
  getTopmostVpNode(complete = true) {
6553
6580
  const rowHeight = this.options.rowHeightPx;
6554
6581
  const gracePx = 1; // ignore subpixel scrolling
@@ -6581,24 +6608,19 @@
6581
6608
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
6582
6609
  return this._getNodeByRowIdx(bottomIdx);
6583
6610
  }
6584
- /** Return preceeding visible node in the viewport. */
6585
- _getPrevNodeInView(node, ofs = 1) {
6611
+ /** Return following visible node in the viewport. */
6612
+ _getNextNodeInView(node, options) {
6613
+ let ofs = (options === null || options === void 0 ? void 0 : options.ofs) || 1;
6614
+ const reverse = !!(options === null || options === void 0 ? void 0 : options.reverse);
6586
6615
  this.visitRows((n) => {
6587
6616
  node = n;
6588
- if (ofs-- <= 0) {
6617
+ if ((options === null || options === void 0 ? void 0 : options.cb) && options.cb(n)) {
6589
6618
  return false;
6590
6619
  }
6591
- }, { reverse: true, start: node || this.getActiveNode() });
6592
- return node;
6593
- }
6594
- /** Return following visible node in the viewport. */
6595
- _getNextNodeInView(node, ofs = 1) {
6596
- this.visitRows((n) => {
6597
- node = n;
6598
6620
  if (ofs-- <= 0) {
6599
6621
  return false;
6600
6622
  }
6601
- }, { reverse: false, start: node || this.getActiveNode() });
6623
+ }, { reverse: reverse, start: node || this.getActiveNode() });
6602
6624
  return node;
6603
6625
  }
6604
6626
  /**
@@ -6718,9 +6740,11 @@
6718
6740
  case "first":
6719
6741
  case "last":
6720
6742
  case "left":
6743
+ case "nextMatch":
6721
6744
  case "pageDown":
6722
6745
  case "pageUp":
6723
6746
  case "parent":
6747
+ case "prevMatch":
6724
6748
  case "right":
6725
6749
  case "up":
6726
6750
  return node.navigate(cmd);
@@ -6912,6 +6936,11 @@
6912
6936
  count(visible = false) {
6913
6937
  return visible ? this.treeRowCount : this.keyMap.size;
6914
6938
  }
6939
+ /** Return the number of *unique* nodes in the data model, i.e. unique `node.refKey`.
6940
+ */
6941
+ countUnique() {
6942
+ return this.refKeyMap.size;
6943
+ }
6915
6944
  /** @internal sanity check. */
6916
6945
  _check() {
6917
6946
  let i = 0;
@@ -6970,12 +6999,14 @@
6970
6999
  * and wrap-around at the end.
6971
7000
  * Used by quicksearch and keyboard navigation.
6972
7001
  */
6973
- findNextNode(match, startNode) {
7002
+ findNextNode(match, startNode, reverse = false) {
6974
7003
  //, visibleOnly) {
6975
7004
  let res = null;
6976
7005
  const firstNode = this.getFirstChild();
7006
+ // Last visible node (calculation is expensive, so do only if we need it):
7007
+ const lastNode = reverse ? this.findRelatedNode(firstNode, "last") : null;
6977
7008
  const matcher = typeof match === "string" ? makeNodeTitleStartMatcher(match) : match;
6978
- startNode = startNode || firstNode;
7009
+ startNode = startNode || (reverse ? lastNode : firstNode);
6979
7010
  function _checkNode(n) {
6980
7011
  // console.log("_check " + n)
6981
7012
  if (matcher(n)) {
@@ -6988,12 +7019,14 @@
6988
7019
  this.visitRows(_checkNode, {
6989
7020
  start: startNode,
6990
7021
  includeSelf: false,
7022
+ reverse: reverse,
6991
7023
  });
6992
7024
  // Wrap around search
6993
7025
  if (!res && startNode !== firstNode) {
6994
7026
  this.visitRows(_checkNode, {
6995
- start: firstNode,
7027
+ start: reverse ? lastNode : firstNode,
6996
7028
  includeSelf: true,
7029
+ reverse: reverse,
6997
7030
  });
6998
7031
  }
6999
7032
  return res;
@@ -7060,7 +7093,7 @@
7060
7093
  // }
7061
7094
  break;
7062
7095
  case "up":
7063
- res = this._getPrevNodeInView(node);
7096
+ res = this._getNextNodeInView(node, { reverse: true });
7064
7097
  break;
7065
7098
  case "down":
7066
7099
  res = this._getNextNodeInView(node);
@@ -7073,7 +7106,10 @@
7073
7106
  res = bottomNode;
7074
7107
  }
7075
7108
  else {
7076
- res = this._getNextNodeInView(node, pageSize);
7109
+ res = this._getNextNodeInView(node, {
7110
+ reverse: false,
7111
+ ofs: pageSize,
7112
+ });
7077
7113
  }
7078
7114
  }
7079
7115
  break;
@@ -7088,10 +7124,23 @@
7088
7124
  res = topNode;
7089
7125
  }
7090
7126
  else {
7091
- res = this._getPrevNodeInView(node, pageSize);
7127
+ res = this._getNextNodeInView(node, {
7128
+ reverse: true,
7129
+ ofs: pageSize,
7130
+ });
7092
7131
  }
7093
7132
  }
7094
7133
  break;
7134
+ case "prevMatch":
7135
+ // fallthrough
7136
+ case "nextMatch":
7137
+ if (!this.isFilterActive) {
7138
+ this.logWarn(`${where}: Filter is not active.`);
7139
+ break;
7140
+ }
7141
+ res = this.findNextNode((n) => n.isMatched(), node, where === "prevMatch");
7142
+ res === null || res === void 0 ? void 0 : res.setActive();
7143
+ break;
7095
7144
  default:
7096
7145
  this.logWarn("Unknown relation '" + where + "'.");
7097
7146
  }
@@ -7153,6 +7202,12 @@
7153
7202
  getFirstChild() {
7154
7203
  return this.root.getFirstChild();
7155
7204
  }
7205
+ /**
7206
+ * Return the last top level node if any (not the invisible root node).
7207
+ */
7208
+ getLastChild() {
7209
+ return this.root.getLastChild();
7210
+ }
7156
7211
  /**
7157
7212
  * Return the node that currently has keyboard focus or null.
7158
7213
  * Alias for {@link Wunderbaum.focusNode}.
@@ -7480,6 +7535,51 @@
7480
7535
  _setFocusNode(node) {
7481
7536
  this._focusNode = node;
7482
7537
  }
7538
+ /** Return the current selection/expansion/activation status. @experimental */
7539
+ getState(options) {
7540
+ var _a, _b;
7541
+ let expandedKeys = undefined;
7542
+ if (options.expandedKeys !== false) {
7543
+ expandedKeys = [];
7544
+ for (const node of this) {
7545
+ if (node.expanded) {
7546
+ expandedKeys.push(node.key);
7547
+ }
7548
+ }
7549
+ }
7550
+ const state = {
7551
+ activeKey: (_b = (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null,
7552
+ activeColIdx: this.activeColIdx,
7553
+ selectedKeys: options.selectedKeys === false
7554
+ ? undefined
7555
+ : this.getSelectedNodes().flatMap((n) => n.key),
7556
+ expandedKeys: expandedKeys,
7557
+ };
7558
+ return state;
7559
+ }
7560
+ /** Apply selection/expansion/activation status. @experimental */
7561
+ setState(state, options) {
7562
+ this.runWithDeferredUpdate(() => {
7563
+ var _a, _b;
7564
+ if (state.selectedKeys) {
7565
+ this.selectAll(false);
7566
+ for (const key of state.selectedKeys) {
7567
+ (_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setSelected(true);
7568
+ }
7569
+ }
7570
+ if (state.expandedKeys) {
7571
+ for (const key of state.expandedKeys) {
7572
+ (_b = this.findKey(key)) === null || _b === void 0 ? void 0 : _b.setExpanded(true);
7573
+ }
7574
+ }
7575
+ if (state.activeKey) {
7576
+ this.setActiveNode(state.activeKey);
7577
+ }
7578
+ if (state.activeColIdx != null) {
7579
+ this.setColumn(state.activeColIdx);
7580
+ }
7581
+ });
7582
+ }
7483
7583
  update(change, node, options) {
7484
7584
  // this.log(`update(${change}) node=${node}`);
7485
7585
  if (!(node instanceof WunderbaumNode)) {
@@ -7761,11 +7861,11 @@
7761
7861
  // }
7762
7862
  return modified;
7763
7863
  }
7764
- _insertIcon(icon, elem) {
7765
- const iconElem = document.createElement("i");
7766
- iconElem.className = icon;
7767
- elem.appendChild(iconElem);
7768
- }
7864
+ // protected _insertIcon(icon: string, elem: HTMLElement) {
7865
+ // const iconElem = document.createElement("i");
7866
+ // iconElem.className = icon;
7867
+ // elem.appendChild(iconElem);
7868
+ // }
7769
7869
  /** Create/update header markup from `this.columns` definition.
7770
7870
  * @internal
7771
7871
  */
@@ -7863,6 +7963,104 @@
7863
7963
  this._updateViewportImmediately();
7864
7964
  }
7865
7965
  }
7966
+ /** @internal */
7967
+ _createNodeIcon(node, showLoading, showBadge) {
7968
+ const iconMap = this.iconMap;
7969
+ let iconElem;
7970
+ let icon = node.getOption("icon");
7971
+ if (node._errorInfo) {
7972
+ icon = iconMap.error;
7973
+ }
7974
+ else if (node._isLoading && showLoading) {
7975
+ // Status nodes, or nodes without expander (< minExpandLevel) should
7976
+ // display the 'loading' status with the i.wb-icon span
7977
+ icon = iconMap.loading;
7978
+ }
7979
+ if (icon === false) {
7980
+ return null; // explicitly disabled: don't try default icons
7981
+ }
7982
+ if (typeof icon === "string") ;
7983
+ else if (node.statusNodeType) {
7984
+ icon = iconMap[node.statusNodeType];
7985
+ }
7986
+ else if (node.expanded) {
7987
+ icon = iconMap.folderOpen;
7988
+ }
7989
+ else if (node.children) {
7990
+ icon = iconMap.folder;
7991
+ }
7992
+ else if (node.lazy) {
7993
+ icon = iconMap.folderLazy;
7994
+ }
7995
+ else {
7996
+ icon = iconMap.doc;
7997
+ }
7998
+ if (!icon) {
7999
+ iconElem = document.createElement("i");
8000
+ iconElem.className = "wb-icon";
8001
+ }
8002
+ else if (icon.indexOf("<") >= 0) {
8003
+ // HTML
8004
+ iconElem = elemFromHtml(icon);
8005
+ }
8006
+ else if (TEST_IMG.test(icon)) {
8007
+ // Image URL
8008
+ iconElem = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
8009
+ }
8010
+ else {
8011
+ // Class name
8012
+ iconElem = document.createElement("i");
8013
+ iconElem.className = "wb-icon " + icon;
8014
+ }
8015
+ // Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
8016
+ const cbRes = showBadge && node._callEvent("iconBadge", { iconSpan: iconElem });
8017
+ let badge = null;
8018
+ if (cbRes != null && cbRes !== false) {
8019
+ let classes = "";
8020
+ let tooltip = "";
8021
+ if (isPlainObject(cbRes)) {
8022
+ badge = "" + cbRes.badge;
8023
+ classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
8024
+ tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
8025
+ }
8026
+ else if (typeof cbRes === "number") {
8027
+ badge = "" + cbRes;
8028
+ }
8029
+ else {
8030
+ badge = cbRes; // string or HTMLSpanElement
8031
+ }
8032
+ if (typeof badge === "string") {
8033
+ badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
8034
+ }
8035
+ if (badge) {
8036
+ iconElem.append(badge);
8037
+ }
8038
+ }
8039
+ return iconElem;
8040
+ }
8041
+ _updateTopBreadcrumb() {
8042
+ const breadcrumb = this.breadcrumb;
8043
+ const topmost = this.getTopmostVpNode(true);
8044
+ const parentList = topmost === null || topmost === void 0 ? void 0 : topmost.getParentList(false, false);
8045
+ if (parentList === null || parentList === void 0 ? void 0 : parentList.length) {
8046
+ breadcrumb.innerHTML = "";
8047
+ for (const n of topmost.getParentList(false, false)) {
8048
+ const icon = this._createNodeIcon(n, false, false);
8049
+ if (icon) {
8050
+ breadcrumb.append(icon, " ");
8051
+ }
8052
+ const part = document.createElement("a");
8053
+ part.textContent = n.title;
8054
+ part.href = "#";
8055
+ part.classList.add("wb-breadcrumb");
8056
+ part.dataset.key = n.key;
8057
+ breadcrumb.append(part, this.options.strings.breadcrumbDelimiter);
8058
+ }
8059
+ }
8060
+ else {
8061
+ breadcrumb.innerHTML = "&nbsp;";
8062
+ }
8063
+ }
7866
8064
  /**
7867
8065
  * This is the actual update method, which is wrapped inside a throttle method.
7868
8066
  * It calls `updateColumns()` and `_updateRows()`.
@@ -7873,7 +8071,6 @@
7873
8071
  * @internal
7874
8072
  */
7875
8073
  _updateViewportImmediately() {
7876
- var _a;
7877
8074
  if (this._disableUpdateCount) {
7878
8075
  this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount}).`);
7879
8076
  this._disableUpdateIgnoreCount++;
@@ -7920,11 +8117,8 @@
7920
8117
  this._updateRows();
7921
8118
  // console.profileEnd(`_updateViewportImmediately()`)
7922
8119
  }
7923
- if (this.options.connectTopBreadcrumb) {
7924
- assert(this.options.connectTopBreadcrumb.textContent != null, `Invalid 'connectTopBreadcrumb' option (input element expected).`);
7925
- let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
7926
- path = path ? path + " >" : "";
7927
- this.options.connectTopBreadcrumb.textContent = path;
8120
+ if (this.breadcrumb) {
8121
+ this._updateTopBreadcrumb();
7928
8122
  }
7929
8123
  this._callEvent("update");
7930
8124
  }
@@ -7990,8 +8184,9 @@
7990
8184
  // this.debug("render", opts);
7991
8185
  const obsoleteNodes = new Set();
7992
8186
  this.nodeListElement.childNodes.forEach((elem) => {
7993
- const tr = elem;
7994
- obsoleteNodes.add(tr._wb_node);
8187
+ if (elem._wb_node) {
8188
+ obsoleteNodes.add(elem._wb_node);
8189
+ }
7995
8190
  });
7996
8191
  let idx = 0;
7997
8192
  let top = 0;
@@ -8292,7 +8487,7 @@
8292
8487
  }
8293
8488
  Wunderbaum.sequence = 0;
8294
8489
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
8295
- Wunderbaum.version = "v0.12.0"; // Set to semver by 'grunt release'
8490
+ Wunderbaum.version = "v0.13.0"; // Set to semver by 'grunt release'
8296
8491
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
8297
8492
  Wunderbaum.util = util;
8298
8493