wunderbaum 0.10.1 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wunderbaum",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "title": "A treegrid control.",
5
5
  "description": "JavaScript tree/grid/treegrid control.",
6
6
  "homepage": "https://github.com/mar10/wunderbaum",
@@ -54,7 +54,6 @@
54
54
  "@typescript-eslint/eslint-plugin": "^6.7.5",
55
55
  "@typescript-eslint/parser": "^6.7.5",
56
56
  "concurrently": "^8.1.0",
57
- "docsify-cli": "^4.4.4",
58
57
  "eslint": "^8.51.0",
59
58
  "eslint-config-jquery": "^3.0.0",
60
59
  "eslint-config-prettier": "^8.8.0",
@@ -101,7 +100,7 @@
101
100
  },
102
101
  "scripts": {
103
102
  "test": "npm run lint && npm run build:js && grunt ci --verbose",
104
- "docs": "typedoc && touch docs/api/.nojekyll && rm docs/unittest/*.*; cp test/unit/*.* docs/unittest",
103
+ "api_docs": "typedoc && touch docs/api/.nojekyll && rm docs/unittest/*.*; cp test/unit/*.* docs/unittest",
105
104
  "format": "eslint src --fix && prettier src docs/demo -w && npm run lint",
106
105
  "lint": "prettier src docs/demo --check && eslint src docs/demo && tsc -t esnext --moduleResolution node --noEmit src/wunderbaum.ts",
107
106
  "build:minjs:umd": "terser build/wunderbaum.umd.js --compress --mangle --source-map \"base='build',url='wunderbaum.umd.min.js.map',filename='wunderbaum.umd.js'\" --output build/wunderbaum.umd.min.js",
@@ -110,13 +109,13 @@
110
109
  "build:scss": "sass src/wunderbaum.scss build/wunderbaum.css",
111
110
  "build:js": "rollup -c rollup.config.mjs && npm run build:minjs",
112
111
  "build:types": "tsc -t esnext --moduleResolution node -d --emitDeclarationOnly --outFile build/wunderbaum.d.ts src/wunderbaum.ts",
113
- "build": "npm run format && mkdir build; rm build/*.*; ls build && npm run build:js -s && npm run build:scss && npm run build:types -s && npm run docs",
112
+ "build": "npm run format && mkdir build; rm build/*.*; ls build && npm run build:js -s && npm run build:scss && npm run build:types -s && npm run api_docs",
114
113
  "make_dist": "npm run build && rm dist/*.* ; cp build/*.* dist",
115
114
  "watch:umd": "nodemon --watch src --ext 'ts' -x \"npm run build:minjs\"",
116
115
  "watch": "nodemon",
117
116
  "serve": "http-server test -p 8080 -o /",
118
- "docsify": "docsify serve docs -o",
119
- "dev": "concurrently \"http-server . -p 8080 -o /docs/demo \" \"nodemon\""
117
+ "dev": "concurrently \"http-server . -p 8080 -o /docs/demo \" \"nodemon\"",
118
+ "dev_mkdocs": "pipenv run mkdocs serve"
120
119
  },
121
120
  "npmName": "wunderbaum",
122
121
  "npmFileMap": [
package/src/common.ts CHANGED
@@ -64,6 +64,15 @@ export const iconMaps: { [key: string]: { [key: string]: string } } = {
64
64
  folderOpen: "bi bi-folder2-open",
65
65
  folderLazy: "bi bi-folder-symlink",
66
66
  doc: "bi bi-file-earmark",
67
+ colSortable: "bi bi-chevron-expand",
68
+ // colSortable: "bi bi-arrow-down-up",
69
+ // colSortAsc: "bi bi-chevron-down",
70
+ // colSortDesc: "bi bi-chevron-up",
71
+ colSortAsc: "bi bi-arrow-down",
72
+ colSortDesc: "bi bi-arrow-up",
73
+ colFilter: "bi bi-filter-circle",
74
+ colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
75
+ colMenu: "bi bi-three-dots-vertical",
67
76
  },
68
77
  fontawesome6: {
69
78
  error: "fa-solid fa-triangle-exclamation",
@@ -82,6 +91,12 @@ export const iconMaps: { [key: string]: { [key: string]: string } } = {
82
91
  folderOpen: "fa-regular fa-folder-open",
83
92
  folderLazy: "fa-solid fa-folder-plus",
84
93
  doc: "fa-regular fa-file",
94
+ colSortable: "fa-solid fa-fw fa-sort",
95
+ colSortAsc: "fa-solid fa-fw fa-sort-up",
96
+ colSortDesc: "fa-solid fa-fw fa-sort-down",
97
+ colFilter: "fa-solid fa-fw fa-filter",
98
+ colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
99
+ colMenu: "fa-solid fa-fw fa-ellipsis-v",
85
100
  },
86
101
  };
87
102
 
@@ -149,7 +164,7 @@ export const KEY_TO_ACTION_DICT: { [key: string]: string } = {
149
164
 
150
165
  /** Return a callback that returns true if the node title matches the string
151
166
  * or regular expression.
152
- * @see {@link WunderbaumNode.findAll()}
167
+ * @see {@link WunderbaumNode.findAll}
153
168
  */
154
169
  export function makeNodeTitleMatcher(match: string | RegExp): MatcherCallback {
155
170
  if (match instanceof RegExp) {
package/src/types.ts CHANGED
@@ -72,11 +72,23 @@ export type BoolOrStringOptionResolver = (
72
72
  export type NodeAnyCallback = (node: WunderbaumNode) => any;
73
73
  /** A callback that receives a node instance and returns a string value. */
74
74
  export type NodeStringCallback = (node: WunderbaumNode) => string;
75
+ /** A callback that receives a node instance and property name returns a value. */
76
+ export type NodePropertyGetterCallback = (
77
+ node: WunderbaumNode,
78
+ propName: string
79
+ ) => any;
75
80
  /** A callback that receives a node instance and returns an iteration modifier. */
76
81
  export type NodeVisitCallback = (node: WunderbaumNode) => NodeVisitResponse;
77
- /** A callback that receives a node instance and returns a string value. */
82
+ /**
83
+ * Returned by `NodeVisitCallback` to control iteration.
84
+ * `false` stops iteration, `skip` skips descendants but continues.
85
+ * All other values continue iteration.
86
+ */
78
87
  export type NodeVisitResponse = "skip" | boolean | void;
79
- /** A callback that receives a node-data dictionary and a node instance and returns an iteration modifier. */
88
+ /**
89
+ * A callback that receives a node-data dictionary and a node instance and
90
+ * returns an iteration modifier.
91
+ */
80
92
  export type NodeToDictCallback = (
81
93
  dict: WbNodeData,
82
94
  node: WunderbaumNode
@@ -264,6 +276,12 @@ export interface WbSelectEventType extends WbNodeEventType {
264
276
  flag: boolean;
265
277
  }
266
278
 
279
+ export interface WbButtonClickEventType extends WbTreeEventType {
280
+ info: WbEventInfo;
281
+ /** The associated command, e.g. 'menu', 'sort', 'filter', ... */
282
+ command: string;
283
+ }
284
+
267
285
  export interface WbRenderEventType extends WbNodeEventType {
268
286
  /**
269
287
  * True if the node's markup was not yet created. In this case the render
@@ -351,30 +369,58 @@ export interface ColumnDefinition {
351
369
  */
352
370
  minWidth?: string | number;
353
371
  /** Allow user to resize the column.
354
- * Default: false.
372
+ * @default false (or global tree option `columnsSortable`)
373
+ * @see {@link WunderbaumOptions.columnsResizable}.
374
+ * @since 0.10.0
355
375
  */
356
376
  resizable?: boolean;
357
377
  /** Optional custom column width when user resized by mouse drag.
358
378
  * Default: unset.
359
379
  */
360
380
  customWidthPx?: number;
361
- /** Allow user to sort the column. Default: false. <br>
362
- * **Note:** Sorting is not implemented yet.
381
+ /** Display a 'filter' button in the column header. Default: false. <br>
382
+ * Note: The actual filtering must be implemented in the `buttonClick()` event.
383
+ * @default false (or global tree option `columnsFilterable`)
384
+ * @since 0.11.0
385
+ */
386
+ filterable?: boolean;
387
+ /** .
388
+ * Default: inactive. <br>
389
+ * Note: The actual filtering must be implemented in the `buttonClick()` event.
390
+ */
391
+ filterActive?: boolean;
392
+ /** Display a 'sort' button in the column header. Default: false. <br>
393
+ * Note: The actual sorting must be implemented in the `buttonClick()` event.
394
+ * @default false (or global tree option `columnsSortable`)
395
+ * @see {@link WunderbaumOptions.columnsSortable}.
396
+ * @since 0.11.0
363
397
  */
364
398
  sortable?: boolean;
365
399
  /** Optional custom column sort orde when user clicked the sort icon.
366
- * Default: unset. <br>
367
- * **Note:** Sorting is not implemented yet.
400
+ * Default: unset, e.g. not sorted. <br>
401
+ * Note: The actual sorting must be implemented in the `buttonClick()` event.
402
+ * @since 0.11.0
368
403
  */
369
404
  sortOrder?: SortOrderType;
405
+ /** Display a menu icon that may open a context menu for this column.
406
+ * Note: The actual functionality must be implemented in the `buttonClick()` event.
407
+ * @default false (or global tree option `columnsMenu`)
408
+ * @see {@link WunderbaumOptions.columnsMenu}.
409
+ * @since 0.11.0
410
+ */
411
+ menu?: boolean;
370
412
  /** Optional class names that are added to all `span.wb-col` header AND data
371
- * elements of that column.
413
+ * elements of that column. Separate multiple classes with space.
372
414
  */
373
415
  classes?: string;
374
416
  /** If `headerClasses` is a set, it will be used for the header element only
375
417
  * (unlike `classes`, which is used for body and header cells).
418
+ * Separate multiple classes with space.
376
419
  */
377
420
  headerClasses?: string;
421
+ // /** A list of icon definitions added to the column header.
422
+ // */
423
+ // headerIcons?: string;
378
424
  /** Optional HTML content that is rendered into all `span.wb-col` elements of that column.*/
379
425
  html?: string;
380
426
  /** @internal */
@@ -464,7 +510,7 @@ export type NodeFilterResponse = "skip" | "branch" | boolean | void;
464
510
  export type NodeFilterCallback = (node: WunderbaumNode) => NodeFilterResponse;
465
511
 
466
512
  /**
467
- * Possible values for {@link WunderbaumNode.update()} and {@link Wunderbaum.update()}.
513
+ * Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
468
514
  */
469
515
  export enum ChangeType {
470
516
  /** Re-render the whole viewport, headers, and all rows. */
@@ -493,7 +539,7 @@ export enum RenderFlag {
493
539
  scroll = "scroll",
494
540
  }
495
541
 
496
- /** Possible values for {@link WunderbaumNode.setStatus()}. */
542
+ /** Possible values for {@link WunderbaumNode.setStatus}. */
497
543
  export enum NodeStatusType {
498
544
  ok = "ok",
499
545
  loading = "loading",
@@ -525,7 +571,7 @@ export enum NavModeEnum {
525
571
  * METHOD OPTIONS TYPES
526
572
  * ---------------------------------------------------------------------------*/
527
573
 
528
- /** Possible values for {@link WunderbaumNode.addChildren()}. */
574
+ /** Possible values for {@link WunderbaumNode.addChildren}. */
529
575
  export interface AddChildrenOptions {
530
576
  /** Insert children before this node (or index)
531
577
  * @default undefined or null: append as last child
@@ -541,12 +587,12 @@ export interface AddChildrenOptions {
541
587
  _level?: number;
542
588
  }
543
589
 
544
- /** Possible values for {@link Wunderbaum.applyCommand()} and {@link WunderbaumNode.applyCommand()}. */
590
+ /** Possible values for {@link Wunderbaum.applyCommand} and {@link WunderbaumNode.applyCommand}. */
545
591
  export interface ApplyCommandOptions {
546
592
  [key: string]: unknown;
547
593
  }
548
594
 
549
- /** Possible values for {@link Wunderbaum.expandAll()} and {@link WunderbaumNode.expandAll()}. */
595
+ /** Possible values for {@link Wunderbaum.expandAll} and {@link WunderbaumNode.expandAll}. */
550
596
  export interface ExpandAllOptions {
551
597
  /** Restrict expand level @default 99 */
552
598
  depth?: number;
@@ -559,7 +605,7 @@ export interface ExpandAllOptions {
559
605
  }
560
606
 
561
607
  /**
562
- * Possible option values for {@link Wunderbaum.filterNodes()}.
608
+ * Possible option values for {@link Wunderbaum.filterNodes}.
563
609
  * The defaults are inherited from the tree instances ´tree.options.filter`
564
610
  * settings (see also {@link FilterOptionsType}).
565
611
  */
@@ -585,7 +631,7 @@ export interface FilterNodesOptions {
585
631
  noData?: boolean | string;
586
632
  }
587
633
 
588
- /** Possible values for {@link WunderbaumNode.makeVisible()}. */
634
+ /** Possible values for {@link WunderbaumNode.makeVisible}. */
589
635
  export interface MakeVisibleOptions {
590
636
  /** Do not animate expand (currently not implemented). @default false */
591
637
  noAnimation?: boolean;
@@ -595,7 +641,7 @@ export interface MakeVisibleOptions {
595
641
  noEvents?: boolean;
596
642
  }
597
643
 
598
- /** Possible values for {@link WunderbaumNode.navigate()}. */
644
+ /** Possible values for {@link WunderbaumNode.navigate}. */
599
645
  export interface NavigateOptions {
600
646
  /** Activate the new node (otherwise focus only). @default true */
601
647
  activate?: boolean;
@@ -603,7 +649,7 @@ export interface NavigateOptions {
603
649
  event?: Event;
604
650
  }
605
651
 
606
- /** Possible values for {@link WunderbaumNode._render()}. */
652
+ /** Possible values for {@link WunderbaumNode._render}. */
607
653
  export interface RenderOptions {
608
654
  /** Which parts need update? @default ChangeType.data */
609
655
  change?: ChangeType;
@@ -621,7 +667,7 @@ export interface RenderOptions {
621
667
  resizeCols?: boolean;
622
668
  }
623
669
 
624
- /** Possible values for {@link WunderbaumNode.scrollIntoView()} `options` argument. */
670
+ /** Possible values for {@link WunderbaumNode.scrollIntoView} `options` argument. */
625
671
  export interface ScrollIntoViewOptions {
626
672
  /** Do not animate (currently not implemented). @default false */
627
673
  noAnimation?: boolean;
@@ -633,7 +679,7 @@ export interface ScrollIntoViewOptions {
633
679
  ofsY?: number;
634
680
  }
635
681
 
636
- /** Possible values for {@link Wunderbaum.scrollTo()} `options` argument. */
682
+ /** Possible values for {@link Wunderbaum.scrollTo} `options` argument. */
637
683
  export interface ScrollToOptions extends ScrollIntoViewOptions {
638
684
  /** Which node to scroll into the viewport.*/
639
685
  node: WunderbaumNode;
@@ -653,7 +699,7 @@ export interface SetActiveOptions {
653
699
  focusTree?: boolean;
654
700
  /** Optional original event that will be passed to the (de)activate handler. */
655
701
  event?: Event;
656
- /** Also call {@link Wunderbaum.setColumn()}. */
702
+ /** Also call {@link Wunderbaum.setColumn}. */
657
703
  colIdx?: number | string;
658
704
  /**
659
705
  * Focus embedded input control of the grid cell if any (requires colIdx >= 0).
@@ -663,7 +709,7 @@ export interface SetActiveOptions {
663
709
  edit?: boolean;
664
710
  }
665
711
 
666
- /** Possible values for {@link Wunderbaum.setColumn()} `options` argument. */
712
+ /** Possible values for {@link Wunderbaum.setColumn} `options` argument. */
667
713
  export interface SetColumnOptions {
668
714
  /**
669
715
  * Focus embedded input control of the grid cell if any .
@@ -689,7 +735,7 @@ export interface SetExpandedOptions {
689
735
  scrollIntoView?: boolean;
690
736
  }
691
737
 
692
- /** Possible values for {@link WunderbaumNode.update()} `options` argument. */
738
+ /** Possible values for {@link WunderbaumNode.update} `options` argument. */
693
739
  export interface UpdateOptions {
694
740
  /** Force immediate redraw instead of throttled/async mode. @default false */
695
741
  immediate?: boolean;
@@ -697,7 +743,7 @@ export interface UpdateOptions {
697
743
  // removeMarkup?: boolean;
698
744
  }
699
745
 
700
- /** Possible values for {@link WunderbaumNode.setSelected()} `options` argument. */
746
+ /** Possible values for {@link WunderbaumNode.setSelected} `options` argument. */
701
747
  export interface SetSelectedOptions {
702
748
  /** Ignore restrictions, e.g. (`unselectable`). @default false */
703
749
  force?: boolean;
@@ -711,7 +757,7 @@ export interface SetSelectedOptions {
711
757
  callback?: NodeSelectCallback;
712
758
  }
713
759
 
714
- /** Possible values for {@link WunderbaumNode.setStatus()} `options` argument. */
760
+ /** Possible values for {@link WunderbaumNode.setStatus} `options` argument. */
715
761
  export interface SetStatusOptions {
716
762
  /** Displayed as status node title. */
717
763
  message?: string;
@@ -719,7 +765,57 @@ export interface SetStatusOptions {
719
765
  details?: string;
720
766
  }
721
767
 
722
- /** Options passed to {@link Wunderbaum.visitRows()}. */
768
+ /**
769
+ * Possible values for {@link WunderbaumNode.sortByProperty} `options` argument.
770
+ */
771
+ export interface ResetOrderOptions {
772
+ /** Sort descendants recursively. @default true */
773
+ recursive?: boolean;
774
+ /** The name of the node property that will be renumbered.
775
+ * @default `_nativeIndex`.
776
+ */
777
+ propName?: string;
778
+ }
779
+
780
+ /**
781
+ * Possible values for {@link WunderbaumNode.sortByProperty} `options` argument.
782
+ */
783
+ export interface SortByPropertyOptions {
784
+ /** Column ID as defined in `tree.columns` definition. Required if updateColInfo is true.*/
785
+ colId?: string;
786
+ /** The name of the node property that will be used for sorting.
787
+ * @default use the `colId` as property name.
788
+ */
789
+ propName?: string;
790
+ // /** If defined, this callback is used to extract the value to be sorted. */
791
+ // vallueGetter?: NodePropertyGetterCallback;
792
+ /** Sort order. @default Use value from column definition (rotated).*/
793
+ order?: SortOrderType;
794
+ /**
795
+ * Sort by this property if order is `undefined`.
796
+ * See also {@link WunderbaumNode.resetNativeChildOrder}.
797
+ * @default `_nativeIndex`.
798
+ */
799
+ nativeOrderPropName?: string;
800
+ /** Sort string values case insensitive. @default false */
801
+ caseInsensitive?: boolean;
802
+ /** Sort descendants recursively. @default true */
803
+ deep?: boolean;
804
+ // /** Rotate sort order (asc -> desc -> none) before sorting. @default false */
805
+ // rotateOrder?: boolean;
806
+ /**
807
+ * Rotate sort order (asc -> desc -> none) before sorting.
808
+ * Update the sort icons in the column header
809
+ * Note:
810
+ * Sorting is done in-place. There is no 'unsorted' state, but we can
811
+ * call `setCurrentSortOrder()` to renumber the `node._sortIdx` property,
812
+ * which will be used as sort key, when `order` is `undefined`.
813
+ * @default false
814
+ */
815
+ updateColInfo?: boolean;
816
+ }
817
+
818
+ /** Options passed to {@link Wunderbaum.visitRows}. */
723
819
  export interface VisitRowsOptions {
724
820
  /** Skip filtered nodes and children of collapsed nodes. @default false */
725
821
  includeHidden?: boolean;
@@ -998,7 +1094,7 @@ export type DndOptionsType = {
998
1094
  // */
999
1095
  // setTextTypeJson: boolean;
1000
1096
  /**
1001
- * Optional callback passed to `toDict` on dragStart @since 2.38
1097
+ * Optional callback passed to `toDict` on dragStart
1002
1098
  * @default null
1003
1099
  * @category Callback
1004
1100
  */
package/src/util.ts CHANGED
@@ -746,6 +746,12 @@ export function getOption(
746
746
  return value ?? defaultValue;
747
747
  }
748
748
 
749
+ /** Return the next value from a list of values (rotating). @since 0.11 */
750
+ export function rotate(value: any, values: any[]): any {
751
+ const idx = values.indexOf(value);
752
+ return values[(idx + 1) % values.length];
753
+ }
754
+
749
755
  /** Convert an Array or space-separated string to a Set. */
750
756
  export function toSet(val: any): Set<string> {
751
757
  if (val instanceof Set) {
@@ -776,12 +782,8 @@ export function toSet(val: any): Set<string> {
776
782
  * ```
777
783
  */
778
784
  export function toPixel(
779
- // val: string | number | undefined | null,
780
785
  ...defaults: (string | number | undefined | null)[]
781
786
  ): number {
782
- // if (typeof val === "number") {
783
- // return val;
784
- // }
785
787
  for (const d of defaults) {
786
788
  if (typeof d === "number") {
787
789
  return d;
@@ -802,12 +804,8 @@ export function toPixel(
802
804
  * ```
803
805
  */
804
806
  export function toBool(
805
- // val: boolean | undefined | null,
806
807
  ...boolDefaults: (boolean | undefined | null)[]
807
808
  ): boolean {
808
- // if (val != null) {
809
- // return !!val;
810
- // }
811
809
  for (const d of boolDefaults) {
812
810
  if (d != null) {
813
811
  return !!d;
@@ -29,7 +29,7 @@ export class GridExtension extends WunderbaumExtension<GridOptionsType> {
29
29
  const allow =
30
30
  colDef &&
31
31
  this.tree.element.contains(e.dragElem) &&
32
- toBool(colDef.resizable, tree.options.resizableColumns, false);
32
+ toBool(colDef.resizable, tree.options.columnsResizable, false);
33
33
 
34
34
  // this.tree.log("dragstart", colDef, e, info);
35
35
 
package/src/wb_node.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  ApplyCommandType,
14
14
  ChangeType,
15
15
  CheckboxOption,
16
+ ColumnDefinition,
16
17
  ColumnEventInfoMap,
17
18
  ExpandAllOptions,
18
19
  IconOption,
@@ -27,12 +28,15 @@ import {
27
28
  NodeVisitCallback,
28
29
  NodeVisitResponse,
29
30
  RenderOptions,
31
+ ResetOrderOptions,
30
32
  ScrollIntoViewOptions,
31
33
  SetActiveOptions,
32
34
  SetExpandedOptions,
33
35
  SetSelectedOptions,
34
36
  SetStatusOptions,
37
+ SortByPropertyOptions,
35
38
  SortCallback,
39
+ SortOrderType,
36
40
  SourceType,
37
41
  TooltipOption,
38
42
  TristateType,
@@ -104,12 +108,26 @@ export class WunderbaumNode {
104
108
  * @see Use {@link setKey} to modify.
105
109
  */
106
110
  public readonly refKey: string | undefined = undefined;
111
+ /**
112
+ * Array of child nodes (null for leaf nodes).
113
+ * For lazy nodes, this is `null` or ùndefined` until the children are loaded
114
+ * and leaf nodes may be `[]` (empty array).
115
+ * @see {@link hasChildren}, {@link addChildren}, {@link lazy}.
116
+ */
107
117
  public children: WunderbaumNode[] | null = null;
118
+ /** Render a checkbox or radio button @see {@link selected}. */
108
119
  public checkbox?: CheckboxOption;
120
+ /** If true, this node's children are considerd radio buttons.
121
+ * @see {@link isRadio}.
122
+ */
109
123
  public radiogroup?: boolean;
110
124
  /** If true, (in grid mode) no cells are rendered, except for the node title.*/
111
125
  public colspan?: boolean;
126
+ /** Icon definition. */
112
127
  public icon?: IconOption;
128
+ /** Lazy loading flag.
129
+ * @see {@link isLazy}, {@link isLoaded}, {@link isUnloaded}.
130
+ */
113
131
  public lazy?: boolean;
114
132
  /** Expansion state.
115
133
  * @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
@@ -118,7 +136,11 @@ export class WunderbaumNode {
118
136
  * @see {@link isSelected}, {@link setSelected}, {@link toggleSelected}. */
119
137
  public selected?: boolean;
120
138
  public unselectable?: boolean;
139
+ /** Node type (used for styling).
140
+ * @see {@link Wunderbaum.types}.
141
+ */
121
142
  public type?: string;
143
+ /** Tooltip definition (`true`: use node's title). */
122
144
  public tooltip?: string | boolean;
123
145
  /** Additional classes added to `div.wb-row`.
124
146
  * @see {@link hasClass}, {@link setClass}. */
@@ -787,7 +809,7 @@ export class WunderbaumNode {
787
809
  return this.classes ? this.classes.has(className) : false;
788
810
  }
789
811
 
790
- /** Return true if node ist the currently focused node. */
812
+ /** Return true if node ist the currently focused node. @since 0.9.0 */
791
813
  hasFocus(): boolean {
792
814
  return this.tree.focusNode === this;
793
815
  }
@@ -1054,6 +1076,8 @@ export class WunderbaumNode {
1054
1076
  if (tree.options.selectMode === "hier") {
1055
1077
  this.fixSelection3FromEndNodes();
1056
1078
  }
1079
+ // Allow to un-sort nodes after sorting
1080
+ this.resetNativeChildOrder();
1057
1081
 
1058
1082
  this._callEvent("load");
1059
1083
  }
@@ -2091,7 +2115,7 @@ export class WunderbaumNode {
2091
2115
  *
2092
2116
  * @param name name of the option property (on node and tree)
2093
2117
  * @param defaultValue return this if nothing else matched
2094
- * {@link Wunderbaum.getOption|Wunderbaum.getOption()}
2118
+ * {@link Wunderbaum.getOption|Wunderbaum.getOption}
2095
2119
  */
2096
2120
  getOption(name: string, defaultValue?: any) {
2097
2121
  const tree = this.tree;
@@ -2130,7 +2154,7 @@ export class WunderbaumNode {
2130
2154
  }
2131
2155
 
2132
2156
  /** Make sure that this node is visible in the viewport.
2133
- * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
2157
+ * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo}
2134
2158
  */
2135
2159
  async scrollIntoView(options?: ScrollIntoViewOptions) {
2136
2160
  const opts = Object.assign({ node: this }, options);
@@ -2284,9 +2308,9 @@ export class WunderbaumNode {
2284
2308
  * and column content. It can be reduced to 'ChangeType.status' if only
2285
2309
  * active/focus/selected state has changed.
2286
2310
  *
2287
- * This method will eventually call {@link WunderbaumNode._render()} with
2311
+ * This method will eventually call {@link WunderbaumNode._render} with
2288
2312
  * default options, but may be more consistent with the tree's
2289
- * {@link Wunderbaum.update()} API.
2313
+ * {@link Wunderbaum.update} API.
2290
2314
  */
2291
2315
  update(change: ChangeType = ChangeType.data) {
2292
2316
  util.assert(
@@ -2669,6 +2693,93 @@ export class WunderbaumNode {
2669
2693
  // this.triggerModify("sort"); // TODO
2670
2694
  }
2671
2695
 
2696
+ /**
2697
+ * Renumber nodes `_nativeIndex`. This is useful to allow to restore the
2698
+ * order after sorting a column.
2699
+ * This method is automatically called after loading new child nodes.
2700
+ * @since 0.11.0
2701
+ */
2702
+ resetNativeChildOrder(options?: ResetOrderOptions) {
2703
+ const { recursive = true, propName = "_nativeIndex" } = options ?? {};
2704
+
2705
+ if (this.children) {
2706
+ this.children.forEach((child, i) => {
2707
+ child.data[propName] = i;
2708
+ if (recursive && child.children) {
2709
+ child.resetNativeChildOrder(options);
2710
+ }
2711
+ });
2712
+ }
2713
+ }
2714
+
2715
+ /**
2716
+ * Convenience method to implement column sorting.
2717
+ * @since 0.11.0
2718
+ */
2719
+ sortByProperty(options: SortByPropertyOptions) {
2720
+ const {
2721
+ caseInsensitive = true,
2722
+ deep = true,
2723
+ nativeOrderPropName = "_nativeIndex",
2724
+ updateColInfo = false,
2725
+ } = options;
2726
+
2727
+ let order: SortOrderType;
2728
+ let colDef: ColumnDefinition | null;
2729
+
2730
+ if (updateColInfo) {
2731
+ colDef = this.tree["_columnsById"][options.colId!];
2732
+ util.assert(colDef, `Invalid colId specified: ${options.colId}`);
2733
+ order =
2734
+ options.order ??
2735
+ util.rotate(colDef!.sortOrder, ["asc", "desc", undefined]);
2736
+
2737
+ for (const col of this.tree.columns) {
2738
+ col.sortOrder = col === colDef ? order : undefined;
2739
+ }
2740
+
2741
+ this.tree.update(ChangeType.colStructure);
2742
+ } else {
2743
+ order = options.order ?? "asc";
2744
+ }
2745
+
2746
+ let propName = options.propName ?? (options.colId || "");
2747
+ if (propName === "*") {
2748
+ propName = "title";
2749
+ }
2750
+ if (order == null) {
2751
+ propName = nativeOrderPropName;
2752
+ order = "asc";
2753
+ }
2754
+ this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
2755
+ util.assert(propName, "No property name specified");
2756
+
2757
+ const cmp = (a: WunderbaumNode, b: WunderbaumNode) => {
2758
+ let av, bv;
2759
+ if (NODE_DICT_PROPS.has(<string>propName)) {
2760
+ av = a[propName as keyof WunderbaumNode];
2761
+ bv = b[propName as keyof WunderbaumNode];
2762
+ } else {
2763
+ av = a.data[propName];
2764
+ bv = b.data[propName];
2765
+ }
2766
+ if (caseInsensitive) {
2767
+ if (typeof av === "string") {
2768
+ av = av.toLowerCase();
2769
+ }
2770
+ if (typeof bv === "string") {
2771
+ bv = bv.toLowerCase();
2772
+ }
2773
+ }
2774
+ if (order === "desc") {
2775
+ return av === bv ? 0 : av > bv ? -1 : 1;
2776
+ }
2777
+ return av === bv ? 0 : av > bv ? 1 : -1;
2778
+ };
2779
+
2780
+ return this.sortChildren(cmp, deep);
2781
+ }
2782
+
2672
2783
  /**
2673
2784
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
2674
2785
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...