wunderbaum 0.13.0 → 0.14.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.
package/src/wb_node.ts CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  ApplyCommandType,
14
14
  ChangeType,
15
15
  CheckboxOption,
16
- ColumnDefinition,
17
16
  ColumnEventInfoMap,
18
17
  ExpandAllOptions,
19
18
  IconOption,
@@ -37,7 +36,7 @@ import {
37
36
  SetStatusOptions,
38
37
  SortByPropertyOptions,
39
38
  SortCallback,
40
- SortOrderType,
39
+ SortOptions,
41
40
  SourceType,
42
41
  TooltipOption,
43
42
  TristateType,
@@ -49,9 +48,11 @@ import {
49
48
  ICON_WIDTH,
50
49
  KEY_TO_NAVIGATION_MAP,
51
50
  makeNodeTitleMatcher,
51
+ NODE_TYPE_FOLDER,
52
52
  nodeTitleSorter,
53
53
  RESERVED_TREE_SOURCE_KEYS,
54
- TEST_IMG,
54
+ TEST_FILE_PATH,
55
+ TEST_HTML,
55
56
  TITLE_SPAN_PAD_Y,
56
57
  } from "./common";
57
58
  import { Deferred } from "./deferred";
@@ -186,13 +187,13 @@ export class WunderbaumNode {
186
187
  _rowIdx: number | undefined = 0;
187
188
  _rowElem: HTMLDivElement | undefined = undefined;
188
189
 
189
- constructor(tree: Wunderbaum, parent: WunderbaumNode, data: any) {
190
+ constructor(tree: Wunderbaum, parent: WunderbaumNode, data: WbNodeData) {
190
191
  util.assert(!parent || parent.tree === tree, `Invalid parent: ${parent}`);
191
192
  util.assert(!data.children, "'children' not allowed here");
192
193
 
193
194
  this.tree = tree;
194
195
  this.parent = parent;
195
- this.key = "" + (data.key ?? ++WunderbaumNode.sequence);
196
+ this.key = tree._calculateKey(data, parent);
196
197
  this.title = "" + (data.title ?? "<" + this.key + ">");
197
198
  this.expanded = !!data.expanded;
198
199
  this.lazy = !!data.lazy;
@@ -332,12 +333,19 @@ export class WunderbaumNode {
332
333
  nodeData = [<WbNodeData>nodeData];
333
334
  }
334
335
  const forceExpand =
335
- applyMinExpanLevel && _level < tree.options.minExpandLevel!;
336
+ applyMinExpanLevel && _level < tree.options.minExpandLevel;
336
337
  for (const child of <WbNodeData[]>nodeData) {
337
338
  const subChildren = child.children;
339
+ // Remove children property from source data because it should not be
340
+ // passed to the constructor of WunderbaumNode:
338
341
  delete child.children;
339
342
 
340
343
  const n = new WunderbaumNode(tree, this, child);
344
+
345
+ // Set `children` property again, so it can be used in `reload()`
346
+ if (subChildren != null) {
347
+ child.children = subChildren;
348
+ }
341
349
  if (forceExpand && !n.isUnloaded()) {
342
350
  n.expanded = true;
343
351
  }
@@ -804,7 +812,7 @@ export class WunderbaumNode {
804
812
  }
805
813
  return l;
806
814
  }
807
- /** Return a string representing the hierachical node path, e.g. "a/b/c".
815
+ /** Return a string representing the hierarchical node path, e.g. "a/b/c".
808
816
  * @param includeSelf
809
817
  * @param part property name or callback
810
818
  * @param separator
@@ -814,10 +822,6 @@ export class WunderbaumNode {
814
822
  part: keyof WunderbaumNode | NodeAnyCallback = "title",
815
823
  separator: string = "/"
816
824
  ) {
817
- // includeSelf = includeSelf !== false;
818
- // part = part || "title";
819
- // separator = separator || "/";
820
-
821
825
  let val;
822
826
  const path: string[] = [];
823
827
  const isFunc = typeof part === "function";
@@ -834,7 +838,7 @@ export class WunderbaumNode {
834
838
  return path.join(separator);
835
839
  }
836
840
 
837
- /** Return the preceeding node (under the same parent) or null. */
841
+ /** Return the preceding node (under the same parent) or null. */
838
842
  getPrevSibling(): WunderbaumNode | null {
839
843
  const ac = this.parent.children!;
840
844
  const idx = ac.indexOf(this);
@@ -866,7 +870,7 @@ export class WunderbaumNode {
866
870
  return this.classes ? this.classes.has(className) : false;
867
871
  }
868
872
 
869
- /** Return true if node ist the currently focused node. @since 0.9.0 */
873
+ /** Return true if node is the currently focused node. @since 0.9.0 */
870
874
  hasFocus(): boolean {
871
875
  return this.tree.focusNode === this;
872
876
  }
@@ -927,7 +931,7 @@ export class WunderbaumNode {
927
931
  * an expand operation is currently possible.
928
932
  */
929
933
  isExpandable(andCollapsed = false): boolean {
930
- // `false` is never expandable (unoffical)
934
+ // `false` is never expandable (unofficial)
931
935
  if ((andCollapsed && this.expanded) || <any>this.children === false) {
932
936
  return false;
933
937
  }
@@ -1002,12 +1006,12 @@ export class WunderbaumNode {
1002
1006
  return !this.selected && !!this._partsel;
1003
1007
  }
1004
1008
 
1005
- /** Return true if this node has DOM representaion, i.e. is displayed in the viewport. */
1009
+ /** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
1006
1010
  isRadio(): boolean {
1007
1011
  return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
1008
1012
  }
1009
1013
 
1010
- /** Return true if this node has DOM representaion, i.e. is displayed in the viewport. */
1014
+ /** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
1011
1015
  isRendered(): boolean {
1012
1016
  return !!this._rowElem;
1013
1017
  }
@@ -1645,7 +1649,7 @@ export class WunderbaumNode {
1645
1649
  protected _render_markup(opts: RenderOptions) {
1646
1650
  const tree = this.tree;
1647
1651
  const treeOptions = tree.options;
1648
- const rowHeight = treeOptions.rowHeightPx!;
1652
+ const rowHeight = treeOptions.rowHeightPx;
1649
1653
  const checkbox = this.getOption("checkbox");
1650
1654
  const columns = tree.columns;
1651
1655
  const level = this.getLevel();
@@ -1866,11 +1870,11 @@ export class WunderbaumNode {
1866
1870
  const rowDiv = this._rowElem!;
1867
1871
 
1868
1872
  // Row markup already exists
1869
- const nodeElem = rowDiv.querySelector("span.wb-node") as HTMLSpanElement;
1870
- const expanderSpan = nodeElem.querySelector(
1873
+ const nodeSpan = rowDiv.querySelector("span.wb-node") as HTMLSpanElement;
1874
+ const expanderElem = nodeSpan.querySelector(
1871
1875
  "i.wb-expander"
1872
1876
  ) as HTMLLIElement;
1873
- const checkboxSpan = nodeElem.querySelector(
1877
+ const checkboxElem = nodeSpan.querySelector(
1874
1878
  "i.wb-checkbox"
1875
1879
  ) as HTMLLIElement;
1876
1880
 
@@ -1903,7 +1907,7 @@ export class WunderbaumNode {
1903
1907
  rowDiv.classList.add(...typeInfo.classes);
1904
1908
  }
1905
1909
 
1906
- if (expanderSpan) {
1910
+ if (expanderElem) {
1907
1911
  let image = null;
1908
1912
  if (this._isLoading) {
1909
1913
  image = iconMap.loading;
@@ -1918,14 +1922,17 @@ export class WunderbaumNode {
1918
1922
  }
1919
1923
 
1920
1924
  if (image == null) {
1921
- expanderSpan.classList.add("wb-indent");
1922
- } else if (TEST_IMG.test(image)) {
1923
- expanderSpan.style.backgroundImage = `url('${image}')`;
1925
+ expanderElem.className = "wb-expander";
1926
+ expanderElem.classList.add("wb-indent");
1927
+ } else if (TEST_HTML.test(image)) {
1928
+ expanderElem.replaceWith(util.elemFromHtml(image));
1929
+ } else if (TEST_FILE_PATH.test(image)) {
1930
+ expanderElem.style.backgroundImage = `url('${image}')`;
1924
1931
  } else {
1925
- expanderSpan.className = "wb-expander " + image;
1932
+ expanderElem.className = "wb-expander " + image;
1926
1933
  }
1927
1934
  }
1928
- if (checkboxSpan) {
1935
+ if (checkboxElem) {
1929
1936
  let cbclass = "wb-checkbox ";
1930
1937
  if (this.isRadio()) {
1931
1938
  cbclass += "wb-radio ";
@@ -1945,7 +1952,7 @@ export class WunderbaumNode {
1945
1952
  cbclass += iconMap.checkUnchecked;
1946
1953
  }
1947
1954
  }
1948
- checkboxSpan.className = cbclass;
1955
+ checkboxElem.className = cbclass;
1949
1956
  }
1950
1957
  // Fix active cell in cell-nav mode
1951
1958
  if (!opts.isNew) {
@@ -1955,9 +1962,9 @@ export class WunderbaumNode {
1955
1962
  colSpan.classList.remove("wb-error", "wb-invalid");
1956
1963
  }
1957
1964
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
1958
- const iconSpan = nodeElem.querySelector("i.wb-icon") as HTMLElement;
1965
+ const iconSpan = nodeSpan.querySelector("i.wb-icon") as HTMLElement;
1959
1966
  if (iconSpan) {
1960
- this._createIcon(nodeElem, iconSpan, !expanderSpan);
1967
+ this._createIcon(nodeSpan, iconSpan, !expanderElem);
1961
1968
  }
1962
1969
  }
1963
1970
  // Adjust column width
@@ -2217,6 +2224,7 @@ export class WunderbaumNode {
2217
2224
  async setExpanded(flag: boolean = true, options?: SetExpandedOptions) {
2218
2225
  const { force, scrollIntoView, immediate, resetLazy } = options ?? {};
2219
2226
  const sendEvents = !options?.noEvents; // Default: send events
2227
+
2220
2228
  if (
2221
2229
  !flag &&
2222
2230
  this.isExpanded() &&
@@ -2283,6 +2291,32 @@ export class WunderbaumNode {
2283
2291
  setKey(key: string | null, refKey: string | null) {
2284
2292
  throw new Error("Not yet implemented");
2285
2293
  }
2294
+ // /**
2295
+ // * Calculate a *stable*, unique key for this node from its refKey (or title).
2296
+ // * We also add information from the parent, because a refKey may occur multiple
2297
+ // * times in a tree.
2298
+ // */
2299
+ // calcUniqueKey() {
2300
+ // // Assuming that the parent's key was calculated the same way, we implicitly
2301
+ // // involve the whole refKey-path:
2302
+ // const s = this.key + (this.refKey || this.title);
2303
+ // // 32-bit has a high probability of collisions, so we pump up to 64-bit
2304
+ // // https://security.stackexchange.com/q/209882/207588
2305
+ // const h1 = util.murmurHash3(s, true);
2306
+ // return "id_" + h1 + util.murmurHash3(h1 + s, true);
2307
+ // // const l = [];
2308
+ // // // eslint-disable-next-line @typescript-eslint/no-this-alias
2309
+ // // let node: WunderbaumNode = this;
2310
+ // // while (node.parent) {
2311
+ // // l.unshift(node.refKey || node.key);
2312
+ // // node = node.parent;
2313
+ // // }
2314
+ // // const path = l.join("/");
2315
+ // // 32-bit has a high probability of collisions, so we pump up to 64-bit
2316
+ // // https://security.stackexchange.com/q/209882/207588
2317
+ // // const h1 = util.murmurHash3(path, true);
2318
+ // // return "id_" + h1 + util.murmurHash3(h1 + path, true);
2319
+ // }
2286
2320
 
2287
2321
  /**
2288
2322
  * Trigger a repaint, typically after a status or data change.
@@ -2320,6 +2354,24 @@ export class WunderbaumNode {
2320
2354
  return nodeList;
2321
2355
  }
2322
2356
 
2357
+ /**
2358
+ * Return an array of refKey values.
2359
+ *
2360
+ * RefKeys are unique identifiers for a node data, and are used to identify
2361
+ * clones.
2362
+ * If more than one node has the same refKey, it is only returned once.
2363
+ * @param selected if true, only return refKeys of selected nodes.
2364
+ */
2365
+ getRefKeys(selected = false): string[] {
2366
+ const refKeys = new Set<string>();
2367
+ this.visit((node) => {
2368
+ if (node.refKey != null && (!selected || node.selected)) {
2369
+ refKeys.add(node.refKey);
2370
+ }
2371
+ });
2372
+ return Array.from(refKeys);
2373
+ }
2374
+
2323
2375
  /** Toggle the check/uncheck state. */
2324
2376
  toggleSelected(options?: SetSelectedOptions): TristateType {
2325
2377
  let flag = this.isSelected();
@@ -2521,9 +2573,11 @@ export class WunderbaumNode {
2521
2573
  this.selected = flag;
2522
2574
  if (selectMode === "hier") {
2523
2575
  this.fixSelection3AfterClick();
2524
- } else if (selectMode === "single") {
2576
+ } else if (selectMode === "single" && flag) {
2525
2577
  tree.visit((n) => {
2526
- n.selected = false;
2578
+ if (n !== this) {
2579
+ n.selected = false;
2580
+ }
2527
2581
  });
2528
2582
  }
2529
2583
  }
@@ -2592,7 +2646,7 @@ export class WunderbaumNode {
2592
2646
  _setStatusNode({
2593
2647
  statusNodeType: status,
2594
2648
  title:
2595
- tree.options.strings!.loading +
2649
+ tree.options.strings.loading +
2596
2650
  (message ? " (" + message + ")" : ""),
2597
2651
  checkbox: false,
2598
2652
  colspan: true,
@@ -2605,7 +2659,7 @@ export class WunderbaumNode {
2605
2659
  _setStatusNode({
2606
2660
  statusNodeType: status,
2607
2661
  title:
2608
- tree.options.strings!.loadError +
2662
+ tree.options.strings.loadError +
2609
2663
  (message ? " (" + message + ")" : ""),
2610
2664
  checkbox: false,
2611
2665
  colspan: true,
@@ -2618,7 +2672,7 @@ export class WunderbaumNode {
2618
2672
  case "noData":
2619
2673
  _setStatusNode({
2620
2674
  statusNodeType: status,
2621
- title: message || tree.options.strings!.noData,
2675
+ title: message || tree.options.strings.noData,
2622
2676
  checkbox: false,
2623
2677
  colspan: true,
2624
2678
  tooltip: details,
@@ -2646,35 +2700,19 @@ export class WunderbaumNode {
2646
2700
  this.update();
2647
2701
  }
2648
2702
 
2649
- _sortChildren(cmp: SortCallback, deep: boolean): void {
2650
- const cl = this.children;
2651
-
2652
- if (!cl) {
2653
- return;
2654
- }
2655
- cl.sort(cmp);
2656
- if (deep) {
2657
- for (let i = 0, l = cl.length; i < l; i++) {
2658
- if (cl[i].children) {
2659
- cl[i]._sortChildren(cmp, deep);
2660
- }
2661
- }
2662
- }
2663
- }
2664
-
2665
2703
  /**
2666
2704
  * Sort child list by title or custom criteria.
2667
2705
  * @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
2668
2706
  * (defaults to sorting by title).
2669
2707
  * @param {boolean} deep pass true to sort all descendant nodes recursively
2708
+ * @deprecated use {@link sort}
2670
2709
  */
2671
2710
  sortChildren(
2672
2711
  cmp: SortCallback | null = nodeTitleSorter,
2673
2712
  deep: boolean = false
2674
2713
  ): void {
2675
- this._sortChildren(cmp || nodeTitleSorter, deep);
2676
- this.tree.update(ChangeType.structure);
2677
- // this.triggerModify("sort"); // TODO
2714
+ this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
2715
+ return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
2678
2716
  }
2679
2717
 
2680
2718
  /**
@@ -2699,82 +2737,159 @@ export class WunderbaumNode {
2699
2737
  /**
2700
2738
  * Convenience method to implement column sorting.
2701
2739
  * @since 0.11.0
2740
+ * @deprecated use {@link sort}
2702
2741
  */
2703
2742
  sortByProperty(options: SortByPropertyOptions) {
2704
- const {
2705
- caseInsensitive = true,
2743
+ this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
2744
+ return this.sort(options);
2745
+ }
2746
+
2747
+ /**
2748
+ * Implement column sorting.
2749
+ * @since 0.14.0
2750
+ */
2751
+ sort(options: SortOptions) {
2752
+ const tree = this.tree;
2753
+ let {
2754
+ propName = undefined,
2706
2755
  deep = true,
2707
- nativeOrderPropName = "_nativeIndex",
2756
+ key = undefined,
2757
+ order = undefined,
2758
+ caseInsensitive = true,
2759
+ cmp = undefined,
2760
+ // Support click on column sort header:
2708
2761
  updateColInfo = false,
2762
+ nativeOrderPropName = "_nativeIndex",
2763
+ colId = undefined,
2709
2764
  } = options;
2710
2765
 
2711
- let order: SortOrderType;
2712
- let colDef: ColumnDefinition | null;
2766
+ propName ??= colId;
2767
+ if (propName === "*") {
2768
+ propName = "title";
2769
+ }
2770
+
2771
+ const isFolder =
2772
+ tree.options.sortFoldersFirst === true
2773
+ ? (node: WunderbaumNode) =>
2774
+ node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
2775
+ : tree.options.sortFoldersFirst;
2713
2776
 
2714
2777
  if (updateColInfo) {
2715
- colDef = this.tree["_columnsById"][options.colId!];
2778
+ const colDef = this.tree["_columnsById"][options.colId!];
2716
2779
  util.assert(colDef, `Invalid colId specified: ${options.colId}`);
2717
- order =
2718
- options.order ??
2719
- util.rotate(colDef!.sortOrder, ["asc", "desc", undefined]);
2780
+ order ??= util.rotate(colDef.sortOrder, ["asc", "desc", undefined]);
2720
2781
 
2721
2782
  for (const col of this.tree.columns) {
2722
2783
  col.sortOrder = col === colDef ? order : undefined;
2723
2784
  }
2724
-
2785
+ if (order === undefined) {
2786
+ propName = nativeOrderPropName;
2787
+ order = "asc";
2788
+ }
2725
2789
  this.tree.update(ChangeType.colStructure);
2726
2790
  } else {
2727
- order = options.order ?? "asc";
2791
+ propName ??= "title";
2792
+ order ??= "asc";
2728
2793
  }
2729
2794
 
2730
- let propName = options.propName ?? (options.colId || "");
2731
- if (propName === "*") {
2732
- propName = "title";
2733
- }
2734
- if (order == null) {
2735
- propName = nativeOrderPropName;
2736
- order = "asc";
2737
- }
2738
- this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
2739
- util.assert(propName, "No property name specified");
2795
+ this.logDebug(`sort(), propName=${propName}, ${order}`, options);
2796
+ util.assert(propName || cmp || key, "No `propName` or `key` specified");
2740
2797
 
2741
- const cmp = (a: WunderbaumNode, b: WunderbaumNode) => {
2742
- let av, bv;
2743
- if (NODE_DICT_PROPS.has(<string>propName)) {
2744
- av = a[propName as keyof WunderbaumNode];
2745
- bv = b[propName as keyof WunderbaumNode];
2746
- } else {
2747
- av = a.data[propName];
2748
- bv = b.data[propName];
2749
- }
2750
- if (av == null && bv == null) {
2751
- return 0;
2752
- }
2753
- if (av == null) {
2754
- av = typeof bv === "string" ? "" : 0;
2755
- } else if (typeof av === "boolean") {
2756
- av = av ? 1 : 0;
2757
- }
2758
- if (bv == null) {
2759
- bv = typeof av === "string" ? "" : 0;
2760
- } else if (typeof bv === "boolean") {
2761
- bv = bv ? 1 : 0;
2798
+ // Define a key callback from the parameters we have
2799
+ if (key == null && cmp == null) {
2800
+ key = (node) => {
2801
+ let val;
2802
+ if (NODE_DICT_PROPS.has(<string>propName)) {
2803
+ val = node[propName as keyof WunderbaumNode];
2804
+ } else {
2805
+ val = node.data[propName!];
2806
+ }
2807
+ if (caseInsensitive && typeof val === "string") {
2808
+ val = val.toLowerCase();
2809
+ }
2810
+ return val;
2811
+ };
2812
+ }
2813
+ // Define a compare callback that uses the key callback
2814
+ if (cmp) {
2815
+ util.assert(!key, "`key` and `cmp` are mutually exclusive");
2816
+ tree.logDeprecate("SortOptions.cmp", {
2817
+ since: "0.14.0",
2818
+ hint: "use the `key` callback instead",
2819
+ });
2820
+ } else {
2821
+ if (options.propName || options.caseInsensitive) {
2822
+ tree.logWarn("sort(): ignoring propName, caseInsensitive");
2762
2823
  }
2763
- if (caseInsensitive) {
2764
- if (typeof av === "string") {
2765
- av = av.toLowerCase();
2824
+
2825
+ cmp = (a, b) => {
2826
+ if (isFolder) {
2827
+ const isFolderA = isFolder(a);
2828
+ if (isFolderA !== isFolder(b)) {
2829
+ return isFolderA ? -1 : 1;
2830
+ }
2831
+ }
2832
+ let x = key!(a);
2833
+ let y = key!(b);
2834
+ // Assure we have reasonable comparisons with null values:
2835
+ if (x == null) {
2836
+ x = typeof y === "string" ? "" : 0;
2837
+ } else if (typeof x === "boolean") {
2838
+ x = x ? 1 : 0;
2839
+ }
2840
+ if (y == null) {
2841
+ y = typeof x === "string" ? "" : 0;
2842
+ } else if (typeof y === "boolean") {
2843
+ y = y ? 1 : 0;
2766
2844
  }
2767
- if (typeof bv === "string") {
2768
- bv = bv.toLowerCase();
2845
+
2846
+ if (order === "desc") {
2847
+ return x === y ? 0 : x > y ? -1 : 1;
2769
2848
  }
2849
+ return x === y ? 0 : x > y ? 1 : -1;
2850
+ };
2851
+ }
2852
+
2853
+ function _sortChildren(cl: WunderbaumNode[]): void {
2854
+ if (!cl) {
2855
+ return;
2770
2856
  }
2771
- if (order === "desc") {
2772
- return av === bv ? 0 : av > bv ? -1 : 1;
2857
+ cl.sort(cmp);
2858
+ if (deep) {
2859
+ for (let i = 0, l = cl.length; i < l; i++) {
2860
+ if (cl[i].children) {
2861
+ _sortChildren(cl[i].children!);
2862
+ }
2863
+ }
2773
2864
  }
2774
- return av === bv ? 0 : av > bv ? 1 : -1;
2775
- };
2865
+ }
2866
+ if (this.children) {
2867
+ _sortChildren(this.children);
2868
+ }
2869
+ this.tree.update(ChangeType.structure);
2870
+ // this.triggerModify("sort"); // TODO
2871
+ }
2776
2872
 
2777
- return this.sortChildren(cmp, deep);
2873
+ /**
2874
+ * Re-apply current sorting if any (use after lazy load).
2875
+ * Example:
2876
+ * ```js
2877
+ * load: function (e) {
2878
+ * // Whe loading a lazy branch, apply current sort order if any
2879
+ * e.node.resort();
2880
+ * },
2881
+ * ```
2882
+ * @since 0.14.0
2883
+ */
2884
+ resort(options: SortOptions = {}): void {
2885
+ for (const colDef of this.tree.columns) {
2886
+ if (colDef.sortOrder) {
2887
+ options.colId = colDef.id;
2888
+ options.order = colDef.sortOrder;
2889
+ this.sort(options);
2890
+ break;
2891
+ }
2892
+ }
2778
2893
  }
2779
2894
 
2780
2895
  /**
@@ -2821,7 +2936,8 @@ export class WunderbaumNode {
2821
2936
  * @param {function} callback the callback function.
2822
2937
  * Return false to stop iteration, return "skip" to skip this node and
2823
2938
  * its children only.
2824
- * @see {@link IterableIterator<WunderbaumNode>}, {@link Wunderbaum.visit}.
2939
+ * @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
2940
+ * @see {@link Wunderbaum.visit}.
2825
2941
  */
2826
2942
  visit(
2827
2943
  callback: NodeVisitCallback,