wunderbaum 0.10.1 → 0.11.1

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,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,
@@ -46,7 +50,6 @@ import {
46
50
  makeNodeTitleMatcher,
47
51
  nodeTitleSorter,
48
52
  RESERVED_TREE_SOURCE_KEYS,
49
- ROW_HEIGHT,
50
53
  TEST_IMG,
51
54
  TITLE_SPAN_PAD_Y,
52
55
  } from "./common";
@@ -79,6 +82,21 @@ const NODE_DICT_PROPS = new Set<string>(NODE_PROPS);
79
82
  NODE_DICT_PROPS.delete("_partsel");
80
83
  NODE_DICT_PROPS.delete("unselectable");
81
84
 
85
+ // /** Node properties that are of type bool (or boolean & string).
86
+ // * When parsing, we accept 0 for false and 1 for true for better JSON compression.
87
+ // */
88
+ // export const NODE_BOOL_PROPS: Set<string> = new Set([
89
+ // "checkbox",
90
+ // "colspan",
91
+ // "expanded",
92
+ // "icon",
93
+ // "iconTooltip",
94
+ // "radiogroup",
95
+ // "selected",
96
+ // "tooltip",
97
+ // "unselectable",
98
+ // ]);
99
+
82
100
  /**
83
101
  * A single tree node.
84
102
  *
@@ -104,12 +122,26 @@ export class WunderbaumNode {
104
122
  * @see Use {@link setKey} to modify.
105
123
  */
106
124
  public readonly refKey: string | undefined = undefined;
125
+ /**
126
+ * Array of child nodes (null for leaf nodes).
127
+ * For lazy nodes, this is `null` or ùndefined` until the children are loaded
128
+ * and leaf nodes may be `[]` (empty array).
129
+ * @see {@link hasChildren}, {@link addChildren}, {@link lazy}.
130
+ */
107
131
  public children: WunderbaumNode[] | null = null;
132
+ /** Render a checkbox or radio button @see {@link selected}. */
108
133
  public checkbox?: CheckboxOption;
134
+ /** If true, this node's children are considerd radio buttons.
135
+ * @see {@link isRadio}.
136
+ */
109
137
  public radiogroup?: boolean;
110
138
  /** If true, (in grid mode) no cells are rendered, except for the node title.*/
111
139
  public colspan?: boolean;
140
+ /** Icon definition. */
112
141
  public icon?: IconOption;
142
+ /** Lazy loading flag.
143
+ * @see {@link isLazy}, {@link isLoaded}, {@link isUnloaded}.
144
+ */
113
145
  public lazy?: boolean;
114
146
  /** Expansion state.
115
147
  * @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
@@ -118,8 +150,14 @@ export class WunderbaumNode {
118
150
  * @see {@link isSelected}, {@link setSelected}, {@link toggleSelected}. */
119
151
  public selected?: boolean;
120
152
  public unselectable?: boolean;
153
+ /** Node type (used for styling).
154
+ * @see {@link Wunderbaum.types}.
155
+ */
121
156
  public type?: string;
122
- public tooltip?: string | boolean;
157
+ /** Tooltip definition (`true`: use node's title). */
158
+ public tooltip?: TooltipOption;
159
+ /** Icon tooltip definition (`true`: use node's title). */
160
+ public iconTooltip?: TooltipOption;
123
161
  /** Additional classes added to `div.wb-row`.
124
162
  * @see {@link hasClass}, {@link setClass}. */
125
163
  public classes: Set<string> | null = null; //new Set<string>();
@@ -149,24 +187,30 @@ export class WunderbaumNode {
149
187
 
150
188
  this.tree = tree;
151
189
  this.parent = parent;
152
-
153
190
  this.key = "" + (data.key ?? ++WunderbaumNode.sequence);
154
191
  this.title = "" + (data.title ?? "<" + this.key + ">");
192
+ this.expanded = !!data.expanded;
193
+ this.lazy = !!data.lazy;
194
+
195
+ // We set the following node properties only if a matching data value is
196
+ // passed
155
197
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
156
198
  data.type != null ? (this.type = "" + data.type) : 0;
157
- this.expanded = data.expanded === true;
158
- data.icon != null ? (this.icon = data.icon) : 0;
159
- this.lazy = data.lazy === true;
199
+ data.icon != null ? (this.icon = util.intToBool(data.icon)) : 0;
200
+ data.tooltip != null ? (this.tooltip = util.intToBool(data.tooltip)) : 0;
201
+ data.iconTooltip != null
202
+ ? (this.iconTooltip = util.intToBool(data.iconTooltip))
203
+ : 0;
160
204
  data.statusNodeType != null
161
205
  ? (this.statusNodeType = ("" + data.statusNodeType) as NodeStatusType)
162
206
  : 0;
163
207
  data.colspan != null ? (this.colspan = !!data.colspan) : 0;
164
208
 
165
209
  // Selection
166
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
210
+ data.checkbox != null ? util.intToBool(data.checkbox) : 0;
167
211
  data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
168
- this.selected = data.selected === true;
169
- data.unselectable === true ? (this.unselectable = true) : 0;
212
+ data.selected != null ? (this.selected = !!data.selected) : 0;
213
+ data.unselectable != null ? (this.unselectable = !!data.unselectable) : 0;
170
214
 
171
215
  if (data.classes) {
172
216
  this.setClass(data.classes);
@@ -787,7 +831,7 @@ export class WunderbaumNode {
787
831
  return this.classes ? this.classes.has(className) : false;
788
832
  }
789
833
 
790
- /** Return true if node ist the currently focused node. */
834
+ /** Return true if node ist the currently focused node. @since 0.9.0 */
791
835
  hasFocus(): boolean {
792
836
  return this.tree.focusNode === this;
793
837
  }
@@ -1054,6 +1098,8 @@ export class WunderbaumNode {
1054
1098
  if (tree.options.selectMode === "hier") {
1055
1099
  this.fixSelection3FromEndNodes();
1056
1100
  }
1101
+ // Allow to un-sort nodes after sorting
1102
+ this.resetNativeChildOrder();
1057
1103
 
1058
1104
  this._callEvent("load");
1059
1105
  }
@@ -1116,9 +1162,9 @@ export class WunderbaumNode {
1116
1162
  // Check for overlapping requests
1117
1163
  if (this._requestId) {
1118
1164
  this.logWarn(
1119
- `Recursive load request #${requestId} while #${this._requestId} is pending.`
1165
+ `Recursive load request #${requestId} while #${this._requestId} is pending. ` +
1166
+ "The previous request will be ignored."
1120
1167
  );
1121
- // node.debug("Send load request #" + requestId);
1122
1168
  }
1123
1169
  this._requestId = requestId;
1124
1170
 
@@ -1636,6 +1682,7 @@ export class WunderbaumNode {
1636
1682
  protected _render_markup(opts: RenderOptions) {
1637
1683
  const tree = this.tree;
1638
1684
  const treeOptions = tree.options;
1685
+ const rowHeight = treeOptions.rowHeightPx!;
1639
1686
  const checkbox = this.getOption("checkbox");
1640
1687
  const columns = tree.columns;
1641
1688
  const level = this.getLevel();
@@ -1657,7 +1704,7 @@ export class WunderbaumNode {
1657
1704
  rowDiv = document.createElement("div");
1658
1705
  rowDiv.classList.add("wb-row");
1659
1706
 
1660
- rowDiv.style.top = this._rowIdx! * ROW_HEIGHT + "px";
1707
+ rowDiv.style.top = this._rowIdx! * rowHeight + "px";
1661
1708
 
1662
1709
  this._rowElem = rowDiv;
1663
1710
 
@@ -2091,7 +2138,7 @@ export class WunderbaumNode {
2091
2138
  *
2092
2139
  * @param name name of the option property (on node and tree)
2093
2140
  * @param defaultValue return this if nothing else matched
2094
- * {@link Wunderbaum.getOption|Wunderbaum.getOption()}
2141
+ * {@link Wunderbaum.getOption|Wunderbaum.getOption}
2095
2142
  */
2096
2143
  getOption(name: string, defaultValue?: any) {
2097
2144
  const tree = this.tree;
@@ -2130,7 +2177,7 @@ export class WunderbaumNode {
2130
2177
  }
2131
2178
 
2132
2179
  /** Make sure that this node is visible in the viewport.
2133
- * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
2180
+ * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo}
2134
2181
  */
2135
2182
  async scrollIntoView(options?: ScrollIntoViewOptions) {
2136
2183
  const opts = Object.assign({ node: this }, options);
@@ -2284,9 +2331,9 @@ export class WunderbaumNode {
2284
2331
  * and column content. It can be reduced to 'ChangeType.status' if only
2285
2332
  * active/focus/selected state has changed.
2286
2333
  *
2287
- * This method will eventually call {@link WunderbaumNode._render()} with
2334
+ * This method will eventually call {@link WunderbaumNode._render} with
2288
2335
  * default options, but may be more consistent with the tree's
2289
- * {@link Wunderbaum.update()} API.
2336
+ * {@link Wunderbaum.update} API.
2290
2337
  */
2291
2338
  update(change: ChangeType = ChangeType.data) {
2292
2339
  util.assert(
@@ -2669,6 +2716,106 @@ export class WunderbaumNode {
2669
2716
  // this.triggerModify("sort"); // TODO
2670
2717
  }
2671
2718
 
2719
+ /**
2720
+ * Renumber nodes `_nativeIndex`. This is useful to allow to restore the
2721
+ * order after sorting a column.
2722
+ * This method is automatically called after loading new child nodes.
2723
+ * @since 0.11.0
2724
+ */
2725
+ resetNativeChildOrder(options?: ResetOrderOptions) {
2726
+ const { recursive = true, propName = "_nativeIndex" } = options ?? {};
2727
+
2728
+ if (this.children) {
2729
+ this.children.forEach((child, i) => {
2730
+ child.data[propName] = i;
2731
+ if (recursive && child.children) {
2732
+ child.resetNativeChildOrder(options);
2733
+ }
2734
+ });
2735
+ }
2736
+ }
2737
+
2738
+ /**
2739
+ * Convenience method to implement column sorting.
2740
+ * @since 0.11.0
2741
+ */
2742
+ sortByProperty(options: SortByPropertyOptions) {
2743
+ const {
2744
+ caseInsensitive = true,
2745
+ deep = true,
2746
+ nativeOrderPropName = "_nativeIndex",
2747
+ updateColInfo = false,
2748
+ } = options;
2749
+
2750
+ let order: SortOrderType;
2751
+ let colDef: ColumnDefinition | null;
2752
+
2753
+ if (updateColInfo) {
2754
+ colDef = this.tree["_columnsById"][options.colId!];
2755
+ util.assert(colDef, `Invalid colId specified: ${options.colId}`);
2756
+ order =
2757
+ options.order ??
2758
+ util.rotate(colDef!.sortOrder, ["asc", "desc", undefined]);
2759
+
2760
+ for (const col of this.tree.columns) {
2761
+ col.sortOrder = col === colDef ? order : undefined;
2762
+ }
2763
+
2764
+ this.tree.update(ChangeType.colStructure);
2765
+ } else {
2766
+ order = options.order ?? "asc";
2767
+ }
2768
+
2769
+ let propName = options.propName ?? (options.colId || "");
2770
+ if (propName === "*") {
2771
+ propName = "title";
2772
+ }
2773
+ if (order == null) {
2774
+ propName = nativeOrderPropName;
2775
+ order = "asc";
2776
+ }
2777
+ this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
2778
+ util.assert(propName, "No property name specified");
2779
+
2780
+ const cmp = (a: WunderbaumNode, b: WunderbaumNode) => {
2781
+ let av, bv;
2782
+ if (NODE_DICT_PROPS.has(<string>propName)) {
2783
+ av = a[propName as keyof WunderbaumNode];
2784
+ bv = b[propName as keyof WunderbaumNode];
2785
+ } else {
2786
+ av = a.data[propName];
2787
+ bv = b.data[propName];
2788
+ }
2789
+ if (av == null && bv == null) {
2790
+ return 0;
2791
+ }
2792
+ if (av == null) {
2793
+ av = typeof bv === "string" ? "" : 0;
2794
+ } else if (typeof av === "boolean") {
2795
+ av = av ? 1 : 0;
2796
+ }
2797
+ if (bv == null) {
2798
+ bv = typeof av === "string" ? "" : 0;
2799
+ } else if (typeof bv === "boolean") {
2800
+ bv = bv ? 1 : 0;
2801
+ }
2802
+ if (caseInsensitive) {
2803
+ if (typeof av === "string") {
2804
+ av = av.toLowerCase();
2805
+ }
2806
+ if (typeof bv === "string") {
2807
+ bv = bv.toLowerCase();
2808
+ }
2809
+ }
2810
+ if (order === "desc") {
2811
+ return av === bv ? 0 : av > bv ? -1 : 1;
2812
+ }
2813
+ return av === bv ? 0 : av > bv ? 1 : -1;
2814
+ };
2815
+
2816
+ return this.sortChildren(cmp, deep);
2817
+ }
2818
+
2672
2819
  /**
2673
2820
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
2674
2821
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
package/src/wb_options.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  NodeTypeDefinitionMap,
21
21
  SelectModeType,
22
22
  WbActivateEventType,
23
+ WbButtonClickEventType,
23
24
  WbCancelableEventResultType,
24
25
  WbChangeEventType,
25
26
  WbClickEventType,
@@ -217,11 +218,30 @@ export interface WunderbaumOptions {
217
218
  * Default: false
218
219
  */
219
220
  fixedCol?: boolean;
221
+ /**
222
+ * Default value for ColumnDefinition.filterable option.
223
+ * Default: false
224
+ * @since 0.11.0
225
+ */
226
+ columnsFilterable?: boolean;
227
+ /**
228
+ * Default value for ColumnDefinition.menu option.
229
+ * Default: false
230
+ * @since 0.11.0
231
+ */
232
+ columnsMenu?: boolean;
220
233
  /**
221
234
  * Default value for ColumnDefinition.resizable option.
222
235
  * Default: false
236
+ * @since 0.10.0
237
+ */
238
+ columnsResizable?: boolean;
239
+ /**
240
+ * Default value for ColumnDefinition.sortable option.
241
+ * Default: false
242
+ * @since 0.11.0
223
243
  */
224
- resizableColumns?: boolean;
244
+ columnsSortable?: boolean;
225
245
 
226
246
  // --- Selection ---
227
247
  /**
@@ -271,11 +291,15 @@ export interface WunderbaumOptions {
271
291
  */
272
292
  beforeExpand?: (e: WbExpandEventType) => WbCancelableEventResultType;
273
293
  /**
274
- *
275
294
  * Return `false` to prevent default handling, i.e. (de)selecting the node.
276
295
  * @category Callback
277
296
  */
278
297
  beforeSelect?: (e: WbSelectEventType) => WbCancelableEventResultType;
298
+ /**
299
+ * Return `false` to prevent default handling, i.e. (de)selecting the node.
300
+ * @category Callback
301
+ */
302
+ buttonClick?: (e: WbButtonClickEventType) => void;
279
303
  /**
280
304
  *
281
305
  * @category Callback
@@ -393,6 +393,16 @@ div.wunderbaum {
393
393
  // border-right-color: red;
394
394
  }
395
395
  }
396
+
397
+ i.wb-col-icon {
398
+ float: inline-end;
399
+ padding-left: 2px;
400
+
401
+ &:hover {
402
+ cursor: pointer;
403
+ color: var(--wb-focus-border-color);
404
+ }
405
+ }
396
406
  }
397
407
 
398
408
  span.wb-col {