wunderbaum 0.3.4 → 0.4.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/README.md +3 -3
- package/dist/wunderbaum.css +381 -291
- package/dist/wunderbaum.css.map +1 -0
- package/dist/wunderbaum.d.ts +717 -642
- package/dist/wunderbaum.esm.js +591 -165
- package/dist/wunderbaum.esm.min.js +23 -23
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +591 -165
- package/dist/wunderbaum.umd.min.js +31 -30
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +10 -9
- package/src/common.ts +2 -2
- package/src/types.ts +38 -11
- package/src/wb_ext_dnd.ts +62 -21
- package/src/wb_ext_edit.ts +2 -2
- package/src/wb_ext_filter.ts +1 -2
- package/src/wb_ext_keynav.ts +4 -4
- package/src/wb_node.ts +361 -82
- package/src/wb_options.ts +11 -4
- package/src/wunderbaum.scss +18 -13
- package/src/wunderbaum.ts +134 -53
package/src/wb_node.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import "./wunderbaum.scss";
|
|
8
7
|
import * as util from "./util";
|
|
9
8
|
|
|
10
9
|
import { Wunderbaum } from "./wunderbaum";
|
|
@@ -33,6 +32,7 @@ import {
|
|
|
33
32
|
SortCallback,
|
|
34
33
|
NodeToDictCallback,
|
|
35
34
|
WbNodeData,
|
|
35
|
+
TristateType,
|
|
36
36
|
} from "./types";
|
|
37
37
|
import {
|
|
38
38
|
iconMap,
|
|
@@ -48,31 +48,18 @@ import {
|
|
|
48
48
|
} from "./common";
|
|
49
49
|
import { Deferred } from "./deferred";
|
|
50
50
|
|
|
51
|
-
/**
|
|
51
|
+
/** WunderbaumNode properties that can be passed with source data.
|
|
52
|
+
* (Any other source properties will be stored as `node.data.PROP`.)
|
|
53
|
+
*/
|
|
52
54
|
const NODE_PROPS = new Set<string>([
|
|
53
|
-
// TODO: use NODE_ATTRS instead?
|
|
54
|
-
"classes",
|
|
55
|
-
"expanded",
|
|
56
|
-
"icon",
|
|
57
|
-
"key",
|
|
58
|
-
"lazy",
|
|
59
|
-
"refKey",
|
|
60
|
-
"selected",
|
|
61
|
-
"title",
|
|
62
|
-
"tooltip",
|
|
63
|
-
"type",
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
const NODE_ATTRS = new Set<string>([
|
|
67
55
|
"checkbox",
|
|
68
|
-
"expanded",
|
|
69
56
|
"classes",
|
|
70
|
-
"
|
|
57
|
+
"expanded",
|
|
71
58
|
"icon",
|
|
72
59
|
"iconTooltip",
|
|
73
60
|
"key",
|
|
74
61
|
"lazy",
|
|
75
|
-
"
|
|
62
|
+
"_partsel",
|
|
76
63
|
"radiogroup",
|
|
77
64
|
"refKey",
|
|
78
65
|
"selected",
|
|
@@ -81,9 +68,16 @@ const NODE_ATTRS = new Set<string>([
|
|
|
81
68
|
"tooltip",
|
|
82
69
|
"type",
|
|
83
70
|
"unselectable",
|
|
84
|
-
"unselectableIgnore",
|
|
85
|
-
"unselectableStatus",
|
|
71
|
+
// "unselectableIgnore",
|
|
72
|
+
// "unselectableStatus",
|
|
86
73
|
]);
|
|
74
|
+
/** WunderbaumNode properties that will be returned by `node.toDict()`.)
|
|
75
|
+
*/
|
|
76
|
+
const NODE_DICT_PROPS = new Set<string>(NODE_PROPS);
|
|
77
|
+
NODE_DICT_PROPS.delete("_partsel");
|
|
78
|
+
NODE_DICT_PROPS.delete("unselectable");
|
|
79
|
+
// NODE_DICT_PROPS.delete("unselectableIgnore");
|
|
80
|
+
// NODE_DICT_PROPS.delete("unselectableStatus");
|
|
87
81
|
|
|
88
82
|
/**
|
|
89
83
|
* A single tree node.
|
|
@@ -112,6 +106,7 @@ export class WunderbaumNode {
|
|
|
112
106
|
public readonly refKey: string | undefined = undefined;
|
|
113
107
|
public children: WunderbaumNode[] | null = null;
|
|
114
108
|
public checkbox?: boolean;
|
|
109
|
+
public radiogroup?: boolean;
|
|
115
110
|
/** If true, (in grid mode) no cells are rendered, except for the node title.*/
|
|
116
111
|
public colspan?: boolean;
|
|
117
112
|
public icon?: boolean | string;
|
|
@@ -120,8 +115,11 @@ export class WunderbaumNode {
|
|
|
120
115
|
* @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
|
|
121
116
|
public expanded: boolean = false;
|
|
122
117
|
/** Selection state.
|
|
123
|
-
* @see {@link isSelected}, {@link setSelected}. */
|
|
118
|
+
* @see {@link isSelected}, {@link setSelected}, {@link toggleSelected}. */
|
|
124
119
|
public selected: boolean = false;
|
|
120
|
+
public unselectable?: boolean;
|
|
121
|
+
// public unselectableStatus?: boolean;
|
|
122
|
+
// public unselectableIgnore?: boolean;
|
|
125
123
|
public type?: string;
|
|
126
124
|
public tooltip?: string;
|
|
127
125
|
/** Additional classes added to `div.wb-row`.
|
|
@@ -150,22 +148,32 @@ export class WunderbaumNode {
|
|
|
150
148
|
constructor(tree: Wunderbaum, parent: WunderbaumNode, data: any) {
|
|
151
149
|
util.assert(!parent || parent.tree === tree);
|
|
152
150
|
util.assert(!data.children);
|
|
151
|
+
|
|
153
152
|
this.tree = tree;
|
|
154
153
|
this.parent = parent;
|
|
154
|
+
|
|
155
155
|
this.key = "" + (data.key ?? ++WunderbaumNode.sequence);
|
|
156
156
|
this.title = "" + (data.title ?? "<" + this.key + ">");
|
|
157
|
-
|
|
158
157
|
data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
|
|
159
|
-
data.statusNodeType != null
|
|
160
|
-
? (this.statusNodeType = "" + data.statusNodeType)
|
|
161
|
-
: 0;
|
|
162
158
|
data.type != null ? (this.type = "" + data.type) : 0;
|
|
163
|
-
data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
|
|
164
|
-
data.colspan != null ? (this.colspan = !!data.colspan) : 0;
|
|
165
159
|
this.expanded = data.expanded === true;
|
|
166
160
|
data.icon != null ? (this.icon = data.icon) : 0;
|
|
167
161
|
this.lazy = data.lazy === true;
|
|
162
|
+
data.statusNodeType != null
|
|
163
|
+
? (this.statusNodeType = "" + data.statusNodeType)
|
|
164
|
+
: 0;
|
|
165
|
+
data.colspan != null ? (this.colspan = !!data.colspan) : 0;
|
|
166
|
+
|
|
167
|
+
// Selection
|
|
168
|
+
data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
|
|
169
|
+
data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
|
|
168
170
|
this.selected = data.selected === true;
|
|
171
|
+
data.unselectable === true ? (this.unselectable = true) : 0;
|
|
172
|
+
// data.unselectableStatus != null
|
|
173
|
+
// ? (this.unselectableStatus = !!data.unselectableStatus)
|
|
174
|
+
// : 0;
|
|
175
|
+
// data.unselectableIgnore === true ? (this.unselectableIgnore = true) : 0;
|
|
176
|
+
|
|
169
177
|
if (data.classes) {
|
|
170
178
|
this.setClass(data.classes);
|
|
171
179
|
}
|
|
@@ -311,13 +319,16 @@ export class WunderbaumNode {
|
|
|
311
319
|
// insert nodeList after children[pos]
|
|
312
320
|
this.children.splice(pos, 0, ...nodeList);
|
|
313
321
|
}
|
|
314
|
-
// TODO:
|
|
315
|
-
// if (tree.options.selectMode === 3) {
|
|
316
|
-
// this.fixSelection3FromEndNodes();
|
|
317
|
-
// }
|
|
318
322
|
// this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
|
|
319
|
-
tree.
|
|
323
|
+
tree.update(ChangeType.structure);
|
|
320
324
|
} finally {
|
|
325
|
+
// if (tree.options.selectMode === "hier") {
|
|
326
|
+
// if (this.parent && this.parent.children) {
|
|
327
|
+
// this.fixSelection3FromEndNodes();
|
|
328
|
+
// } else {
|
|
329
|
+
// // my happen when loading __root__;
|
|
330
|
+
// }
|
|
331
|
+
// }
|
|
321
332
|
tree.enableUpdate(true);
|
|
322
333
|
}
|
|
323
334
|
// if(isTopCall && loadLazy){
|
|
@@ -369,6 +380,18 @@ export class WunderbaumNode {
|
|
|
369
380
|
return this.tree.applyCommand(cmd, this, options);
|
|
370
381
|
}
|
|
371
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Collapse all expanded sibling nodes if any.
|
|
385
|
+
* (Automatically called when `autoCollapse` is true.)
|
|
386
|
+
*/
|
|
387
|
+
collapseSiblings(options?: SetExpandedOptions): any {
|
|
388
|
+
for (let node of this.parent.children!) {
|
|
389
|
+
if (node !== this && node.expanded) {
|
|
390
|
+
node.setExpanded(false, options);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
372
395
|
/**
|
|
373
396
|
* Add/remove one or more classes to `<div class='wb-row'>`.
|
|
374
397
|
*
|
|
@@ -410,7 +433,7 @@ export class WunderbaumNode {
|
|
|
410
433
|
const minExpandLevel = this.tree.options.minExpandLevel;
|
|
411
434
|
let { depth = 99, loadLazy, force } = options ?? {};
|
|
412
435
|
|
|
413
|
-
const
|
|
436
|
+
const expandOpts = {
|
|
414
437
|
scrollIntoView: false,
|
|
415
438
|
force: force,
|
|
416
439
|
loadLazy: loadLazy,
|
|
@@ -434,7 +457,7 @@ export class WunderbaumNode {
|
|
|
434
457
|
// Node is collapsed and may be expanded (i.e. has children or is lazy)
|
|
435
458
|
// Expanding may be async, so we store the promise.
|
|
436
459
|
// Also the recursion is delayed until expansion finished.
|
|
437
|
-
const p = cn.setExpanded(true,
|
|
460
|
+
const p = cn.setExpanded(true, expandOpts);
|
|
438
461
|
promises.push(p);
|
|
439
462
|
p.then(async () => {
|
|
440
463
|
await _iter(cn, level_1);
|
|
@@ -448,7 +471,7 @@ export class WunderbaumNode {
|
|
|
448
471
|
// Collapsing is always synchronous, so no promises required
|
|
449
472
|
if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
|
|
450
473
|
// Do not collapse until minExpandLevel
|
|
451
|
-
cn.setExpanded(false,
|
|
474
|
+
cn.setExpanded(false, expandOpts);
|
|
452
475
|
}
|
|
453
476
|
_iter(cn, level_1); // recursion, even if cn was already collapsed
|
|
454
477
|
}
|
|
@@ -874,9 +897,11 @@ export class WunderbaumNode {
|
|
|
874
897
|
return this.tree.root === this;
|
|
875
898
|
}
|
|
876
899
|
|
|
877
|
-
/** Return true if this node is selected, i.e. the checkbox is set.
|
|
878
|
-
|
|
879
|
-
|
|
900
|
+
/** Return true if this node is selected, i.e. the checkbox is set.
|
|
901
|
+
* `undefined` if partly selected (tri-state), false otherwise.
|
|
902
|
+
*/
|
|
903
|
+
isSelected(): TristateType {
|
|
904
|
+
return this.selected ? true : this._partsel ? undefined : false;
|
|
880
905
|
}
|
|
881
906
|
|
|
882
907
|
/** Return true if this node is a temporarily generated system node like
|
|
@@ -968,7 +993,7 @@ export class WunderbaumNode {
|
|
|
968
993
|
tree.logInfo("Redefine columns", source.columns);
|
|
969
994
|
tree.columns = source.columns;
|
|
970
995
|
delete source.columns;
|
|
971
|
-
tree.
|
|
996
|
+
tree.update(ChangeType.colStructure);
|
|
972
997
|
}
|
|
973
998
|
|
|
974
999
|
this.addChildren(source.children);
|
|
@@ -980,6 +1005,9 @@ export class WunderbaumNode {
|
|
|
980
1005
|
tree.logDebug(`Add source.${key} to tree.data.${key}`);
|
|
981
1006
|
}
|
|
982
1007
|
}
|
|
1008
|
+
if (tree.options.selectMode === "hier") {
|
|
1009
|
+
this.fixSelection3FromEndNodes();
|
|
1010
|
+
}
|
|
983
1011
|
|
|
984
1012
|
this._callEvent("load");
|
|
985
1013
|
}
|
|
@@ -1131,9 +1159,9 @@ export class WunderbaumNode {
|
|
|
1131
1159
|
|
|
1132
1160
|
if (wasExpanded) {
|
|
1133
1161
|
this.expanded = true;
|
|
1134
|
-
this.tree.
|
|
1162
|
+
this.tree.update(ChangeType.structure);
|
|
1135
1163
|
} else {
|
|
1136
|
-
this.
|
|
1164
|
+
this.update(); // Fix expander icon to 'loaded'
|
|
1137
1165
|
}
|
|
1138
1166
|
} catch (e) {
|
|
1139
1167
|
this.logError("Error during loadLazy()", e);
|
|
@@ -1306,7 +1334,7 @@ export class WunderbaumNode {
|
|
|
1306
1334
|
// Fix node.tree for all source nodes
|
|
1307
1335
|
// util.assert(false, "Cross-tree move is not yet implemented.");
|
|
1308
1336
|
this.logWarn("Cross-tree moveTo is experimental!");
|
|
1309
|
-
this.visit(
|
|
1337
|
+
this.visit((n) => {
|
|
1310
1338
|
// TODO: fix selection state and activation, ...
|
|
1311
1339
|
n.tree = targetNode.tree;
|
|
1312
1340
|
}, true);
|
|
@@ -1315,7 +1343,7 @@ export class WunderbaumNode {
|
|
|
1315
1343
|
// DragAndDrop to generate a dragend event on the source node
|
|
1316
1344
|
setTimeout(() => {
|
|
1317
1345
|
// Even indentation may have changed:
|
|
1318
|
-
tree.
|
|
1346
|
+
tree.update(ChangeType.any);
|
|
1319
1347
|
}, 0);
|
|
1320
1348
|
// TODO: fix selection state
|
|
1321
1349
|
// TODO: fix active state
|
|
@@ -1364,7 +1392,7 @@ export class WunderbaumNode {
|
|
|
1364
1392
|
n.removeMarkup();
|
|
1365
1393
|
tree._unregisterNode(n);
|
|
1366
1394
|
}, true);
|
|
1367
|
-
tree.
|
|
1395
|
+
tree.update(ChangeType.structure);
|
|
1368
1396
|
}
|
|
1369
1397
|
|
|
1370
1398
|
/** Remove all descendants of this node. */
|
|
@@ -1397,7 +1425,7 @@ export class WunderbaumNode {
|
|
|
1397
1425
|
if (!this.isRootNode()) {
|
|
1398
1426
|
this.expanded = false;
|
|
1399
1427
|
}
|
|
1400
|
-
this.tree.
|
|
1428
|
+
this.tree.update(ChangeType.structure);
|
|
1401
1429
|
}
|
|
1402
1430
|
|
|
1403
1431
|
/** Remove all HTML markup from the DOM. */
|
|
@@ -1498,12 +1526,13 @@ export class WunderbaumNode {
|
|
|
1498
1526
|
|
|
1499
1527
|
/**
|
|
1500
1528
|
* Create a whole new `<div class="wb-row">` element.
|
|
1501
|
-
* @see {@link WunderbaumNode.
|
|
1529
|
+
* @see {@link WunderbaumNode._render}
|
|
1502
1530
|
*/
|
|
1503
1531
|
protected _render_markup(opts: RenderOptions) {
|
|
1504
1532
|
const tree = this.tree;
|
|
1505
1533
|
const treeOptions = tree.options;
|
|
1506
|
-
const checkbox = this.getOption("checkbox")
|
|
1534
|
+
const checkbox = this.getOption("checkbox");
|
|
1535
|
+
// const checkbox = this.getOption("checkbox") !== false;
|
|
1507
1536
|
const columns = tree.columns;
|
|
1508
1537
|
const level = this.getLevel();
|
|
1509
1538
|
let elem: HTMLElement;
|
|
@@ -1542,6 +1571,9 @@ export class WunderbaumNode {
|
|
|
1542
1571
|
if (checkbox) {
|
|
1543
1572
|
checkboxSpan = document.createElement("i");
|
|
1544
1573
|
checkboxSpan.classList.add("wb-checkbox");
|
|
1574
|
+
if (checkbox === "radio" || this.parent.radiogroup) {
|
|
1575
|
+
checkboxSpan.classList.add("wb-radio");
|
|
1576
|
+
}
|
|
1545
1577
|
nodeElem.appendChild(checkboxSpan);
|
|
1546
1578
|
ofsTitlePx += ICON_WIDTH;
|
|
1547
1579
|
}
|
|
@@ -1633,7 +1665,7 @@ export class WunderbaumNode {
|
|
|
1633
1665
|
/**
|
|
1634
1666
|
* Render `node.title`, `.icon` into an existing row.
|
|
1635
1667
|
*
|
|
1636
|
-
* @see {@link WunderbaumNode.
|
|
1668
|
+
* @see {@link WunderbaumNode._render}
|
|
1637
1669
|
*/
|
|
1638
1670
|
protected _render_data(opts: RenderOptions) {
|
|
1639
1671
|
util.assert(this._rowElem);
|
|
@@ -1706,7 +1738,7 @@ export class WunderbaumNode {
|
|
|
1706
1738
|
|
|
1707
1739
|
/**
|
|
1708
1740
|
* Update row classes to reflect active, focuses, etc.
|
|
1709
|
-
* @see {@link WunderbaumNode.
|
|
1741
|
+
* @see {@link WunderbaumNode._render}
|
|
1710
1742
|
*/
|
|
1711
1743
|
protected _render_status(opts: RenderOptions) {
|
|
1712
1744
|
// this.log("_render_status", opts);
|
|
@@ -1728,6 +1760,7 @@ export class WunderbaumNode {
|
|
|
1728
1760
|
this.expanded ? rowClasses.push("wb-expanded") : 0;
|
|
1729
1761
|
this.lazy ? rowClasses.push("wb-lazy") : 0;
|
|
1730
1762
|
this.selected ? rowClasses.push("wb-selected") : 0;
|
|
1763
|
+
this._partsel ? rowClasses.push("wb-partsel") : 0;
|
|
1731
1764
|
this === tree.activeNode ? rowClasses.push("wb-active") : 0;
|
|
1732
1765
|
this === tree.focusNode ? rowClasses.push("wb-focus") : 0;
|
|
1733
1766
|
this._errorInfo ? rowClasses.push("wb-error") : 0;
|
|
@@ -1768,11 +1801,26 @@ export class WunderbaumNode {
|
|
|
1768
1801
|
}
|
|
1769
1802
|
}
|
|
1770
1803
|
if (checkboxSpan) {
|
|
1771
|
-
|
|
1772
|
-
|
|
1804
|
+
let cbclass = "wb-checkbox ";
|
|
1805
|
+
if (this.parent.radiogroup) {
|
|
1806
|
+
cbclass += "wb-radio ";
|
|
1807
|
+
if (this.selected) {
|
|
1808
|
+
cbclass += iconMap.radioChecked;
|
|
1809
|
+
// } else if (this._partsel) {
|
|
1810
|
+
// cbclass += iconMap.radioUnknown;
|
|
1811
|
+
} else {
|
|
1812
|
+
cbclass += iconMap.radioUnchecked;
|
|
1813
|
+
}
|
|
1773
1814
|
} else {
|
|
1774
|
-
|
|
1815
|
+
if (this.selected) {
|
|
1816
|
+
cbclass += iconMap.checkChecked;
|
|
1817
|
+
} else if (this._partsel) {
|
|
1818
|
+
cbclass += iconMap.checkUnknown;
|
|
1819
|
+
} else {
|
|
1820
|
+
cbclass += iconMap.checkUnchecked;
|
|
1821
|
+
}
|
|
1775
1822
|
}
|
|
1823
|
+
checkboxSpan.className = cbclass;
|
|
1776
1824
|
}
|
|
1777
1825
|
// Fix active cell in cell-nav mode
|
|
1778
1826
|
if (!opts.isNew) {
|
|
@@ -1801,7 +1849,7 @@ export class WunderbaumNode {
|
|
|
1801
1849
|
}
|
|
1802
1850
|
}
|
|
1803
1851
|
|
|
1804
|
-
|
|
1852
|
+
/*
|
|
1805
1853
|
* Create or update node's markup.
|
|
1806
1854
|
*
|
|
1807
1855
|
* `options.change` defaults to ChangeType.data, which updates the title,
|
|
@@ -1812,10 +1860,10 @@ export class WunderbaumNode {
|
|
|
1812
1860
|
* `options.change` should be set to ChangeType.status instead for best
|
|
1813
1861
|
* efficiency.
|
|
1814
1862
|
*
|
|
1815
|
-
* Calling `
|
|
1816
|
-
* @see {@link WunderbaumNode.
|
|
1863
|
+
* Calling `update()` is almost always a better alternative.
|
|
1864
|
+
* @see {@link WunderbaumNode.update}
|
|
1817
1865
|
*/
|
|
1818
|
-
|
|
1866
|
+
_render(options?: RenderOptions) {
|
|
1819
1867
|
// this.log("render", options);
|
|
1820
1868
|
const opts = Object.assign({ change: ChangeType.data }, options);
|
|
1821
1869
|
if (!this._rowElem) {
|
|
@@ -1846,7 +1894,7 @@ export class WunderbaumNode {
|
|
|
1846
1894
|
this.expanded = false;
|
|
1847
1895
|
this.lazy = true;
|
|
1848
1896
|
this.children = null;
|
|
1849
|
-
this.tree.
|
|
1897
|
+
this.tree.update(ChangeType.structure);
|
|
1850
1898
|
}
|
|
1851
1899
|
|
|
1852
1900
|
/** Convert node (or whole branch) into a plain object.
|
|
@@ -1863,7 +1911,7 @@ export class WunderbaumNode {
|
|
|
1863
1911
|
toDict(recursive = false, callback?: NodeToDictCallback): WbNodeData {
|
|
1864
1912
|
const dict: any = {};
|
|
1865
1913
|
|
|
1866
|
-
|
|
1914
|
+
NODE_DICT_PROPS.forEach((propName: string) => {
|
|
1867
1915
|
const val = (<any>this)[propName];
|
|
1868
1916
|
|
|
1869
1917
|
if (val instanceof Set) {
|
|
@@ -1996,7 +2044,7 @@ export class WunderbaumNode {
|
|
|
1996
2044
|
return;
|
|
1997
2045
|
}
|
|
1998
2046
|
tree.activeNode = null;
|
|
1999
|
-
prev?.
|
|
2047
|
+
prev?.update(ChangeType.status);
|
|
2000
2048
|
}
|
|
2001
2049
|
} else if (prev === this || retrigger) {
|
|
2002
2050
|
this._callEvent("deactivate", { nextNode: null, event: orgEvent });
|
|
@@ -2009,8 +2057,8 @@ export class WunderbaumNode {
|
|
|
2009
2057
|
if (focusNode || focusTree) tree.focusNode = this;
|
|
2010
2058
|
if (focusTree) tree.setFocus();
|
|
2011
2059
|
}
|
|
2012
|
-
prev?.
|
|
2013
|
-
this.
|
|
2060
|
+
prev?.update(ChangeType.status);
|
|
2061
|
+
this.update(ChangeType.status);
|
|
2014
2062
|
}
|
|
2015
2063
|
if (
|
|
2016
2064
|
options &&
|
|
@@ -2044,13 +2092,16 @@ export class WunderbaumNode {
|
|
|
2044
2092
|
return; // Nothing to do
|
|
2045
2093
|
}
|
|
2046
2094
|
// this.log("setExpanded()");
|
|
2095
|
+
if (flag && this.getOption("autoCollapse")) {
|
|
2096
|
+
this.collapseSiblings(options);
|
|
2097
|
+
}
|
|
2047
2098
|
if (flag && this.lazy && this.children == null) {
|
|
2048
2099
|
await this.loadLazy();
|
|
2049
2100
|
}
|
|
2050
2101
|
this.expanded = flag;
|
|
2051
2102
|
const updateOpts = { immediate: immediate };
|
|
2052
2103
|
// const updateOpts = { immediate: !!util.getOption(options, "immediate") };
|
|
2053
|
-
this.tree.
|
|
2104
|
+
this.tree.update(ChangeType.structure, updateOpts);
|
|
2054
2105
|
if (flag && scrollIntoView !== false) {
|
|
2055
2106
|
const lastChild = this.getLastChild();
|
|
2056
2107
|
if (lastChild) {
|
|
@@ -2068,14 +2119,14 @@ export class WunderbaumNode {
|
|
|
2068
2119
|
util.assert(!!flag, "blur is not yet implemented");
|
|
2069
2120
|
const prev = this.tree.focusNode;
|
|
2070
2121
|
this.tree.focusNode = this;
|
|
2071
|
-
prev?.
|
|
2072
|
-
this.
|
|
2122
|
+
prev?.update();
|
|
2123
|
+
this.update();
|
|
2073
2124
|
}
|
|
2074
2125
|
|
|
2075
2126
|
/** Set a new icon path or class. */
|
|
2076
2127
|
setIcon(icon: string) {
|
|
2077
2128
|
this.icon = icon;
|
|
2078
|
-
this.
|
|
2129
|
+
this.update();
|
|
2079
2130
|
}
|
|
2080
2131
|
|
|
2081
2132
|
/** Change node's {@link key} and/or {@link refKey}. */
|
|
@@ -2083,6 +2134,13 @@ export class WunderbaumNode {
|
|
|
2083
2134
|
throw new Error("Not yet implemented");
|
|
2084
2135
|
}
|
|
2085
2136
|
|
|
2137
|
+
/**
|
|
2138
|
+
* @deprecated since v0.3.6: use `update()` instead.
|
|
2139
|
+
*/
|
|
2140
|
+
setModified(change: ChangeType = ChangeType.data): void {
|
|
2141
|
+
this.logWarn("setModified() is deprecated: use update() instead.");
|
|
2142
|
+
return this.update(change);
|
|
2143
|
+
}
|
|
2086
2144
|
/**
|
|
2087
2145
|
* Trigger a repaint, typically after a status or data change.
|
|
2088
2146
|
*
|
|
@@ -2090,23 +2148,244 @@ export class WunderbaumNode {
|
|
|
2090
2148
|
* and column content. It can be reduced to 'ChangeType.status' if only
|
|
2091
2149
|
* active/focus/selected state has changed.
|
|
2092
2150
|
*
|
|
2093
|
-
* This method will eventually call {@link WunderbaumNode.
|
|
2151
|
+
* This method will eventually call {@link WunderbaumNode._render()} with
|
|
2094
2152
|
* default options, but may be more consistent with the tree's
|
|
2095
|
-
* {@link Wunderbaum.
|
|
2153
|
+
* {@link Wunderbaum.update()} API.
|
|
2096
2154
|
*/
|
|
2097
|
-
|
|
2155
|
+
update(change: ChangeType = ChangeType.data) {
|
|
2098
2156
|
util.assert(change === ChangeType.status || change === ChangeType.data);
|
|
2099
|
-
this.tree.
|
|
2157
|
+
this.tree.update(change, this);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
/**
|
|
2161
|
+
* Return an array of selected nodes.
|
|
2162
|
+
* @param stopOnParents only return the topmost selected node (useful with selectMode 'hier')
|
|
2163
|
+
*/
|
|
2164
|
+
getSelectedNodes(stopOnParents: boolean = false): WunderbaumNode[] {
|
|
2165
|
+
let nodeList: WunderbaumNode[] = [];
|
|
2166
|
+
this.visit((node) => {
|
|
2167
|
+
if (node.selected) {
|
|
2168
|
+
nodeList.push(node);
|
|
2169
|
+
if (stopOnParents === true) {
|
|
2170
|
+
return "skip"; // stop processing this branch
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
});
|
|
2174
|
+
return nodeList;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/** Toggle the check/uncheck state. */
|
|
2178
|
+
toggleSelected(options?: SetSelectedOptions): TristateType {
|
|
2179
|
+
let flag = this.isSelected();
|
|
2180
|
+
if (flag === undefined) {
|
|
2181
|
+
flag = this._anySelectable();
|
|
2182
|
+
} else {
|
|
2183
|
+
flag = !flag;
|
|
2184
|
+
}
|
|
2185
|
+
return this.setSelected(flag, options);
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/** Return true if at least on selectable descendant end-node is unselected. @internal */
|
|
2189
|
+
_anySelectable(): boolean {
|
|
2190
|
+
let found = false;
|
|
2191
|
+
this.visit((node) => {
|
|
2192
|
+
if (
|
|
2193
|
+
node.selected === false &&
|
|
2194
|
+
!node.unselectable &&
|
|
2195
|
+
!node.hasChildren() &&
|
|
2196
|
+
!node.parent.radiogroup
|
|
2197
|
+
) {
|
|
2198
|
+
found = true;
|
|
2199
|
+
return false; // Stop iteration
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
return found;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/* Apply selection state to a single node. */
|
|
2206
|
+
protected _changeSelectStatusProps(state: TristateType): boolean {
|
|
2207
|
+
let changed = false;
|
|
2208
|
+
switch (state) {
|
|
2209
|
+
case false:
|
|
2210
|
+
changed = this.selected || this._partsel;
|
|
2211
|
+
this.selected = false;
|
|
2212
|
+
this._partsel = false;
|
|
2213
|
+
break;
|
|
2214
|
+
case true:
|
|
2215
|
+
changed = !this.selected || !this._partsel;
|
|
2216
|
+
this.selected = true;
|
|
2217
|
+
this._partsel = true;
|
|
2218
|
+
break;
|
|
2219
|
+
case undefined:
|
|
2220
|
+
changed = this.selected || !this._partsel;
|
|
2221
|
+
this.selected = false;
|
|
2222
|
+
this._partsel = true;
|
|
2223
|
+
break;
|
|
2224
|
+
default:
|
|
2225
|
+
util.error(`Invalid state: ${state}`);
|
|
2226
|
+
}
|
|
2227
|
+
if (changed) {
|
|
2228
|
+
this.update();
|
|
2229
|
+
}
|
|
2230
|
+
return changed;
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Fix selection status, after this node was (de)selected in `selectMode: 'hier'`.
|
|
2234
|
+
* This includes (de)selecting all descendants.
|
|
2235
|
+
*/
|
|
2236
|
+
fixSelection3AfterClick(opts?: SetSelectedOptions): void {
|
|
2237
|
+
const force = !!opts?.force;
|
|
2238
|
+
let flag = this.isSelected();
|
|
2239
|
+
|
|
2240
|
+
this.visit((node) => {
|
|
2241
|
+
if (node.radiogroup) {
|
|
2242
|
+
return "skip"; // Don't (de)select this branch
|
|
2243
|
+
}
|
|
2244
|
+
if (force || !node.getOption("unselectable")) {
|
|
2245
|
+
node._changeSelectStatusProps(flag);
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
this.fixSelection3FromEndNodes();
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
/**
|
|
2252
|
+
* Fix selection status for multi-hier mode.
|
|
2253
|
+
* Only end-nodes are considered to update the descendants branch and parents.
|
|
2254
|
+
* Should be called after this node has loaded new children or after
|
|
2255
|
+
* children have been modified using the API.
|
|
2256
|
+
*/
|
|
2257
|
+
fixSelection3FromEndNodes(opts?: SetSelectedOptions): void {
|
|
2258
|
+
const force = !!opts?.force;
|
|
2259
|
+
util.assert(
|
|
2260
|
+
this.tree.options.selectMode === "hier",
|
|
2261
|
+
"expected selectMode 'hier'"
|
|
2262
|
+
);
|
|
2263
|
+
|
|
2264
|
+
// Visit all end nodes and adjust their parent's `selected` and `_partsel`
|
|
2265
|
+
// attributes. Return selection state true, false, or undefined.
|
|
2266
|
+
const _walk = (node: WunderbaumNode) => {
|
|
2267
|
+
let state;
|
|
2268
|
+
const children = node.children;
|
|
2269
|
+
|
|
2270
|
+
if (children && children.length) {
|
|
2271
|
+
// check all children recursively
|
|
2272
|
+
let allSelected = true;
|
|
2273
|
+
let someSelected = false;
|
|
2274
|
+
|
|
2275
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
2276
|
+
const child = children[i];
|
|
2277
|
+
// the selection state of a node is not relevant; we need the end-nodes
|
|
2278
|
+
const s = _walk(child);
|
|
2279
|
+
if (s !== false) {
|
|
2280
|
+
someSelected = true;
|
|
2281
|
+
}
|
|
2282
|
+
if (s !== true) {
|
|
2283
|
+
allSelected = false;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
state = allSelected ? true : someSelected ? undefined : false;
|
|
2287
|
+
} else {
|
|
2288
|
+
// This is an end-node: simply report the status
|
|
2289
|
+
state = !!node.selected;
|
|
2290
|
+
}
|
|
2291
|
+
// #939: Keep a `_partsel` flag that was explicitly set on a lazy node
|
|
2292
|
+
if (
|
|
2293
|
+
node._partsel &&
|
|
2294
|
+
!node.selected &&
|
|
2295
|
+
node.lazy &&
|
|
2296
|
+
node.children == null
|
|
2297
|
+
) {
|
|
2298
|
+
state = undefined;
|
|
2299
|
+
}
|
|
2300
|
+
if (force || !node.getOption("unselectable")) {
|
|
2301
|
+
node._changeSelectStatusProps(state);
|
|
2302
|
+
}
|
|
2303
|
+
return state;
|
|
2304
|
+
};
|
|
2305
|
+
_walk(this);
|
|
2306
|
+
|
|
2307
|
+
// Update parent's state
|
|
2308
|
+
this.visitParents((node) => {
|
|
2309
|
+
let state;
|
|
2310
|
+
const children = node.children!;
|
|
2311
|
+
let allSelected = true;
|
|
2312
|
+
let someSelected = false;
|
|
2313
|
+
|
|
2314
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
2315
|
+
const child = children[i];
|
|
2316
|
+
|
|
2317
|
+
state = !!child.selected;
|
|
2318
|
+
// When fixing the parents, we trust the sibling status (i.e. we don't recurse)
|
|
2319
|
+
if (state || child._partsel) {
|
|
2320
|
+
someSelected = true;
|
|
2321
|
+
}
|
|
2322
|
+
if (!state) {
|
|
2323
|
+
allSelected = false;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
state = allSelected ? true : someSelected ? undefined : false;
|
|
2327
|
+
node._changeSelectStatusProps(state);
|
|
2328
|
+
});
|
|
2100
2329
|
}
|
|
2101
2330
|
|
|
2102
2331
|
/** Modify the check/uncheck state. */
|
|
2103
|
-
setSelected(
|
|
2104
|
-
|
|
2105
|
-
|
|
2332
|
+
setSelected(
|
|
2333
|
+
flag: boolean = true,
|
|
2334
|
+
options?: SetSelectedOptions
|
|
2335
|
+
): TristateType {
|
|
2336
|
+
const tree = this.tree;
|
|
2337
|
+
const sendEvents = !options?.noEvents; // Default: send events
|
|
2338
|
+
const prev = this.isSelected();
|
|
2339
|
+
const isRadio = this.parent && this.parent.radiogroup;
|
|
2340
|
+
const selectMode = tree.options.selectMode;
|
|
2341
|
+
const canSelect = options?.force || !this.getOption("unselectable");
|
|
2342
|
+
|
|
2343
|
+
flag = !!flag;
|
|
2344
|
+
// this.logDebug(`setSelected(${flag})`, this);
|
|
2345
|
+
if (!canSelect) {
|
|
2346
|
+
return prev;
|
|
2347
|
+
}
|
|
2348
|
+
if (options?.propagateDown && selectMode === "multi") {
|
|
2349
|
+
tree.runWithDeferredUpdate(() => {
|
|
2350
|
+
this.visit((node) => {
|
|
2351
|
+
node.setSelected(flag);
|
|
2352
|
+
});
|
|
2353
|
+
});
|
|
2354
|
+
return prev;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
if (
|
|
2358
|
+
flag === prev ||
|
|
2359
|
+
(sendEvents && this._callEvent("beforeSelect", { flag: flag }) === false)
|
|
2360
|
+
) {
|
|
2361
|
+
return prev;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
tree.runWithDeferredUpdate(() => {
|
|
2365
|
+
if (isRadio) {
|
|
2366
|
+
// Radiobutton Group
|
|
2367
|
+
if (!flag && !options?.force) {
|
|
2368
|
+
return prev; // don't uncheck radio buttons
|
|
2369
|
+
}
|
|
2370
|
+
for (let sibling of this.parent.children!) {
|
|
2371
|
+
sibling.selected = sibling === this;
|
|
2372
|
+
}
|
|
2373
|
+
} else {
|
|
2374
|
+
this.selected = flag;
|
|
2375
|
+
if (selectMode === "hier") {
|
|
2376
|
+
this.fixSelection3AfterClick();
|
|
2377
|
+
} else if (selectMode === "single") {
|
|
2378
|
+
tree.visit((n) => {
|
|
2379
|
+
n.selected = false;
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
if (sendEvents) {
|
|
2106
2386
|
this._callEvent("select", { flag: flag });
|
|
2107
2387
|
}
|
|
2108
|
-
|
|
2109
|
-
this.setModified();
|
|
2388
|
+
return prev;
|
|
2110
2389
|
}
|
|
2111
2390
|
|
|
2112
2391
|
/** Display node status (ok, loading, error, noData) using styles and a dummy child node. */
|
|
@@ -2141,7 +2420,7 @@ export class WunderbaumNode {
|
|
|
2141
2420
|
|
|
2142
2421
|
statusNode = this.addNode(data, "prependChild");
|
|
2143
2422
|
statusNode.match = true;
|
|
2144
|
-
tree.
|
|
2423
|
+
tree.update(ChangeType.structure);
|
|
2145
2424
|
|
|
2146
2425
|
return statusNode;
|
|
2147
2426
|
};
|
|
@@ -2157,7 +2436,7 @@ export class WunderbaumNode {
|
|
|
2157
2436
|
this._isLoading = true;
|
|
2158
2437
|
this._errorInfo = null;
|
|
2159
2438
|
if (this.parent) {
|
|
2160
|
-
this.
|
|
2439
|
+
this.update(ChangeType.status);
|
|
2161
2440
|
} else {
|
|
2162
2441
|
// If this is the invisible root, add a visible top-level node
|
|
2163
2442
|
_setStatusNode({
|
|
@@ -2170,7 +2449,7 @@ export class WunderbaumNode {
|
|
|
2170
2449
|
tooltip: details,
|
|
2171
2450
|
});
|
|
2172
2451
|
}
|
|
2173
|
-
// this.
|
|
2452
|
+
// this.update();
|
|
2174
2453
|
break;
|
|
2175
2454
|
case "error":
|
|
2176
2455
|
_setStatusNode({
|
|
@@ -2200,14 +2479,14 @@ export class WunderbaumNode {
|
|
|
2200
2479
|
default:
|
|
2201
2480
|
util.error("invalid node status " + status);
|
|
2202
2481
|
}
|
|
2203
|
-
tree.
|
|
2482
|
+
tree.update(ChangeType.structure);
|
|
2204
2483
|
return statusNode;
|
|
2205
2484
|
}
|
|
2206
2485
|
|
|
2207
2486
|
/** Rename this node. */
|
|
2208
2487
|
setTitle(title: string): void {
|
|
2209
2488
|
this.title = title;
|
|
2210
|
-
this.
|
|
2489
|
+
this.update();
|
|
2211
2490
|
// this.triggerModify("rename"); // TODO
|
|
2212
2491
|
}
|
|
2213
2492
|
|
|
@@ -2238,7 +2517,7 @@ export class WunderbaumNode {
|
|
|
2238
2517
|
deep: boolean = false
|
|
2239
2518
|
): void {
|
|
2240
2519
|
this._sortChildren(cmp || nodeTitleSorter, deep);
|
|
2241
|
-
this.tree.
|
|
2520
|
+
this.tree.update(ChangeType.structure);
|
|
2242
2521
|
// this.triggerModify("sort"); // TODO
|
|
2243
2522
|
}
|
|
2244
2523
|
|
|
@@ -2275,7 +2554,7 @@ export class WunderbaumNode {
|
|
|
2275
2554
|
}
|
|
2276
2555
|
|
|
2277
2556
|
/**
|
|
2278
|
-
* Call `callback(node)` for all
|
|
2557
|
+
* Call `callback(node)` for all descendant nodes in hierarchical order (depth-first, pre-order).
|
|
2279
2558
|
*
|
|
2280
2559
|
* Stop iteration, if fn() returns false. Skip current branch, if fn()
|
|
2281
2560
|
* returns "skip".<br>
|