wunderbaum 0.11.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wunderbaum",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "title": "A treegrid control.",
5
5
  "description": "JavaScript tree/grid/treegrid control.",
6
6
  "homepage": "https://github.com/mar10/wunderbaum",
@@ -60,7 +60,7 @@
60
60
  "eslint-plugin-prettier": "^4.2.1",
61
61
  "grunt": "^1.6.1",
62
62
  "grunt-contrib-connect": "^3.0.0",
63
- "grunt-contrib-qunit": "^7.0.0",
63
+ "grunt-contrib-qunit": "^10.1.1",
64
64
  "grunt-contrib-watch": "^1.1.0",
65
65
  "grunt-exec": "^3.0.0",
66
66
  "grunt-yabs": "^1.3.0",
@@ -70,7 +70,7 @@
70
70
  "postcss-url": "^10.1.3",
71
71
  "prettier": "^2.8.8",
72
72
  "pretty-quick": "^3.1.3",
73
- "puppeteer": "^20.5.0",
73
+ "puppeteer": "^23.10.0",
74
74
  "qunit": "^2.19.4",
75
75
  "rollup": "^3.23.0",
76
76
  "rollup-plugin-scss": "^4.0.0",
@@ -80,7 +80,8 @@
80
80
  "ts-node": "^10.9.1",
81
81
  "tslib": "^2.5.2",
82
82
  "typedoc": "^0.25.2",
83
- "typescript": "^5.2.2"
83
+ "typescript": "^5.2.2",
84
+ "yarn-audit-fix": "^10.1.1"
84
85
  },
85
86
  "nodemonConfig": {
86
87
  "watch": [
@@ -131,5 +132,6 @@
131
132
  "wunderbaum.umd.min.js.map"
132
133
  ]
133
134
  }
134
- ]
135
+ ],
136
+ "packageManager": "yarn@4.4.1+sha512.f825273d0689cc9ead3259c14998037662f1dcd06912637b21a450e8da7cfeb4b1965bbee73d16927baa1201054126bc385c6f43ff4aa705c8631d26e12460f1"
135
137
  }
package/src/common.ts CHANGED
@@ -12,7 +12,7 @@ export const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
12
12
  /**
13
13
  * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
14
14
  */
15
- export const ROW_HEIGHT = 22;
15
+ export const DEFAULT_ROW_HEIGHT = 22;
16
16
  /**
17
17
  * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
18
18
  */
package/src/types.ts CHANGED
@@ -119,7 +119,7 @@ export interface WbNodeData {
119
119
  colspan?: boolean;
120
120
  expanded?: boolean;
121
121
  icon?: IconOption;
122
- iconTooltip?: boolean | string;
122
+ iconTooltip?: TooltipOption;
123
123
  key?: string;
124
124
  lazy?: boolean;
125
125
  /** Make child nodes single-select radio buttons. */
@@ -128,7 +128,7 @@ export interface WbNodeData {
128
128
  selected?: boolean;
129
129
  statusNodeType?: NodeStatusType;
130
130
  title: string;
131
- tooltip?: boolean | string;
131
+ tooltip?: TooltipOption;
132
132
  type?: string;
133
133
  unselectable?: boolean;
134
134
  /** @internal */
@@ -335,8 +335,8 @@ export interface NodeTypeDefinition {
335
335
  colspan?: boolean;
336
336
  /** Default icon for matching nodes. */
337
337
  icon?: IconOption;
338
- /** Default icon for matching nodes. */
339
- iconTooltip?: string | boolean;
338
+ /** Default icon tooltip for matching nodes. */
339
+ iconTooltip?: TooltipOption;
340
340
  // and more
341
341
  [key: string]: unknown;
342
342
  }
package/src/util.ts CHANGED
@@ -814,6 +814,18 @@ export function toBool(
814
814
  throw new Error("No default boolean value provided");
815
815
  }
816
816
 
817
+ /**
818
+ * Return `val` unless `val` is a number in which case we convert to boolean.
819
+ * This is useful when a boolean value is stored as a 0/1 (e.g. in JSON) and
820
+ * we still want to maintain string values. null and undefined are returned as
821
+ * is. E.g. `checkbox` may be boolean or 'radio'.
822
+ */
823
+ export function intToBool(
824
+ val: boolean | number | string | undefined
825
+ ): boolean | string | undefined {
826
+ return typeof val === "number" ? !!val : val;
827
+ }
828
+
817
829
  // /** Check if a string is contained in an Array or Set. */
818
830
  // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
819
831
  // return Array.prototype.includes.call(items, s)
package/src/wb_ext_dnd.ts CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  DropRegionType,
15
15
  DropRegionTypeSet,
16
16
  } from "./types";
17
- import { ROW_HEIGHT } from "./common";
18
17
  import { DebouncedFunction, throttle } from "./debounce";
19
18
 
20
19
  const nodeMimeType = "application/x-wunderbaum-node";
@@ -135,21 +134,22 @@ export class DndExtension extends WunderbaumExtension<DndOptionsType> {
135
134
  e: DragEvent,
136
135
  allowed: DropRegionTypeSet | null
137
136
  ): DropRegionType | false {
137
+ const rowHeight = this.tree.options.rowHeightPx!;
138
138
  const dy = e.offsetY;
139
139
 
140
140
  if (!allowed) {
141
141
  return false;
142
142
  } else if (allowed.size === 3) {
143
- return dy < 0.25 * ROW_HEIGHT
143
+ return dy < 0.25 * rowHeight
144
144
  ? "before"
145
- : dy > 0.75 * ROW_HEIGHT
145
+ : dy > 0.75 * rowHeight
146
146
  ? "after"
147
147
  : "over";
148
148
  } else if (allowed.size === 1 && allowed.has("over")) {
149
149
  return "over";
150
150
  } else {
151
151
  // Only 'before' and 'after':
152
- return dy > ROW_HEIGHT / 2 ? "after" : "before";
152
+ return dy > rowHeight / 2 ? "after" : "before";
153
153
  }
154
154
  // return "over";
155
155
  }
@@ -452,7 +452,12 @@ export class DndExtension extends WunderbaumExtension<DndOptionsType> {
452
452
  }
453
453
  this.lastAllowedDropRegions = regionSet;
454
454
  this.lastDropEffect = dt.dropEffect;
455
+
456
+ const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
455
457
  targetNode.setClass("wb-drop-target");
458
+ targetNode.setClass("wb-drop-over", region === "over");
459
+ targetNode.setClass("wb-drop-before", region === "before");
460
+ targetNode.setClass("wb-drop-after", region === "after");
456
461
 
457
462
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
458
463
  return false;
@@ -213,6 +213,10 @@ export class EditExtension extends WunderbaumExtension<EditOptionsType> {
213
213
  if (!node) {
214
214
  return;
215
215
  }
216
+ if (node.isStatusNode()) {
217
+ node.logWarn("Cannot edit status node.");
218
+ return;
219
+ }
216
220
  this.tree.logDebug(`startEditTitle(node=${node})`);
217
221
  let inputHtml = node._callEvent("edit.beforeEdit");
218
222
  if (inputHtml === false) {
package/src/wb_node.ts CHANGED
@@ -50,7 +50,6 @@ import {
50
50
  makeNodeTitleMatcher,
51
51
  nodeTitleSorter,
52
52
  RESERVED_TREE_SOURCE_KEYS,
53
- ROW_HEIGHT,
54
53
  TEST_IMG,
55
54
  TITLE_SPAN_PAD_Y,
56
55
  } from "./common";
@@ -83,6 +82,21 @@ const NODE_DICT_PROPS = new Set<string>(NODE_PROPS);
83
82
  NODE_DICT_PROPS.delete("_partsel");
84
83
  NODE_DICT_PROPS.delete("unselectable");
85
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
+
86
100
  /**
87
101
  * A single tree node.
88
102
  *
@@ -141,7 +155,9 @@ export class WunderbaumNode {
141
155
  */
142
156
  public type?: string;
143
157
  /** Tooltip definition (`true`: use node's title). */
144
- public tooltip?: string | boolean;
158
+ public tooltip?: TooltipOption;
159
+ /** Icon tooltip definition (`true`: use node's title). */
160
+ public iconTooltip?: TooltipOption;
145
161
  /** Additional classes added to `div.wb-row`.
146
162
  * @see {@link hasClass}, {@link setClass}. */
147
163
  public classes: Set<string> | null = null; //new Set<string>();
@@ -171,24 +187,30 @@ export class WunderbaumNode {
171
187
 
172
188
  this.tree = tree;
173
189
  this.parent = parent;
174
-
175
190
  this.key = "" + (data.key ?? ++WunderbaumNode.sequence);
176
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
177
197
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
178
198
  data.type != null ? (this.type = "" + data.type) : 0;
179
- this.expanded = data.expanded === true;
180
- data.icon != null ? (this.icon = data.icon) : 0;
181
- 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;
182
204
  data.statusNodeType != null
183
205
  ? (this.statusNodeType = ("" + data.statusNodeType) as NodeStatusType)
184
206
  : 0;
185
207
  data.colspan != null ? (this.colspan = !!data.colspan) : 0;
186
208
 
187
209
  // Selection
188
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
210
+ data.checkbox != null ? util.intToBool(data.checkbox) : 0;
189
211
  data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
190
- this.selected = data.selected === true;
191
- 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;
192
214
 
193
215
  if (data.classes) {
194
216
  this.setClass(data.classes);
@@ -1140,9 +1162,9 @@ export class WunderbaumNode {
1140
1162
  // Check for overlapping requests
1141
1163
  if (this._requestId) {
1142
1164
  this.logWarn(
1143
- `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."
1144
1167
  );
1145
- // node.debug("Send load request #" + requestId);
1146
1168
  }
1147
1169
  this._requestId = requestId;
1148
1170
 
@@ -1660,6 +1682,7 @@ export class WunderbaumNode {
1660
1682
  protected _render_markup(opts: RenderOptions) {
1661
1683
  const tree = this.tree;
1662
1684
  const treeOptions = tree.options;
1685
+ const rowHeight = treeOptions.rowHeightPx!;
1663
1686
  const checkbox = this.getOption("checkbox");
1664
1687
  const columns = tree.columns;
1665
1688
  const level = this.getLevel();
@@ -1681,7 +1704,7 @@ export class WunderbaumNode {
1681
1704
  rowDiv = document.createElement("div");
1682
1705
  rowDiv.classList.add("wb-row");
1683
1706
 
1684
- rowDiv.style.top = this._rowIdx! * ROW_HEIGHT + "px";
1707
+ rowDiv.style.top = this._rowIdx! * rowHeight + "px";
1685
1708
 
1686
1709
  this._rowElem = rowDiv;
1687
1710
 
@@ -2763,6 +2786,19 @@ export class WunderbaumNode {
2763
2786
  av = a.data[propName];
2764
2787
  bv = b.data[propName];
2765
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
+ }
2766
2802
  if (caseInsensitive) {
2767
2803
  if (typeof av === "string") {
2768
2804
  av = av.toLowerCase();
package/src/wunderbaum.ts CHANGED
@@ -61,7 +61,7 @@ import {
61
61
  makeNodeTitleStartMatcher,
62
62
  nodeTitleSorter,
63
63
  RENDER_MAX_PREFETCH,
64
- ROW_HEIGHT,
64
+ DEFAULT_ROW_HEIGHT,
65
65
  } from "./common";
66
66
  import { WunderbaumNode } from "./wb_node";
67
67
  import { Deferred } from "./deferred";
@@ -197,7 +197,7 @@ export class Wunderbaum {
197
197
  debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
198
198
  header: null, // Show/hide header (pass bool or string)
199
199
  // headerHeightPx: ROW_HEIGHT,
200
- rowHeightPx: ROW_HEIGHT,
200
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
201
201
  iconMap: "bootstrap",
202
202
  columns: null,
203
203
  types: null,
@@ -294,6 +294,16 @@ export class Wunderbaum {
294
294
  this.element.tabIndex = 0;
295
295
  }
296
296
 
297
+ if (opts.rowHeightPx !== DEFAULT_ROW_HEIGHT) {
298
+ this.element.style.setProperty(
299
+ "--wb-row-outer-height",
300
+ opts.rowHeightPx + "px"
301
+ );
302
+ this.element.style.setProperty(
303
+ "--wb-row-inner-height",
304
+ opts.rowHeightPx - 2 + "px"
305
+ );
306
+ }
297
307
  // Attach tree instance to <div>
298
308
  (<any>this.element)._wb_tree = this;
299
309
 
@@ -759,6 +769,7 @@ export class Wunderbaum {
759
769
 
760
770
  /** Return the topmost visible node in the viewport. */
761
771
  getTopmostVpNode(complete = true) {
772
+ const rowHeight = this.options.rowHeightPx!;
762
773
  const gracePx = 1; // ignore subpixel scrolling
763
774
  const scrollParent = this.element;
764
775
  // const headerHeight = this.headerElement.clientHeight; // May be 0
@@ -766,15 +777,16 @@ export class Wunderbaum {
766
777
  let topIdx: number;
767
778
 
768
779
  if (complete) {
769
- topIdx = Math.ceil((scrollTop - gracePx) / ROW_HEIGHT);
780
+ topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
770
781
  } else {
771
- topIdx = Math.floor(scrollTop / ROW_HEIGHT);
782
+ topIdx = Math.floor(scrollTop / rowHeight);
772
783
  }
773
784
  return this._getNodeByRowIdx(topIdx)!;
774
785
  }
775
786
 
776
787
  /** Return the lowest visible node in the viewport. */
777
788
  getLowestVpNode(complete = true) {
789
+ const rowHeight = this.options.rowHeightPx!;
778
790
  const scrollParent = this.element;
779
791
  const headerHeight = this.headerElement.clientHeight; // May be 0
780
792
  const scrollTop = scrollParent.scrollTop;
@@ -782,9 +794,9 @@ export class Wunderbaum {
782
794
  let bottomIdx: number;
783
795
 
784
796
  if (complete) {
785
- bottomIdx = Math.floor((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
797
+ bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
786
798
  } else {
787
- bottomIdx = Math.ceil((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
799
+ bottomIdx = Math.ceil((scrollTop + clientHeight) / rowHeight) - 1;
788
800
  }
789
801
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
790
802
  return this._getNodeByRowIdx(bottomIdx)!;
@@ -1282,9 +1294,10 @@ export class Wunderbaum {
1282
1294
  * @param includeHidden Not yet implemented
1283
1295
  */
1284
1296
  findRelatedNode(node: WunderbaumNode, where: string, includeHidden = false) {
1297
+ const rowHeight = this.options.rowHeightPx!;
1285
1298
  let res = null;
1286
1299
  const pageSize = Math.floor(
1287
- this.listContainerElement.clientHeight / ROW_HEIGHT
1300
+ this.listContainerElement.clientHeight / rowHeight
1288
1301
  );
1289
1302
 
1290
1303
  switch (where) {
@@ -1633,7 +1646,7 @@ export class Wunderbaum {
1633
1646
  const PADDING = 2; // leave some pixels between viewport bounds
1634
1647
 
1635
1648
  let node;
1636
- WunderbaumNode;
1649
+ // WunderbaumNode;
1637
1650
  let options: ScrollToOptions | undefined;
1638
1651
 
1639
1652
  if (nodeOrOpts instanceof WunderbaumNode) {
@@ -1644,14 +1657,15 @@ export class Wunderbaum {
1644
1657
  }
1645
1658
  util.assert(node && node._rowIdx != null, `Invalid node: ${node}`);
1646
1659
 
1660
+ const rowHeight = this.options.rowHeightPx!;
1647
1661
  const scrollParent = this.element;
1648
1662
  const headerHeight = this.headerElement.clientHeight; // May be 0
1649
1663
  const scrollTop = scrollParent.scrollTop;
1650
1664
  const vpHeight = scrollParent.clientHeight;
1651
- const rowTop = node._rowIdx! * ROW_HEIGHT + headerHeight;
1665
+ const rowTop = node._rowIdx! * rowHeight + headerHeight;
1652
1666
  const vpTop = headerHeight;
1653
1667
  const vpRowTop = rowTop - scrollTop;
1654
- const vpRowBottom = vpRowTop + ROW_HEIGHT;
1668
+ const vpRowBottom = vpRowTop + rowHeight;
1655
1669
  const topNode = options?.topNode;
1656
1670
 
1657
1671
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts , options);
@@ -1664,7 +1678,7 @@ export class Wunderbaum {
1664
1678
  } else {
1665
1679
  // Node is below viewport
1666
1680
  // this.log("Below viewport");
1667
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
1681
+ newScrollTop = rowTop + rowHeight - vpHeight + PADDING; // leave some pixels between viewport bounds
1668
1682
  }
1669
1683
  } else {
1670
1684
  // Node is above viewport
@@ -2362,20 +2376,20 @@ export class Wunderbaum {
2362
2376
  options = Object.assign({ newNodesOnly: false }, options);
2363
2377
  const newNodesOnly = !!options.newNodesOnly;
2364
2378
 
2365
- const row_height = ROW_HEIGHT;
2366
- const vp_height = this.element.clientHeight;
2379
+ const rowHeight = this.options.rowHeightPx!;
2380
+ const vpHeight = this.element.clientHeight;
2367
2381
  const prefetch = RENDER_MAX_PREFETCH;
2368
2382
  // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
2369
2383
  const ofs = this.element.scrollTop;
2370
2384
 
2371
- let startIdx = Math.max(0, ofs / row_height - prefetch);
2385
+ let startIdx = Math.max(0, ofs / rowHeight - prefetch);
2372
2386
  startIdx = Math.floor(startIdx);
2373
2387
  // Make sure start is always even, so the alternating row colors don't
2374
2388
  // change when scrolling:
2375
2389
  if (startIdx % 2) {
2376
2390
  startIdx--;
2377
2391
  }
2378
- let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
2392
+ let endIdx = Math.max(0, (ofs + vpHeight) / rowHeight + prefetch);
2379
2393
  endIdx = Math.ceil(endIdx);
2380
2394
 
2381
2395
  // this.debug("render", opts);
@@ -2408,20 +2422,20 @@ export class Wunderbaum {
2408
2422
  } else if (rowDiv && newNodesOnly) {
2409
2423
  obsoleteNodes.delete(node);
2410
2424
  // no need to update existing node markup
2411
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
2425
+ rowDiv.style.top = idx * rowHeight + "px";
2412
2426
  prevElem = rowDiv;
2413
2427
  } else {
2414
2428
  obsoleteNodes.delete(node);
2415
2429
  // Create new markup
2416
2430
  if (rowDiv) {
2417
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
2431
+ rowDiv.style.top = idx * rowHeight + "px";
2418
2432
  }
2419
2433
  node._render({ top: top, after: prevElem });
2420
2434
  // node.log("render", top, prevElem, "=>", node._rowElem);
2421
2435
  prevElem = node._rowElem!;
2422
2436
  }
2423
2437
  idx++;
2424
- top += row_height;
2438
+ top += rowHeight;
2425
2439
  });
2426
2440
  this.treeRowCount = idx;
2427
2441
  for (const n of obsoleteNodes) {