wunderbaum 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/wunderbaum.css +40 -11
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +648 -306
- package/dist/wunderbaum.esm.js +920 -356
- package/dist/wunderbaum.esm.min.js +27 -27
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +920 -356
- package/dist/wunderbaum.umd.min.js +31 -31
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/common.ts +49 -6
- package/src/types.ts +182 -33
- package/src/util.ts +75 -15
- package/src/wb_ext_dnd.ts +21 -20
- package/src/wb_ext_edit.ts +2 -2
- package/src/wb_ext_filter.ts +122 -43
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_extension_base.ts +19 -3
- package/src/wb_node.ts +239 -195
- package/src/wb_options.ts +172 -117
- package/src/wunderbaum.scss +9 -1
- package/src/wunderbaum.ts +574 -126
package/dist/wunderbaum.umd.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/*!
|
|
8
8
|
* Wunderbaum - debounce.ts
|
|
9
9
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
10
|
-
* v0.
|
|
10
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
11
11
|
*/
|
|
12
12
|
/*
|
|
13
13
|
* debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
/*!
|
|
300
300
|
* Wunderbaum - util
|
|
301
301
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
302
|
-
* v0.
|
|
302
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
303
303
|
*/
|
|
304
304
|
/** @module util */
|
|
305
305
|
/** Readable names for `MouseEvent.button` */
|
|
@@ -449,7 +449,7 @@
|
|
|
449
449
|
}
|
|
450
450
|
return obj;
|
|
451
451
|
}
|
|
452
|
-
/** Shortcut for `throw new Error(msg)
|
|
452
|
+
/** Shortcut for `throw new Error(msg)`. */
|
|
453
453
|
function error(msg) {
|
|
454
454
|
throw new Error(msg);
|
|
455
455
|
}
|
|
@@ -682,18 +682,6 @@
|
|
|
682
682
|
}
|
|
683
683
|
return obj;
|
|
684
684
|
}
|
|
685
|
-
// /** Return a EventTarget from selector or cast an existing element. */
|
|
686
|
-
// export function eventTargetFromSelector(
|
|
687
|
-
// obj: string | EventTarget
|
|
688
|
-
// ): EventTarget | null {
|
|
689
|
-
// if (!obj) {
|
|
690
|
-
// return null;
|
|
691
|
-
// }
|
|
692
|
-
// if (typeof obj === "string") {
|
|
693
|
-
// return document.querySelector(obj) as EventTarget;
|
|
694
|
-
// }
|
|
695
|
-
// return obj as EventTarget;
|
|
696
|
-
// }
|
|
697
685
|
/**
|
|
698
686
|
* Return a canonical descriptive string for a keyboard or mouse event.
|
|
699
687
|
*
|
|
@@ -976,6 +964,10 @@
|
|
|
976
964
|
}
|
|
977
965
|
throw new Error(`Expected a string like '123px': ${defaults}`);
|
|
978
966
|
}
|
|
967
|
+
/** Cast any value to <T>. */
|
|
968
|
+
function unsafeCast(value) {
|
|
969
|
+
return value;
|
|
970
|
+
}
|
|
979
971
|
/** Return the the boolean value of the first non-null element.
|
|
980
972
|
* Example:
|
|
981
973
|
* ```js
|
|
@@ -1049,7 +1041,7 @@
|
|
|
1049
1041
|
const throttledFn = (...args) => {
|
|
1050
1042
|
if (waiting) {
|
|
1051
1043
|
pendingArgs = args;
|
|
1052
|
-
// console.log(`adaptiveThrottle()
|
|
1044
|
+
// console.log(`adaptiveThrottle() queueing request #${waiting}...`, args);
|
|
1053
1045
|
waiting += 1;
|
|
1054
1046
|
}
|
|
1055
1047
|
else {
|
|
@@ -1104,6 +1096,60 @@
|
|
|
1104
1096
|
};
|
|
1105
1097
|
return throttledFn;
|
|
1106
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* MurmurHash3 implementation for strings.
|
|
1101
|
+
* @param key The input string to hash.
|
|
1102
|
+
* @param asString Optional convert result to zero-padded string of 8 characters.
|
|
1103
|
+
* @param seed Optional seed value.
|
|
1104
|
+
* @returns A 32-bit hash as a number or string.
|
|
1105
|
+
*/
|
|
1106
|
+
function murmurHash3(key, asString = true, seed = 0) {
|
|
1107
|
+
let h1 = seed;
|
|
1108
|
+
const remainder = key.length & 3; // key.length % 4
|
|
1109
|
+
const bytes = key.length - remainder;
|
|
1110
|
+
const c1 = 0xcc9e2d51;
|
|
1111
|
+
const c2 = 0x1b873593;
|
|
1112
|
+
let i = 0;
|
|
1113
|
+
while (i < bytes) {
|
|
1114
|
+
let k1 = (key.charCodeAt(i) & 0xff) |
|
|
1115
|
+
((key.charCodeAt(++i) & 0xff) << 8) |
|
|
1116
|
+
((key.charCodeAt(++i) & 0xff) << 16) |
|
|
1117
|
+
((key.charCodeAt(++i) & 0xff) << 24);
|
|
1118
|
+
++i;
|
|
1119
|
+
k1 = Math.imul(k1, c1);
|
|
1120
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1121
|
+
k1 = Math.imul(k1, c2);
|
|
1122
|
+
h1 ^= k1;
|
|
1123
|
+
h1 = (h1 << 13) | (h1 >>> 19);
|
|
1124
|
+
h1 = Math.imul(h1, 5) + 0xe6546b64;
|
|
1125
|
+
}
|
|
1126
|
+
let k1 = 0;
|
|
1127
|
+
switch (remainder) {
|
|
1128
|
+
case 3:
|
|
1129
|
+
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
1130
|
+
// fall through
|
|
1131
|
+
case 2:
|
|
1132
|
+
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
1133
|
+
// fall through
|
|
1134
|
+
case 1:
|
|
1135
|
+
k1 ^= key.charCodeAt(i) & 0xff;
|
|
1136
|
+
k1 = Math.imul(k1, c1);
|
|
1137
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1138
|
+
k1 = Math.imul(k1, c2);
|
|
1139
|
+
h1 ^= k1;
|
|
1140
|
+
}
|
|
1141
|
+
h1 ^= key.length;
|
|
1142
|
+
h1 ^= h1 >>> 16;
|
|
1143
|
+
h1 = Math.imul(h1, 0x85ebca6b);
|
|
1144
|
+
h1 ^= h1 >>> 13;
|
|
1145
|
+
h1 = Math.imul(h1, 0xc2b2ae35);
|
|
1146
|
+
h1 ^= h1 >>> 16;
|
|
1147
|
+
if (asString) {
|
|
1148
|
+
// Convert to 8 digit hex string
|
|
1149
|
+
return (h1 >>> 0).toString(16).padStart(8, "0");
|
|
1150
|
+
}
|
|
1151
|
+
return h1 >>> 0; // Convert to unsigned 32-bit integer
|
|
1152
|
+
}
|
|
1107
1153
|
|
|
1108
1154
|
var util = /*#__PURE__*/Object.freeze({
|
|
1109
1155
|
__proto__: null,
|
|
@@ -1134,6 +1180,7 @@
|
|
|
1134
1180
|
isFunction: isFunction,
|
|
1135
1181
|
isMac: isMac,
|
|
1136
1182
|
isPlainObject: isPlainObject,
|
|
1183
|
+
murmurHash3: murmurHash3,
|
|
1137
1184
|
noop: noop,
|
|
1138
1185
|
onEvent: onEvent,
|
|
1139
1186
|
overrideMethod: overrideMethod,
|
|
@@ -1147,13 +1194,14 @@
|
|
|
1147
1194
|
toPixel: toPixel,
|
|
1148
1195
|
toSet: toSet,
|
|
1149
1196
|
toggleCheckbox: toggleCheckbox,
|
|
1150
|
-
type: type
|
|
1197
|
+
type: type,
|
|
1198
|
+
unsafeCast: unsafeCast
|
|
1151
1199
|
});
|
|
1152
1200
|
|
|
1153
1201
|
/*!
|
|
1154
1202
|
* Wunderbaum - types
|
|
1155
1203
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1156
|
-
* v0.
|
|
1204
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1157
1205
|
*/
|
|
1158
1206
|
/**
|
|
1159
1207
|
* Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
|
|
@@ -1221,7 +1269,7 @@
|
|
|
1221
1269
|
/*!
|
|
1222
1270
|
* Wunderbaum - wb_extension_base
|
|
1223
1271
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1224
|
-
* v0.
|
|
1272
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1225
1273
|
*/
|
|
1226
1274
|
class WunderbaumExtension {
|
|
1227
1275
|
constructor(tree, id, defaults) {
|
|
@@ -1280,7 +1328,7 @@
|
|
|
1280
1328
|
/*!
|
|
1281
1329
|
* Wunderbaum - ext-filter
|
|
1282
1330
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1283
|
-
* v0.
|
|
1331
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1284
1332
|
*/
|
|
1285
1333
|
const START_MARKER = "\uFFF7";
|
|
1286
1334
|
const END_MARKER = "\uFFF8";
|
|
@@ -1292,7 +1340,7 @@
|
|
|
1292
1340
|
autoApply: true, // Re-apply last filter if lazy data is loaded
|
|
1293
1341
|
autoExpand: false, // Expand all branches that contain matches while filtered
|
|
1294
1342
|
matchBranch: false, // Whether to implicitly match all children of matched nodes
|
|
1295
|
-
|
|
1343
|
+
connect: null, // Element or selector of an input control for filter query strings
|
|
1296
1344
|
fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
|
|
1297
1345
|
hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
|
|
1298
1346
|
highlight: true, // Highlight matches by wrapping inside <mark> tags
|
|
@@ -1300,36 +1348,117 @@
|
|
|
1300
1348
|
mode: "dim", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
|
|
1301
1349
|
noData: true, // Display a 'no data' status node if result is empty
|
|
1302
1350
|
});
|
|
1351
|
+
this.queryInput = null;
|
|
1352
|
+
this.prevButton = null;
|
|
1353
|
+
this.nextButton = null;
|
|
1354
|
+
this.modeButton = null;
|
|
1355
|
+
this.matchInfoElem = null;
|
|
1303
1356
|
this.lastFilterArgs = null;
|
|
1304
1357
|
}
|
|
1305
1358
|
init() {
|
|
1306
1359
|
super.init();
|
|
1307
|
-
const
|
|
1308
|
-
if (
|
|
1309
|
-
this.
|
|
1310
|
-
assert(this.queryInput, `Invalid 'filter.connectInput' option: ${connectInput}.`);
|
|
1311
|
-
onEvent(this.queryInput, "input", debounce((e) => {
|
|
1312
|
-
// this.tree.log("query", e);
|
|
1313
|
-
this.filterNodes(this.queryInput.value.trim(), {});
|
|
1314
|
-
}, 700));
|
|
1360
|
+
const connect = this.getPluginOption("connect");
|
|
1361
|
+
if (connect) {
|
|
1362
|
+
this._connectControls();
|
|
1315
1363
|
}
|
|
1316
1364
|
}
|
|
1317
1365
|
setPluginOption(name, value) {
|
|
1318
|
-
// alert("filter opt=" + name + ", " + value)
|
|
1319
1366
|
super.setPluginOption(name, value);
|
|
1320
1367
|
switch (name) {
|
|
1321
1368
|
case "mode":
|
|
1322
|
-
this.tree.filterMode =
|
|
1369
|
+
this.tree.filterMode =
|
|
1370
|
+
value === "hide" ? "hide" : value === "mark" ? "mark" : "dim";
|
|
1323
1371
|
this.tree.updateFilter();
|
|
1324
1372
|
break;
|
|
1325
1373
|
}
|
|
1326
1374
|
}
|
|
1375
|
+
_updatedConnectedControls() {
|
|
1376
|
+
var _a;
|
|
1377
|
+
const filterActive = this.tree.filterMode !== null;
|
|
1378
|
+
const activeNode = this.tree.getActiveNode();
|
|
1379
|
+
const matchCount = filterActive ? this.countMatches() : 0;
|
|
1380
|
+
const strings = this.treeOpts.strings;
|
|
1381
|
+
let matchIdx = "?";
|
|
1382
|
+
if (this.matchInfoElem) {
|
|
1383
|
+
if (filterActive) {
|
|
1384
|
+
let info;
|
|
1385
|
+
if (matchCount === 0) {
|
|
1386
|
+
info = strings.noMatch;
|
|
1387
|
+
}
|
|
1388
|
+
else if (activeNode && activeNode.match >= 1) {
|
|
1389
|
+
matchIdx = (_a = activeNode.match) !== null && _a !== void 0 ? _a : "?";
|
|
1390
|
+
info = strings.matchIndex;
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
info = strings.queryResult;
|
|
1394
|
+
}
|
|
1395
|
+
info = info
|
|
1396
|
+
.replace("${count}", this.tree.count().toLocaleString())
|
|
1397
|
+
.replace("${match}", "" + matchIdx)
|
|
1398
|
+
.replace("${matches}", matchCount.toLocaleString());
|
|
1399
|
+
this.matchInfoElem.textContent = info;
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
this.matchInfoElem.textContent = "";
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (this.nextButton instanceof HTMLButtonElement) {
|
|
1406
|
+
this.nextButton.disabled = !matchCount;
|
|
1407
|
+
}
|
|
1408
|
+
if (this.prevButton instanceof HTMLButtonElement) {
|
|
1409
|
+
this.prevButton.disabled = !matchCount;
|
|
1410
|
+
}
|
|
1411
|
+
if (this.modeButton) {
|
|
1412
|
+
this.modeButton.disabled = !filterActive;
|
|
1413
|
+
this.modeButton.classList.toggle("wb-filter-hide", this.tree.filterMode === "hide");
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
_connectControls() {
|
|
1417
|
+
const tree = this.tree;
|
|
1418
|
+
const connect = this.getPluginOption("connect");
|
|
1419
|
+
if (!connect) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
this.queryInput = elemFromSelector(connect.inputElem);
|
|
1423
|
+
if (!this.queryInput) {
|
|
1424
|
+
throw new Error(`Invalid 'filter.connect' option: ${connect.inputElem}.`);
|
|
1425
|
+
}
|
|
1426
|
+
this.prevButton = elemFromSelector(connect.prevButton);
|
|
1427
|
+
this.nextButton = elemFromSelector(connect.nextButton);
|
|
1428
|
+
this.modeButton = elemFromSelector(connect.modeButton);
|
|
1429
|
+
this.matchInfoElem = elemFromSelector(connect.matchInfoElem);
|
|
1430
|
+
if (this.prevButton) {
|
|
1431
|
+
onEvent(this.prevButton, "click", () => {
|
|
1432
|
+
tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "prevMatch");
|
|
1433
|
+
this._updatedConnectedControls();
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (this.nextButton) {
|
|
1437
|
+
onEvent(this.nextButton, "click", () => {
|
|
1438
|
+
tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "nextMatch");
|
|
1439
|
+
this._updatedConnectedControls();
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
if (this.modeButton) {
|
|
1443
|
+
onEvent(this.modeButton, "click", (e) => {
|
|
1444
|
+
if (!this.tree.filterMode) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
this.setPluginOption("mode", tree.filterMode === "dim" ? "hide" : "dim");
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
onEvent(this.queryInput, "input", debounce((e) => {
|
|
1451
|
+
this.filterNodes(this.queryInput.value.trim(), {});
|
|
1452
|
+
}, 700));
|
|
1453
|
+
this._updatedConnectedControls();
|
|
1454
|
+
}
|
|
1327
1455
|
_applyFilterNoUpdate(filter, _opts) {
|
|
1328
1456
|
return this.tree.runWithDeferredUpdate(() => {
|
|
1329
1457
|
return this._applyFilterImpl(filter, _opts);
|
|
1330
1458
|
});
|
|
1331
1459
|
}
|
|
1332
1460
|
_applyFilterImpl(filter, _opts) {
|
|
1461
|
+
var _a;
|
|
1333
1462
|
let //temp,
|
|
1334
1463
|
count = 0;
|
|
1335
1464
|
const start = Date.now();
|
|
@@ -1411,11 +1540,11 @@
|
|
|
1411
1540
|
return !!res;
|
|
1412
1541
|
};
|
|
1413
1542
|
}
|
|
1414
|
-
tree.filterMode = opts.mode;
|
|
1543
|
+
tree.filterMode = (_a = opts.mode) !== null && _a !== void 0 ? _a : "dim";
|
|
1415
1544
|
// eslint-disable-next-line prefer-rest-params
|
|
1416
1545
|
this.lastFilterArgs = arguments;
|
|
1417
1546
|
tree.element.classList.toggle("wb-ext-filter-hide", !!hideMode);
|
|
1418
|
-
tree.element.classList.toggle("wb-ext-filter-dim",
|
|
1547
|
+
tree.element.classList.toggle("wb-ext-filter-dim", opts.mode === "dim");
|
|
1419
1548
|
tree.element.classList.toggle("wb-ext-filter-hide-expanders", !!opts.hideExpanders);
|
|
1420
1549
|
// Reset current filter
|
|
1421
1550
|
tree.root.subMatchCount = 0;
|
|
@@ -1424,10 +1553,6 @@
|
|
|
1424
1553
|
delete node.titleWithHighlight;
|
|
1425
1554
|
node.subMatchCount = 0;
|
|
1426
1555
|
});
|
|
1427
|
-
// statusNode = tree.root.findDirectChild(KEY_NODATA);
|
|
1428
|
-
// if (statusNode) {
|
|
1429
|
-
// statusNode.remove();
|
|
1430
|
-
// }
|
|
1431
1556
|
tree.setStatus(NodeStatusType.ok);
|
|
1432
1557
|
// Adjust node.hide, .match, and .subMatchCount properties
|
|
1433
1558
|
treeOpts.autoCollapse = false; // #528
|
|
@@ -1438,7 +1563,7 @@
|
|
|
1438
1563
|
let res = filter(node);
|
|
1439
1564
|
if (res === "skip") {
|
|
1440
1565
|
node.visit(function (c) {
|
|
1441
|
-
c.match =
|
|
1566
|
+
c.match = undefined;
|
|
1442
1567
|
}, true);
|
|
1443
1568
|
return "skip";
|
|
1444
1569
|
}
|
|
@@ -1449,7 +1574,7 @@
|
|
|
1449
1574
|
}
|
|
1450
1575
|
if (res) {
|
|
1451
1576
|
count++;
|
|
1452
|
-
node.match =
|
|
1577
|
+
node.match = count;
|
|
1453
1578
|
node.visitParents((p) => {
|
|
1454
1579
|
if (p !== node) {
|
|
1455
1580
|
p.subMatchCount += 1;
|
|
@@ -1476,6 +1601,7 @@
|
|
|
1476
1601
|
}
|
|
1477
1602
|
// Redraw whole tree
|
|
1478
1603
|
tree.logDebug(`Filter '${filter}' found ${count} nodes in ${Date.now() - start} ms.`);
|
|
1604
|
+
this._updatedConnectedControls();
|
|
1479
1605
|
return count;
|
|
1480
1606
|
}
|
|
1481
1607
|
/**
|
|
@@ -1490,6 +1616,10 @@
|
|
|
1490
1616
|
*/
|
|
1491
1617
|
filterBranches(filter, options) {
|
|
1492
1618
|
assert(options.matchBranch === undefined, "filterBranches() is deprecated.");
|
|
1619
|
+
this.tree.logDeprecate("filterBranches()", {
|
|
1620
|
+
since: "0.9.0",
|
|
1621
|
+
hint: "Use `filterNodes` instead and set `options.matchBranch: true`",
|
|
1622
|
+
});
|
|
1493
1623
|
options.matchBranch = true;
|
|
1494
1624
|
return this._applyFilterNoUpdate(filter, options);
|
|
1495
1625
|
}
|
|
@@ -1520,34 +1650,22 @@
|
|
|
1520
1650
|
else {
|
|
1521
1651
|
tree.logWarn("updateFilter(): no filter active.");
|
|
1522
1652
|
}
|
|
1653
|
+
this._updatedConnectedControls();
|
|
1523
1654
|
}
|
|
1524
1655
|
/**
|
|
1525
1656
|
* [ext-filter] Reset the filter.
|
|
1526
1657
|
*/
|
|
1527
1658
|
clearFilter() {
|
|
1528
1659
|
const tree = this.tree;
|
|
1529
|
-
// statusNode = tree.root.findDirectChild(KEY_NODATA),
|
|
1530
|
-
// escapeTitles = tree.options.escapeTitles;
|
|
1531
1660
|
tree.enableUpdate(false);
|
|
1532
|
-
// if (statusNode) {
|
|
1533
|
-
// statusNode.remove();
|
|
1534
|
-
// }
|
|
1535
1661
|
tree.setStatus(NodeStatusType.ok);
|
|
1536
1662
|
// we also counted root node's subMatchCount
|
|
1537
1663
|
delete tree.root.match;
|
|
1538
1664
|
delete tree.root.subMatchCount;
|
|
1539
1665
|
tree.visit((node) => {
|
|
1540
|
-
// if (node.match && node._rowElem) {
|
|
1541
|
-
// let titleElem = node._rowElem.querySelector("span.wb-title")!;
|
|
1542
|
-
// node._callEvent("enhanceTitle", { titleElem: titleElem });
|
|
1543
|
-
// }
|
|
1544
1666
|
delete node.match;
|
|
1545
1667
|
delete node.subMatchCount;
|
|
1546
1668
|
delete node.titleWithHighlight;
|
|
1547
|
-
// if (node.subMatchBadge) {
|
|
1548
|
-
// node.subMatchBadge.remove();
|
|
1549
|
-
// delete node.subMatchBadge;
|
|
1550
|
-
// }
|
|
1551
1669
|
if (node._filterAutoExpanded && node.expanded) {
|
|
1552
1670
|
node.setExpanded(false, {
|
|
1553
1671
|
noAnimation: true,
|
|
@@ -1561,12 +1679,12 @@
|
|
|
1561
1679
|
tree.element.classList.remove(
|
|
1562
1680
|
// "wb-ext-filter",
|
|
1563
1681
|
"wb-ext-filter-dim", "wb-ext-filter-hide");
|
|
1564
|
-
|
|
1682
|
+
this._updatedConnectedControls();
|
|
1565
1683
|
tree.enableUpdate(true);
|
|
1566
1684
|
}
|
|
1567
1685
|
}
|
|
1568
1686
|
/**
|
|
1569
|
-
* @description Marks the matching
|
|
1687
|
+
* @description Marks the matching characters of `text` either by `mark` or
|
|
1570
1688
|
* by exotic*Chars (if `escapeTitles` is `true`) based on `matches`
|
|
1571
1689
|
* which is an array of matching groups.
|
|
1572
1690
|
* @param {string} text
|
|
@@ -1605,7 +1723,7 @@
|
|
|
1605
1723
|
/*!
|
|
1606
1724
|
* Wunderbaum - ext-keynav
|
|
1607
1725
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1608
|
-
* v0.
|
|
1726
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1609
1727
|
*/
|
|
1610
1728
|
const QUICKSEARCH_DELAY = 500;
|
|
1611
1729
|
class KeynavExtension extends WunderbaumExtension {
|
|
@@ -1969,7 +2087,7 @@
|
|
|
1969
2087
|
/*!
|
|
1970
2088
|
* Wunderbaum - ext-logger
|
|
1971
2089
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1972
|
-
* v0.
|
|
2090
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1973
2091
|
*/
|
|
1974
2092
|
class LoggerExtension extends WunderbaumExtension {
|
|
1975
2093
|
constructor(tree) {
|
|
@@ -2011,7 +2129,7 @@
|
|
|
2011
2129
|
/*!
|
|
2012
2130
|
* Wunderbaum - ext-dnd
|
|
2013
2131
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2014
|
-
* v0.
|
|
2132
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2015
2133
|
*/
|
|
2016
2134
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
2017
2135
|
class DndExtension extends WunderbaumExtension {
|
|
@@ -2247,7 +2365,6 @@
|
|
|
2247
2365
|
*/
|
|
2248
2366
|
onDragEvent(e) {
|
|
2249
2367
|
var _a;
|
|
2250
|
-
// const tree = this.tree;
|
|
2251
2368
|
const dndOpts = this.treeOpts.dnd;
|
|
2252
2369
|
const srcNode = Wunderbaum.getNode(e);
|
|
2253
2370
|
if (!srcNode) {
|
|
@@ -2273,7 +2390,7 @@
|
|
|
2273
2390
|
return false;
|
|
2274
2391
|
}
|
|
2275
2392
|
const nodeData = srcNode.toDict(true, (n) => {
|
|
2276
|
-
// We don't want to
|
|
2393
|
+
// We don't want to reuse the key on drop:
|
|
2277
2394
|
n._orgKey = n.key;
|
|
2278
2395
|
delete n.key;
|
|
2279
2396
|
});
|
|
@@ -2335,6 +2452,7 @@
|
|
|
2335
2452
|
};
|
|
2336
2453
|
if (!targetNode) {
|
|
2337
2454
|
this._leaveNode();
|
|
2455
|
+
e.preventDefault(); // Don't open file in browser when dropped in empty area
|
|
2338
2456
|
return;
|
|
2339
2457
|
}
|
|
2340
2458
|
if (["drop"].includes(e.type)) {
|
|
@@ -2440,19 +2558,20 @@
|
|
|
2440
2558
|
nodeData = nodeData ? JSON.parse(nodeData) : null;
|
|
2441
2559
|
const srcNode = this.srcNode;
|
|
2442
2560
|
const lastDropEffect = this.lastDropEffect;
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2561
|
+
/* Before v0.14.0, we decoupled `_callEvent` like so:
|
|
2562
|
+
Decouple this call, because drop actions may prevent the dragend
|
|
2563
|
+
event from being fired on some browsers.
|
|
2564
|
+
setTimeout(() => {...}, 10);
|
|
2565
|
+
however this made e.dataTransfer.items inaccessible */
|
|
2566
|
+
targetNode._callEvent("dnd.drop", {
|
|
2567
|
+
event: e,
|
|
2568
|
+
region: region,
|
|
2569
|
+
suggestedDropMode: region === "over" ? "appendChild" : region,
|
|
2570
|
+
suggestedDropEffect: lastDropEffect,
|
|
2571
|
+
sourceNode: srcNode,
|
|
2572
|
+
sourceNodeData: nodeData,
|
|
2573
|
+
dataTransfer: e.dataTransfer,
|
|
2574
|
+
});
|
|
2456
2575
|
}
|
|
2457
2576
|
return false;
|
|
2458
2577
|
}
|
|
@@ -2461,7 +2580,7 @@
|
|
|
2461
2580
|
/*!
|
|
2462
2581
|
* Wunderbaum - drag_observer
|
|
2463
2582
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2464
|
-
* v0.
|
|
2583
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2465
2584
|
*/
|
|
2466
2585
|
/**
|
|
2467
2586
|
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
@@ -2610,7 +2729,7 @@
|
|
|
2610
2729
|
/*!
|
|
2611
2730
|
* Wunderbaum - common
|
|
2612
2731
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2613
|
-
* v0.
|
|
2732
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2614
2733
|
*/
|
|
2615
2734
|
const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
|
|
2616
2735
|
/**
|
|
@@ -2630,12 +2749,18 @@
|
|
|
2630
2749
|
const RENDER_MAX_PREFETCH = 5;
|
|
2631
2750
|
/** Minimum column width if not set otherwise. */
|
|
2632
2751
|
const DEFAULT_MIN_COL_WIDTH = 4;
|
|
2752
|
+
/**
|
|
2753
|
+
* A value for `node.type` that by convention may be used to mark a node as directory.
|
|
2754
|
+
* It may be used to sort 'directories' to the top.
|
|
2755
|
+
*/
|
|
2756
|
+
const NODE_TYPE_FOLDER = "folder";
|
|
2633
2757
|
/** Regular expression to detect if a string describes an image URL (in contrast
|
|
2634
2758
|
* to a class name). Strings are considered image urls if they contain '.' or '/'.
|
|
2759
|
+
* `<` is ignored, because it is probably an html tag.
|
|
2635
2760
|
*/
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2761
|
+
const TEST_FILE_PATH = /^(?!.*<).*[/.]/;
|
|
2762
|
+
/** Regular expression to detect if a string describes an HTML element. */
|
|
2763
|
+
const TEST_HTML = /</;
|
|
2639
2764
|
/**
|
|
2640
2765
|
* Default node icons for icon libraries
|
|
2641
2766
|
*
|
|
@@ -2643,7 +2768,7 @@
|
|
|
2643
2768
|
* - 'fontawesome6' {@link https://fontawesome.com/icons}
|
|
2644
2769
|
*
|
|
2645
2770
|
*/
|
|
2646
|
-
const
|
|
2771
|
+
const defaultIconMaps = {
|
|
2647
2772
|
bootstrap: {
|
|
2648
2773
|
error: "bi bi-exclamation-triangle",
|
|
2649
2774
|
// loading: "bi bi-hourglass-split wb-busy",
|
|
@@ -2691,7 +2816,7 @@
|
|
|
2691
2816
|
radioChecked: "fa-solid fa-circle",
|
|
2692
2817
|
radioUnchecked: "fa-regular fa-circle",
|
|
2693
2818
|
radioUnknown: "fa-regular fa-circle-question",
|
|
2694
|
-
folder: "fa-
|
|
2819
|
+
folder: "fa-regular fa-folder-closed",
|
|
2695
2820
|
folderOpen: "fa-regular fa-folder-open",
|
|
2696
2821
|
folderLazy: "fa-solid fa-folder-plus",
|
|
2697
2822
|
doc: "fa-regular fa-file",
|
|
@@ -2723,29 +2848,20 @@
|
|
|
2723
2848
|
// "Escape",
|
|
2724
2849
|
// ]);
|
|
2725
2850
|
/** Map `KeyEvent.key` to navigation action. */
|
|
2726
|
-
const
|
|
2727
|
-
" ": "toggleSelect",
|
|
2728
|
-
"+": "expand",
|
|
2729
|
-
Add: "expand",
|
|
2851
|
+
const KEY_TO_NAVIGATION_MAP = {
|
|
2730
2852
|
ArrowDown: "down",
|
|
2731
2853
|
ArrowLeft: "left",
|
|
2732
2854
|
ArrowRight: "right",
|
|
2733
2855
|
ArrowUp: "up",
|
|
2734
2856
|
Backspace: "parent",
|
|
2735
|
-
"/": "collapseAll",
|
|
2736
|
-
Divide: "collapseAll",
|
|
2737
2857
|
End: "lastCol",
|
|
2738
2858
|
Home: "firstCol",
|
|
2739
2859
|
"Control+End": "last",
|
|
2740
2860
|
"Control+Home": "first",
|
|
2741
2861
|
"Meta+ArrowDown": "last", // macOs
|
|
2742
2862
|
"Meta+ArrowUp": "first", // macOs
|
|
2743
|
-
"*": "expandAll",
|
|
2744
|
-
Multiply: "expandAll",
|
|
2745
2863
|
PageDown: "pageDown",
|
|
2746
2864
|
PageUp: "pageUp",
|
|
2747
|
-
"-": "collapse",
|
|
2748
|
-
Subtract: "collapse",
|
|
2749
2865
|
};
|
|
2750
2866
|
/** Return a callback that returns true if the node title matches the string
|
|
2751
2867
|
* or regular expression.
|
|
@@ -2773,12 +2889,20 @@
|
|
|
2773
2889
|
return reMatch.test(node.title);
|
|
2774
2890
|
};
|
|
2775
2891
|
}
|
|
2776
|
-
/** Compare two nodes by title (case-insensitive).
|
|
2892
|
+
/** Compare two nodes by title (case-insensitive).
|
|
2893
|
+
* @deprecated Use `key` option instead of `cmp` in sort methods.
|
|
2894
|
+
*/
|
|
2777
2895
|
function nodeTitleSorter(a, b) {
|
|
2778
2896
|
const x = a.title.toLowerCase();
|
|
2779
2897
|
const y = b.title.toLowerCase();
|
|
2780
2898
|
return x === y ? 0 : x > y ? 1 : -1;
|
|
2781
2899
|
}
|
|
2900
|
+
// /** Compare nodes by title (case-insensitive). */
|
|
2901
|
+
// export function nodeTitleKeyGetter(
|
|
2902
|
+
// node: WunderbaumNode
|
|
2903
|
+
// ): string | number | Array<any> {
|
|
2904
|
+
// return node.title.toLowerCase();
|
|
2905
|
+
// }
|
|
2782
2906
|
/**
|
|
2783
2907
|
* Convert 'flat' to 'nested' format.
|
|
2784
2908
|
*
|
|
@@ -2969,7 +3093,7 @@
|
|
|
2969
3093
|
/*!
|
|
2970
3094
|
* Wunderbaum - ext-grid
|
|
2971
3095
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2972
|
-
* v0.
|
|
3096
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2973
3097
|
*/
|
|
2974
3098
|
class GridExtension extends WunderbaumExtension {
|
|
2975
3099
|
constructor(tree) {
|
|
@@ -3029,7 +3153,7 @@
|
|
|
3029
3153
|
super.init();
|
|
3030
3154
|
}
|
|
3031
3155
|
/**
|
|
3032
|
-
*
|
|
3156
|
+
* Handles drag and sragstop events for column resizing.
|
|
3033
3157
|
*/
|
|
3034
3158
|
handleDrag(e) {
|
|
3035
3159
|
const custom = e.customData;
|
|
@@ -3060,7 +3184,7 @@
|
|
|
3060
3184
|
/*!
|
|
3061
3185
|
* Wunderbaum - deferred
|
|
3062
3186
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3063
|
-
* v0.
|
|
3187
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3064
3188
|
*/
|
|
3065
3189
|
/**
|
|
3066
3190
|
* Implement a ES6 Promise, that exposes a resolve() and reject() method.
|
|
@@ -3113,7 +3237,7 @@
|
|
|
3113
3237
|
/*!
|
|
3114
3238
|
* Wunderbaum - wunderbaum_node
|
|
3115
3239
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3116
|
-
* v0.
|
|
3240
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3117
3241
|
*/
|
|
3118
3242
|
/** WunderbaumNode properties that can be passed with source data.
|
|
3119
3243
|
* (Any other source properties will be stored as `node.data.PROP`.)
|
|
@@ -3164,7 +3288,7 @@
|
|
|
3164
3288
|
*/
|
|
3165
3289
|
class WunderbaumNode {
|
|
3166
3290
|
constructor(tree, parent, data) {
|
|
3167
|
-
var _a
|
|
3291
|
+
var _a;
|
|
3168
3292
|
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
3169
3293
|
* times within a tree (in this case we have 'clone nodes').
|
|
3170
3294
|
* @see Use {@link setKey} to modify.
|
|
@@ -3194,8 +3318,8 @@
|
|
|
3194
3318
|
assert(!data.children, "'children' not allowed here");
|
|
3195
3319
|
this.tree = tree;
|
|
3196
3320
|
this.parent = parent;
|
|
3197
|
-
this.key =
|
|
3198
|
-
this.title = "" + ((
|
|
3321
|
+
this.key = tree._calculateKey(data, parent);
|
|
3322
|
+
this.title = "" + ((_a = data.title) !== null && _a !== void 0 ? _a : "<" + this.key + ">");
|
|
3199
3323
|
this.expanded = !!data.expanded;
|
|
3200
3324
|
this.lazy = !!data.lazy;
|
|
3201
3325
|
// We set the following node properties only if a matching data value is
|
|
@@ -3316,8 +3440,14 @@
|
|
|
3316
3440
|
const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
|
|
3317
3441
|
for (const child of nodeData) {
|
|
3318
3442
|
const subChildren = child.children;
|
|
3443
|
+
// Remove children property from source data because it should not be
|
|
3444
|
+
// passed to the constructor of WunderbaumNode:
|
|
3319
3445
|
delete child.children;
|
|
3320
3446
|
const n = new WunderbaumNode(tree, this, child);
|
|
3447
|
+
// Set `children` property again, so it can be used in `reload()`
|
|
3448
|
+
if (subChildren != null) {
|
|
3449
|
+
child.children = subChildren;
|
|
3450
|
+
}
|
|
3321
3451
|
if (forceExpand && !n.isUnloaded()) {
|
|
3322
3452
|
n.expanded = true;
|
|
3323
3453
|
}
|
|
@@ -3733,15 +3863,12 @@
|
|
|
3733
3863
|
}
|
|
3734
3864
|
return l;
|
|
3735
3865
|
}
|
|
3736
|
-
/** Return a string representing the
|
|
3866
|
+
/** Return a string representing the hierarchical node path, e.g. "a/b/c".
|
|
3737
3867
|
* @param includeSelf
|
|
3738
3868
|
* @param part property name or callback
|
|
3739
3869
|
* @param separator
|
|
3740
3870
|
*/
|
|
3741
3871
|
getPath(includeSelf = true, part = "title", separator = "/") {
|
|
3742
|
-
// includeSelf = includeSelf !== false;
|
|
3743
|
-
// part = part || "title";
|
|
3744
|
-
// separator = separator || "/";
|
|
3745
3872
|
let val;
|
|
3746
3873
|
const path = [];
|
|
3747
3874
|
const isFunc = typeof part === "function";
|
|
@@ -3756,7 +3883,7 @@
|
|
|
3756
3883
|
}, includeSelf);
|
|
3757
3884
|
return path.join(separator);
|
|
3758
3885
|
}
|
|
3759
|
-
/** Return the
|
|
3886
|
+
/** Return the preceding node (under the same parent) or null. */
|
|
3760
3887
|
getPrevSibling() {
|
|
3761
3888
|
const ac = this.parent.children;
|
|
3762
3889
|
const idx = ac.indexOf(this);
|
|
@@ -3785,7 +3912,7 @@
|
|
|
3785
3912
|
hasClass(className) {
|
|
3786
3913
|
return this.classes ? this.classes.has(className) : false;
|
|
3787
3914
|
}
|
|
3788
|
-
/** Return true if node
|
|
3915
|
+
/** Return true if node is the currently focused node. @since 0.9.0 */
|
|
3789
3916
|
hasFocus() {
|
|
3790
3917
|
return this.tree.focusNode === this;
|
|
3791
3918
|
}
|
|
@@ -3840,7 +3967,7 @@
|
|
|
3840
3967
|
* an expand operation is currently possible.
|
|
3841
3968
|
*/
|
|
3842
3969
|
isExpandable(andCollapsed = false) {
|
|
3843
|
-
// `false` is never expandable (
|
|
3970
|
+
// `false` is never expandable (unofficial)
|
|
3844
3971
|
if ((andCollapsed && this.expanded) || this.children === false) {
|
|
3845
3972
|
return false;
|
|
3846
3973
|
}
|
|
@@ -3895,7 +4022,7 @@
|
|
|
3895
4022
|
isParentOf(other) {
|
|
3896
4023
|
return other && other.parent === this;
|
|
3897
4024
|
}
|
|
3898
|
-
/**
|
|
4025
|
+
/** Return true if this node is partially loaded. @experimental */
|
|
3899
4026
|
isPartload() {
|
|
3900
4027
|
return !!this._partload;
|
|
3901
4028
|
}
|
|
@@ -3903,11 +4030,11 @@
|
|
|
3903
4030
|
isPartsel() {
|
|
3904
4031
|
return !this.selected && !!this._partsel;
|
|
3905
4032
|
}
|
|
3906
|
-
/** Return true if this node has DOM
|
|
4033
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3907
4034
|
isRadio() {
|
|
3908
4035
|
return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
|
|
3909
4036
|
}
|
|
3910
|
-
/** Return true if this node has DOM
|
|
4037
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3911
4038
|
isRendered() {
|
|
3912
4039
|
return !!this._rowElem;
|
|
3913
4040
|
}
|
|
@@ -4353,10 +4480,11 @@
|
|
|
4353
4480
|
* @param options
|
|
4354
4481
|
*/
|
|
4355
4482
|
async navigate(where, options) {
|
|
4483
|
+
var _a;
|
|
4356
4484
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
4357
|
-
|
|
4485
|
+
const navType = ((_a = KEY_TO_NAVIGATION_MAP[where]) !== null && _a !== void 0 ? _a : where);
|
|
4358
4486
|
// Otherwise activate or focus the related node
|
|
4359
|
-
const node = this.findRelatedNode(
|
|
4487
|
+
const node = this.findRelatedNode(navType);
|
|
4360
4488
|
if (!node) {
|
|
4361
4489
|
this.logWarn(`Could not find related node '${where}'.`);
|
|
4362
4490
|
return Promise.resolve(this);
|
|
@@ -4453,86 +4581,17 @@
|
|
|
4453
4581
|
renderColInfosById: renderColInfosById,
|
|
4454
4582
|
};
|
|
4455
4583
|
}
|
|
4456
|
-
_createIcon(
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
}
|
|
4462
|
-
else if (this._isLoading && showLoading) {
|
|
4463
|
-
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
4464
|
-
// display the 'loading' status with the i.wb-icon span
|
|
4465
|
-
icon = iconMap.loading;
|
|
4466
|
-
}
|
|
4467
|
-
if (icon === false) {
|
|
4468
|
-
return null; // explicitly disabled: don't try default icons
|
|
4469
|
-
}
|
|
4470
|
-
if (typeof icon === "string") ;
|
|
4471
|
-
else if (this.statusNodeType) {
|
|
4472
|
-
icon = iconMap[this.statusNodeType];
|
|
4473
|
-
}
|
|
4474
|
-
else if (this.expanded) {
|
|
4475
|
-
icon = iconMap.folderOpen;
|
|
4476
|
-
}
|
|
4477
|
-
else if (this.children) {
|
|
4478
|
-
icon = iconMap.folder;
|
|
4479
|
-
}
|
|
4480
|
-
else if (this.lazy) {
|
|
4481
|
-
icon = iconMap.folderLazy;
|
|
4482
|
-
}
|
|
4483
|
-
else {
|
|
4484
|
-
icon = iconMap.doc;
|
|
4485
|
-
}
|
|
4486
|
-
// this.log("_createIcon: " + icon);
|
|
4487
|
-
if (!icon) {
|
|
4488
|
-
iconSpan = document.createElement("i");
|
|
4489
|
-
iconSpan.className = "wb-icon";
|
|
4490
|
-
}
|
|
4491
|
-
else if (icon.indexOf("<") >= 0) {
|
|
4492
|
-
// HTML
|
|
4493
|
-
iconSpan = elemFromHtml(icon);
|
|
4494
|
-
}
|
|
4495
|
-
else if (TEST_IMG.test(icon)) {
|
|
4496
|
-
// Image URL
|
|
4497
|
-
iconSpan = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
|
|
4498
|
-
}
|
|
4499
|
-
else {
|
|
4500
|
-
// Class name
|
|
4501
|
-
iconSpan = document.createElement("i");
|
|
4502
|
-
iconSpan.className = "wb-icon " + icon;
|
|
4503
|
-
}
|
|
4504
|
-
if (replaceChild) {
|
|
4505
|
-
parentElem.replaceChild(iconSpan, replaceChild);
|
|
4506
|
-
}
|
|
4507
|
-
else {
|
|
4508
|
-
parentElem.appendChild(iconSpan);
|
|
4509
|
-
}
|
|
4510
|
-
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
4511
|
-
const cbRes = this._callEvent("iconBadge", { iconSpan: iconSpan });
|
|
4512
|
-
let badge = null;
|
|
4513
|
-
if (cbRes != null && cbRes !== false) {
|
|
4514
|
-
let classes = "";
|
|
4515
|
-
let tooltip = "";
|
|
4516
|
-
if (isPlainObject(cbRes)) {
|
|
4517
|
-
badge = "" + cbRes.badge;
|
|
4518
|
-
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
4519
|
-
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
4520
|
-
}
|
|
4521
|
-
else if (typeof cbRes === "number") {
|
|
4522
|
-
badge = "" + cbRes;
|
|
4584
|
+
_createIcon(parentElem, replaceChild, showLoading) {
|
|
4585
|
+
const iconElem = this.tree._createNodeIcon(this, showLoading, true);
|
|
4586
|
+
if (iconElem) {
|
|
4587
|
+
if (replaceChild) {
|
|
4588
|
+
parentElem.replaceChild(iconElem, replaceChild);
|
|
4523
4589
|
}
|
|
4524
4590
|
else {
|
|
4525
|
-
|
|
4526
|
-
}
|
|
4527
|
-
if (typeof badge === "string") {
|
|
4528
|
-
badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
|
|
4529
|
-
}
|
|
4530
|
-
if (badge) {
|
|
4531
|
-
iconSpan.append(badge);
|
|
4591
|
+
parentElem.appendChild(iconElem);
|
|
4532
4592
|
}
|
|
4533
4593
|
}
|
|
4534
|
-
|
|
4535
|
-
return iconSpan;
|
|
4594
|
+
return iconElem;
|
|
4536
4595
|
}
|
|
4537
4596
|
/**
|
|
4538
4597
|
* Create a whole new `<div class="wb-row">` element.
|
|
@@ -4587,7 +4646,7 @@
|
|
|
4587
4646
|
}
|
|
4588
4647
|
// Render the icon (show a 'loading' icon if we do not have an expander that
|
|
4589
4648
|
// we would prefer).
|
|
4590
|
-
const iconSpan = this._createIcon(
|
|
4649
|
+
const iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
|
|
4591
4650
|
if (iconSpan) {
|
|
4592
4651
|
ofsTitlePx += ICON_WIDTH;
|
|
4593
4652
|
}
|
|
@@ -4730,9 +4789,9 @@
|
|
|
4730
4789
|
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
4731
4790
|
const rowDiv = this._rowElem;
|
|
4732
4791
|
// Row markup already exists
|
|
4733
|
-
const
|
|
4734
|
-
const
|
|
4735
|
-
const
|
|
4792
|
+
const nodeSpan = rowDiv.querySelector("span.wb-node");
|
|
4793
|
+
const expanderElem = nodeSpan.querySelector("i.wb-expander");
|
|
4794
|
+
const checkboxElem = nodeSpan.querySelector("i.wb-checkbox");
|
|
4736
4795
|
const rowClasses = ["wb-row"];
|
|
4737
4796
|
this.expanded ? rowClasses.push("wb-expanded") : 0;
|
|
4738
4797
|
this.lazy ? rowClasses.push("wb-lazy") : 0;
|
|
@@ -4757,7 +4816,7 @@
|
|
|
4757
4816
|
if (typeInfo && typeInfo.classes) {
|
|
4758
4817
|
rowDiv.classList.add(...typeInfo.classes);
|
|
4759
4818
|
}
|
|
4760
|
-
if (
|
|
4819
|
+
if (expanderElem) {
|
|
4761
4820
|
let image = null;
|
|
4762
4821
|
if (this._isLoading) {
|
|
4763
4822
|
image = iconMap.loading;
|
|
@@ -4774,16 +4833,20 @@
|
|
|
4774
4833
|
image = iconMap.expanderLazy;
|
|
4775
4834
|
}
|
|
4776
4835
|
if (image == null) {
|
|
4777
|
-
|
|
4836
|
+
expanderElem.className = "wb-expander";
|
|
4837
|
+
expanderElem.classList.add("wb-indent");
|
|
4838
|
+
}
|
|
4839
|
+
else if (TEST_HTML.test(image)) {
|
|
4840
|
+
expanderElem.replaceWith(elemFromHtml(image));
|
|
4778
4841
|
}
|
|
4779
|
-
else if (
|
|
4780
|
-
|
|
4842
|
+
else if (TEST_FILE_PATH.test(image)) {
|
|
4843
|
+
expanderElem.style.backgroundImage = `url('${image}')`;
|
|
4781
4844
|
}
|
|
4782
4845
|
else {
|
|
4783
|
-
|
|
4846
|
+
expanderElem.className = "wb-expander " + image;
|
|
4784
4847
|
}
|
|
4785
4848
|
}
|
|
4786
|
-
if (
|
|
4849
|
+
if (checkboxElem) {
|
|
4787
4850
|
let cbclass = "wb-checkbox ";
|
|
4788
4851
|
if (this.isRadio()) {
|
|
4789
4852
|
cbclass += "wb-radio ";
|
|
@@ -4807,7 +4870,7 @@
|
|
|
4807
4870
|
cbclass += iconMap.checkUnchecked;
|
|
4808
4871
|
}
|
|
4809
4872
|
}
|
|
4810
|
-
|
|
4873
|
+
checkboxElem.className = cbclass;
|
|
4811
4874
|
}
|
|
4812
4875
|
// Fix active cell in cell-nav mode
|
|
4813
4876
|
if (!opts.isNew) {
|
|
@@ -4817,9 +4880,9 @@
|
|
|
4817
4880
|
colSpan.classList.remove("wb-error", "wb-invalid");
|
|
4818
4881
|
}
|
|
4819
4882
|
// Update icon (if not opts.isNew, which would rebuild markup anyway)
|
|
4820
|
-
const iconSpan =
|
|
4883
|
+
const iconSpan = nodeSpan.querySelector("i.wb-icon");
|
|
4821
4884
|
if (iconSpan) {
|
|
4822
|
-
this._createIcon(
|
|
4885
|
+
this._createIcon(nodeSpan, iconSpan, !expanderElem);
|
|
4823
4886
|
}
|
|
4824
4887
|
}
|
|
4825
4888
|
// Adjust column width
|
|
@@ -5124,6 +5187,32 @@
|
|
|
5124
5187
|
setKey(key, refKey) {
|
|
5125
5188
|
throw new Error("Not yet implemented");
|
|
5126
5189
|
}
|
|
5190
|
+
// /**
|
|
5191
|
+
// * Calculate a *stable*, unique key for this node from its refKey (or title).
|
|
5192
|
+
// * We also add information from the parent, because a refKey may occur multiple
|
|
5193
|
+
// * times in a tree.
|
|
5194
|
+
// */
|
|
5195
|
+
// calcUniqueKey() {
|
|
5196
|
+
// // Assuming that the parent's key was calculated the same way, we implicitly
|
|
5197
|
+
// // involve the whole refKey-path:
|
|
5198
|
+
// const s = this.key + (this.refKey || this.title);
|
|
5199
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5200
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5201
|
+
// const h1 = util.murmurHash3(s, true);
|
|
5202
|
+
// return "id_" + h1 + util.murmurHash3(h1 + s, true);
|
|
5203
|
+
// // const l = [];
|
|
5204
|
+
// // // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
5205
|
+
// // let node: WunderbaumNode = this;
|
|
5206
|
+
// // while (node.parent) {
|
|
5207
|
+
// // l.unshift(node.refKey || node.key);
|
|
5208
|
+
// // node = node.parent;
|
|
5209
|
+
// // }
|
|
5210
|
+
// // const path = l.join("/");
|
|
5211
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5212
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5213
|
+
// // const h1 = util.murmurHash3(path, true);
|
|
5214
|
+
// // return "id_" + h1 + util.murmurHash3(h1 + path, true);
|
|
5215
|
+
// }
|
|
5127
5216
|
/**
|
|
5128
5217
|
* Trigger a repaint, typically after a status or data change.
|
|
5129
5218
|
*
|
|
@@ -5155,6 +5244,23 @@
|
|
|
5155
5244
|
});
|
|
5156
5245
|
return nodeList;
|
|
5157
5246
|
}
|
|
5247
|
+
/**
|
|
5248
|
+
* Return an array of refKey values.
|
|
5249
|
+
*
|
|
5250
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
5251
|
+
* clones.
|
|
5252
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
5253
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
5254
|
+
*/
|
|
5255
|
+
getRefKeys(selected = false) {
|
|
5256
|
+
const refKeys = new Set();
|
|
5257
|
+
this.visit((node) => {
|
|
5258
|
+
if (node.refKey != null && (!selected || node.selected)) {
|
|
5259
|
+
refKeys.add(node.refKey);
|
|
5260
|
+
}
|
|
5261
|
+
});
|
|
5262
|
+
return Array.from(refKeys);
|
|
5263
|
+
}
|
|
5158
5264
|
/** Toggle the check/uncheck state. */
|
|
5159
5265
|
toggleSelected(options) {
|
|
5160
5266
|
let flag = this.isSelected();
|
|
@@ -5334,9 +5440,11 @@
|
|
|
5334
5440
|
if (selectMode === "hier") {
|
|
5335
5441
|
this.fixSelection3AfterClick();
|
|
5336
5442
|
}
|
|
5337
|
-
else if (selectMode === "single") {
|
|
5443
|
+
else if (selectMode === "single" && flag) {
|
|
5338
5444
|
tree.visit((n) => {
|
|
5339
|
-
n
|
|
5445
|
+
if (n !== this) {
|
|
5446
|
+
n.selected = false;
|
|
5447
|
+
}
|
|
5340
5448
|
});
|
|
5341
5449
|
}
|
|
5342
5450
|
}
|
|
@@ -5368,7 +5476,7 @@
|
|
|
5368
5476
|
assert(data.statusNodeType, "Not a status node");
|
|
5369
5477
|
assert(!firstChild || !firstChild.isStatusNode(), "Child must not be a status node");
|
|
5370
5478
|
statusNode = this.addNode(data, "prependChild");
|
|
5371
|
-
statusNode.match =
|
|
5479
|
+
statusNode.match = -1; // Mark as 'match' to avoid hiding
|
|
5372
5480
|
tree.update(ChangeType.structure);
|
|
5373
5481
|
return statusNode;
|
|
5374
5482
|
};
|
|
@@ -5438,30 +5546,16 @@
|
|
|
5438
5546
|
this.tooltip = tooltip;
|
|
5439
5547
|
this.update();
|
|
5440
5548
|
}
|
|
5441
|
-
_sortChildren(cmp, deep) {
|
|
5442
|
-
const cl = this.children;
|
|
5443
|
-
if (!cl) {
|
|
5444
|
-
return;
|
|
5445
|
-
}
|
|
5446
|
-
cl.sort(cmp);
|
|
5447
|
-
if (deep) {
|
|
5448
|
-
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5449
|
-
if (cl[i].children) {
|
|
5450
|
-
cl[i]._sortChildren(cmp, deep);
|
|
5451
|
-
}
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
}
|
|
5455
5549
|
/**
|
|
5456
5550
|
* Sort child list by title or custom criteria.
|
|
5457
5551
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
5458
5552
|
* (defaults to sorting by title).
|
|
5459
5553
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
5554
|
+
* @deprecated use {@link sort}
|
|
5460
5555
|
*/
|
|
5461
5556
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
5462
|
-
this.
|
|
5463
|
-
this.
|
|
5464
|
-
// this.triggerModify("sort"); // TODO
|
|
5557
|
+
this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
|
|
5558
|
+
return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
|
|
5465
5559
|
}
|
|
5466
5560
|
/**
|
|
5467
5561
|
* Renumber nodes `_nativeIndex`. This is useful to allow to restore the
|
|
@@ -5483,74 +5577,142 @@
|
|
|
5483
5577
|
/**
|
|
5484
5578
|
* Convenience method to implement column sorting.
|
|
5485
5579
|
* @since 0.11.0
|
|
5580
|
+
* @deprecated use {@link sort}
|
|
5486
5581
|
*/
|
|
5487
5582
|
sortByProperty(options) {
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5583
|
+
this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
|
|
5584
|
+
return this.sort(options);
|
|
5585
|
+
}
|
|
5586
|
+
/**
|
|
5587
|
+
* Implement column sorting.
|
|
5588
|
+
* @since 0.14.0
|
|
5589
|
+
*/
|
|
5590
|
+
sort(options) {
|
|
5591
|
+
const tree = this.tree;
|
|
5592
|
+
let { propName = undefined, deep = true, key = undefined, order = undefined, caseInsensitive = true, cmp = undefined,
|
|
5593
|
+
// Support click on column sort header:
|
|
5594
|
+
updateColInfo = false, nativeOrderPropName = "_nativeIndex", colId = undefined, } = options;
|
|
5595
|
+
propName !== null && propName !== void 0 ? propName : (propName = colId);
|
|
5596
|
+
if (propName === "*") {
|
|
5597
|
+
propName = "title";
|
|
5598
|
+
}
|
|
5599
|
+
const isFolder = tree.options.sortFoldersFirst === true
|
|
5600
|
+
? (node) => node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
|
|
5601
|
+
: tree.options.sortFoldersFirst;
|
|
5492
5602
|
if (updateColInfo) {
|
|
5493
|
-
colDef = this.tree["_columnsById"][options.colId];
|
|
5603
|
+
const colDef = this.tree["_columnsById"][options.colId];
|
|
5494
5604
|
assert(colDef, `Invalid colId specified: ${options.colId}`);
|
|
5495
|
-
order =
|
|
5496
|
-
(_a = options.order) !== null && _a !== void 0 ? _a : rotate(colDef.sortOrder, ["asc", "desc", undefined]);
|
|
5605
|
+
order !== null && order !== void 0 ? order : (order = rotate(colDef.sortOrder, ["asc", "desc", undefined]));
|
|
5497
5606
|
for (const col of this.tree.columns) {
|
|
5498
5607
|
col.sortOrder = col === colDef ? order : undefined;
|
|
5499
5608
|
}
|
|
5609
|
+
if (order === undefined) {
|
|
5610
|
+
propName = nativeOrderPropName;
|
|
5611
|
+
order = "asc";
|
|
5612
|
+
}
|
|
5500
5613
|
this.tree.update(ChangeType.colStructure);
|
|
5501
5614
|
}
|
|
5502
5615
|
else {
|
|
5503
|
-
|
|
5616
|
+
propName !== null && propName !== void 0 ? propName : (propName = "title");
|
|
5617
|
+
order !== null && order !== void 0 ? order : (order = "asc");
|
|
5618
|
+
}
|
|
5619
|
+
this.logDebug(`sort(), propName=${propName}, ${order}`, options);
|
|
5620
|
+
assert(propName || cmp || key, "No `propName` or `key` specified");
|
|
5621
|
+
// Define a key callback from the parameters we have
|
|
5622
|
+
if (key == null && cmp == null) {
|
|
5623
|
+
key = (node) => {
|
|
5624
|
+
let val;
|
|
5625
|
+
if (NODE_DICT_PROPS.has(propName)) {
|
|
5626
|
+
val = node[propName];
|
|
5627
|
+
}
|
|
5628
|
+
else {
|
|
5629
|
+
val = node.data[propName];
|
|
5630
|
+
}
|
|
5631
|
+
if (caseInsensitive && typeof val === "string") {
|
|
5632
|
+
val = val.toLowerCase();
|
|
5633
|
+
}
|
|
5634
|
+
return val;
|
|
5635
|
+
};
|
|
5504
5636
|
}
|
|
5505
|
-
|
|
5506
|
-
if (
|
|
5507
|
-
|
|
5637
|
+
// Define a compare callback that uses the key callback
|
|
5638
|
+
if (cmp) {
|
|
5639
|
+
assert(!key, "`key` and `cmp` are mutually exclusive");
|
|
5640
|
+
tree.logDeprecate("SortOptions.cmp", {
|
|
5641
|
+
since: "0.14.0",
|
|
5642
|
+
hint: "use the `key` callback instead",
|
|
5643
|
+
});
|
|
5508
5644
|
}
|
|
5509
|
-
|
|
5510
|
-
propName
|
|
5511
|
-
|
|
5645
|
+
else {
|
|
5646
|
+
if (options.propName || options.caseInsensitive) {
|
|
5647
|
+
tree.logWarn("sort(): ignoring propName, caseInsensitive");
|
|
5648
|
+
}
|
|
5649
|
+
cmp = (a, b) => {
|
|
5650
|
+
if (isFolder) {
|
|
5651
|
+
const isFolderA = isFolder(a);
|
|
5652
|
+
if (isFolderA !== isFolder(b)) {
|
|
5653
|
+
return isFolderA ? -1 : 1;
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
let x = key(a);
|
|
5657
|
+
let y = key(b);
|
|
5658
|
+
// Assure we have reasonable comparisons with null values:
|
|
5659
|
+
if (x == null) {
|
|
5660
|
+
x = typeof y === "string" ? "" : 0;
|
|
5661
|
+
}
|
|
5662
|
+
else if (typeof x === "boolean") {
|
|
5663
|
+
x = x ? 1 : 0;
|
|
5664
|
+
}
|
|
5665
|
+
if (y == null) {
|
|
5666
|
+
y = typeof x === "string" ? "" : 0;
|
|
5667
|
+
}
|
|
5668
|
+
else if (typeof y === "boolean") {
|
|
5669
|
+
y = y ? 1 : 0;
|
|
5670
|
+
}
|
|
5671
|
+
if (order === "desc") {
|
|
5672
|
+
return x === y ? 0 : x > y ? -1 : 1;
|
|
5673
|
+
}
|
|
5674
|
+
return x === y ? 0 : x > y ? 1 : -1;
|
|
5675
|
+
};
|
|
5512
5676
|
}
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
let av, bv;
|
|
5517
|
-
if (NODE_DICT_PROPS.has(propName)) {
|
|
5518
|
-
av = a[propName];
|
|
5519
|
-
bv = b[propName];
|
|
5520
|
-
}
|
|
5521
|
-
else {
|
|
5522
|
-
av = a.data[propName];
|
|
5523
|
-
bv = b.data[propName];
|
|
5524
|
-
}
|
|
5525
|
-
if (av == null && bv == null) {
|
|
5526
|
-
return 0;
|
|
5527
|
-
}
|
|
5528
|
-
if (av == null) {
|
|
5529
|
-
av = typeof bv === "string" ? "" : 0;
|
|
5530
|
-
}
|
|
5531
|
-
else if (typeof av === "boolean") {
|
|
5532
|
-
av = av ? 1 : 0;
|
|
5533
|
-
}
|
|
5534
|
-
if (bv == null) {
|
|
5535
|
-
bv = typeof av === "string" ? "" : 0;
|
|
5536
|
-
}
|
|
5537
|
-
else if (typeof bv === "boolean") {
|
|
5538
|
-
bv = bv ? 1 : 0;
|
|
5677
|
+
function _sortChildren(cl) {
|
|
5678
|
+
if (!cl) {
|
|
5679
|
+
return;
|
|
5539
5680
|
}
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5681
|
+
cl.sort(cmp);
|
|
5682
|
+
if (deep) {
|
|
5683
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5684
|
+
if (cl[i].children) {
|
|
5685
|
+
_sortChildren(cl[i].children);
|
|
5686
|
+
}
|
|
5546
5687
|
}
|
|
5547
5688
|
}
|
|
5548
|
-
|
|
5549
|
-
|
|
5689
|
+
}
|
|
5690
|
+
if (this.children) {
|
|
5691
|
+
_sortChildren(this.children);
|
|
5692
|
+
}
|
|
5693
|
+
this.tree.update(ChangeType.structure);
|
|
5694
|
+
// this.triggerModify("sort"); // TODO
|
|
5695
|
+
}
|
|
5696
|
+
/**
|
|
5697
|
+
* Re-apply current sorting if any (use after lazy load).
|
|
5698
|
+
* Example:
|
|
5699
|
+
* ```js
|
|
5700
|
+
* load: function (e) {
|
|
5701
|
+
* // Whe loading a lazy branch, apply current sort order if any
|
|
5702
|
+
* e.node.resort();
|
|
5703
|
+
* },
|
|
5704
|
+
* ```
|
|
5705
|
+
* @since 0.14.0
|
|
5706
|
+
*/
|
|
5707
|
+
resort(options = {}) {
|
|
5708
|
+
for (const colDef of this.tree.columns) {
|
|
5709
|
+
if (colDef.sortOrder) {
|
|
5710
|
+
options.colId = colDef.id;
|
|
5711
|
+
options.order = colDef.sortOrder;
|
|
5712
|
+
this.sort(options);
|
|
5713
|
+
break;
|
|
5550
5714
|
}
|
|
5551
|
-
|
|
5552
|
-
};
|
|
5553
|
-
return this.sortChildren(cmp, deep);
|
|
5715
|
+
}
|
|
5554
5716
|
}
|
|
5555
5717
|
/**
|
|
5556
5718
|
* Trigger `modifyChild` event on a parent to signal that a child was modified.
|
|
@@ -5587,7 +5749,8 @@
|
|
|
5587
5749
|
* @param {function} callback the callback function.
|
|
5588
5750
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
5589
5751
|
* its children only.
|
|
5590
|
-
* @see
|
|
5752
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
5753
|
+
* @see {@link Wunderbaum.visit}.
|
|
5591
5754
|
*/
|
|
5592
5755
|
visit(callback, includeSelf = false) {
|
|
5593
5756
|
let res = true;
|
|
@@ -5660,7 +5823,7 @@
|
|
|
5660
5823
|
/*!
|
|
5661
5824
|
* Wunderbaum - ext-edit
|
|
5662
5825
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
5663
|
-
* v0.
|
|
5826
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5664
5827
|
*/
|
|
5665
5828
|
// const START_MARKER = "\uFFF7";
|
|
5666
5829
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -5895,7 +6058,7 @@
|
|
|
5895
6058
|
newValue = newValue.trim();
|
|
5896
6059
|
}
|
|
5897
6060
|
if (!node) {
|
|
5898
|
-
this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
6061
|
+
// this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
5899
6062
|
return;
|
|
5900
6063
|
}
|
|
5901
6064
|
node.logDebug(`stopEditTitle(${apply})`, options, focusElem, newValue);
|
|
@@ -5979,7 +6142,7 @@
|
|
|
5979
6142
|
newNode.setClass("wb-edit-new");
|
|
5980
6143
|
this.relatedNode = node;
|
|
5981
6144
|
// Don't filter new nodes:
|
|
5982
|
-
newNode.match =
|
|
6145
|
+
newNode.match = -1;
|
|
5983
6146
|
newNode.makeVisible({ noAnimation: true }).then(() => {
|
|
5984
6147
|
this.startEditTitle(newNode);
|
|
5985
6148
|
});
|
|
@@ -5995,8 +6158,8 @@
|
|
|
5995
6158
|
* https://github.com/mar10/wunderbaum
|
|
5996
6159
|
*
|
|
5997
6160
|
* Released under the MIT license.
|
|
5998
|
-
* @version v0.
|
|
5999
|
-
* @date
|
|
6161
|
+
* @version v0.14.0
|
|
6162
|
+
* @date Fri, 20 Mar 2026 16:58:31 GMT
|
|
6000
6163
|
*/
|
|
6001
6164
|
// import "./wunderbaum.scss";
|
|
6002
6165
|
class WbSystemRoot extends WunderbaumNode {
|
|
@@ -6045,18 +6208,21 @@
|
|
|
6045
6208
|
this._disableUpdateIgnoreCount = 0;
|
|
6046
6209
|
this._activeNode = null;
|
|
6047
6210
|
this._focusNode = null;
|
|
6211
|
+
this._initialSource = null;
|
|
6048
6212
|
/** Shared properties, referenced by `node.type`. */
|
|
6049
6213
|
this.types = {};
|
|
6050
6214
|
/** List of column definitions. */
|
|
6051
|
-
this.columns = [];
|
|
6215
|
+
this.columns = [];
|
|
6052
6216
|
this._columnsById = {};
|
|
6053
6217
|
// Modification Status
|
|
6054
6218
|
this.pendingChangeTypes = new Set();
|
|
6055
6219
|
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
6056
6220
|
this._util = util;
|
|
6057
6221
|
// --- SELECT ---
|
|
6058
|
-
// /** @internal */
|
|
6059
6222
|
// public selectRangeAnchor: WunderbaumNode | null = null;
|
|
6223
|
+
// --- BREADCRUMB ---
|
|
6224
|
+
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
6225
|
+
this.breadcrumb = null;
|
|
6060
6226
|
// --- FILTER ---
|
|
6061
6227
|
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
6062
6228
|
this.filterMode = null;
|
|
@@ -6071,45 +6237,57 @@
|
|
|
6071
6237
|
this.lastQuicksearchTerm = "";
|
|
6072
6238
|
// --- EDIT ---
|
|
6073
6239
|
this.lastClickTime = 0;
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6240
|
+
// Set default options and merge with user options
|
|
6241
|
+
const initOptions = Object.assign({
|
|
6242
|
+
id: undefined,
|
|
6243
|
+
source: [], // URL for GET/PUT, Ajax options, or callback
|
|
6244
|
+
element: unsafeCast(null),
|
|
6078
6245
|
debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
|
|
6079
6246
|
header: null, // Show/hide header (pass bool or string)
|
|
6080
|
-
// headerHeightPx: ROW_HEIGHT,
|
|
6081
6247
|
rowHeightPx: DEFAULT_ROW_HEIGHT,
|
|
6082
6248
|
iconMap: "bootstrap",
|
|
6083
|
-
columns: null,
|
|
6084
|
-
types:
|
|
6085
|
-
// escapeTitles: true,
|
|
6249
|
+
columns: [], //util.unsafeCast<ColumnDefinitionList>(null),
|
|
6250
|
+
types: {},
|
|
6086
6251
|
enabled: true,
|
|
6087
6252
|
fixedCol: false,
|
|
6088
6253
|
showSpinner: false,
|
|
6089
6254
|
checkbox: false,
|
|
6090
6255
|
minExpandLevel: 0,
|
|
6091
6256
|
emptyChildListExpandable: false,
|
|
6092
|
-
// updateThrottleWait: 200,
|
|
6093
6257
|
skeleton: false,
|
|
6094
|
-
|
|
6258
|
+
autoCollapse: false,
|
|
6259
|
+
adjustHeight: true,
|
|
6260
|
+
connectTopBreadcrumb: null,
|
|
6261
|
+
columnsFilterable: false,
|
|
6262
|
+
columnsMenu: false,
|
|
6263
|
+
columnsResizable: false,
|
|
6264
|
+
columnsSortable: false,
|
|
6095
6265
|
selectMode: "multi", // SelectModeType
|
|
6266
|
+
scrollIntoViewOnExpandClick: true,
|
|
6267
|
+
// --- Extensions (actually set by exensions on init)
|
|
6268
|
+
dnd: unsafeCast(null),
|
|
6269
|
+
edit: unsafeCast(null),
|
|
6270
|
+
filter: unsafeCast(null),
|
|
6096
6271
|
// --- KeyNav ---
|
|
6097
|
-
navigationModeOption: null,
|
|
6272
|
+
navigationModeOption: unsafeCast(null),
|
|
6098
6273
|
quicksearch: true,
|
|
6099
6274
|
// --- Events ---
|
|
6100
|
-
iconBadge: null,
|
|
6101
|
-
change: null,
|
|
6102
|
-
//
|
|
6103
|
-
error: null,
|
|
6104
|
-
receive: null,
|
|
6275
|
+
// iconBadge: null,
|
|
6276
|
+
// change: null,
|
|
6277
|
+
// ...
|
|
6105
6278
|
// --- Strings ---
|
|
6106
6279
|
strings: {
|
|
6107
6280
|
loadError: "Error",
|
|
6108
6281
|
loading: "Loading...",
|
|
6109
|
-
// loading: "Loading…",
|
|
6110
6282
|
noData: "No data",
|
|
6283
|
+
breadcrumbDelimiter: " » ",
|
|
6284
|
+
queryResult: "Found ${matches} of ${count}",
|
|
6285
|
+
noMatch: "No results",
|
|
6286
|
+
matchIndex: "${match} of ${matches}",
|
|
6111
6287
|
},
|
|
6112
|
-
}, options)
|
|
6288
|
+
}, options);
|
|
6289
|
+
const opts = initOptions;
|
|
6290
|
+
this.options = opts;
|
|
6113
6291
|
const readyDeferred = new Deferred();
|
|
6114
6292
|
this.ready = readyDeferred.promise();
|
|
6115
6293
|
let readyOk = false;
|
|
@@ -6136,7 +6314,8 @@
|
|
|
6136
6314
|
this._callEvent("init", { error: err });
|
|
6137
6315
|
}
|
|
6138
6316
|
});
|
|
6139
|
-
this.id =
|
|
6317
|
+
this.id = initOptions.id || "wb_" + ++Wunderbaum.sequence;
|
|
6318
|
+
delete initOptions.id;
|
|
6140
6319
|
this.root = new WbSystemRoot(this);
|
|
6141
6320
|
this._registerExtension(new KeynavExtension(this));
|
|
6142
6321
|
this._registerExtension(new EditExtension(this));
|
|
@@ -6146,19 +6325,20 @@
|
|
|
6146
6325
|
this._registerExtension(new LoggerExtension(this));
|
|
6147
6326
|
this._updateViewportThrottled = adaptiveThrottle(this._updateViewportImmediately.bind(this), {});
|
|
6148
6327
|
// --- Evaluate options
|
|
6149
|
-
this.columns =
|
|
6150
|
-
delete
|
|
6328
|
+
this.columns = initOptions.columns || [];
|
|
6329
|
+
delete initOptions.columns;
|
|
6151
6330
|
if (!this.columns || !this.columns.length) {
|
|
6152
6331
|
const title = typeof opts.header === "string" ? opts.header : this.id;
|
|
6153
6332
|
this.columns = [{ id: "*", title: title, width: "*" }];
|
|
6154
6333
|
}
|
|
6155
|
-
if (
|
|
6156
|
-
this.setTypes(
|
|
6334
|
+
if (initOptions.types) {
|
|
6335
|
+
this.setTypes(initOptions.types, true);
|
|
6157
6336
|
}
|
|
6158
|
-
delete
|
|
6337
|
+
delete initOptions.types;
|
|
6159
6338
|
// --- Create Markup
|
|
6160
|
-
this.element = elemFromSelector(
|
|
6161
|
-
assert(!!this.element, `Invalid 'element' option: ${
|
|
6339
|
+
this.element = elemFromSelector(initOptions.element);
|
|
6340
|
+
assert(!!this.element, `Invalid 'element' option: ${initOptions.element}`);
|
|
6341
|
+
delete initOptions.element;
|
|
6162
6342
|
this.element.classList.add("wunderbaum");
|
|
6163
6343
|
if (!this.element.getAttribute("tabindex")) {
|
|
6164
6344
|
this.element.tabIndex = 0;
|
|
@@ -6213,6 +6393,19 @@
|
|
|
6213
6393
|
this.headerElement =
|
|
6214
6394
|
this.element.querySelector("div.wb-header");
|
|
6215
6395
|
this.element.classList.toggle("wb-grid", this.columns.length > 1);
|
|
6396
|
+
if (this.options.connectTopBreadcrumb) {
|
|
6397
|
+
this.breadcrumb = elemFromSelector(this.options.connectTopBreadcrumb);
|
|
6398
|
+
assert(!this.breadcrumb || this.breadcrumb.innerHTML != null, `Invalid 'connectTopBreadcrumb' option: ${this.breadcrumb}.`);
|
|
6399
|
+
this.breadcrumb.addEventListener("click", (e) => {
|
|
6400
|
+
// const node = Wunderbaum.getNode(e)!;
|
|
6401
|
+
const elem = e.target;
|
|
6402
|
+
if (elem && elem.matches("a.wb-breadcrumb")) {
|
|
6403
|
+
const node = this.keyMap.get(elem.dataset.key);
|
|
6404
|
+
node === null || node === void 0 ? void 0 : node.setActive();
|
|
6405
|
+
e.preventDefault();
|
|
6406
|
+
}
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6216
6409
|
this._initExtensions();
|
|
6217
6410
|
// --- apply initial options
|
|
6218
6411
|
["enabled", "fixedCol"].forEach((optName) => {
|
|
@@ -6221,11 +6414,11 @@
|
|
|
6221
6414
|
}
|
|
6222
6415
|
});
|
|
6223
6416
|
// --- Load initial data
|
|
6224
|
-
if (
|
|
6417
|
+
if (initOptions.source) {
|
|
6225
6418
|
if (opts.showSpinner) {
|
|
6226
6419
|
this.nodeListElement.innerHTML = `<progress class='spinner'>${opts.strings.loading}</progress>`;
|
|
6227
6420
|
}
|
|
6228
|
-
this.load(
|
|
6421
|
+
this.load(initOptions.source)
|
|
6229
6422
|
.then(() => {
|
|
6230
6423
|
// The source may have defined columns, so we may adjust the nav mode
|
|
6231
6424
|
if (opts.navigationModeOption == null) {
|
|
@@ -6258,15 +6451,18 @@
|
|
|
6258
6451
|
// has a wrong value at start???
|
|
6259
6452
|
this.update(ChangeType.any);
|
|
6260
6453
|
// --- Bind listeners
|
|
6261
|
-
this.
|
|
6262
|
-
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6263
|
-
this.update(ChangeType.scroll);
|
|
6264
|
-
});
|
|
6454
|
+
this._registerEventHandlers();
|
|
6265
6455
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
6266
6456
|
// this.log("ResizeObserver: Size changed", entries);
|
|
6267
6457
|
this.update(ChangeType.resize);
|
|
6268
6458
|
});
|
|
6269
6459
|
this.resizeObserver.observe(this.element);
|
|
6460
|
+
}
|
|
6461
|
+
_registerEventHandlers() {
|
|
6462
|
+
this.element.addEventListener("scroll", (e) => {
|
|
6463
|
+
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6464
|
+
this.update(ChangeType.scroll);
|
|
6465
|
+
});
|
|
6270
6466
|
onEvent(this.element, "click", ".wb-button,.wb-col-icon", (e) => {
|
|
6271
6467
|
var _a, _b;
|
|
6272
6468
|
const info = Wunderbaum.getEventInfo(e);
|
|
@@ -6282,9 +6478,6 @@
|
|
|
6282
6478
|
const node = info.node;
|
|
6283
6479
|
const mouseEvent = e;
|
|
6284
6480
|
// this.log("click", info);
|
|
6285
|
-
// if (this._selectRange(info) === false) {
|
|
6286
|
-
// return;
|
|
6287
|
-
// }
|
|
6288
6481
|
if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
|
|
6289
6482
|
this.lastClickTime = Date.now();
|
|
6290
6483
|
return false;
|
|
@@ -6303,20 +6496,22 @@
|
|
|
6303
6496
|
(!slowClickDelay || Date.now() - this.lastClickTime < slowClickDelay)) {
|
|
6304
6497
|
node.startEditTitle();
|
|
6305
6498
|
}
|
|
6306
|
-
if (info.colIdx >= 0) {
|
|
6307
|
-
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6308
|
-
}
|
|
6309
|
-
else {
|
|
6310
|
-
node.setActive(true, { event: e });
|
|
6311
|
-
}
|
|
6312
6499
|
if (info.region === NodeRegion.expander) {
|
|
6313
6500
|
node.setExpanded(!node.isExpanded(), {
|
|
6314
|
-
scrollIntoView: options.scrollIntoViewOnExpandClick !== false,
|
|
6501
|
+
scrollIntoView: this.options.scrollIntoViewOnExpandClick !== false,
|
|
6315
6502
|
});
|
|
6316
6503
|
}
|
|
6317
6504
|
else if (info.region === NodeRegion.checkbox) {
|
|
6318
6505
|
node.toggleSelected();
|
|
6319
6506
|
}
|
|
6507
|
+
else {
|
|
6508
|
+
if (info.colIdx >= 0) {
|
|
6509
|
+
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6510
|
+
}
|
|
6511
|
+
else {
|
|
6512
|
+
node.setActive(true, { event: e });
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6320
6515
|
}
|
|
6321
6516
|
this.lastClickTime = Date.now();
|
|
6322
6517
|
});
|
|
@@ -6352,7 +6547,7 @@
|
|
|
6352
6547
|
const targetNode = Wunderbaum.getNode(e);
|
|
6353
6548
|
this._callEvent("focus", { flag: flag, event: e });
|
|
6354
6549
|
if (flag && this.isRowNav() && !this.isEditingTitle()) {
|
|
6355
|
-
if (
|
|
6550
|
+
if (this.options.navigationModeOption === NavModeEnum.row) {
|
|
6356
6551
|
targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
|
|
6357
6552
|
}
|
|
6358
6553
|
else {
|
|
@@ -6419,11 +6614,12 @@
|
|
|
6419
6614
|
}
|
|
6420
6615
|
/**
|
|
6421
6616
|
* Return the icon-function -> icon-definition mapping.
|
|
6617
|
+
* @deprecated Use {@link Wunderbaum.iconMaps}
|
|
6422
6618
|
*/
|
|
6423
6619
|
get iconMap() {
|
|
6424
6620
|
const map = this.options.iconMap;
|
|
6425
6621
|
if (typeof map === "string") {
|
|
6426
|
-
return
|
|
6622
|
+
return defaultIconMaps[map];
|
|
6427
6623
|
}
|
|
6428
6624
|
return map;
|
|
6429
6625
|
}
|
|
@@ -6476,7 +6672,38 @@
|
|
|
6476
6672
|
ext.init();
|
|
6477
6673
|
}
|
|
6478
6674
|
}
|
|
6479
|
-
/**
|
|
6675
|
+
/**
|
|
6676
|
+
* Calculate a *stable*, unique key for a node from its refKey (or title).
|
|
6677
|
+
* We also add information from the parent, because a refKey may occur multiple
|
|
6678
|
+
* times in a tree (but not as child of the same parent).
|
|
6679
|
+
* @internal
|
|
6680
|
+
*/
|
|
6681
|
+
_calculateKey(data, parent) {
|
|
6682
|
+
if (data.key) {
|
|
6683
|
+
// Always use an explicitly passed key
|
|
6684
|
+
return data.key;
|
|
6685
|
+
}
|
|
6686
|
+
// Auto-keys are optional, use a monotonic counter by default:
|
|
6687
|
+
if (!this.options.autoKeys) {
|
|
6688
|
+
return "" + ++WunderbaumNode.sequence;
|
|
6689
|
+
}
|
|
6690
|
+
// Add the parent's key to the hash. Assuming this was generated by the
|
|
6691
|
+
// same algorithm, this should incorporate the whole path:
|
|
6692
|
+
const s = (parent ? parent.key : "") + (data.refKey || data.title);
|
|
6693
|
+
// 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
6694
|
+
// https://security.stackexchange.com/q/209882/207588
|
|
6695
|
+
const h1 = murmurHash3(s, true);
|
|
6696
|
+
let key = "id_" + h1 + murmurHash3(h1 + s, true);
|
|
6697
|
+
// Check for collisions
|
|
6698
|
+
// (Most likely if the same title occurs multiple in the same parent).
|
|
6699
|
+
const existingNode = this.keyMap.get(key);
|
|
6700
|
+
if (existingNode) {
|
|
6701
|
+
key += "." + ++Wunderbaum.sequence;
|
|
6702
|
+
this.logWarn(`Node with existing key: '${existingNode}', using ${key}.`, data);
|
|
6703
|
+
}
|
|
6704
|
+
return key;
|
|
6705
|
+
}
|
|
6706
|
+
/** Add node to tree's bookkeeping data structures. @internal */
|
|
6480
6707
|
_registerNode(node) {
|
|
6481
6708
|
const key = node.key;
|
|
6482
6709
|
assert(key != null, `Missing key: '${node}'.`);
|
|
@@ -6493,7 +6720,7 @@
|
|
|
6493
6720
|
}
|
|
6494
6721
|
}
|
|
6495
6722
|
}
|
|
6496
|
-
/** Remove node from tree's bookkeeping data structures. */
|
|
6723
|
+
/** Remove node from tree's bookkeeping data structures. @internal */
|
|
6497
6724
|
_unregisterNode(node) {
|
|
6498
6725
|
// Remove refKey reference from map (if any)
|
|
6499
6726
|
const rk = node.refKey;
|
|
@@ -6576,7 +6803,10 @@
|
|
|
6576
6803
|
});
|
|
6577
6804
|
return node;
|
|
6578
6805
|
}
|
|
6579
|
-
/** Return the topmost visible node in the viewport.
|
|
6806
|
+
/** Return the topmost visible node in the viewport.
|
|
6807
|
+
* @param complete If `false`, the node is considered visible if at least one
|
|
6808
|
+
* pixel is visible.
|
|
6809
|
+
*/
|
|
6580
6810
|
getTopmostVpNode(complete = true) {
|
|
6581
6811
|
const rowHeight = this.options.rowHeightPx;
|
|
6582
6812
|
const gracePx = 1; // ignore subpixel scrolling
|
|
@@ -6609,7 +6839,7 @@
|
|
|
6609
6839
|
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
6610
6840
|
return this._getNodeByRowIdx(bottomIdx);
|
|
6611
6841
|
}
|
|
6612
|
-
/** Return
|
|
6842
|
+
/** Return preceding visible node in the viewport. */
|
|
6613
6843
|
_getPrevNodeInView(node, ofs = 1) {
|
|
6614
6844
|
this.visitRows((n) => {
|
|
6615
6845
|
node = n;
|
|
@@ -6620,13 +6850,18 @@
|
|
|
6620
6850
|
return node;
|
|
6621
6851
|
}
|
|
6622
6852
|
/** Return following visible node in the viewport. */
|
|
6623
|
-
_getNextNodeInView(node,
|
|
6853
|
+
_getNextNodeInView(node, options) {
|
|
6854
|
+
let ofs = (options === null || options === void 0 ? void 0 : options.ofs) || 1;
|
|
6855
|
+
const reverse = !!(options === null || options === void 0 ? void 0 : options.reverse);
|
|
6624
6856
|
this.visitRows((n) => {
|
|
6625
6857
|
node = n;
|
|
6858
|
+
if ((options === null || options === void 0 ? void 0 : options.cb) && options.cb(n)) {
|
|
6859
|
+
return false;
|
|
6860
|
+
}
|
|
6626
6861
|
if (ofs-- <= 0) {
|
|
6627
6862
|
return false;
|
|
6628
6863
|
}
|
|
6629
|
-
}, { reverse:
|
|
6864
|
+
}, { reverse: reverse, start: node || this.getActiveNode() });
|
|
6630
6865
|
return node;
|
|
6631
6866
|
}
|
|
6632
6867
|
/**
|
|
@@ -6746,9 +6981,11 @@
|
|
|
6746
6981
|
case "first":
|
|
6747
6982
|
case "last":
|
|
6748
6983
|
case "left":
|
|
6984
|
+
case "nextMatch":
|
|
6749
6985
|
case "pageDown":
|
|
6750
6986
|
case "pageUp":
|
|
6751
6987
|
case "parent":
|
|
6988
|
+
case "prevMatch":
|
|
6752
6989
|
case "right":
|
|
6753
6990
|
case "up":
|
|
6754
6991
|
return node.navigate(cmd);
|
|
@@ -6863,22 +7100,39 @@
|
|
|
6863
7100
|
/** Run code, but defer rendering of viewport until done.
|
|
6864
7101
|
*
|
|
6865
7102
|
* ```js
|
|
6866
|
-
* tree.runWithDeferredUpdate(() => {
|
|
6867
|
-
* return
|
|
7103
|
+
* const res = tree.runWithDeferredUpdate(() => {
|
|
7104
|
+
* return someFunctionThatWouldUpdateManyNodes();
|
|
6868
7105
|
* });
|
|
6869
7106
|
* ```
|
|
6870
7107
|
*/
|
|
6871
|
-
runWithDeferredUpdate(func
|
|
7108
|
+
runWithDeferredUpdate(func) {
|
|
6872
7109
|
try {
|
|
6873
7110
|
this.enableUpdate(false);
|
|
6874
7111
|
const res = func();
|
|
6875
|
-
assert(!(res instanceof Promise), `Promise return not allowed: ${res}`);
|
|
7112
|
+
assert(!(res instanceof Promise), `Promise return not allowed (see 'runWithDeferredUpdateAsync()'): ${res}`);
|
|
6876
7113
|
return res;
|
|
6877
7114
|
}
|
|
6878
7115
|
finally {
|
|
6879
7116
|
this.enableUpdate(true);
|
|
6880
7117
|
}
|
|
6881
7118
|
}
|
|
7119
|
+
/** Run code, but defer rendering of viewport until done.
|
|
7120
|
+
*
|
|
7121
|
+
* ```js
|
|
7122
|
+
* const res = await tree.runWithDeferredUpdate(async () => {
|
|
7123
|
+
* return someAsyncFunctionThatWouldUpdateManyNodes();
|
|
7124
|
+
* });
|
|
7125
|
+
* ```
|
|
7126
|
+
*/
|
|
7127
|
+
async runWithDeferredUpdateAsync(func) {
|
|
7128
|
+
try {
|
|
7129
|
+
this.enableUpdate(false);
|
|
7130
|
+
return await func();
|
|
7131
|
+
}
|
|
7132
|
+
finally {
|
|
7133
|
+
this.enableUpdate(true);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
6882
7136
|
/** Recursively expand all expandable nodes (triggers lazy load if needed). */
|
|
6883
7137
|
async expandAll(flag = true, options) {
|
|
6884
7138
|
await this.root.expandAll(flag, options);
|
|
@@ -6898,6 +7152,17 @@
|
|
|
6898
7152
|
getSelectedNodes(stopOnParents = false) {
|
|
6899
7153
|
return this.root.getSelectedNodes(stopOnParents);
|
|
6900
7154
|
}
|
|
7155
|
+
/**
|
|
7156
|
+
* Return an array of refKey values.
|
|
7157
|
+
*
|
|
7158
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
7159
|
+
* clones.
|
|
7160
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
7161
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
7162
|
+
*/
|
|
7163
|
+
getRefKeys(selected = false) {
|
|
7164
|
+
return this.root.getRefKeys(selected);
|
|
7165
|
+
}
|
|
6901
7166
|
/*
|
|
6902
7167
|
* Return an array of selected nodes.
|
|
6903
7168
|
*/
|
|
@@ -6940,6 +7205,11 @@
|
|
|
6940
7205
|
count(visible = false) {
|
|
6941
7206
|
return visible ? this.treeRowCount : this.keyMap.size;
|
|
6942
7207
|
}
|
|
7208
|
+
/** Return the number of *unique* nodes in the data model, i.e. unique `node.refKey`.
|
|
7209
|
+
*/
|
|
7210
|
+
countUnique() {
|
|
7211
|
+
return this.refKeyMap.size;
|
|
7212
|
+
}
|
|
6943
7213
|
/** @internal sanity check. */
|
|
6944
7214
|
_check() {
|
|
6945
7215
|
let i = 0;
|
|
@@ -6998,12 +7268,14 @@
|
|
|
6998
7268
|
* and wrap-around at the end.
|
|
6999
7269
|
* Used by quicksearch and keyboard navigation.
|
|
7000
7270
|
*/
|
|
7001
|
-
findNextNode(match, startNode) {
|
|
7271
|
+
findNextNode(match, startNode, reverse = false) {
|
|
7002
7272
|
//, visibleOnly) {
|
|
7003
7273
|
let res = null;
|
|
7004
7274
|
const firstNode = this.getFirstChild();
|
|
7275
|
+
// Last visible node (calculation is expensive, so do only if we need it):
|
|
7276
|
+
const lastNode = reverse ? this.findRelatedNode(firstNode, "last") : null;
|
|
7005
7277
|
const matcher = typeof match === "string" ? makeNodeTitleStartMatcher(match) : match;
|
|
7006
|
-
startNode = startNode || firstNode;
|
|
7278
|
+
startNode = startNode || (reverse ? lastNode : firstNode);
|
|
7007
7279
|
function _checkNode(n) {
|
|
7008
7280
|
// console.log("_check " + n)
|
|
7009
7281
|
if (matcher(n)) {
|
|
@@ -7016,12 +7288,14 @@
|
|
|
7016
7288
|
this.visitRows(_checkNode, {
|
|
7017
7289
|
start: startNode,
|
|
7018
7290
|
includeSelf: false,
|
|
7291
|
+
reverse: reverse,
|
|
7019
7292
|
});
|
|
7020
7293
|
// Wrap around search
|
|
7021
7294
|
if (!res && startNode !== firstNode) {
|
|
7022
7295
|
this.visitRows(_checkNode, {
|
|
7023
|
-
start: firstNode,
|
|
7296
|
+
start: reverse ? lastNode : firstNode,
|
|
7024
7297
|
includeSelf: true,
|
|
7298
|
+
reverse: reverse,
|
|
7025
7299
|
});
|
|
7026
7300
|
}
|
|
7027
7301
|
return res;
|
|
@@ -7088,7 +7362,7 @@
|
|
|
7088
7362
|
// }
|
|
7089
7363
|
break;
|
|
7090
7364
|
case "up":
|
|
7091
|
-
res = this.
|
|
7365
|
+
res = this._getNextNodeInView(node, { reverse: true });
|
|
7092
7366
|
break;
|
|
7093
7367
|
case "down":
|
|
7094
7368
|
res = this._getNextNodeInView(node);
|
|
@@ -7101,7 +7375,10 @@
|
|
|
7101
7375
|
res = bottomNode;
|
|
7102
7376
|
}
|
|
7103
7377
|
else {
|
|
7104
|
-
res = this._getNextNodeInView(node,
|
|
7378
|
+
res = this._getNextNodeInView(node, {
|
|
7379
|
+
reverse: false,
|
|
7380
|
+
ofs: pageSize,
|
|
7381
|
+
});
|
|
7105
7382
|
}
|
|
7106
7383
|
}
|
|
7107
7384
|
break;
|
|
@@ -7116,10 +7393,23 @@
|
|
|
7116
7393
|
res = topNode;
|
|
7117
7394
|
}
|
|
7118
7395
|
else {
|
|
7119
|
-
res = this.
|
|
7396
|
+
res = this._getNextNodeInView(node, {
|
|
7397
|
+
reverse: true,
|
|
7398
|
+
ofs: pageSize,
|
|
7399
|
+
});
|
|
7120
7400
|
}
|
|
7121
7401
|
}
|
|
7122
7402
|
break;
|
|
7403
|
+
case "prevMatch":
|
|
7404
|
+
// fallthrough
|
|
7405
|
+
case "nextMatch":
|
|
7406
|
+
if (!this.isFilterActive) {
|
|
7407
|
+
this.logWarn(`${where}: Filter is not active.`);
|
|
7408
|
+
break;
|
|
7409
|
+
}
|
|
7410
|
+
res = this.findNextNode((n) => n.isMatched(), node, where === "prevMatch");
|
|
7411
|
+
res === null || res === void 0 ? void 0 : res.setActive();
|
|
7412
|
+
break;
|
|
7123
7413
|
default:
|
|
7124
7414
|
this.logWarn("Unknown relation '" + where + "'.");
|
|
7125
7415
|
}
|
|
@@ -7154,6 +7444,18 @@
|
|
|
7154
7444
|
format(name_cb, connectors) {
|
|
7155
7445
|
return this.root.format(name_cb, connectors);
|
|
7156
7446
|
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Always returns null (so a tree instance behaves as `tree.root`).
|
|
7449
|
+
*/
|
|
7450
|
+
get parent() {
|
|
7451
|
+
return null;
|
|
7452
|
+
}
|
|
7453
|
+
/**
|
|
7454
|
+
* Return a list of top-level nodes.
|
|
7455
|
+
*/
|
|
7456
|
+
get children() {
|
|
7457
|
+
return this.root.children || [];
|
|
7458
|
+
}
|
|
7157
7459
|
/**
|
|
7158
7460
|
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
7159
7461
|
*/
|
|
@@ -7181,6 +7483,12 @@
|
|
|
7181
7483
|
getFirstChild() {
|
|
7182
7484
|
return this.root.getFirstChild();
|
|
7183
7485
|
}
|
|
7486
|
+
/**
|
|
7487
|
+
* Return the last top level node if any (not the invisible root node).
|
|
7488
|
+
*/
|
|
7489
|
+
getLastChild() {
|
|
7490
|
+
return this.root.getLastChild();
|
|
7491
|
+
}
|
|
7184
7492
|
/**
|
|
7185
7493
|
* Return the node that currently has keyboard focus or null.
|
|
7186
7494
|
* Alias for {@link Wunderbaum.focusNode}.
|
|
@@ -7266,7 +7574,7 @@
|
|
|
7266
7574
|
}
|
|
7267
7575
|
/** Return true if any node title or grid cell is currently beeing edited.
|
|
7268
7576
|
*
|
|
7269
|
-
* See also {@link
|
|
7577
|
+
* See also {@link isEditingTitle}.
|
|
7270
7578
|
*/
|
|
7271
7579
|
isEditing() {
|
|
7272
7580
|
const focusElem = this.nodeListElement.querySelector("input:focus,select:focus");
|
|
@@ -7274,7 +7582,7 @@
|
|
|
7274
7582
|
}
|
|
7275
7583
|
/** Return true if any node is currently in edit-title mode.
|
|
7276
7584
|
*
|
|
7277
|
-
* See also {@link WunderbaumNode.isEditingTitle} and {@link
|
|
7585
|
+
* See also {@link WunderbaumNode.isEditingTitle} and {@link isEditing}.
|
|
7278
7586
|
*/
|
|
7279
7587
|
isEditingTitle() {
|
|
7280
7588
|
return this._callMethod("edit.isEditingTitle");
|
|
@@ -7294,7 +7602,7 @@
|
|
|
7294
7602
|
return res;
|
|
7295
7603
|
}
|
|
7296
7604
|
/** Write to `console.log` with tree name as prefix if opts.debugLevel >= 4.
|
|
7297
|
-
* @see {@link
|
|
7605
|
+
* @see {@link logDebug}
|
|
7298
7606
|
*/
|
|
7299
7607
|
log(...args) {
|
|
7300
7608
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7303,7 +7611,7 @@
|
|
|
7303
7611
|
}
|
|
7304
7612
|
/** Write to `console.debug` with tree name as prefix if opts.debugLevel >= 4.
|
|
7305
7613
|
* and browser console level includes debug/verbose messages.
|
|
7306
|
-
* @see {@link
|
|
7614
|
+
* @see {@link log}
|
|
7307
7615
|
*/
|
|
7308
7616
|
logDebug(...args) {
|
|
7309
7617
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7341,6 +7649,19 @@
|
|
|
7341
7649
|
console.warn(this.toString(), ...args); // eslint-disable-line no-console
|
|
7342
7650
|
}
|
|
7343
7651
|
}
|
|
7652
|
+
/** Emit a warning for deprecated methods. @internal */
|
|
7653
|
+
logDeprecate(method, options) {
|
|
7654
|
+
if (this.options.debugLevel >= 2) {
|
|
7655
|
+
let msg = `${this}: ${method} is deprecated`;
|
|
7656
|
+
if (options === null || options === void 0 ? void 0 : options.since) {
|
|
7657
|
+
msg += ` since ${options.since}`;
|
|
7658
|
+
}
|
|
7659
|
+
if (options === null || options === void 0 ? void 0 : options.hint) {
|
|
7660
|
+
msg += ` (${options.since})`;
|
|
7661
|
+
}
|
|
7662
|
+
console.warn(msg + "."); // eslint-disable-line no-console
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7344
7665
|
/** Reset column widths to default. @since 0.10.0 */
|
|
7345
7666
|
resetColumns() {
|
|
7346
7667
|
this.columns.forEach((col) => {
|
|
@@ -7508,6 +7829,69 @@
|
|
|
7508
7829
|
_setFocusNode(node) {
|
|
7509
7830
|
this._focusNode = node;
|
|
7510
7831
|
}
|
|
7832
|
+
/** Return the current selection/expansion/activation status. @experimental */
|
|
7833
|
+
getState(options = {}) {
|
|
7834
|
+
var _a, _b;
|
|
7835
|
+
const { activeKey = true, expandedKeys = false, selectedKeys = false, } = options;
|
|
7836
|
+
const expandSet = new Set();
|
|
7837
|
+
if (expandedKeys) {
|
|
7838
|
+
for (const node of this) {
|
|
7839
|
+
if (node.isExpanded() && node.hasChildren()) {
|
|
7840
|
+
expandSet.add(node.key);
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
// Parents of active node are always expanded
|
|
7845
|
+
if (activeKey && this.activeNode) {
|
|
7846
|
+
this.activeNode.visitParents((n) => {
|
|
7847
|
+
if (n.parent) {
|
|
7848
|
+
expandSet.add(n.key);
|
|
7849
|
+
}
|
|
7850
|
+
}, false);
|
|
7851
|
+
}
|
|
7852
|
+
const state = {
|
|
7853
|
+
expandedKeys: expandSet.size ? Array.from(expandSet) : undefined,
|
|
7854
|
+
activeKey: (_b = (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null,
|
|
7855
|
+
activeColIdx: this.activeColIdx,
|
|
7856
|
+
selectedKeys: selectedKeys
|
|
7857
|
+
? this.getSelectedNodes().flatMap((n) => n.key)
|
|
7858
|
+
: undefined,
|
|
7859
|
+
};
|
|
7860
|
+
return state;
|
|
7861
|
+
}
|
|
7862
|
+
/** Apply selection/expansion/activation status. @experimental */
|
|
7863
|
+
async setState(state, options = {}) {
|
|
7864
|
+
const { expandLazy = true } = options;
|
|
7865
|
+
return this.runWithDeferredUpdateAsync(async () => {
|
|
7866
|
+
var _a, _b;
|
|
7867
|
+
if (state.expandedKeys && state.expandedKeys.length) {
|
|
7868
|
+
if (expandLazy) {
|
|
7869
|
+
// Expand all keys recursively, even if they are not in the tree yet
|
|
7870
|
+
await this._loadLazyNodes(state.expandedKeys, {
|
|
7871
|
+
expand: true,
|
|
7872
|
+
noEvents: true,
|
|
7873
|
+
});
|
|
7874
|
+
}
|
|
7875
|
+
else {
|
|
7876
|
+
for (const key of state.expandedKeys) {
|
|
7877
|
+
(_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setExpanded(true);
|
|
7878
|
+
}
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7881
|
+
if (state.activeKey) {
|
|
7882
|
+
this.setActiveNode(state.activeKey);
|
|
7883
|
+
}
|
|
7884
|
+
if (state.selectedKeys) {
|
|
7885
|
+
this.selectAll(false);
|
|
7886
|
+
for (const key of state.selectedKeys) {
|
|
7887
|
+
(_b = this.findKey(key)) === null || _b === void 0 ? void 0 : _b.setSelected(true);
|
|
7888
|
+
}
|
|
7889
|
+
}
|
|
7890
|
+
if (this.isCellNav() && state.activeColIdx != null) {
|
|
7891
|
+
this.setColumn(state.activeColIdx);
|
|
7892
|
+
}
|
|
7893
|
+
});
|
|
7894
|
+
}
|
|
7511
7895
|
update(change, node, options) {
|
|
7512
7896
|
// this.log(`update(${change}) node=${node}`);
|
|
7513
7897
|
if (!(node instanceof WunderbaumNode)) {
|
|
@@ -7664,18 +8048,33 @@
|
|
|
7664
8048
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
7665
8049
|
* (defaults to sorting by title).
|
|
7666
8050
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
8051
|
+
* @deprecated use {@link sort}
|
|
7667
8052
|
*/
|
|
7668
8053
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
7669
|
-
this.
|
|
8054
|
+
this.logDeprecate("sortChildren()", { since: "0.14.0" });
|
|
8055
|
+
return this.sort({
|
|
8056
|
+
cmp: cmp ? cmp : undefined,
|
|
8057
|
+
deep: deep,
|
|
8058
|
+
propName: "title",
|
|
8059
|
+
});
|
|
7670
8060
|
}
|
|
7671
8061
|
/**
|
|
7672
8062
|
* Convenience method to implement column sorting.
|
|
7673
8063
|
* @see {@link WunderbaumNode.sortByProperty}.
|
|
7674
8064
|
* @since 0.11.0
|
|
8065
|
+
* @deprecated use {@link sort}
|
|
7675
8066
|
*/
|
|
7676
8067
|
sortByProperty(options) {
|
|
8068
|
+
this.logDeprecate("sortByProperty()", { since: "0.14.0" });
|
|
7677
8069
|
this.root.sortByProperty(options);
|
|
7678
8070
|
}
|
|
8071
|
+
/**
|
|
8072
|
+
* Sort nodes list by title or custom criteria.
|
|
8073
|
+
* @since 0.14.0
|
|
8074
|
+
*/
|
|
8075
|
+
sort(options) {
|
|
8076
|
+
this.root.sort(options);
|
|
8077
|
+
}
|
|
7679
8078
|
/** Convert tree to an array of plain objects.
|
|
7680
8079
|
*
|
|
7681
8080
|
* @param callback is called for every node, in order to allow
|
|
@@ -7789,11 +8188,11 @@
|
|
|
7789
8188
|
// }
|
|
7790
8189
|
return modified;
|
|
7791
8190
|
}
|
|
7792
|
-
_insertIcon(icon, elem) {
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
}
|
|
8191
|
+
// protected _insertIcon(icon: string, elem: HTMLElement) {
|
|
8192
|
+
// const iconElem = document.createElement("i");
|
|
8193
|
+
// iconElem.className = icon;
|
|
8194
|
+
// elem.appendChild(iconElem);
|
|
8195
|
+
// }
|
|
7797
8196
|
/** Create/update header markup from `this.columns` definition.
|
|
7798
8197
|
* @internal
|
|
7799
8198
|
*/
|
|
@@ -7891,6 +8290,102 @@
|
|
|
7891
8290
|
this._updateViewportImmediately();
|
|
7892
8291
|
}
|
|
7893
8292
|
}
|
|
8293
|
+
/** @internal */
|
|
8294
|
+
_createNodeIcon(node, showLoading, showBadge) {
|
|
8295
|
+
const iconMap = this.iconMap;
|
|
8296
|
+
let iconElem;
|
|
8297
|
+
let icon = node.getOption("icon");
|
|
8298
|
+
if (node._errorInfo) {
|
|
8299
|
+
icon = iconMap.error;
|
|
8300
|
+
}
|
|
8301
|
+
else if (node._isLoading && showLoading) {
|
|
8302
|
+
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
8303
|
+
// display the 'loading' status with the i.wb-icon span
|
|
8304
|
+
icon = iconMap.loading;
|
|
8305
|
+
}
|
|
8306
|
+
if (icon === false) {
|
|
8307
|
+
return null; // explicitly disabled: don't try default icons
|
|
8308
|
+
}
|
|
8309
|
+
if (typeof icon === "string") ;
|
|
8310
|
+
else if (node.statusNodeType) {
|
|
8311
|
+
icon = iconMap[node.statusNodeType];
|
|
8312
|
+
}
|
|
8313
|
+
else if (node.expanded) {
|
|
8314
|
+
icon = iconMap.folderOpen;
|
|
8315
|
+
}
|
|
8316
|
+
else if (node.children) {
|
|
8317
|
+
icon = iconMap.folder;
|
|
8318
|
+
}
|
|
8319
|
+
else if (node.lazy) {
|
|
8320
|
+
icon = iconMap.folderLazy;
|
|
8321
|
+
}
|
|
8322
|
+
else {
|
|
8323
|
+
icon = iconMap.doc;
|
|
8324
|
+
}
|
|
8325
|
+
if (!icon) {
|
|
8326
|
+
iconElem = document.createElement("i");
|
|
8327
|
+
iconElem.className = "wb-icon";
|
|
8328
|
+
}
|
|
8329
|
+
else if (TEST_HTML.test(icon)) {
|
|
8330
|
+
iconElem = elemFromHtml(icon);
|
|
8331
|
+
}
|
|
8332
|
+
else if (TEST_FILE_PATH.test(icon)) {
|
|
8333
|
+
iconElem = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
|
|
8334
|
+
}
|
|
8335
|
+
else {
|
|
8336
|
+
// Class name
|
|
8337
|
+
iconElem = document.createElement("i");
|
|
8338
|
+
iconElem.className = "wb-icon " + icon;
|
|
8339
|
+
}
|
|
8340
|
+
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
8341
|
+
const cbRes = showBadge && node._callEvent("iconBadge", { iconSpan: iconElem });
|
|
8342
|
+
let badge = null;
|
|
8343
|
+
if (cbRes != null && cbRes !== false) {
|
|
8344
|
+
let classes = "";
|
|
8345
|
+
let tooltip = "";
|
|
8346
|
+
if (isPlainObject(cbRes)) {
|
|
8347
|
+
badge = "" + cbRes.badge;
|
|
8348
|
+
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
8349
|
+
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
8350
|
+
}
|
|
8351
|
+
else if (typeof cbRes === "number") {
|
|
8352
|
+
badge = "" + cbRes;
|
|
8353
|
+
}
|
|
8354
|
+
else {
|
|
8355
|
+
badge = cbRes; // string or HTMLSpanElement
|
|
8356
|
+
}
|
|
8357
|
+
if (typeof badge === "string") {
|
|
8358
|
+
badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
|
|
8359
|
+
}
|
|
8360
|
+
if (badge) {
|
|
8361
|
+
iconElem.append(badge);
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
return iconElem;
|
|
8365
|
+
}
|
|
8366
|
+
_updateTopBreadcrumb() {
|
|
8367
|
+
const breadcrumb = this.breadcrumb;
|
|
8368
|
+
const topmost = this.getTopmostVpNode(true);
|
|
8369
|
+
const parentList = topmost === null || topmost === void 0 ? void 0 : topmost.getParentList(false, false);
|
|
8370
|
+
if (parentList === null || parentList === void 0 ? void 0 : parentList.length) {
|
|
8371
|
+
breadcrumb.innerHTML = "";
|
|
8372
|
+
for (const n of topmost.getParentList(false, false)) {
|
|
8373
|
+
const icon = this._createNodeIcon(n, false, false);
|
|
8374
|
+
if (icon) {
|
|
8375
|
+
breadcrumb.append(icon, " ");
|
|
8376
|
+
}
|
|
8377
|
+
const part = document.createElement("a");
|
|
8378
|
+
part.textContent = n.title;
|
|
8379
|
+
part.href = "#";
|
|
8380
|
+
part.classList.add("wb-breadcrumb");
|
|
8381
|
+
part.dataset.key = n.key;
|
|
8382
|
+
breadcrumb.append(part, this.options.strings.breadcrumbDelimiter);
|
|
8383
|
+
}
|
|
8384
|
+
}
|
|
8385
|
+
else {
|
|
8386
|
+
breadcrumb.innerHTML = " ";
|
|
8387
|
+
}
|
|
8388
|
+
}
|
|
7894
8389
|
/**
|
|
7895
8390
|
* This is the actual update method, which is wrapped inside a throttle method.
|
|
7896
8391
|
* It calls `updateColumns()` and `_updateRows()`.
|
|
@@ -7901,7 +8396,6 @@
|
|
|
7901
8396
|
* @internal
|
|
7902
8397
|
*/
|
|
7903
8398
|
_updateViewportImmediately() {
|
|
7904
|
-
var _a;
|
|
7905
8399
|
if (this._disableUpdateCount) {
|
|
7906
8400
|
this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount}).`);
|
|
7907
8401
|
this._disableUpdateIgnoreCount++;
|
|
@@ -7948,11 +8442,8 @@
|
|
|
7948
8442
|
this._updateRows();
|
|
7949
8443
|
// console.profileEnd(`_updateViewportImmediately()`)
|
|
7950
8444
|
}
|
|
7951
|
-
if (this.
|
|
7952
|
-
|
|
7953
|
-
let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
|
|
7954
|
-
path = path ? path + " >" : "";
|
|
7955
|
-
this.options.connectTopBreadcrumb.textContent = path;
|
|
8445
|
+
if (this.breadcrumb) {
|
|
8446
|
+
this._updateTopBreadcrumb();
|
|
7956
8447
|
}
|
|
7957
8448
|
this._callEvent("update");
|
|
7958
8449
|
}
|
|
@@ -8076,7 +8567,8 @@
|
|
|
8076
8567
|
}
|
|
8077
8568
|
/**
|
|
8078
8569
|
* Call `callback(node)` for all nodes in hierarchical order (depth-first, pre-order).
|
|
8079
|
-
* @see
|
|
8570
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
8571
|
+
* @see {@link WunderbaumNode.visit}.
|
|
8080
8572
|
*
|
|
8081
8573
|
* @param {function} callback the callback function.
|
|
8082
8574
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
@@ -8219,11 +8711,71 @@
|
|
|
8219
8711
|
*
|
|
8220
8712
|
* Previous data is cleared. Note that also column- and type defintions may
|
|
8221
8713
|
* be passed with the `source` object.
|
|
8714
|
+
* @see {@link Wunderbaum.reload} for a shortcut to reload the last ajax request
|
|
8715
|
+
* and restore the previous state.
|
|
8222
8716
|
*/
|
|
8223
|
-
load(source) {
|
|
8717
|
+
async load(source) {
|
|
8224
8718
|
this.clear();
|
|
8719
|
+
this._initialSource = source;
|
|
8225
8720
|
return this.root.load(source);
|
|
8226
8721
|
}
|
|
8722
|
+
/** Reload the tree and optionally restore state.
|
|
8723
|
+
* Source defaults to last ajax url if any.
|
|
8724
|
+
* Restoring the active node requires stable keys
|
|
8725
|
+
* @see {@link WunderbaumOptions.autoKeys}
|
|
8726
|
+
* @see {@link Wunderbaum.load}
|
|
8727
|
+
* @experimental
|
|
8728
|
+
*/
|
|
8729
|
+
async reload(options = {}) {
|
|
8730
|
+
const { source = this._initialSource, reactivate = true } = options;
|
|
8731
|
+
if (!source) {
|
|
8732
|
+
this.logWarn("No previous ajax source to reload.");
|
|
8733
|
+
return;
|
|
8734
|
+
}
|
|
8735
|
+
if (!reactivate) {
|
|
8736
|
+
return this.load(source);
|
|
8737
|
+
}
|
|
8738
|
+
const state = this.getState();
|
|
8739
|
+
await this.load(source);
|
|
8740
|
+
return this.setState(state);
|
|
8741
|
+
}
|
|
8742
|
+
/**
|
|
8743
|
+
* Make sure that all nodes in the given keyList are accessible.
|
|
8744
|
+
* This may include loading lazy parent nodes.
|
|
8745
|
+
* Recursively load (and optionally expand) all requested node paths.
|
|
8746
|
+
*/
|
|
8747
|
+
async _loadLazyNodes(keyList, options = {}) {
|
|
8748
|
+
const { expand = true } = options;
|
|
8749
|
+
const keySet = new Set(keyList);
|
|
8750
|
+
// Make sure that all parent nodes are loaded (and expand if requested)
|
|
8751
|
+
while (keySet.size > 0) {
|
|
8752
|
+
const pendingNodes = [];
|
|
8753
|
+
const curSet = new Set(keySet);
|
|
8754
|
+
for (const key of curSet) {
|
|
8755
|
+
const node = this.findKey(key);
|
|
8756
|
+
if (!node) {
|
|
8757
|
+
continue; // key not yet found (need to load lazy parent?)
|
|
8758
|
+
}
|
|
8759
|
+
keySet.delete(key);
|
|
8760
|
+
if (expand) {
|
|
8761
|
+
pendingNodes.push(node.setExpanded(true));
|
|
8762
|
+
}
|
|
8763
|
+
else if (node.isUnloaded()) {
|
|
8764
|
+
pendingNodes.push(node.loadLazy());
|
|
8765
|
+
}
|
|
8766
|
+
if (node._rowElem) {
|
|
8767
|
+
node._render(); // show spinner even is update is suppressed
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
if (pendingNodes.length === 0) {
|
|
8771
|
+
// will not load any more nodes, so if if there are still keys
|
|
8772
|
+
// left in the set, we will never find them
|
|
8773
|
+
this.logWarn(`Could not expand ${keySet.size} nodes:`, keySet);
|
|
8774
|
+
break;
|
|
8775
|
+
}
|
|
8776
|
+
await Promise.allSettled(pendingNodes);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8227
8779
|
/**
|
|
8228
8780
|
* Disable render requests during operations that would trigger many updates.
|
|
8229
8781
|
*
|
|
@@ -8321,9 +8873,21 @@
|
|
|
8321
8873
|
}
|
|
8322
8874
|
Wunderbaum.sequence = 0;
|
|
8323
8875
|
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
8324
|
-
Wunderbaum.version = "v0.
|
|
8876
|
+
Wunderbaum.version = "v0.14.0"; // Set to semver by 'grunt release'
|
|
8325
8877
|
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
8326
8878
|
Wunderbaum.util = util;
|
|
8879
|
+
/** A map of default iconMaps.
|
|
8880
|
+
* May be used as default, when passing partial icon definition maps:
|
|
8881
|
+
* ```js
|
|
8882
|
+
* const tree = new mar10.Wunderbaum({
|
|
8883
|
+
* ...
|
|
8884
|
+
* iconMap: Object.assign(Wunderbaum.iconMaps.bootstrap, {
|
|
8885
|
+
* folder: "bi bi-archive",
|
|
8886
|
+
* }),
|
|
8887
|
+
* });
|
|
8888
|
+
* ```
|
|
8889
|
+
*/
|
|
8890
|
+
Wunderbaum.iconMaps = defaultIconMaps;
|
|
8327
8891
|
|
|
8328
8892
|
exports.Wunderbaum = Wunderbaum;
|
|
8329
8893
|
|