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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - ext-filter
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
@@ -13,6 +13,7 @@ import {
13
13
  onEvent,
14
14
  } from "./util";
15
15
  import {
16
+ FilterConnectType,
16
17
  FilterNodesOptions,
17
18
  FilterOptionsType,
18
19
  NodeFilterCallback,
@@ -29,7 +30,11 @@ const RE_START_MARKER = new RegExp(escapeRegex(START_MARKER), "g");
29
30
  const RE_END_MARTKER = new RegExp(escapeRegex(END_MARKER), "g");
30
31
 
31
32
  export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
32
- public queryInput?: HTMLInputElement;
33
+ public queryInput: HTMLInputElement | null = null;
34
+ public prevButton: HTMLElement | HTMLAnchorElement | null = null;
35
+ public nextButton: HTMLElement | HTMLAnchorElement | null = null;
36
+ public modeButton: HTMLButtonElement | null = null;
37
+ public matchInfoElem: HTMLElement | null = null;
33
38
  public lastFilterArgs: IArguments | null = null;
34
39
 
35
40
  constructor(tree: Wunderbaum) {
@@ -37,7 +42,7 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
37
42
  autoApply: true, // Re-apply last filter if lazy data is loaded
38
43
  autoExpand: false, // Expand all branches that contain matches while filtered
39
44
  matchBranch: false, // Whether to implicitly match all children of matched nodes
40
- connectInput: null, // Element or selector of an input control for filter query strings
45
+ connect: null, // Element or selector of an input control for filter query strings
41
46
  fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
42
47
  hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
43
48
  highlight: true, // Highlight matches by wrapping inside <mark> tags
@@ -49,35 +54,118 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
49
54
 
50
55
  init() {
51
56
  super.init();
52
- const connectInput = this.getPluginOption("connectInput");
53
- if (connectInput) {
54
- this.queryInput = elemFromSelector(connectInput) as HTMLInputElement;
55
- assert(
56
- this.queryInput,
57
- `Invalid 'filter.connectInput' option: ${connectInput}.`
58
- );
59
- onEvent(
60
- this.queryInput,
61
- "input",
62
- debounce((e) => {
63
- // this.tree.log("query", e);
64
- this.filterNodes(this.queryInput!.value.trim(), {});
65
- }, 700)
66
- );
57
+ const connect: FilterConnectType = this.getPluginOption("connect");
58
+ if (connect) {
59
+ this._connectControls();
67
60
  }
68
61
  }
69
62
 
70
63
  setPluginOption(name: string, value: any): void {
71
- // alert("filter opt=" + name + ", " + value)
72
64
  super.setPluginOption(name, value);
73
65
  switch (name) {
74
66
  case "mode":
75
- this.tree.filterMode = value === "hide" ? "hide" : "dim";
67
+ this.tree.filterMode =
68
+ value === "hide" ? "hide" : value === "mark" ? "mark" : "dim";
76
69
  this.tree.updateFilter();
77
70
  break;
78
71
  }
79
72
  }
80
73
 
74
+ _updatedConnectedControls() {
75
+ const filterActive = this.tree.filterMode !== null;
76
+ const activeNode = this.tree.getActiveNode();
77
+ const matchCount = filterActive ? this.countMatches() : 0;
78
+ const strings = this.treeOpts.strings!;
79
+ let matchIdx: string | number = "?";
80
+
81
+ if (this.matchInfoElem) {
82
+ if (filterActive) {
83
+ let info;
84
+ if (matchCount === 0) {
85
+ info = strings.noMatch;
86
+ } else if (activeNode && activeNode.match! >= 1) {
87
+ matchIdx = activeNode.match ?? "?";
88
+ info = strings.matchIndex;
89
+ } else {
90
+ info = strings.queryResult;
91
+ }
92
+ info = info
93
+ .replace("${count}", this.tree.count().toLocaleString())
94
+ .replace("${match}", "" + matchIdx)
95
+ .replace("${matches}", matchCount.toLocaleString());
96
+ this.matchInfoElem.textContent = info;
97
+ } else {
98
+ this.matchInfoElem.textContent = "";
99
+ }
100
+ }
101
+
102
+ if (this.nextButton instanceof HTMLButtonElement) {
103
+ this.nextButton.disabled = !matchCount;
104
+ }
105
+ if (this.prevButton instanceof HTMLButtonElement) {
106
+ this.prevButton.disabled = !matchCount;
107
+ }
108
+ if (this.modeButton) {
109
+ this.modeButton.disabled = !filterActive;
110
+ this.modeButton.classList.toggle(
111
+ "wb-filter-hide",
112
+ this.tree.filterMode === "hide"
113
+ );
114
+ }
115
+ }
116
+ _connectControls() {
117
+ const tree = this.tree;
118
+ const connect: FilterConnectType = this.getPluginOption("connect");
119
+ if (!connect) {
120
+ return;
121
+ }
122
+ this.queryInput = elemFromSelector(connect.inputElem);
123
+ if (!this.queryInput) {
124
+ throw new Error(`Invalid 'filter.connect' option: ${connect.inputElem}.`);
125
+ }
126
+ this.prevButton = elemFromSelector(connect.prevButton!);
127
+ this.nextButton = elemFromSelector(connect.nextButton!);
128
+ this.modeButton = elemFromSelector(connect.modeButton!);
129
+ this.matchInfoElem = elemFromSelector(connect.matchInfoElem!);
130
+ if (this.prevButton) {
131
+ onEvent(this.prevButton, "click", () => {
132
+ tree.findRelatedNode(
133
+ tree.getActiveNode() || tree.getFirstChild()!,
134
+ "prevMatch"
135
+ );
136
+ this._updatedConnectedControls();
137
+ });
138
+ }
139
+ if (this.nextButton) {
140
+ onEvent(this.nextButton, "click", () => {
141
+ tree.findRelatedNode(
142
+ tree.getActiveNode() || tree.getFirstChild()!,
143
+ "nextMatch"
144
+ );
145
+ this._updatedConnectedControls();
146
+ });
147
+ }
148
+ if (this.modeButton) {
149
+ onEvent(this.modeButton, "click", (e) => {
150
+ if (!this.tree.filterMode) {
151
+ return;
152
+ }
153
+ this.setPluginOption(
154
+ "mode",
155
+ tree.filterMode === "dim" ? "hide" : "dim"
156
+ );
157
+ });
158
+ }
159
+ onEvent(
160
+ this.queryInput,
161
+ "input",
162
+ debounce((e) => {
163
+ this.filterNodes(this.queryInput!.value.trim(), {});
164
+ }, 700)
165
+ );
166
+ this._updatedConnectedControls();
167
+ }
168
+
81
169
  _applyFilterNoUpdate(
82
170
  filter: string | RegExp | NodeFilterCallback,
83
171
  _opts: FilterNodesOptions
@@ -98,7 +186,7 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
98
186
  const treeOpts = tree.options;
99
187
  const prevAutoCollapse = treeOpts.autoCollapse;
100
188
  // Use default options from `tree.options.filter`, but allow to override them
101
- const opts = extend({}, treeOpts.filter, _opts);
189
+ const opts: FilterOptionsType = extend({}, treeOpts.filter, _opts);
102
190
  const hideMode = opts.mode === "hide";
103
191
  const matchBranch = !!opts.matchBranch;
104
192
  const leavesOnly = !!opts.leavesOnly && !matchBranch;
@@ -176,12 +264,12 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
176
264
  };
177
265
  }
178
266
 
179
- tree.filterMode = opts.mode;
180
- // eslint-disable-next-line prefer-rest-params, prefer-spread
267
+ tree.filterMode = opts.mode ?? "dim";
268
+ // eslint-disable-next-line prefer-rest-params
181
269
  this.lastFilterArgs = arguments;
182
270
 
183
271
  tree.element.classList.toggle("wb-ext-filter-hide", !!hideMode);
184
- tree.element.classList.toggle("wb-ext-filter-dim", !hideMode);
272
+ tree.element.classList.toggle("wb-ext-filter-dim", opts.mode === "dim");
185
273
  tree.element.classList.toggle(
186
274
  "wb-ext-filter-hide-expanders",
187
275
  !!opts.hideExpanders
@@ -193,10 +281,6 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
193
281
  delete node.titleWithHighlight;
194
282
  node.subMatchCount = 0;
195
283
  });
196
- // statusNode = tree.root.findDirectChild(KEY_NODATA);
197
- // if (statusNode) {
198
- // statusNode.remove();
199
- // }
200
284
  tree.setStatus(NodeStatusType.ok);
201
285
 
202
286
  // Adjust node.hide, .match, and .subMatchCount properties
@@ -210,7 +294,7 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
210
294
 
211
295
  if (res === "skip") {
212
296
  node.visit(function (c) {
213
- c.match = false;
297
+ c.match = undefined;
214
298
  }, true);
215
299
  return "skip";
216
300
  }
@@ -223,7 +307,7 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
223
307
 
224
308
  if (res) {
225
309
  count++;
226
- node.match = true;
310
+ node.match = count;
227
311
  node.visitParents((p) => {
228
312
  if (p !== node) {
229
313
  p.subMatchCount! += 1;
@@ -252,6 +336,8 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
252
336
  tree.logDebug(
253
337
  `Filter '${filter}' found ${count} nodes in ${Date.now() - start} ms.`
254
338
  );
339
+ this._updatedConnectedControls();
340
+
255
341
  return count;
256
342
  }
257
343
 
@@ -309,6 +395,7 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
309
395
  } else {
310
396
  tree.logWarn("updateFilter(): no filter active.");
311
397
  }
398
+ this._updatedConnectedControls();
312
399
  }
313
400
 
314
401
  /**
@@ -316,30 +403,17 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
316
403
  */
317
404
  clearFilter() {
318
405
  const tree = this.tree;
319
- // statusNode = tree.root.findDirectChild(KEY_NODATA),
320
- // escapeTitles = tree.options.escapeTitles;
321
406
  tree.enableUpdate(false);
322
407
 
323
- // if (statusNode) {
324
- // statusNode.remove();
325
- // }
326
408
  tree.setStatus(NodeStatusType.ok);
327
409
  // we also counted root node's subMatchCount
328
410
  delete tree.root.match;
329
411
  delete tree.root.subMatchCount;
330
412
 
331
413
  tree.visit((node) => {
332
- // if (node.match && node._rowElem) {
333
- // let titleElem = node._rowElem.querySelector("span.wb-title")!;
334
- // node._callEvent("enhanceTitle", { titleElem: titleElem });
335
- // }
336
414
  delete node.match;
337
415
  delete node.subMatchCount;
338
416
  delete node.titleWithHighlight;
339
- // if (node.subMatchBadge) {
340
- // node.subMatchBadge.remove();
341
- // delete node.subMatchBadge;
342
- // }
343
417
  if (node._filterAutoExpanded && node.expanded) {
344
418
  node.setExpanded(false, {
345
419
  noAnimation: true,
@@ -355,7 +429,8 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
355
429
  "wb-ext-filter-dim",
356
430
  "wb-ext-filter-hide"
357
431
  );
358
- // tree._callHook("treeStructureChanged", this, "clearFilter");
432
+ this._updatedConnectedControls();
433
+
359
434
  tree.enableUpdate(true);
360
435
  }
361
436
  }
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - ext-grid
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  import { Wunderbaum } from "./wunderbaum";
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - ext-keynav
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - ext-logger
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
@@ -1,10 +1,11 @@
1
1
  /*!
2
2
  * Wunderbaum - wb_extension_base
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
7
7
  import * as util from "./util";
8
+ import { WunderbaumOptions } from "./wb_options";
8
9
  import { Wunderbaum } from "./wunderbaum";
9
10
 
10
11
  export type ExtensionsDict = { [key: string]: WunderbaumExtension<any> };
@@ -13,7 +14,7 @@ export abstract class WunderbaumExtension<TOptions> {
13
14
  public enabled = true;
14
15
  readonly id: string;
15
16
  readonly tree: Wunderbaum;
16
- readonly treeOpts: any;
17
+ readonly treeOpts: WunderbaumOptions;
17
18
  readonly extensionOpts: any;
18
19
 
19
20
  constructor(tree: Wunderbaum, id: string, defaults: TOptions) {
@@ -23,7 +24,7 @@ export abstract class WunderbaumExtension<TOptions> {
23
24
 
24
25
  const opts = tree.options as any;
25
26
 
26
- if (this.treeOpts[id] === undefined) {
27
+ if ((<any>this.treeOpts)[id] === undefined) {
27
28
  opts[id] = this.extensionOpts = util.extend({}, defaults);
28
29
  } else {
29
30
  // TODO: do we break existing object instance references here?
package/src/wb_node.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - wunderbaum_node
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
@@ -21,6 +21,7 @@ import {
21
21
  MakeVisibleOptions,
22
22
  MatcherCallback,
23
23
  NavigateOptions,
24
+ NavigationType,
24
25
  NodeAnyCallback,
25
26
  NodeStatusType,
26
27
  NodeStringCallback,
@@ -46,7 +47,7 @@ import {
46
47
  import {
47
48
  decompressSourceData,
48
49
  ICON_WIDTH,
49
- KEY_TO_ACTION_DICT,
50
+ KEY_TO_NAVIGATION_MAP,
50
51
  makeNodeTitleMatcher,
51
52
  nodeTitleSorter,
52
53
  RESERVED_TREE_SOURCE_KEYS,
@@ -171,7 +172,11 @@ export class WunderbaumNode {
171
172
  _partsel = false;
172
173
  _partload = false;
173
174
  // --- FILTER ---
174
- public match?: boolean; // Added and removed by filter code
175
+ /**
176
+ * > 0 if matched (-1 to keep system nodes visible);
177
+ * Added and removed by filter code.
178
+ */
179
+ public match?: number;
175
180
  public subMatchCount?: number = 0;
176
181
  // public subMatchBadge?: HTMLElement;
177
182
  /** @internal */
@@ -656,7 +661,7 @@ export class WunderbaumNode {
656
661
  *
657
662
  * @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
658
663
  */
659
- findRelatedNode(where: string, includeHidden = false) {
664
+ findRelatedNode(where: NavigationType, includeHidden = false) {
660
665
  return this.tree.findRelatedNode(this, where, includeHidden);
661
666
  }
662
667
 
@@ -987,7 +992,7 @@ export class WunderbaumNode {
987
992
  return other && other.parent === this;
988
993
  }
989
994
 
990
- /** (experimental) Return true if this node is partially loaded. */
995
+ /** Return true if this node is partially loaded. @experimental */
991
996
  isPartload(): boolean {
992
997
  return !!this._partload;
993
998
  }
@@ -1509,12 +1514,12 @@ export class WunderbaumNode {
1509
1514
  * e.g. `ArrowLeft` = 'left'.
1510
1515
  * @param options
1511
1516
  */
1512
- async navigate(where: string, options?: NavigateOptions) {
1517
+ async navigate(where: NavigationType | string, options?: NavigateOptions) {
1513
1518
  // Allow to pass 'ArrowLeft' instead of 'left'
1514
- where = KEY_TO_ACTION_DICT[where] || where;
1519
+ const navType = (KEY_TO_NAVIGATION_MAP[where] ?? where) as NavigationType;
1515
1520
 
1516
1521
  // Otherwise activate or focus the related node
1517
- const node = this.findRelatedNode(where);
1522
+ const node = this.findRelatedNode(navType);
1518
1523
  if (!node) {
1519
1524
  this.logWarn(`Could not find related node '${where}'.`);
1520
1525
  return Promise.resolve(this);
@@ -1618,91 +1623,19 @@ export class WunderbaumNode {
1618
1623
  }
1619
1624
 
1620
1625
  protected _createIcon(
1621
- iconMap: any,
1622
1626
  parentElem: HTMLElement,
1623
1627
  replaceChild: HTMLElement | null,
1624
1628
  showLoading: boolean
1625
1629
  ): HTMLElement | null {
1626
- let iconSpan;
1627
- let icon = this.getOption("icon");
1628
- if (this._errorInfo) {
1629
- icon = iconMap.error;
1630
- } else if (this._isLoading && showLoading) {
1631
- // Status nodes, or nodes without expander (< minExpandLevel) should
1632
- // display the 'loading' status with the i.wb-icon span
1633
- icon = iconMap.loading;
1634
- }
1635
- if (icon === false) {
1636
- return null; // explicitly disabled: don't try default icons
1637
- }
1638
- if (typeof icon === "string") {
1639
- // Callback returned an icon definition
1640
- // icon = icon.trim()
1641
- } else if (this.statusNodeType) {
1642
- icon = (<any>iconMap)[this.statusNodeType];
1643
- } else if (this.expanded) {
1644
- icon = iconMap.folderOpen;
1645
- } else if (this.children) {
1646
- icon = iconMap.folder;
1647
- } else if (this.lazy) {
1648
- icon = iconMap.folderLazy;
1649
- } else {
1650
- icon = iconMap.doc;
1651
- }
1652
-
1653
- // this.log("_createIcon: " + icon);
1654
- if (!icon) {
1655
- iconSpan = document.createElement("i");
1656
- iconSpan.className = "wb-icon";
1657
- } else if (icon.indexOf("<") >= 0) {
1658
- // HTML
1659
- iconSpan = util.elemFromHtml(icon);
1660
- } else if (TEST_IMG.test(icon)) {
1661
- // Image URL
1662
- iconSpan = util.elemFromHtml(
1663
- `<i class="wb-icon" style="background-image: url('${icon}');">`
1664
- );
1665
- } else {
1666
- // Class name
1667
- iconSpan = document.createElement("i");
1668
- iconSpan.className = "wb-icon " + icon;
1669
- }
1670
- if (replaceChild) {
1671
- parentElem.replaceChild(iconSpan, replaceChild);
1672
- } else {
1673
- parentElem.appendChild(iconSpan);
1674
- }
1675
-
1676
- // Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
1677
-
1678
- const cbRes = this._callEvent("iconBadge", { iconSpan: iconSpan });
1679
- let badge = null;
1680
- if (cbRes != null && cbRes !== false) {
1681
- let classes = "";
1682
- let tooltip = "";
1683
- if (util.isPlainObject(cbRes)) {
1684
- badge = "" + cbRes.badge;
1685
- classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
1686
- tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
1687
- } else if (typeof cbRes === "number") {
1688
- badge = "" + cbRes;
1630
+ const iconElem = this.tree._createNodeIcon(this, showLoading, true);
1631
+ if (iconElem) {
1632
+ if (replaceChild) {
1633
+ parentElem.replaceChild(iconElem, replaceChild);
1689
1634
  } else {
1690
- badge = cbRes; // string or HTMLSpanElement
1691
- }
1692
- if (typeof badge === "string") {
1693
- badge = util.elemFromHtml(
1694
- `<span class="wb-badge${classes}"${tooltip}>${util.escapeHtml(
1695
- badge
1696
- )}</span>`
1697
- );
1698
- }
1699
- if (badge) {
1700
- iconSpan.append(<HTMLSpanElement>badge);
1635
+ parentElem.appendChild(iconElem);
1701
1636
  }
1702
1637
  }
1703
-
1704
- // this.log("_createIcon: ", iconSpan);
1705
- return iconSpan;
1638
+ return iconElem;
1706
1639
  }
1707
1640
 
1708
1641
  /**
@@ -1773,12 +1706,7 @@ export class WunderbaumNode {
1773
1706
 
1774
1707
  // Render the icon (show a 'loading' icon if we do not have an expander that
1775
1708
  // we would prefer).
1776
- const iconSpan = this._createIcon(
1777
- tree.iconMap,
1778
- nodeElem,
1779
- null,
1780
- !expanderSpan
1781
- );
1709
+ const iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
1782
1710
  if (iconSpan) {
1783
1711
  ofsTitlePx += ICON_WIDTH;
1784
1712
  }
@@ -2029,7 +1957,7 @@ export class WunderbaumNode {
2029
1957
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
2030
1958
  const iconSpan = nodeElem.querySelector("i.wb-icon") as HTMLElement;
2031
1959
  if (iconSpan) {
2032
- this._createIcon(tree.iconMap, nodeElem, iconSpan, !expanderSpan);
1960
+ this._createIcon(nodeElem, iconSpan, !expanderSpan);
2033
1961
  }
2034
1962
  }
2035
1963
  // Adjust column width
@@ -2437,7 +2365,8 @@ export class WunderbaumNode {
2437
2365
  case undefined:
2438
2366
  changed = this.selected || !this._partsel;
2439
2367
  this.selected = false;
2440
- this._partsel = true;
2368
+ // #110: end nodess cannot have a `_partsel` flag
2369
+ this._partsel = this.hasChildren() ? true : false;
2441
2370
  break;
2442
2371
  default:
2443
2372
  util.error(`Invalid state: ${state}`);
@@ -2640,7 +2569,7 @@ export class WunderbaumNode {
2640
2569
  );
2641
2570
 
2642
2571
  statusNode = this.addNode(data, "prependChild");
2643
- statusNode.match = true;
2572
+ statusNode.match = -1; // Mark as 'match' to avoid hiding
2644
2573
  tree.update(ChangeType.structure);
2645
2574
 
2646
2575
  return statusNode;
@@ -2663,7 +2592,7 @@ export class WunderbaumNode {
2663
2592
  _setStatusNode({
2664
2593
  statusNodeType: status,
2665
2594
  title:
2666
- tree.options.strings.loading +
2595
+ tree.options.strings!.loading +
2667
2596
  (message ? " (" + message + ")" : ""),
2668
2597
  checkbox: false,
2669
2598
  colspan: true,
@@ -2676,7 +2605,7 @@ export class WunderbaumNode {
2676
2605
  _setStatusNode({
2677
2606
  statusNodeType: status,
2678
2607
  title:
2679
- tree.options.strings.loadError +
2608
+ tree.options.strings!.loadError +
2680
2609
  (message ? " (" + message + ")" : ""),
2681
2610
  checkbox: false,
2682
2611
  colspan: true,
@@ -2689,7 +2618,7 @@ export class WunderbaumNode {
2689
2618
  case "noData":
2690
2619
  _setStatusNode({
2691
2620
  statusNodeType: status,
2692
- title: message || tree.options.strings.noData,
2621
+ title: message || tree.options.strings!.noData,
2693
2622
  checkbox: false,
2694
2623
  colspan: true,
2695
2624
  tooltip: details,
package/src/wb_options.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum - utils
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
@@ -13,12 +13,14 @@ import {
13
13
  DynamicIconOption,
14
14
  EditOptionsType,
15
15
  FilterOptionsType,
16
+ IconMapType,
16
17
  // GridOptionsType,
17
18
  // KeynavOptionsType,
18
19
  // LoggerOptionsType,
19
20
  NavModeEnum,
20
21
  NodeTypeDefinitionMap,
21
22
  SelectModeType,
23
+ TranslationsType,
22
24
  WbActivateEventType,
23
25
  WbButtonClickEventType,
24
26
  WbCancelableEventResultType,
@@ -114,7 +116,7 @@ export interface WunderbaumOptions {
114
116
  /**
115
117
  * Translation map for some system messages.
116
118
  */
117
- strings?: any; //[key: string] string;
119
+ strings?: TranslationsType;
118
120
  /**
119
121
  * 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
120
122
  * Default: 3 (4 in local debug environment)
@@ -149,7 +151,7 @@ export interface WunderbaumOptions {
149
151
  * Note: the icon font must be loaded separately.
150
152
  * Default: "bootstrap"
151
153
  */
152
- iconMap?: string | { [key: string]: string };
154
+ iconMap?: string | IconMapType;
153
155
  /**
154
156
  * Collapse siblings when a node is expanded.
155
157
  * Default: false
@@ -170,10 +172,10 @@ export interface WunderbaumOptions {
170
172
  */
171
173
  adjustHeight?: boolean;
172
174
  /**
173
- * HTMLElement that receives the top nodes breadcrumb.
175
+ * HTMLElement or selector that receives the top nodes breadcrumb.
174
176
  * Default: undefined
175
177
  */
176
- connectTopBreadcrumb?: HTMLElement;
178
+ connectTopBreadcrumb?: HTMLElement | string;
177
179
  /**
178
180
  * Default: NavModeEnum.startRow
179
181
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * Wunderbaum style sheet (generated from wunderbaum.scss)
3
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3
+ * Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  // @use "sass:meta";
@@ -69,8 +69,8 @@ $header-height: $row-outer-height;
69
69
  // $level-rainbow: rgba(255, 255, 64, 0.07), rgba(127, 255, 127, 0.07),
70
70
  // rgba(255, 127, 255, 0.07), rgba(79, 236, 236, 0.07);
71
71
  // Slightly stronger*
72
- $level-rainbow: rgb(255, 255, 201), rgb(218, 255, 218), rgb(255, 217, 254),
73
- rgb(204, 250, 250);
72
+ $level-rainbow:
73
+ rgb(255, 255, 201), rgb(218, 255, 218), rgb(255, 217, 254), rgb(204, 250, 250);
74
74
 
75
75
  // ----------------------------------------------------------------------------
76
76
  // --- Define CSS variables with calculated default values
@@ -759,6 +759,10 @@ div.wunderbaum {
759
759
  }
760
760
 
761
761
  /* --- TOOL CLASSES --- */
762
+ a.wb-breadcrumb {
763
+ cursor: pointer;
764
+ text-decoration: none;
765
+ }
762
766
 
763
767
  .wb-helper-center {
764
768
  text-align: center;
@@ -797,7 +801,11 @@ div.wunderbaum {
797
801
  // .wb-helper-edit-text {
798
802
  // // content-editable: true;
799
803
  // }
800
-
804
+ button.wb-filter-hide {
805
+ font-weight: bolder;
806
+ // background-color: var(--wb-active-color);
807
+ // -webkit-appearance: auto; /* Removes default Safari styles */
808
+ }
801
809
  /* RTL support */
802
810
  .wb-helper-start,
803
811
  .wb-helper-start > input {