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.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - debounce.ts
|
|
3
3
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
4
|
-
* v0.
|
|
4
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
/*
|
|
7
7
|
* debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
|
|
@@ -293,7 +293,7 @@ function throttle(func, wait = 0, options = {}) {
|
|
|
293
293
|
/*!
|
|
294
294
|
* Wunderbaum - util
|
|
295
295
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
296
|
-
* v0.
|
|
296
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
297
297
|
*/
|
|
298
298
|
/** @module util */
|
|
299
299
|
/** Readable names for `MouseEvent.button` */
|
|
@@ -443,7 +443,7 @@ function each(obj, callback) {
|
|
|
443
443
|
}
|
|
444
444
|
return obj;
|
|
445
445
|
}
|
|
446
|
-
/** Shortcut for `throw new Error(msg)
|
|
446
|
+
/** Shortcut for `throw new Error(msg)`. */
|
|
447
447
|
function error(msg) {
|
|
448
448
|
throw new Error(msg);
|
|
449
449
|
}
|
|
@@ -676,18 +676,6 @@ function elemFromSelector(obj) {
|
|
|
676
676
|
}
|
|
677
677
|
return obj;
|
|
678
678
|
}
|
|
679
|
-
// /** Return a EventTarget from selector or cast an existing element. */
|
|
680
|
-
// export function eventTargetFromSelector(
|
|
681
|
-
// obj: string | EventTarget
|
|
682
|
-
// ): EventTarget | null {
|
|
683
|
-
// if (!obj) {
|
|
684
|
-
// return null;
|
|
685
|
-
// }
|
|
686
|
-
// if (typeof obj === "string") {
|
|
687
|
-
// return document.querySelector(obj) as EventTarget;
|
|
688
|
-
// }
|
|
689
|
-
// return obj as EventTarget;
|
|
690
|
-
// }
|
|
691
679
|
/**
|
|
692
680
|
* Return a canonical descriptive string for a keyboard or mouse event.
|
|
693
681
|
*
|
|
@@ -970,6 +958,10 @@ function toPixel(...defaults) {
|
|
|
970
958
|
}
|
|
971
959
|
throw new Error(`Expected a string like '123px': ${defaults}`);
|
|
972
960
|
}
|
|
961
|
+
/** Cast any value to <T>. */
|
|
962
|
+
function unsafeCast(value) {
|
|
963
|
+
return value;
|
|
964
|
+
}
|
|
973
965
|
/** Return the the boolean value of the first non-null element.
|
|
974
966
|
* Example:
|
|
975
967
|
* ```js
|
|
@@ -1043,7 +1035,7 @@ function adaptiveThrottle(callback, options) {
|
|
|
1043
1035
|
const throttledFn = (...args) => {
|
|
1044
1036
|
if (waiting) {
|
|
1045
1037
|
pendingArgs = args;
|
|
1046
|
-
// console.log(`adaptiveThrottle()
|
|
1038
|
+
// console.log(`adaptiveThrottle() queueing request #${waiting}...`, args);
|
|
1047
1039
|
waiting += 1;
|
|
1048
1040
|
}
|
|
1049
1041
|
else {
|
|
@@ -1098,6 +1090,60 @@ function adaptiveThrottle(callback, options) {
|
|
|
1098
1090
|
};
|
|
1099
1091
|
return throttledFn;
|
|
1100
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* MurmurHash3 implementation for strings.
|
|
1095
|
+
* @param key The input string to hash.
|
|
1096
|
+
* @param asString Optional convert result to zero-padded string of 8 characters.
|
|
1097
|
+
* @param seed Optional seed value.
|
|
1098
|
+
* @returns A 32-bit hash as a number or string.
|
|
1099
|
+
*/
|
|
1100
|
+
function murmurHash3(key, asString = true, seed = 0) {
|
|
1101
|
+
let h1 = seed;
|
|
1102
|
+
const remainder = key.length & 3; // key.length % 4
|
|
1103
|
+
const bytes = key.length - remainder;
|
|
1104
|
+
const c1 = 0xcc9e2d51;
|
|
1105
|
+
const c2 = 0x1b873593;
|
|
1106
|
+
let i = 0;
|
|
1107
|
+
while (i < bytes) {
|
|
1108
|
+
let k1 = (key.charCodeAt(i) & 0xff) |
|
|
1109
|
+
((key.charCodeAt(++i) & 0xff) << 8) |
|
|
1110
|
+
((key.charCodeAt(++i) & 0xff) << 16) |
|
|
1111
|
+
((key.charCodeAt(++i) & 0xff) << 24);
|
|
1112
|
+
++i;
|
|
1113
|
+
k1 = Math.imul(k1, c1);
|
|
1114
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1115
|
+
k1 = Math.imul(k1, c2);
|
|
1116
|
+
h1 ^= k1;
|
|
1117
|
+
h1 = (h1 << 13) | (h1 >>> 19);
|
|
1118
|
+
h1 = Math.imul(h1, 5) + 0xe6546b64;
|
|
1119
|
+
}
|
|
1120
|
+
let k1 = 0;
|
|
1121
|
+
switch (remainder) {
|
|
1122
|
+
case 3:
|
|
1123
|
+
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
1124
|
+
// fall through
|
|
1125
|
+
case 2:
|
|
1126
|
+
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
1127
|
+
// fall through
|
|
1128
|
+
case 1:
|
|
1129
|
+
k1 ^= key.charCodeAt(i) & 0xff;
|
|
1130
|
+
k1 = Math.imul(k1, c1);
|
|
1131
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
1132
|
+
k1 = Math.imul(k1, c2);
|
|
1133
|
+
h1 ^= k1;
|
|
1134
|
+
}
|
|
1135
|
+
h1 ^= key.length;
|
|
1136
|
+
h1 ^= h1 >>> 16;
|
|
1137
|
+
h1 = Math.imul(h1, 0x85ebca6b);
|
|
1138
|
+
h1 ^= h1 >>> 13;
|
|
1139
|
+
h1 = Math.imul(h1, 0xc2b2ae35);
|
|
1140
|
+
h1 ^= h1 >>> 16;
|
|
1141
|
+
if (asString) {
|
|
1142
|
+
// Convert to 8 digit hex string
|
|
1143
|
+
return (h1 >>> 0).toString(16).padStart(8, "0");
|
|
1144
|
+
}
|
|
1145
|
+
return h1 >>> 0; // Convert to unsigned 32-bit integer
|
|
1146
|
+
}
|
|
1101
1147
|
|
|
1102
1148
|
var util = /*#__PURE__*/Object.freeze({
|
|
1103
1149
|
__proto__: null,
|
|
@@ -1128,6 +1174,7 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
1128
1174
|
isFunction: isFunction,
|
|
1129
1175
|
isMac: isMac,
|
|
1130
1176
|
isPlainObject: isPlainObject,
|
|
1177
|
+
murmurHash3: murmurHash3,
|
|
1131
1178
|
noop: noop,
|
|
1132
1179
|
onEvent: onEvent,
|
|
1133
1180
|
overrideMethod: overrideMethod,
|
|
@@ -1141,13 +1188,14 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
1141
1188
|
toPixel: toPixel,
|
|
1142
1189
|
toSet: toSet,
|
|
1143
1190
|
toggleCheckbox: toggleCheckbox,
|
|
1144
|
-
type: type
|
|
1191
|
+
type: type,
|
|
1192
|
+
unsafeCast: unsafeCast
|
|
1145
1193
|
});
|
|
1146
1194
|
|
|
1147
1195
|
/*!
|
|
1148
1196
|
* Wunderbaum - types
|
|
1149
1197
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1150
|
-
* v0.
|
|
1198
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1151
1199
|
*/
|
|
1152
1200
|
/**
|
|
1153
1201
|
* Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
|
|
@@ -1215,7 +1263,7 @@ var NavModeEnum;
|
|
|
1215
1263
|
/*!
|
|
1216
1264
|
* Wunderbaum - wb_extension_base
|
|
1217
1265
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1218
|
-
* v0.
|
|
1266
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1219
1267
|
*/
|
|
1220
1268
|
class WunderbaumExtension {
|
|
1221
1269
|
constructor(tree, id, defaults) {
|
|
@@ -1274,7 +1322,7 @@ class WunderbaumExtension {
|
|
|
1274
1322
|
/*!
|
|
1275
1323
|
* Wunderbaum - ext-filter
|
|
1276
1324
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1277
|
-
* v0.
|
|
1325
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1278
1326
|
*/
|
|
1279
1327
|
const START_MARKER = "\uFFF7";
|
|
1280
1328
|
const END_MARKER = "\uFFF8";
|
|
@@ -1286,7 +1334,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1286
1334
|
autoApply: true, // Re-apply last filter if lazy data is loaded
|
|
1287
1335
|
autoExpand: false, // Expand all branches that contain matches while filtered
|
|
1288
1336
|
matchBranch: false, // Whether to implicitly match all children of matched nodes
|
|
1289
|
-
|
|
1337
|
+
connect: null, // Element or selector of an input control for filter query strings
|
|
1290
1338
|
fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
|
|
1291
1339
|
hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
|
|
1292
1340
|
highlight: true, // Highlight matches by wrapping inside <mark> tags
|
|
@@ -1294,36 +1342,117 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1294
1342
|
mode: "dim", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
|
|
1295
1343
|
noData: true, // Display a 'no data' status node if result is empty
|
|
1296
1344
|
});
|
|
1345
|
+
this.queryInput = null;
|
|
1346
|
+
this.prevButton = null;
|
|
1347
|
+
this.nextButton = null;
|
|
1348
|
+
this.modeButton = null;
|
|
1349
|
+
this.matchInfoElem = null;
|
|
1297
1350
|
this.lastFilterArgs = null;
|
|
1298
1351
|
}
|
|
1299
1352
|
init() {
|
|
1300
1353
|
super.init();
|
|
1301
|
-
const
|
|
1302
|
-
if (
|
|
1303
|
-
this.
|
|
1304
|
-
assert(this.queryInput, `Invalid 'filter.connectInput' option: ${connectInput}.`);
|
|
1305
|
-
onEvent(this.queryInput, "input", debounce((e) => {
|
|
1306
|
-
// this.tree.log("query", e);
|
|
1307
|
-
this.filterNodes(this.queryInput.value.trim(), {});
|
|
1308
|
-
}, 700));
|
|
1354
|
+
const connect = this.getPluginOption("connect");
|
|
1355
|
+
if (connect) {
|
|
1356
|
+
this._connectControls();
|
|
1309
1357
|
}
|
|
1310
1358
|
}
|
|
1311
1359
|
setPluginOption(name, value) {
|
|
1312
|
-
// alert("filter opt=" + name + ", " + value)
|
|
1313
1360
|
super.setPluginOption(name, value);
|
|
1314
1361
|
switch (name) {
|
|
1315
1362
|
case "mode":
|
|
1316
|
-
this.tree.filterMode =
|
|
1363
|
+
this.tree.filterMode =
|
|
1364
|
+
value === "hide" ? "hide" : value === "mark" ? "mark" : "dim";
|
|
1317
1365
|
this.tree.updateFilter();
|
|
1318
1366
|
break;
|
|
1319
1367
|
}
|
|
1320
1368
|
}
|
|
1369
|
+
_updatedConnectedControls() {
|
|
1370
|
+
var _a;
|
|
1371
|
+
const filterActive = this.tree.filterMode !== null;
|
|
1372
|
+
const activeNode = this.tree.getActiveNode();
|
|
1373
|
+
const matchCount = filterActive ? this.countMatches() : 0;
|
|
1374
|
+
const strings = this.treeOpts.strings;
|
|
1375
|
+
let matchIdx = "?";
|
|
1376
|
+
if (this.matchInfoElem) {
|
|
1377
|
+
if (filterActive) {
|
|
1378
|
+
let info;
|
|
1379
|
+
if (matchCount === 0) {
|
|
1380
|
+
info = strings.noMatch;
|
|
1381
|
+
}
|
|
1382
|
+
else if (activeNode && activeNode.match >= 1) {
|
|
1383
|
+
matchIdx = (_a = activeNode.match) !== null && _a !== void 0 ? _a : "?";
|
|
1384
|
+
info = strings.matchIndex;
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
info = strings.queryResult;
|
|
1388
|
+
}
|
|
1389
|
+
info = info
|
|
1390
|
+
.replace("${count}", this.tree.count().toLocaleString())
|
|
1391
|
+
.replace("${match}", "" + matchIdx)
|
|
1392
|
+
.replace("${matches}", matchCount.toLocaleString());
|
|
1393
|
+
this.matchInfoElem.textContent = info;
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
this.matchInfoElem.textContent = "";
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
if (this.nextButton instanceof HTMLButtonElement) {
|
|
1400
|
+
this.nextButton.disabled = !matchCount;
|
|
1401
|
+
}
|
|
1402
|
+
if (this.prevButton instanceof HTMLButtonElement) {
|
|
1403
|
+
this.prevButton.disabled = !matchCount;
|
|
1404
|
+
}
|
|
1405
|
+
if (this.modeButton) {
|
|
1406
|
+
this.modeButton.disabled = !filterActive;
|
|
1407
|
+
this.modeButton.classList.toggle("wb-filter-hide", this.tree.filterMode === "hide");
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
_connectControls() {
|
|
1411
|
+
const tree = this.tree;
|
|
1412
|
+
const connect = this.getPluginOption("connect");
|
|
1413
|
+
if (!connect) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
this.queryInput = elemFromSelector(connect.inputElem);
|
|
1417
|
+
if (!this.queryInput) {
|
|
1418
|
+
throw new Error(`Invalid 'filter.connect' option: ${connect.inputElem}.`);
|
|
1419
|
+
}
|
|
1420
|
+
this.prevButton = elemFromSelector(connect.prevButton);
|
|
1421
|
+
this.nextButton = elemFromSelector(connect.nextButton);
|
|
1422
|
+
this.modeButton = elemFromSelector(connect.modeButton);
|
|
1423
|
+
this.matchInfoElem = elemFromSelector(connect.matchInfoElem);
|
|
1424
|
+
if (this.prevButton) {
|
|
1425
|
+
onEvent(this.prevButton, "click", () => {
|
|
1426
|
+
tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "prevMatch");
|
|
1427
|
+
this._updatedConnectedControls();
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
if (this.nextButton) {
|
|
1431
|
+
onEvent(this.nextButton, "click", () => {
|
|
1432
|
+
tree.findRelatedNode(tree.getActiveNode() || tree.getFirstChild(), "nextMatch");
|
|
1433
|
+
this._updatedConnectedControls();
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (this.modeButton) {
|
|
1437
|
+
onEvent(this.modeButton, "click", (e) => {
|
|
1438
|
+
if (!this.tree.filterMode) {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
this.setPluginOption("mode", tree.filterMode === "dim" ? "hide" : "dim");
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
onEvent(this.queryInput, "input", debounce((e) => {
|
|
1445
|
+
this.filterNodes(this.queryInput.value.trim(), {});
|
|
1446
|
+
}, 700));
|
|
1447
|
+
this._updatedConnectedControls();
|
|
1448
|
+
}
|
|
1321
1449
|
_applyFilterNoUpdate(filter, _opts) {
|
|
1322
1450
|
return this.tree.runWithDeferredUpdate(() => {
|
|
1323
1451
|
return this._applyFilterImpl(filter, _opts);
|
|
1324
1452
|
});
|
|
1325
1453
|
}
|
|
1326
1454
|
_applyFilterImpl(filter, _opts) {
|
|
1455
|
+
var _a;
|
|
1327
1456
|
let //temp,
|
|
1328
1457
|
count = 0;
|
|
1329
1458
|
const start = Date.now();
|
|
@@ -1405,11 +1534,11 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1405
1534
|
return !!res;
|
|
1406
1535
|
};
|
|
1407
1536
|
}
|
|
1408
|
-
tree.filterMode = opts.mode;
|
|
1537
|
+
tree.filterMode = (_a = opts.mode) !== null && _a !== void 0 ? _a : "dim";
|
|
1409
1538
|
// eslint-disable-next-line prefer-rest-params
|
|
1410
1539
|
this.lastFilterArgs = arguments;
|
|
1411
1540
|
tree.element.classList.toggle("wb-ext-filter-hide", !!hideMode);
|
|
1412
|
-
tree.element.classList.toggle("wb-ext-filter-dim",
|
|
1541
|
+
tree.element.classList.toggle("wb-ext-filter-dim", opts.mode === "dim");
|
|
1413
1542
|
tree.element.classList.toggle("wb-ext-filter-hide-expanders", !!opts.hideExpanders);
|
|
1414
1543
|
// Reset current filter
|
|
1415
1544
|
tree.root.subMatchCount = 0;
|
|
@@ -1418,10 +1547,6 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1418
1547
|
delete node.titleWithHighlight;
|
|
1419
1548
|
node.subMatchCount = 0;
|
|
1420
1549
|
});
|
|
1421
|
-
// statusNode = tree.root.findDirectChild(KEY_NODATA);
|
|
1422
|
-
// if (statusNode) {
|
|
1423
|
-
// statusNode.remove();
|
|
1424
|
-
// }
|
|
1425
1550
|
tree.setStatus(NodeStatusType.ok);
|
|
1426
1551
|
// Adjust node.hide, .match, and .subMatchCount properties
|
|
1427
1552
|
treeOpts.autoCollapse = false; // #528
|
|
@@ -1432,7 +1557,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1432
1557
|
let res = filter(node);
|
|
1433
1558
|
if (res === "skip") {
|
|
1434
1559
|
node.visit(function (c) {
|
|
1435
|
-
c.match =
|
|
1560
|
+
c.match = undefined;
|
|
1436
1561
|
}, true);
|
|
1437
1562
|
return "skip";
|
|
1438
1563
|
}
|
|
@@ -1443,7 +1568,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1443
1568
|
}
|
|
1444
1569
|
if (res) {
|
|
1445
1570
|
count++;
|
|
1446
|
-
node.match =
|
|
1571
|
+
node.match = count;
|
|
1447
1572
|
node.visitParents((p) => {
|
|
1448
1573
|
if (p !== node) {
|
|
1449
1574
|
p.subMatchCount += 1;
|
|
@@ -1470,6 +1595,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1470
1595
|
}
|
|
1471
1596
|
// Redraw whole tree
|
|
1472
1597
|
tree.logDebug(`Filter '${filter}' found ${count} nodes in ${Date.now() - start} ms.`);
|
|
1598
|
+
this._updatedConnectedControls();
|
|
1473
1599
|
return count;
|
|
1474
1600
|
}
|
|
1475
1601
|
/**
|
|
@@ -1484,6 +1610,10 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1484
1610
|
*/
|
|
1485
1611
|
filterBranches(filter, options) {
|
|
1486
1612
|
assert(options.matchBranch === undefined, "filterBranches() is deprecated.");
|
|
1613
|
+
this.tree.logDeprecate("filterBranches()", {
|
|
1614
|
+
since: "0.9.0",
|
|
1615
|
+
hint: "Use `filterNodes` instead and set `options.matchBranch: true`",
|
|
1616
|
+
});
|
|
1487
1617
|
options.matchBranch = true;
|
|
1488
1618
|
return this._applyFilterNoUpdate(filter, options);
|
|
1489
1619
|
}
|
|
@@ -1514,34 +1644,22 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1514
1644
|
else {
|
|
1515
1645
|
tree.logWarn("updateFilter(): no filter active.");
|
|
1516
1646
|
}
|
|
1647
|
+
this._updatedConnectedControls();
|
|
1517
1648
|
}
|
|
1518
1649
|
/**
|
|
1519
1650
|
* [ext-filter] Reset the filter.
|
|
1520
1651
|
*/
|
|
1521
1652
|
clearFilter() {
|
|
1522
1653
|
const tree = this.tree;
|
|
1523
|
-
// statusNode = tree.root.findDirectChild(KEY_NODATA),
|
|
1524
|
-
// escapeTitles = tree.options.escapeTitles;
|
|
1525
1654
|
tree.enableUpdate(false);
|
|
1526
|
-
// if (statusNode) {
|
|
1527
|
-
// statusNode.remove();
|
|
1528
|
-
// }
|
|
1529
1655
|
tree.setStatus(NodeStatusType.ok);
|
|
1530
1656
|
// we also counted root node's subMatchCount
|
|
1531
1657
|
delete tree.root.match;
|
|
1532
1658
|
delete tree.root.subMatchCount;
|
|
1533
1659
|
tree.visit((node) => {
|
|
1534
|
-
// if (node.match && node._rowElem) {
|
|
1535
|
-
// let titleElem = node._rowElem.querySelector("span.wb-title")!;
|
|
1536
|
-
// node._callEvent("enhanceTitle", { titleElem: titleElem });
|
|
1537
|
-
// }
|
|
1538
1660
|
delete node.match;
|
|
1539
1661
|
delete node.subMatchCount;
|
|
1540
1662
|
delete node.titleWithHighlight;
|
|
1541
|
-
// if (node.subMatchBadge) {
|
|
1542
|
-
// node.subMatchBadge.remove();
|
|
1543
|
-
// delete node.subMatchBadge;
|
|
1544
|
-
// }
|
|
1545
1663
|
if (node._filterAutoExpanded && node.expanded) {
|
|
1546
1664
|
node.setExpanded(false, {
|
|
1547
1665
|
noAnimation: true,
|
|
@@ -1555,12 +1673,12 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1555
1673
|
tree.element.classList.remove(
|
|
1556
1674
|
// "wb-ext-filter",
|
|
1557
1675
|
"wb-ext-filter-dim", "wb-ext-filter-hide");
|
|
1558
|
-
|
|
1676
|
+
this._updatedConnectedControls();
|
|
1559
1677
|
tree.enableUpdate(true);
|
|
1560
1678
|
}
|
|
1561
1679
|
}
|
|
1562
1680
|
/**
|
|
1563
|
-
* @description Marks the matching
|
|
1681
|
+
* @description Marks the matching characters of `text` either by `mark` or
|
|
1564
1682
|
* by exotic*Chars (if `escapeTitles` is `true`) based on `matches`
|
|
1565
1683
|
* which is an array of matching groups.
|
|
1566
1684
|
* @param {string} text
|
|
@@ -1599,7 +1717,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
|
|
|
1599
1717
|
/*!
|
|
1600
1718
|
* Wunderbaum - ext-keynav
|
|
1601
1719
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1602
|
-
* v0.
|
|
1720
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1603
1721
|
*/
|
|
1604
1722
|
const QUICKSEARCH_DELAY = 500;
|
|
1605
1723
|
class KeynavExtension extends WunderbaumExtension {
|
|
@@ -1963,7 +2081,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1963
2081
|
/*!
|
|
1964
2082
|
* Wunderbaum - ext-logger
|
|
1965
2083
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
1966
|
-
* v0.
|
|
2084
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1967
2085
|
*/
|
|
1968
2086
|
class LoggerExtension extends WunderbaumExtension {
|
|
1969
2087
|
constructor(tree) {
|
|
@@ -2005,7 +2123,7 @@ class LoggerExtension extends WunderbaumExtension {
|
|
|
2005
2123
|
/*!
|
|
2006
2124
|
* Wunderbaum - ext-dnd
|
|
2007
2125
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2008
|
-
* v0.
|
|
2126
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2009
2127
|
*/
|
|
2010
2128
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
2011
2129
|
class DndExtension extends WunderbaumExtension {
|
|
@@ -2241,7 +2359,6 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2241
2359
|
*/
|
|
2242
2360
|
onDragEvent(e) {
|
|
2243
2361
|
var _a;
|
|
2244
|
-
// const tree = this.tree;
|
|
2245
2362
|
const dndOpts = this.treeOpts.dnd;
|
|
2246
2363
|
const srcNode = Wunderbaum.getNode(e);
|
|
2247
2364
|
if (!srcNode) {
|
|
@@ -2267,7 +2384,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2267
2384
|
return false;
|
|
2268
2385
|
}
|
|
2269
2386
|
const nodeData = srcNode.toDict(true, (n) => {
|
|
2270
|
-
// We don't want to
|
|
2387
|
+
// We don't want to reuse the key on drop:
|
|
2271
2388
|
n._orgKey = n.key;
|
|
2272
2389
|
delete n.key;
|
|
2273
2390
|
});
|
|
@@ -2329,6 +2446,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2329
2446
|
};
|
|
2330
2447
|
if (!targetNode) {
|
|
2331
2448
|
this._leaveNode();
|
|
2449
|
+
e.preventDefault(); // Don't open file in browser when dropped in empty area
|
|
2332
2450
|
return;
|
|
2333
2451
|
}
|
|
2334
2452
|
if (["drop"].includes(e.type)) {
|
|
@@ -2434,19 +2552,20 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2434
2552
|
nodeData = nodeData ? JSON.parse(nodeData) : null;
|
|
2435
2553
|
const srcNode = this.srcNode;
|
|
2436
2554
|
const lastDropEffect = this.lastDropEffect;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2555
|
+
/* Before v0.14.0, we decoupled `_callEvent` like so:
|
|
2556
|
+
Decouple this call, because drop actions may prevent the dragend
|
|
2557
|
+
event from being fired on some browsers.
|
|
2558
|
+
setTimeout(() => {...}, 10);
|
|
2559
|
+
however this made e.dataTransfer.items inaccessible */
|
|
2560
|
+
targetNode._callEvent("dnd.drop", {
|
|
2561
|
+
event: e,
|
|
2562
|
+
region: region,
|
|
2563
|
+
suggestedDropMode: region === "over" ? "appendChild" : region,
|
|
2564
|
+
suggestedDropEffect: lastDropEffect,
|
|
2565
|
+
sourceNode: srcNode,
|
|
2566
|
+
sourceNodeData: nodeData,
|
|
2567
|
+
dataTransfer: e.dataTransfer,
|
|
2568
|
+
});
|
|
2450
2569
|
}
|
|
2451
2570
|
return false;
|
|
2452
2571
|
}
|
|
@@ -2455,7 +2574,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
2455
2574
|
/*!
|
|
2456
2575
|
* Wunderbaum - drag_observer
|
|
2457
2576
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2458
|
-
* v0.
|
|
2577
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2459
2578
|
*/
|
|
2460
2579
|
/**
|
|
2461
2580
|
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
@@ -2604,7 +2723,7 @@ class DragObserver {
|
|
|
2604
2723
|
/*!
|
|
2605
2724
|
* Wunderbaum - common
|
|
2606
2725
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2607
|
-
* v0.
|
|
2726
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2608
2727
|
*/
|
|
2609
2728
|
const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
|
|
2610
2729
|
/**
|
|
@@ -2624,12 +2743,18 @@ const TITLE_SPAN_PAD_Y = 7;
|
|
|
2624
2743
|
const RENDER_MAX_PREFETCH = 5;
|
|
2625
2744
|
/** Minimum column width if not set otherwise. */
|
|
2626
2745
|
const DEFAULT_MIN_COL_WIDTH = 4;
|
|
2746
|
+
/**
|
|
2747
|
+
* A value for `node.type` that by convention may be used to mark a node as directory.
|
|
2748
|
+
* It may be used to sort 'directories' to the top.
|
|
2749
|
+
*/
|
|
2750
|
+
const NODE_TYPE_FOLDER = "folder";
|
|
2627
2751
|
/** Regular expression to detect if a string describes an image URL (in contrast
|
|
2628
2752
|
* to a class name). Strings are considered image urls if they contain '.' or '/'.
|
|
2753
|
+
* `<` is ignored, because it is probably an html tag.
|
|
2629
2754
|
*/
|
|
2630
|
-
const
|
|
2631
|
-
|
|
2632
|
-
|
|
2755
|
+
const TEST_FILE_PATH = /^(?!.*<).*[/.]/;
|
|
2756
|
+
/** Regular expression to detect if a string describes an HTML element. */
|
|
2757
|
+
const TEST_HTML = /</;
|
|
2633
2758
|
/**
|
|
2634
2759
|
* Default node icons for icon libraries
|
|
2635
2760
|
*
|
|
@@ -2637,7 +2762,7 @@ const TEST_IMG = new RegExp(/\.|\//);
|
|
|
2637
2762
|
* - 'fontawesome6' {@link https://fontawesome.com/icons}
|
|
2638
2763
|
*
|
|
2639
2764
|
*/
|
|
2640
|
-
const
|
|
2765
|
+
const defaultIconMaps = {
|
|
2641
2766
|
bootstrap: {
|
|
2642
2767
|
error: "bi bi-exclamation-triangle",
|
|
2643
2768
|
// loading: "bi bi-hourglass-split wb-busy",
|
|
@@ -2685,7 +2810,7 @@ const iconMaps = {
|
|
|
2685
2810
|
radioChecked: "fa-solid fa-circle",
|
|
2686
2811
|
radioUnchecked: "fa-regular fa-circle",
|
|
2687
2812
|
radioUnknown: "fa-regular fa-circle-question",
|
|
2688
|
-
folder: "fa-
|
|
2813
|
+
folder: "fa-regular fa-folder-closed",
|
|
2689
2814
|
folderOpen: "fa-regular fa-folder-open",
|
|
2690
2815
|
folderLazy: "fa-solid fa-folder-plus",
|
|
2691
2816
|
doc: "fa-regular fa-file",
|
|
@@ -2717,29 +2842,20 @@ const RESERVED_TREE_SOURCE_KEYS = new Set([
|
|
|
2717
2842
|
// "Escape",
|
|
2718
2843
|
// ]);
|
|
2719
2844
|
/** Map `KeyEvent.key` to navigation action. */
|
|
2720
|
-
const
|
|
2721
|
-
" ": "toggleSelect",
|
|
2722
|
-
"+": "expand",
|
|
2723
|
-
Add: "expand",
|
|
2845
|
+
const KEY_TO_NAVIGATION_MAP = {
|
|
2724
2846
|
ArrowDown: "down",
|
|
2725
2847
|
ArrowLeft: "left",
|
|
2726
2848
|
ArrowRight: "right",
|
|
2727
2849
|
ArrowUp: "up",
|
|
2728
2850
|
Backspace: "parent",
|
|
2729
|
-
"/": "collapseAll",
|
|
2730
|
-
Divide: "collapseAll",
|
|
2731
2851
|
End: "lastCol",
|
|
2732
2852
|
Home: "firstCol",
|
|
2733
2853
|
"Control+End": "last",
|
|
2734
2854
|
"Control+Home": "first",
|
|
2735
2855
|
"Meta+ArrowDown": "last", // macOs
|
|
2736
2856
|
"Meta+ArrowUp": "first", // macOs
|
|
2737
|
-
"*": "expandAll",
|
|
2738
|
-
Multiply: "expandAll",
|
|
2739
2857
|
PageDown: "pageDown",
|
|
2740
2858
|
PageUp: "pageUp",
|
|
2741
|
-
"-": "collapse",
|
|
2742
|
-
Subtract: "collapse",
|
|
2743
2859
|
};
|
|
2744
2860
|
/** Return a callback that returns true if the node title matches the string
|
|
2745
2861
|
* or regular expression.
|
|
@@ -2767,12 +2883,20 @@ function makeNodeTitleStartMatcher(s) {
|
|
|
2767
2883
|
return reMatch.test(node.title);
|
|
2768
2884
|
};
|
|
2769
2885
|
}
|
|
2770
|
-
/** Compare two nodes by title (case-insensitive).
|
|
2886
|
+
/** Compare two nodes by title (case-insensitive).
|
|
2887
|
+
* @deprecated Use `key` option instead of `cmp` in sort methods.
|
|
2888
|
+
*/
|
|
2771
2889
|
function nodeTitleSorter(a, b) {
|
|
2772
2890
|
const x = a.title.toLowerCase();
|
|
2773
2891
|
const y = b.title.toLowerCase();
|
|
2774
2892
|
return x === y ? 0 : x > y ? 1 : -1;
|
|
2775
2893
|
}
|
|
2894
|
+
// /** Compare nodes by title (case-insensitive). */
|
|
2895
|
+
// export function nodeTitleKeyGetter(
|
|
2896
|
+
// node: WunderbaumNode
|
|
2897
|
+
// ): string | number | Array<any> {
|
|
2898
|
+
// return node.title.toLowerCase();
|
|
2899
|
+
// }
|
|
2776
2900
|
/**
|
|
2777
2901
|
* Convert 'flat' to 'nested' format.
|
|
2778
2902
|
*
|
|
@@ -2963,7 +3087,7 @@ function decompressSourceData(source) {
|
|
|
2963
3087
|
/*!
|
|
2964
3088
|
* Wunderbaum - ext-grid
|
|
2965
3089
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
2966
|
-
* v0.
|
|
3090
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2967
3091
|
*/
|
|
2968
3092
|
class GridExtension extends WunderbaumExtension {
|
|
2969
3093
|
constructor(tree) {
|
|
@@ -3023,7 +3147,7 @@ class GridExtension extends WunderbaumExtension {
|
|
|
3023
3147
|
super.init();
|
|
3024
3148
|
}
|
|
3025
3149
|
/**
|
|
3026
|
-
*
|
|
3150
|
+
* Handles drag and sragstop events for column resizing.
|
|
3027
3151
|
*/
|
|
3028
3152
|
handleDrag(e) {
|
|
3029
3153
|
const custom = e.customData;
|
|
@@ -3054,7 +3178,7 @@ class GridExtension extends WunderbaumExtension {
|
|
|
3054
3178
|
/*!
|
|
3055
3179
|
* Wunderbaum - deferred
|
|
3056
3180
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3057
|
-
* v0.
|
|
3181
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3058
3182
|
*/
|
|
3059
3183
|
/**
|
|
3060
3184
|
* Implement a ES6 Promise, that exposes a resolve() and reject() method.
|
|
@@ -3107,7 +3231,7 @@ class Deferred {
|
|
|
3107
3231
|
/*!
|
|
3108
3232
|
* Wunderbaum - wunderbaum_node
|
|
3109
3233
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
3110
|
-
* v0.
|
|
3234
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3111
3235
|
*/
|
|
3112
3236
|
/** WunderbaumNode properties that can be passed with source data.
|
|
3113
3237
|
* (Any other source properties will be stored as `node.data.PROP`.)
|
|
@@ -3158,7 +3282,7 @@ NODE_DICT_PROPS.delete("unselectable");
|
|
|
3158
3282
|
*/
|
|
3159
3283
|
class WunderbaumNode {
|
|
3160
3284
|
constructor(tree, parent, data) {
|
|
3161
|
-
var _a
|
|
3285
|
+
var _a;
|
|
3162
3286
|
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
3163
3287
|
* times within a tree (in this case we have 'clone nodes').
|
|
3164
3288
|
* @see Use {@link setKey} to modify.
|
|
@@ -3188,8 +3312,8 @@ class WunderbaumNode {
|
|
|
3188
3312
|
assert(!data.children, "'children' not allowed here");
|
|
3189
3313
|
this.tree = tree;
|
|
3190
3314
|
this.parent = parent;
|
|
3191
|
-
this.key =
|
|
3192
|
-
this.title = "" + ((
|
|
3315
|
+
this.key = tree._calculateKey(data, parent);
|
|
3316
|
+
this.title = "" + ((_a = data.title) !== null && _a !== void 0 ? _a : "<" + this.key + ">");
|
|
3193
3317
|
this.expanded = !!data.expanded;
|
|
3194
3318
|
this.lazy = !!data.lazy;
|
|
3195
3319
|
// We set the following node properties only if a matching data value is
|
|
@@ -3310,8 +3434,14 @@ class WunderbaumNode {
|
|
|
3310
3434
|
const forceExpand = applyMinExpanLevel && _level < tree.options.minExpandLevel;
|
|
3311
3435
|
for (const child of nodeData) {
|
|
3312
3436
|
const subChildren = child.children;
|
|
3437
|
+
// Remove children property from source data because it should not be
|
|
3438
|
+
// passed to the constructor of WunderbaumNode:
|
|
3313
3439
|
delete child.children;
|
|
3314
3440
|
const n = new WunderbaumNode(tree, this, child);
|
|
3441
|
+
// Set `children` property again, so it can be used in `reload()`
|
|
3442
|
+
if (subChildren != null) {
|
|
3443
|
+
child.children = subChildren;
|
|
3444
|
+
}
|
|
3315
3445
|
if (forceExpand && !n.isUnloaded()) {
|
|
3316
3446
|
n.expanded = true;
|
|
3317
3447
|
}
|
|
@@ -3727,15 +3857,12 @@ class WunderbaumNode {
|
|
|
3727
3857
|
}
|
|
3728
3858
|
return l;
|
|
3729
3859
|
}
|
|
3730
|
-
/** Return a string representing the
|
|
3860
|
+
/** Return a string representing the hierarchical node path, e.g. "a/b/c".
|
|
3731
3861
|
* @param includeSelf
|
|
3732
3862
|
* @param part property name or callback
|
|
3733
3863
|
* @param separator
|
|
3734
3864
|
*/
|
|
3735
3865
|
getPath(includeSelf = true, part = "title", separator = "/") {
|
|
3736
|
-
// includeSelf = includeSelf !== false;
|
|
3737
|
-
// part = part || "title";
|
|
3738
|
-
// separator = separator || "/";
|
|
3739
3866
|
let val;
|
|
3740
3867
|
const path = [];
|
|
3741
3868
|
const isFunc = typeof part === "function";
|
|
@@ -3750,7 +3877,7 @@ class WunderbaumNode {
|
|
|
3750
3877
|
}, includeSelf);
|
|
3751
3878
|
return path.join(separator);
|
|
3752
3879
|
}
|
|
3753
|
-
/** Return the
|
|
3880
|
+
/** Return the preceding node (under the same parent) or null. */
|
|
3754
3881
|
getPrevSibling() {
|
|
3755
3882
|
const ac = this.parent.children;
|
|
3756
3883
|
const idx = ac.indexOf(this);
|
|
@@ -3779,7 +3906,7 @@ class WunderbaumNode {
|
|
|
3779
3906
|
hasClass(className) {
|
|
3780
3907
|
return this.classes ? this.classes.has(className) : false;
|
|
3781
3908
|
}
|
|
3782
|
-
/** Return true if node
|
|
3909
|
+
/** Return true if node is the currently focused node. @since 0.9.0 */
|
|
3783
3910
|
hasFocus() {
|
|
3784
3911
|
return this.tree.focusNode === this;
|
|
3785
3912
|
}
|
|
@@ -3834,7 +3961,7 @@ class WunderbaumNode {
|
|
|
3834
3961
|
* an expand operation is currently possible.
|
|
3835
3962
|
*/
|
|
3836
3963
|
isExpandable(andCollapsed = false) {
|
|
3837
|
-
// `false` is never expandable (
|
|
3964
|
+
// `false` is never expandable (unofficial)
|
|
3838
3965
|
if ((andCollapsed && this.expanded) || this.children === false) {
|
|
3839
3966
|
return false;
|
|
3840
3967
|
}
|
|
@@ -3889,7 +4016,7 @@ class WunderbaumNode {
|
|
|
3889
4016
|
isParentOf(other) {
|
|
3890
4017
|
return other && other.parent === this;
|
|
3891
4018
|
}
|
|
3892
|
-
/**
|
|
4019
|
+
/** Return true if this node is partially loaded. @experimental */
|
|
3893
4020
|
isPartload() {
|
|
3894
4021
|
return !!this._partload;
|
|
3895
4022
|
}
|
|
@@ -3897,11 +4024,11 @@ class WunderbaumNode {
|
|
|
3897
4024
|
isPartsel() {
|
|
3898
4025
|
return !this.selected && !!this._partsel;
|
|
3899
4026
|
}
|
|
3900
|
-
/** Return true if this node has DOM
|
|
4027
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3901
4028
|
isRadio() {
|
|
3902
4029
|
return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
|
|
3903
4030
|
}
|
|
3904
|
-
/** Return true if this node has DOM
|
|
4031
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
3905
4032
|
isRendered() {
|
|
3906
4033
|
return !!this._rowElem;
|
|
3907
4034
|
}
|
|
@@ -4347,10 +4474,11 @@ class WunderbaumNode {
|
|
|
4347
4474
|
* @param options
|
|
4348
4475
|
*/
|
|
4349
4476
|
async navigate(where, options) {
|
|
4477
|
+
var _a;
|
|
4350
4478
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
4351
|
-
|
|
4479
|
+
const navType = ((_a = KEY_TO_NAVIGATION_MAP[where]) !== null && _a !== void 0 ? _a : where);
|
|
4352
4480
|
// Otherwise activate or focus the related node
|
|
4353
|
-
const node = this.findRelatedNode(
|
|
4481
|
+
const node = this.findRelatedNode(navType);
|
|
4354
4482
|
if (!node) {
|
|
4355
4483
|
this.logWarn(`Could not find related node '${where}'.`);
|
|
4356
4484
|
return Promise.resolve(this);
|
|
@@ -4447,86 +4575,17 @@ class WunderbaumNode {
|
|
|
4447
4575
|
renderColInfosById: renderColInfosById,
|
|
4448
4576
|
};
|
|
4449
4577
|
}
|
|
4450
|
-
_createIcon(
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
}
|
|
4456
|
-
else if (this._isLoading && showLoading) {
|
|
4457
|
-
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
4458
|
-
// display the 'loading' status with the i.wb-icon span
|
|
4459
|
-
icon = iconMap.loading;
|
|
4460
|
-
}
|
|
4461
|
-
if (icon === false) {
|
|
4462
|
-
return null; // explicitly disabled: don't try default icons
|
|
4463
|
-
}
|
|
4464
|
-
if (typeof icon === "string") ;
|
|
4465
|
-
else if (this.statusNodeType) {
|
|
4466
|
-
icon = iconMap[this.statusNodeType];
|
|
4467
|
-
}
|
|
4468
|
-
else if (this.expanded) {
|
|
4469
|
-
icon = iconMap.folderOpen;
|
|
4470
|
-
}
|
|
4471
|
-
else if (this.children) {
|
|
4472
|
-
icon = iconMap.folder;
|
|
4473
|
-
}
|
|
4474
|
-
else if (this.lazy) {
|
|
4475
|
-
icon = iconMap.folderLazy;
|
|
4476
|
-
}
|
|
4477
|
-
else {
|
|
4478
|
-
icon = iconMap.doc;
|
|
4479
|
-
}
|
|
4480
|
-
// this.log("_createIcon: " + icon);
|
|
4481
|
-
if (!icon) {
|
|
4482
|
-
iconSpan = document.createElement("i");
|
|
4483
|
-
iconSpan.className = "wb-icon";
|
|
4484
|
-
}
|
|
4485
|
-
else if (icon.indexOf("<") >= 0) {
|
|
4486
|
-
// HTML
|
|
4487
|
-
iconSpan = elemFromHtml(icon);
|
|
4488
|
-
}
|
|
4489
|
-
else if (TEST_IMG.test(icon)) {
|
|
4490
|
-
// Image URL
|
|
4491
|
-
iconSpan = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
|
|
4492
|
-
}
|
|
4493
|
-
else {
|
|
4494
|
-
// Class name
|
|
4495
|
-
iconSpan = document.createElement("i");
|
|
4496
|
-
iconSpan.className = "wb-icon " + icon;
|
|
4497
|
-
}
|
|
4498
|
-
if (replaceChild) {
|
|
4499
|
-
parentElem.replaceChild(iconSpan, replaceChild);
|
|
4500
|
-
}
|
|
4501
|
-
else {
|
|
4502
|
-
parentElem.appendChild(iconSpan);
|
|
4503
|
-
}
|
|
4504
|
-
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
4505
|
-
const cbRes = this._callEvent("iconBadge", { iconSpan: iconSpan });
|
|
4506
|
-
let badge = null;
|
|
4507
|
-
if (cbRes != null && cbRes !== false) {
|
|
4508
|
-
let classes = "";
|
|
4509
|
-
let tooltip = "";
|
|
4510
|
-
if (isPlainObject(cbRes)) {
|
|
4511
|
-
badge = "" + cbRes.badge;
|
|
4512
|
-
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
4513
|
-
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
4514
|
-
}
|
|
4515
|
-
else if (typeof cbRes === "number") {
|
|
4516
|
-
badge = "" + cbRes;
|
|
4578
|
+
_createIcon(parentElem, replaceChild, showLoading) {
|
|
4579
|
+
const iconElem = this.tree._createNodeIcon(this, showLoading, true);
|
|
4580
|
+
if (iconElem) {
|
|
4581
|
+
if (replaceChild) {
|
|
4582
|
+
parentElem.replaceChild(iconElem, replaceChild);
|
|
4517
4583
|
}
|
|
4518
4584
|
else {
|
|
4519
|
-
|
|
4520
|
-
}
|
|
4521
|
-
if (typeof badge === "string") {
|
|
4522
|
-
badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
|
|
4523
|
-
}
|
|
4524
|
-
if (badge) {
|
|
4525
|
-
iconSpan.append(badge);
|
|
4585
|
+
parentElem.appendChild(iconElem);
|
|
4526
4586
|
}
|
|
4527
4587
|
}
|
|
4528
|
-
|
|
4529
|
-
return iconSpan;
|
|
4588
|
+
return iconElem;
|
|
4530
4589
|
}
|
|
4531
4590
|
/**
|
|
4532
4591
|
* Create a whole new `<div class="wb-row">` element.
|
|
@@ -4581,7 +4640,7 @@ class WunderbaumNode {
|
|
|
4581
4640
|
}
|
|
4582
4641
|
// Render the icon (show a 'loading' icon if we do not have an expander that
|
|
4583
4642
|
// we would prefer).
|
|
4584
|
-
const iconSpan = this._createIcon(
|
|
4643
|
+
const iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
|
|
4585
4644
|
if (iconSpan) {
|
|
4586
4645
|
ofsTitlePx += ICON_WIDTH;
|
|
4587
4646
|
}
|
|
@@ -4724,9 +4783,9 @@ class WunderbaumNode {
|
|
|
4724
4783
|
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
4725
4784
|
const rowDiv = this._rowElem;
|
|
4726
4785
|
// Row markup already exists
|
|
4727
|
-
const
|
|
4728
|
-
const
|
|
4729
|
-
const
|
|
4786
|
+
const nodeSpan = rowDiv.querySelector("span.wb-node");
|
|
4787
|
+
const expanderElem = nodeSpan.querySelector("i.wb-expander");
|
|
4788
|
+
const checkboxElem = nodeSpan.querySelector("i.wb-checkbox");
|
|
4730
4789
|
const rowClasses = ["wb-row"];
|
|
4731
4790
|
this.expanded ? rowClasses.push("wb-expanded") : 0;
|
|
4732
4791
|
this.lazy ? rowClasses.push("wb-lazy") : 0;
|
|
@@ -4751,7 +4810,7 @@ class WunderbaumNode {
|
|
|
4751
4810
|
if (typeInfo && typeInfo.classes) {
|
|
4752
4811
|
rowDiv.classList.add(...typeInfo.classes);
|
|
4753
4812
|
}
|
|
4754
|
-
if (
|
|
4813
|
+
if (expanderElem) {
|
|
4755
4814
|
let image = null;
|
|
4756
4815
|
if (this._isLoading) {
|
|
4757
4816
|
image = iconMap.loading;
|
|
@@ -4768,16 +4827,20 @@ class WunderbaumNode {
|
|
|
4768
4827
|
image = iconMap.expanderLazy;
|
|
4769
4828
|
}
|
|
4770
4829
|
if (image == null) {
|
|
4771
|
-
|
|
4830
|
+
expanderElem.className = "wb-expander";
|
|
4831
|
+
expanderElem.classList.add("wb-indent");
|
|
4832
|
+
}
|
|
4833
|
+
else if (TEST_HTML.test(image)) {
|
|
4834
|
+
expanderElem.replaceWith(elemFromHtml(image));
|
|
4772
4835
|
}
|
|
4773
|
-
else if (
|
|
4774
|
-
|
|
4836
|
+
else if (TEST_FILE_PATH.test(image)) {
|
|
4837
|
+
expanderElem.style.backgroundImage = `url('${image}')`;
|
|
4775
4838
|
}
|
|
4776
4839
|
else {
|
|
4777
|
-
|
|
4840
|
+
expanderElem.className = "wb-expander " + image;
|
|
4778
4841
|
}
|
|
4779
4842
|
}
|
|
4780
|
-
if (
|
|
4843
|
+
if (checkboxElem) {
|
|
4781
4844
|
let cbclass = "wb-checkbox ";
|
|
4782
4845
|
if (this.isRadio()) {
|
|
4783
4846
|
cbclass += "wb-radio ";
|
|
@@ -4801,7 +4864,7 @@ class WunderbaumNode {
|
|
|
4801
4864
|
cbclass += iconMap.checkUnchecked;
|
|
4802
4865
|
}
|
|
4803
4866
|
}
|
|
4804
|
-
|
|
4867
|
+
checkboxElem.className = cbclass;
|
|
4805
4868
|
}
|
|
4806
4869
|
// Fix active cell in cell-nav mode
|
|
4807
4870
|
if (!opts.isNew) {
|
|
@@ -4811,9 +4874,9 @@ class WunderbaumNode {
|
|
|
4811
4874
|
colSpan.classList.remove("wb-error", "wb-invalid");
|
|
4812
4875
|
}
|
|
4813
4876
|
// Update icon (if not opts.isNew, which would rebuild markup anyway)
|
|
4814
|
-
const iconSpan =
|
|
4877
|
+
const iconSpan = nodeSpan.querySelector("i.wb-icon");
|
|
4815
4878
|
if (iconSpan) {
|
|
4816
|
-
this._createIcon(
|
|
4879
|
+
this._createIcon(nodeSpan, iconSpan, !expanderElem);
|
|
4817
4880
|
}
|
|
4818
4881
|
}
|
|
4819
4882
|
// Adjust column width
|
|
@@ -5118,6 +5181,32 @@ class WunderbaumNode {
|
|
|
5118
5181
|
setKey(key, refKey) {
|
|
5119
5182
|
throw new Error("Not yet implemented");
|
|
5120
5183
|
}
|
|
5184
|
+
// /**
|
|
5185
|
+
// * Calculate a *stable*, unique key for this node from its refKey (or title).
|
|
5186
|
+
// * We also add information from the parent, because a refKey may occur multiple
|
|
5187
|
+
// * times in a tree.
|
|
5188
|
+
// */
|
|
5189
|
+
// calcUniqueKey() {
|
|
5190
|
+
// // Assuming that the parent's key was calculated the same way, we implicitly
|
|
5191
|
+
// // involve the whole refKey-path:
|
|
5192
|
+
// const s = this.key + (this.refKey || this.title);
|
|
5193
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5194
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5195
|
+
// const h1 = util.murmurHash3(s, true);
|
|
5196
|
+
// return "id_" + h1 + util.murmurHash3(h1 + s, true);
|
|
5197
|
+
// // const l = [];
|
|
5198
|
+
// // // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
5199
|
+
// // let node: WunderbaumNode = this;
|
|
5200
|
+
// // while (node.parent) {
|
|
5201
|
+
// // l.unshift(node.refKey || node.key);
|
|
5202
|
+
// // node = node.parent;
|
|
5203
|
+
// // }
|
|
5204
|
+
// // const path = l.join("/");
|
|
5205
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
5206
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
5207
|
+
// // const h1 = util.murmurHash3(path, true);
|
|
5208
|
+
// // return "id_" + h1 + util.murmurHash3(h1 + path, true);
|
|
5209
|
+
// }
|
|
5121
5210
|
/**
|
|
5122
5211
|
* Trigger a repaint, typically after a status or data change.
|
|
5123
5212
|
*
|
|
@@ -5149,6 +5238,23 @@ class WunderbaumNode {
|
|
|
5149
5238
|
});
|
|
5150
5239
|
return nodeList;
|
|
5151
5240
|
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Return an array of refKey values.
|
|
5243
|
+
*
|
|
5244
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
5245
|
+
* clones.
|
|
5246
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
5247
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
5248
|
+
*/
|
|
5249
|
+
getRefKeys(selected = false) {
|
|
5250
|
+
const refKeys = new Set();
|
|
5251
|
+
this.visit((node) => {
|
|
5252
|
+
if (node.refKey != null && (!selected || node.selected)) {
|
|
5253
|
+
refKeys.add(node.refKey);
|
|
5254
|
+
}
|
|
5255
|
+
});
|
|
5256
|
+
return Array.from(refKeys);
|
|
5257
|
+
}
|
|
5152
5258
|
/** Toggle the check/uncheck state. */
|
|
5153
5259
|
toggleSelected(options) {
|
|
5154
5260
|
let flag = this.isSelected();
|
|
@@ -5328,9 +5434,11 @@ class WunderbaumNode {
|
|
|
5328
5434
|
if (selectMode === "hier") {
|
|
5329
5435
|
this.fixSelection3AfterClick();
|
|
5330
5436
|
}
|
|
5331
|
-
else if (selectMode === "single") {
|
|
5437
|
+
else if (selectMode === "single" && flag) {
|
|
5332
5438
|
tree.visit((n) => {
|
|
5333
|
-
n
|
|
5439
|
+
if (n !== this) {
|
|
5440
|
+
n.selected = false;
|
|
5441
|
+
}
|
|
5334
5442
|
});
|
|
5335
5443
|
}
|
|
5336
5444
|
}
|
|
@@ -5362,7 +5470,7 @@ class WunderbaumNode {
|
|
|
5362
5470
|
assert(data.statusNodeType, "Not a status node");
|
|
5363
5471
|
assert(!firstChild || !firstChild.isStatusNode(), "Child must not be a status node");
|
|
5364
5472
|
statusNode = this.addNode(data, "prependChild");
|
|
5365
|
-
statusNode.match =
|
|
5473
|
+
statusNode.match = -1; // Mark as 'match' to avoid hiding
|
|
5366
5474
|
tree.update(ChangeType.structure);
|
|
5367
5475
|
return statusNode;
|
|
5368
5476
|
};
|
|
@@ -5432,30 +5540,16 @@ class WunderbaumNode {
|
|
|
5432
5540
|
this.tooltip = tooltip;
|
|
5433
5541
|
this.update();
|
|
5434
5542
|
}
|
|
5435
|
-
_sortChildren(cmp, deep) {
|
|
5436
|
-
const cl = this.children;
|
|
5437
|
-
if (!cl) {
|
|
5438
|
-
return;
|
|
5439
|
-
}
|
|
5440
|
-
cl.sort(cmp);
|
|
5441
|
-
if (deep) {
|
|
5442
|
-
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5443
|
-
if (cl[i].children) {
|
|
5444
|
-
cl[i]._sortChildren(cmp, deep);
|
|
5445
|
-
}
|
|
5446
|
-
}
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5449
5543
|
/**
|
|
5450
5544
|
* Sort child list by title or custom criteria.
|
|
5451
5545
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
5452
5546
|
* (defaults to sorting by title).
|
|
5453
5547
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
5548
|
+
* @deprecated use {@link sort}
|
|
5454
5549
|
*/
|
|
5455
5550
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
5456
|
-
this.
|
|
5457
|
-
this.
|
|
5458
|
-
// this.triggerModify("sort"); // TODO
|
|
5551
|
+
this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
|
|
5552
|
+
return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
|
|
5459
5553
|
}
|
|
5460
5554
|
/**
|
|
5461
5555
|
* Renumber nodes `_nativeIndex`. This is useful to allow to restore the
|
|
@@ -5477,74 +5571,142 @@ class WunderbaumNode {
|
|
|
5477
5571
|
/**
|
|
5478
5572
|
* Convenience method to implement column sorting.
|
|
5479
5573
|
* @since 0.11.0
|
|
5574
|
+
* @deprecated use {@link sort}
|
|
5480
5575
|
*/
|
|
5481
5576
|
sortByProperty(options) {
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5577
|
+
this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
|
|
5578
|
+
return this.sort(options);
|
|
5579
|
+
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Implement column sorting.
|
|
5582
|
+
* @since 0.14.0
|
|
5583
|
+
*/
|
|
5584
|
+
sort(options) {
|
|
5585
|
+
const tree = this.tree;
|
|
5586
|
+
let { propName = undefined, deep = true, key = undefined, order = undefined, caseInsensitive = true, cmp = undefined,
|
|
5587
|
+
// Support click on column sort header:
|
|
5588
|
+
updateColInfo = false, nativeOrderPropName = "_nativeIndex", colId = undefined, } = options;
|
|
5589
|
+
propName !== null && propName !== void 0 ? propName : (propName = colId);
|
|
5590
|
+
if (propName === "*") {
|
|
5591
|
+
propName = "title";
|
|
5592
|
+
}
|
|
5593
|
+
const isFolder = tree.options.sortFoldersFirst === true
|
|
5594
|
+
? (node) => node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
|
|
5595
|
+
: tree.options.sortFoldersFirst;
|
|
5486
5596
|
if (updateColInfo) {
|
|
5487
|
-
colDef = this.tree["_columnsById"][options.colId];
|
|
5597
|
+
const colDef = this.tree["_columnsById"][options.colId];
|
|
5488
5598
|
assert(colDef, `Invalid colId specified: ${options.colId}`);
|
|
5489
|
-
order =
|
|
5490
|
-
(_a = options.order) !== null && _a !== void 0 ? _a : rotate(colDef.sortOrder, ["asc", "desc", undefined]);
|
|
5599
|
+
order !== null && order !== void 0 ? order : (order = rotate(colDef.sortOrder, ["asc", "desc", undefined]));
|
|
5491
5600
|
for (const col of this.tree.columns) {
|
|
5492
5601
|
col.sortOrder = col === colDef ? order : undefined;
|
|
5493
5602
|
}
|
|
5603
|
+
if (order === undefined) {
|
|
5604
|
+
propName = nativeOrderPropName;
|
|
5605
|
+
order = "asc";
|
|
5606
|
+
}
|
|
5494
5607
|
this.tree.update(ChangeType.colStructure);
|
|
5495
5608
|
}
|
|
5496
5609
|
else {
|
|
5497
|
-
|
|
5610
|
+
propName !== null && propName !== void 0 ? propName : (propName = "title");
|
|
5611
|
+
order !== null && order !== void 0 ? order : (order = "asc");
|
|
5612
|
+
}
|
|
5613
|
+
this.logDebug(`sort(), propName=${propName}, ${order}`, options);
|
|
5614
|
+
assert(propName || cmp || key, "No `propName` or `key` specified");
|
|
5615
|
+
// Define a key callback from the parameters we have
|
|
5616
|
+
if (key == null && cmp == null) {
|
|
5617
|
+
key = (node) => {
|
|
5618
|
+
let val;
|
|
5619
|
+
if (NODE_DICT_PROPS.has(propName)) {
|
|
5620
|
+
val = node[propName];
|
|
5621
|
+
}
|
|
5622
|
+
else {
|
|
5623
|
+
val = node.data[propName];
|
|
5624
|
+
}
|
|
5625
|
+
if (caseInsensitive && typeof val === "string") {
|
|
5626
|
+
val = val.toLowerCase();
|
|
5627
|
+
}
|
|
5628
|
+
return val;
|
|
5629
|
+
};
|
|
5498
5630
|
}
|
|
5499
|
-
|
|
5500
|
-
if (
|
|
5501
|
-
|
|
5631
|
+
// Define a compare callback that uses the key callback
|
|
5632
|
+
if (cmp) {
|
|
5633
|
+
assert(!key, "`key` and `cmp` are mutually exclusive");
|
|
5634
|
+
tree.logDeprecate("SortOptions.cmp", {
|
|
5635
|
+
since: "0.14.0",
|
|
5636
|
+
hint: "use the `key` callback instead",
|
|
5637
|
+
});
|
|
5502
5638
|
}
|
|
5503
|
-
|
|
5504
|
-
propName
|
|
5505
|
-
|
|
5639
|
+
else {
|
|
5640
|
+
if (options.propName || options.caseInsensitive) {
|
|
5641
|
+
tree.logWarn("sort(): ignoring propName, caseInsensitive");
|
|
5642
|
+
}
|
|
5643
|
+
cmp = (a, b) => {
|
|
5644
|
+
if (isFolder) {
|
|
5645
|
+
const isFolderA = isFolder(a);
|
|
5646
|
+
if (isFolderA !== isFolder(b)) {
|
|
5647
|
+
return isFolderA ? -1 : 1;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
let x = key(a);
|
|
5651
|
+
let y = key(b);
|
|
5652
|
+
// Assure we have reasonable comparisons with null values:
|
|
5653
|
+
if (x == null) {
|
|
5654
|
+
x = typeof y === "string" ? "" : 0;
|
|
5655
|
+
}
|
|
5656
|
+
else if (typeof x === "boolean") {
|
|
5657
|
+
x = x ? 1 : 0;
|
|
5658
|
+
}
|
|
5659
|
+
if (y == null) {
|
|
5660
|
+
y = typeof x === "string" ? "" : 0;
|
|
5661
|
+
}
|
|
5662
|
+
else if (typeof y === "boolean") {
|
|
5663
|
+
y = y ? 1 : 0;
|
|
5664
|
+
}
|
|
5665
|
+
if (order === "desc") {
|
|
5666
|
+
return x === y ? 0 : x > y ? -1 : 1;
|
|
5667
|
+
}
|
|
5668
|
+
return x === y ? 0 : x > y ? 1 : -1;
|
|
5669
|
+
};
|
|
5506
5670
|
}
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
let av, bv;
|
|
5511
|
-
if (NODE_DICT_PROPS.has(propName)) {
|
|
5512
|
-
av = a[propName];
|
|
5513
|
-
bv = b[propName];
|
|
5514
|
-
}
|
|
5515
|
-
else {
|
|
5516
|
-
av = a.data[propName];
|
|
5517
|
-
bv = b.data[propName];
|
|
5518
|
-
}
|
|
5519
|
-
if (av == null && bv == null) {
|
|
5520
|
-
return 0;
|
|
5521
|
-
}
|
|
5522
|
-
if (av == null) {
|
|
5523
|
-
av = typeof bv === "string" ? "" : 0;
|
|
5524
|
-
}
|
|
5525
|
-
else if (typeof av === "boolean") {
|
|
5526
|
-
av = av ? 1 : 0;
|
|
5527
|
-
}
|
|
5528
|
-
if (bv == null) {
|
|
5529
|
-
bv = typeof av === "string" ? "" : 0;
|
|
5530
|
-
}
|
|
5531
|
-
else if (typeof bv === "boolean") {
|
|
5532
|
-
bv = bv ? 1 : 0;
|
|
5671
|
+
function _sortChildren(cl) {
|
|
5672
|
+
if (!cl) {
|
|
5673
|
+
return;
|
|
5533
5674
|
}
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5675
|
+
cl.sort(cmp);
|
|
5676
|
+
if (deep) {
|
|
5677
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
5678
|
+
if (cl[i].children) {
|
|
5679
|
+
_sortChildren(cl[i].children);
|
|
5680
|
+
}
|
|
5540
5681
|
}
|
|
5541
5682
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5683
|
+
}
|
|
5684
|
+
if (this.children) {
|
|
5685
|
+
_sortChildren(this.children);
|
|
5686
|
+
}
|
|
5687
|
+
this.tree.update(ChangeType.structure);
|
|
5688
|
+
// this.triggerModify("sort"); // TODO
|
|
5689
|
+
}
|
|
5690
|
+
/**
|
|
5691
|
+
* Re-apply current sorting if any (use after lazy load).
|
|
5692
|
+
* Example:
|
|
5693
|
+
* ```js
|
|
5694
|
+
* load: function (e) {
|
|
5695
|
+
* // Whe loading a lazy branch, apply current sort order if any
|
|
5696
|
+
* e.node.resort();
|
|
5697
|
+
* },
|
|
5698
|
+
* ```
|
|
5699
|
+
* @since 0.14.0
|
|
5700
|
+
*/
|
|
5701
|
+
resort(options = {}) {
|
|
5702
|
+
for (const colDef of this.tree.columns) {
|
|
5703
|
+
if (colDef.sortOrder) {
|
|
5704
|
+
options.colId = colDef.id;
|
|
5705
|
+
options.order = colDef.sortOrder;
|
|
5706
|
+
this.sort(options);
|
|
5707
|
+
break;
|
|
5544
5708
|
}
|
|
5545
|
-
|
|
5546
|
-
};
|
|
5547
|
-
return this.sortChildren(cmp, deep);
|
|
5709
|
+
}
|
|
5548
5710
|
}
|
|
5549
5711
|
/**
|
|
5550
5712
|
* Trigger `modifyChild` event on a parent to signal that a child was modified.
|
|
@@ -5581,7 +5743,8 @@ class WunderbaumNode {
|
|
|
5581
5743
|
* @param {function} callback the callback function.
|
|
5582
5744
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
5583
5745
|
* its children only.
|
|
5584
|
-
* @see
|
|
5746
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
5747
|
+
* @see {@link Wunderbaum.visit}.
|
|
5585
5748
|
*/
|
|
5586
5749
|
visit(callback, includeSelf = false) {
|
|
5587
5750
|
let res = true;
|
|
@@ -5654,7 +5817,7 @@ WunderbaumNode.sequence = 0;
|
|
|
5654
5817
|
/*!
|
|
5655
5818
|
* Wunderbaum - ext-edit
|
|
5656
5819
|
* Copyright (c) 2021-2025, Martin Wendt. Released under the MIT license.
|
|
5657
|
-
* v0.
|
|
5820
|
+
* v0.14.0, Fri, 20 Mar 2026 16:58:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5658
5821
|
*/
|
|
5659
5822
|
// const START_MARKER = "\uFFF7";
|
|
5660
5823
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -5889,7 +6052,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
5889
6052
|
newValue = newValue.trim();
|
|
5890
6053
|
}
|
|
5891
6054
|
if (!node) {
|
|
5892
|
-
this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
6055
|
+
// this.tree.logDebug("stopEditTitle: not in edit mode.");
|
|
5893
6056
|
return;
|
|
5894
6057
|
}
|
|
5895
6058
|
node.logDebug(`stopEditTitle(${apply})`, options, focusElem, newValue);
|
|
@@ -5973,7 +6136,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
5973
6136
|
newNode.setClass("wb-edit-new");
|
|
5974
6137
|
this.relatedNode = node;
|
|
5975
6138
|
// Don't filter new nodes:
|
|
5976
|
-
newNode.match =
|
|
6139
|
+
newNode.match = -1;
|
|
5977
6140
|
newNode.makeVisible({ noAnimation: true }).then(() => {
|
|
5978
6141
|
this.startEditTitle(newNode);
|
|
5979
6142
|
});
|
|
@@ -5989,8 +6152,8 @@ class EditExtension extends WunderbaumExtension {
|
|
|
5989
6152
|
* https://github.com/mar10/wunderbaum
|
|
5990
6153
|
*
|
|
5991
6154
|
* Released under the MIT license.
|
|
5992
|
-
* @version v0.
|
|
5993
|
-
* @date
|
|
6155
|
+
* @version v0.14.0
|
|
6156
|
+
* @date Fri, 20 Mar 2026 16:58:31 GMT
|
|
5994
6157
|
*/
|
|
5995
6158
|
// import "./wunderbaum.scss";
|
|
5996
6159
|
class WbSystemRoot extends WunderbaumNode {
|
|
@@ -6039,18 +6202,21 @@ class Wunderbaum {
|
|
|
6039
6202
|
this._disableUpdateIgnoreCount = 0;
|
|
6040
6203
|
this._activeNode = null;
|
|
6041
6204
|
this._focusNode = null;
|
|
6205
|
+
this._initialSource = null;
|
|
6042
6206
|
/** Shared properties, referenced by `node.type`. */
|
|
6043
6207
|
this.types = {};
|
|
6044
6208
|
/** List of column definitions. */
|
|
6045
|
-
this.columns = [];
|
|
6209
|
+
this.columns = [];
|
|
6046
6210
|
this._columnsById = {};
|
|
6047
6211
|
// Modification Status
|
|
6048
6212
|
this.pendingChangeTypes = new Set();
|
|
6049
6213
|
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
6050
6214
|
this._util = util;
|
|
6051
6215
|
// --- SELECT ---
|
|
6052
|
-
// /** @internal */
|
|
6053
6216
|
// public selectRangeAnchor: WunderbaumNode | null = null;
|
|
6217
|
+
// --- BREADCRUMB ---
|
|
6218
|
+
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
6219
|
+
this.breadcrumb = null;
|
|
6054
6220
|
// --- FILTER ---
|
|
6055
6221
|
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
6056
6222
|
this.filterMode = null;
|
|
@@ -6065,45 +6231,57 @@ class Wunderbaum {
|
|
|
6065
6231
|
this.lastQuicksearchTerm = "";
|
|
6066
6232
|
// --- EDIT ---
|
|
6067
6233
|
this.lastClickTime = 0;
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6234
|
+
// Set default options and merge with user options
|
|
6235
|
+
const initOptions = Object.assign({
|
|
6236
|
+
id: undefined,
|
|
6237
|
+
source: [], // URL for GET/PUT, Ajax options, or callback
|
|
6238
|
+
element: unsafeCast(null),
|
|
6072
6239
|
debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
|
|
6073
6240
|
header: null, // Show/hide header (pass bool or string)
|
|
6074
|
-
// headerHeightPx: ROW_HEIGHT,
|
|
6075
6241
|
rowHeightPx: DEFAULT_ROW_HEIGHT,
|
|
6076
6242
|
iconMap: "bootstrap",
|
|
6077
|
-
columns: null,
|
|
6078
|
-
types:
|
|
6079
|
-
// escapeTitles: true,
|
|
6243
|
+
columns: [], //util.unsafeCast<ColumnDefinitionList>(null),
|
|
6244
|
+
types: {},
|
|
6080
6245
|
enabled: true,
|
|
6081
6246
|
fixedCol: false,
|
|
6082
6247
|
showSpinner: false,
|
|
6083
6248
|
checkbox: false,
|
|
6084
6249
|
minExpandLevel: 0,
|
|
6085
6250
|
emptyChildListExpandable: false,
|
|
6086
|
-
// updateThrottleWait: 200,
|
|
6087
6251
|
skeleton: false,
|
|
6088
|
-
|
|
6252
|
+
autoCollapse: false,
|
|
6253
|
+
adjustHeight: true,
|
|
6254
|
+
connectTopBreadcrumb: null,
|
|
6255
|
+
columnsFilterable: false,
|
|
6256
|
+
columnsMenu: false,
|
|
6257
|
+
columnsResizable: false,
|
|
6258
|
+
columnsSortable: false,
|
|
6089
6259
|
selectMode: "multi", // SelectModeType
|
|
6260
|
+
scrollIntoViewOnExpandClick: true,
|
|
6261
|
+
// --- Extensions (actually set by exensions on init)
|
|
6262
|
+
dnd: unsafeCast(null),
|
|
6263
|
+
edit: unsafeCast(null),
|
|
6264
|
+
filter: unsafeCast(null),
|
|
6090
6265
|
// --- KeyNav ---
|
|
6091
|
-
navigationModeOption: null,
|
|
6266
|
+
navigationModeOption: unsafeCast(null),
|
|
6092
6267
|
quicksearch: true,
|
|
6093
6268
|
// --- Events ---
|
|
6094
|
-
iconBadge: null,
|
|
6095
|
-
change: null,
|
|
6096
|
-
//
|
|
6097
|
-
error: null,
|
|
6098
|
-
receive: null,
|
|
6269
|
+
// iconBadge: null,
|
|
6270
|
+
// change: null,
|
|
6271
|
+
// ...
|
|
6099
6272
|
// --- Strings ---
|
|
6100
6273
|
strings: {
|
|
6101
6274
|
loadError: "Error",
|
|
6102
6275
|
loading: "Loading...",
|
|
6103
|
-
// loading: "Loading…",
|
|
6104
6276
|
noData: "No data",
|
|
6277
|
+
breadcrumbDelimiter: " » ",
|
|
6278
|
+
queryResult: "Found ${matches} of ${count}",
|
|
6279
|
+
noMatch: "No results",
|
|
6280
|
+
matchIndex: "${match} of ${matches}",
|
|
6105
6281
|
},
|
|
6106
|
-
}, options)
|
|
6282
|
+
}, options);
|
|
6283
|
+
const opts = initOptions;
|
|
6284
|
+
this.options = opts;
|
|
6107
6285
|
const readyDeferred = new Deferred();
|
|
6108
6286
|
this.ready = readyDeferred.promise();
|
|
6109
6287
|
let readyOk = false;
|
|
@@ -6130,7 +6308,8 @@ class Wunderbaum {
|
|
|
6130
6308
|
this._callEvent("init", { error: err });
|
|
6131
6309
|
}
|
|
6132
6310
|
});
|
|
6133
|
-
this.id =
|
|
6311
|
+
this.id = initOptions.id || "wb_" + ++Wunderbaum.sequence;
|
|
6312
|
+
delete initOptions.id;
|
|
6134
6313
|
this.root = new WbSystemRoot(this);
|
|
6135
6314
|
this._registerExtension(new KeynavExtension(this));
|
|
6136
6315
|
this._registerExtension(new EditExtension(this));
|
|
@@ -6140,19 +6319,20 @@ class Wunderbaum {
|
|
|
6140
6319
|
this._registerExtension(new LoggerExtension(this));
|
|
6141
6320
|
this._updateViewportThrottled = adaptiveThrottle(this._updateViewportImmediately.bind(this), {});
|
|
6142
6321
|
// --- Evaluate options
|
|
6143
|
-
this.columns =
|
|
6144
|
-
delete
|
|
6322
|
+
this.columns = initOptions.columns || [];
|
|
6323
|
+
delete initOptions.columns;
|
|
6145
6324
|
if (!this.columns || !this.columns.length) {
|
|
6146
6325
|
const title = typeof opts.header === "string" ? opts.header : this.id;
|
|
6147
6326
|
this.columns = [{ id: "*", title: title, width: "*" }];
|
|
6148
6327
|
}
|
|
6149
|
-
if (
|
|
6150
|
-
this.setTypes(
|
|
6328
|
+
if (initOptions.types) {
|
|
6329
|
+
this.setTypes(initOptions.types, true);
|
|
6151
6330
|
}
|
|
6152
|
-
delete
|
|
6331
|
+
delete initOptions.types;
|
|
6153
6332
|
// --- Create Markup
|
|
6154
|
-
this.element = elemFromSelector(
|
|
6155
|
-
assert(!!this.element, `Invalid 'element' option: ${
|
|
6333
|
+
this.element = elemFromSelector(initOptions.element);
|
|
6334
|
+
assert(!!this.element, `Invalid 'element' option: ${initOptions.element}`);
|
|
6335
|
+
delete initOptions.element;
|
|
6156
6336
|
this.element.classList.add("wunderbaum");
|
|
6157
6337
|
if (!this.element.getAttribute("tabindex")) {
|
|
6158
6338
|
this.element.tabIndex = 0;
|
|
@@ -6207,6 +6387,19 @@ class Wunderbaum {
|
|
|
6207
6387
|
this.headerElement =
|
|
6208
6388
|
this.element.querySelector("div.wb-header");
|
|
6209
6389
|
this.element.classList.toggle("wb-grid", this.columns.length > 1);
|
|
6390
|
+
if (this.options.connectTopBreadcrumb) {
|
|
6391
|
+
this.breadcrumb = elemFromSelector(this.options.connectTopBreadcrumb);
|
|
6392
|
+
assert(!this.breadcrumb || this.breadcrumb.innerHTML != null, `Invalid 'connectTopBreadcrumb' option: ${this.breadcrumb}.`);
|
|
6393
|
+
this.breadcrumb.addEventListener("click", (e) => {
|
|
6394
|
+
// const node = Wunderbaum.getNode(e)!;
|
|
6395
|
+
const elem = e.target;
|
|
6396
|
+
if (elem && elem.matches("a.wb-breadcrumb")) {
|
|
6397
|
+
const node = this.keyMap.get(elem.dataset.key);
|
|
6398
|
+
node === null || node === void 0 ? void 0 : node.setActive();
|
|
6399
|
+
e.preventDefault();
|
|
6400
|
+
}
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6210
6403
|
this._initExtensions();
|
|
6211
6404
|
// --- apply initial options
|
|
6212
6405
|
["enabled", "fixedCol"].forEach((optName) => {
|
|
@@ -6215,11 +6408,11 @@ class Wunderbaum {
|
|
|
6215
6408
|
}
|
|
6216
6409
|
});
|
|
6217
6410
|
// --- Load initial data
|
|
6218
|
-
if (
|
|
6411
|
+
if (initOptions.source) {
|
|
6219
6412
|
if (opts.showSpinner) {
|
|
6220
6413
|
this.nodeListElement.innerHTML = `<progress class='spinner'>${opts.strings.loading}</progress>`;
|
|
6221
6414
|
}
|
|
6222
|
-
this.load(
|
|
6415
|
+
this.load(initOptions.source)
|
|
6223
6416
|
.then(() => {
|
|
6224
6417
|
// The source may have defined columns, so we may adjust the nav mode
|
|
6225
6418
|
if (opts.navigationModeOption == null) {
|
|
@@ -6252,15 +6445,18 @@ class Wunderbaum {
|
|
|
6252
6445
|
// has a wrong value at start???
|
|
6253
6446
|
this.update(ChangeType.any);
|
|
6254
6447
|
// --- Bind listeners
|
|
6255
|
-
this.
|
|
6256
|
-
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6257
|
-
this.update(ChangeType.scroll);
|
|
6258
|
-
});
|
|
6448
|
+
this._registerEventHandlers();
|
|
6259
6449
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
6260
6450
|
// this.log("ResizeObserver: Size changed", entries);
|
|
6261
6451
|
this.update(ChangeType.resize);
|
|
6262
6452
|
});
|
|
6263
6453
|
this.resizeObserver.observe(this.element);
|
|
6454
|
+
}
|
|
6455
|
+
_registerEventHandlers() {
|
|
6456
|
+
this.element.addEventListener("scroll", (e) => {
|
|
6457
|
+
// this.log(`scroll, scrollTop:${e.target.scrollTop}`, e);
|
|
6458
|
+
this.update(ChangeType.scroll);
|
|
6459
|
+
});
|
|
6264
6460
|
onEvent(this.element, "click", ".wb-button,.wb-col-icon", (e) => {
|
|
6265
6461
|
var _a, _b;
|
|
6266
6462
|
const info = Wunderbaum.getEventInfo(e);
|
|
@@ -6276,9 +6472,6 @@ class Wunderbaum {
|
|
|
6276
6472
|
const node = info.node;
|
|
6277
6473
|
const mouseEvent = e;
|
|
6278
6474
|
// this.log("click", info);
|
|
6279
|
-
// if (this._selectRange(info) === false) {
|
|
6280
|
-
// return;
|
|
6281
|
-
// }
|
|
6282
6475
|
if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
|
|
6283
6476
|
this.lastClickTime = Date.now();
|
|
6284
6477
|
return false;
|
|
@@ -6297,20 +6490,22 @@ class Wunderbaum {
|
|
|
6297
6490
|
(!slowClickDelay || Date.now() - this.lastClickTime < slowClickDelay)) {
|
|
6298
6491
|
node.startEditTitle();
|
|
6299
6492
|
}
|
|
6300
|
-
if (info.colIdx >= 0) {
|
|
6301
|
-
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6302
|
-
}
|
|
6303
|
-
else {
|
|
6304
|
-
node.setActive(true, { event: e });
|
|
6305
|
-
}
|
|
6306
6493
|
if (info.region === NodeRegion.expander) {
|
|
6307
6494
|
node.setExpanded(!node.isExpanded(), {
|
|
6308
|
-
scrollIntoView: options.scrollIntoViewOnExpandClick !== false,
|
|
6495
|
+
scrollIntoView: this.options.scrollIntoViewOnExpandClick !== false,
|
|
6309
6496
|
});
|
|
6310
6497
|
}
|
|
6311
6498
|
else if (info.region === NodeRegion.checkbox) {
|
|
6312
6499
|
node.toggleSelected();
|
|
6313
6500
|
}
|
|
6501
|
+
else {
|
|
6502
|
+
if (info.colIdx >= 0) {
|
|
6503
|
+
node.setActive(true, { colIdx: info.colIdx, event: e });
|
|
6504
|
+
}
|
|
6505
|
+
else {
|
|
6506
|
+
node.setActive(true, { event: e });
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6314
6509
|
}
|
|
6315
6510
|
this.lastClickTime = Date.now();
|
|
6316
6511
|
});
|
|
@@ -6346,7 +6541,7 @@ class Wunderbaum {
|
|
|
6346
6541
|
const targetNode = Wunderbaum.getNode(e);
|
|
6347
6542
|
this._callEvent("focus", { flag: flag, event: e });
|
|
6348
6543
|
if (flag && this.isRowNav() && !this.isEditingTitle()) {
|
|
6349
|
-
if (
|
|
6544
|
+
if (this.options.navigationModeOption === NavModeEnum.row) {
|
|
6350
6545
|
targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
|
|
6351
6546
|
}
|
|
6352
6547
|
else {
|
|
@@ -6413,11 +6608,12 @@ class Wunderbaum {
|
|
|
6413
6608
|
}
|
|
6414
6609
|
/**
|
|
6415
6610
|
* Return the icon-function -> icon-definition mapping.
|
|
6611
|
+
* @deprecated Use {@link Wunderbaum.iconMaps}
|
|
6416
6612
|
*/
|
|
6417
6613
|
get iconMap() {
|
|
6418
6614
|
const map = this.options.iconMap;
|
|
6419
6615
|
if (typeof map === "string") {
|
|
6420
|
-
return
|
|
6616
|
+
return defaultIconMaps[map];
|
|
6421
6617
|
}
|
|
6422
6618
|
return map;
|
|
6423
6619
|
}
|
|
@@ -6470,7 +6666,38 @@ class Wunderbaum {
|
|
|
6470
6666
|
ext.init();
|
|
6471
6667
|
}
|
|
6472
6668
|
}
|
|
6473
|
-
/**
|
|
6669
|
+
/**
|
|
6670
|
+
* Calculate a *stable*, unique key for a node from its refKey (or title).
|
|
6671
|
+
* We also add information from the parent, because a refKey may occur multiple
|
|
6672
|
+
* times in a tree (but not as child of the same parent).
|
|
6673
|
+
* @internal
|
|
6674
|
+
*/
|
|
6675
|
+
_calculateKey(data, parent) {
|
|
6676
|
+
if (data.key) {
|
|
6677
|
+
// Always use an explicitly passed key
|
|
6678
|
+
return data.key;
|
|
6679
|
+
}
|
|
6680
|
+
// Auto-keys are optional, use a monotonic counter by default:
|
|
6681
|
+
if (!this.options.autoKeys) {
|
|
6682
|
+
return "" + ++WunderbaumNode.sequence;
|
|
6683
|
+
}
|
|
6684
|
+
// Add the parent's key to the hash. Assuming this was generated by the
|
|
6685
|
+
// same algorithm, this should incorporate the whole path:
|
|
6686
|
+
const s = (parent ? parent.key : "") + (data.refKey || data.title);
|
|
6687
|
+
// 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
6688
|
+
// https://security.stackexchange.com/q/209882/207588
|
|
6689
|
+
const h1 = murmurHash3(s, true);
|
|
6690
|
+
let key = "id_" + h1 + murmurHash3(h1 + s, true);
|
|
6691
|
+
// Check for collisions
|
|
6692
|
+
// (Most likely if the same title occurs multiple in the same parent).
|
|
6693
|
+
const existingNode = this.keyMap.get(key);
|
|
6694
|
+
if (existingNode) {
|
|
6695
|
+
key += "." + ++Wunderbaum.sequence;
|
|
6696
|
+
this.logWarn(`Node with existing key: '${existingNode}', using ${key}.`, data);
|
|
6697
|
+
}
|
|
6698
|
+
return key;
|
|
6699
|
+
}
|
|
6700
|
+
/** Add node to tree's bookkeeping data structures. @internal */
|
|
6474
6701
|
_registerNode(node) {
|
|
6475
6702
|
const key = node.key;
|
|
6476
6703
|
assert(key != null, `Missing key: '${node}'.`);
|
|
@@ -6487,7 +6714,7 @@ class Wunderbaum {
|
|
|
6487
6714
|
}
|
|
6488
6715
|
}
|
|
6489
6716
|
}
|
|
6490
|
-
/** Remove node from tree's bookkeeping data structures. */
|
|
6717
|
+
/** Remove node from tree's bookkeeping data structures. @internal */
|
|
6491
6718
|
_unregisterNode(node) {
|
|
6492
6719
|
// Remove refKey reference from map (if any)
|
|
6493
6720
|
const rk = node.refKey;
|
|
@@ -6570,7 +6797,10 @@ class Wunderbaum {
|
|
|
6570
6797
|
});
|
|
6571
6798
|
return node;
|
|
6572
6799
|
}
|
|
6573
|
-
/** Return the topmost visible node in the viewport.
|
|
6800
|
+
/** Return the topmost visible node in the viewport.
|
|
6801
|
+
* @param complete If `false`, the node is considered visible if at least one
|
|
6802
|
+
* pixel is visible.
|
|
6803
|
+
*/
|
|
6574
6804
|
getTopmostVpNode(complete = true) {
|
|
6575
6805
|
const rowHeight = this.options.rowHeightPx;
|
|
6576
6806
|
const gracePx = 1; // ignore subpixel scrolling
|
|
@@ -6603,7 +6833,7 @@ class Wunderbaum {
|
|
|
6603
6833
|
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
6604
6834
|
return this._getNodeByRowIdx(bottomIdx);
|
|
6605
6835
|
}
|
|
6606
|
-
/** Return
|
|
6836
|
+
/** Return preceding visible node in the viewport. */
|
|
6607
6837
|
_getPrevNodeInView(node, ofs = 1) {
|
|
6608
6838
|
this.visitRows((n) => {
|
|
6609
6839
|
node = n;
|
|
@@ -6614,13 +6844,18 @@ class Wunderbaum {
|
|
|
6614
6844
|
return node;
|
|
6615
6845
|
}
|
|
6616
6846
|
/** Return following visible node in the viewport. */
|
|
6617
|
-
_getNextNodeInView(node,
|
|
6847
|
+
_getNextNodeInView(node, options) {
|
|
6848
|
+
let ofs = (options === null || options === void 0 ? void 0 : options.ofs) || 1;
|
|
6849
|
+
const reverse = !!(options === null || options === void 0 ? void 0 : options.reverse);
|
|
6618
6850
|
this.visitRows((n) => {
|
|
6619
6851
|
node = n;
|
|
6852
|
+
if ((options === null || options === void 0 ? void 0 : options.cb) && options.cb(n)) {
|
|
6853
|
+
return false;
|
|
6854
|
+
}
|
|
6620
6855
|
if (ofs-- <= 0) {
|
|
6621
6856
|
return false;
|
|
6622
6857
|
}
|
|
6623
|
-
}, { reverse:
|
|
6858
|
+
}, { reverse: reverse, start: node || this.getActiveNode() });
|
|
6624
6859
|
return node;
|
|
6625
6860
|
}
|
|
6626
6861
|
/**
|
|
@@ -6740,9 +6975,11 @@ class Wunderbaum {
|
|
|
6740
6975
|
case "first":
|
|
6741
6976
|
case "last":
|
|
6742
6977
|
case "left":
|
|
6978
|
+
case "nextMatch":
|
|
6743
6979
|
case "pageDown":
|
|
6744
6980
|
case "pageUp":
|
|
6745
6981
|
case "parent":
|
|
6982
|
+
case "prevMatch":
|
|
6746
6983
|
case "right":
|
|
6747
6984
|
case "up":
|
|
6748
6985
|
return node.navigate(cmd);
|
|
@@ -6857,22 +7094,39 @@ class Wunderbaum {
|
|
|
6857
7094
|
/** Run code, but defer rendering of viewport until done.
|
|
6858
7095
|
*
|
|
6859
7096
|
* ```js
|
|
6860
|
-
* tree.runWithDeferredUpdate(() => {
|
|
6861
|
-
* return
|
|
7097
|
+
* const res = tree.runWithDeferredUpdate(() => {
|
|
7098
|
+
* return someFunctionThatWouldUpdateManyNodes();
|
|
6862
7099
|
* });
|
|
6863
7100
|
* ```
|
|
6864
7101
|
*/
|
|
6865
|
-
runWithDeferredUpdate(func
|
|
7102
|
+
runWithDeferredUpdate(func) {
|
|
6866
7103
|
try {
|
|
6867
7104
|
this.enableUpdate(false);
|
|
6868
7105
|
const res = func();
|
|
6869
|
-
assert(!(res instanceof Promise), `Promise return not allowed: ${res}`);
|
|
7106
|
+
assert(!(res instanceof Promise), `Promise return not allowed (see 'runWithDeferredUpdateAsync()'): ${res}`);
|
|
6870
7107
|
return res;
|
|
6871
7108
|
}
|
|
6872
7109
|
finally {
|
|
6873
7110
|
this.enableUpdate(true);
|
|
6874
7111
|
}
|
|
6875
7112
|
}
|
|
7113
|
+
/** Run code, but defer rendering of viewport until done.
|
|
7114
|
+
*
|
|
7115
|
+
* ```js
|
|
7116
|
+
* const res = await tree.runWithDeferredUpdate(async () => {
|
|
7117
|
+
* return someAsyncFunctionThatWouldUpdateManyNodes();
|
|
7118
|
+
* });
|
|
7119
|
+
* ```
|
|
7120
|
+
*/
|
|
7121
|
+
async runWithDeferredUpdateAsync(func) {
|
|
7122
|
+
try {
|
|
7123
|
+
this.enableUpdate(false);
|
|
7124
|
+
return await func();
|
|
7125
|
+
}
|
|
7126
|
+
finally {
|
|
7127
|
+
this.enableUpdate(true);
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
6876
7130
|
/** Recursively expand all expandable nodes (triggers lazy load if needed). */
|
|
6877
7131
|
async expandAll(flag = true, options) {
|
|
6878
7132
|
await this.root.expandAll(flag, options);
|
|
@@ -6892,6 +7146,17 @@ class Wunderbaum {
|
|
|
6892
7146
|
getSelectedNodes(stopOnParents = false) {
|
|
6893
7147
|
return this.root.getSelectedNodes(stopOnParents);
|
|
6894
7148
|
}
|
|
7149
|
+
/**
|
|
7150
|
+
* Return an array of refKey values.
|
|
7151
|
+
*
|
|
7152
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
7153
|
+
* clones.
|
|
7154
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
7155
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
7156
|
+
*/
|
|
7157
|
+
getRefKeys(selected = false) {
|
|
7158
|
+
return this.root.getRefKeys(selected);
|
|
7159
|
+
}
|
|
6895
7160
|
/*
|
|
6896
7161
|
* Return an array of selected nodes.
|
|
6897
7162
|
*/
|
|
@@ -6934,6 +7199,11 @@ class Wunderbaum {
|
|
|
6934
7199
|
count(visible = false) {
|
|
6935
7200
|
return visible ? this.treeRowCount : this.keyMap.size;
|
|
6936
7201
|
}
|
|
7202
|
+
/** Return the number of *unique* nodes in the data model, i.e. unique `node.refKey`.
|
|
7203
|
+
*/
|
|
7204
|
+
countUnique() {
|
|
7205
|
+
return this.refKeyMap.size;
|
|
7206
|
+
}
|
|
6937
7207
|
/** @internal sanity check. */
|
|
6938
7208
|
_check() {
|
|
6939
7209
|
let i = 0;
|
|
@@ -6992,12 +7262,14 @@ class Wunderbaum {
|
|
|
6992
7262
|
* and wrap-around at the end.
|
|
6993
7263
|
* Used by quicksearch and keyboard navigation.
|
|
6994
7264
|
*/
|
|
6995
|
-
findNextNode(match, startNode) {
|
|
7265
|
+
findNextNode(match, startNode, reverse = false) {
|
|
6996
7266
|
//, visibleOnly) {
|
|
6997
7267
|
let res = null;
|
|
6998
7268
|
const firstNode = this.getFirstChild();
|
|
7269
|
+
// Last visible node (calculation is expensive, so do only if we need it):
|
|
7270
|
+
const lastNode = reverse ? this.findRelatedNode(firstNode, "last") : null;
|
|
6999
7271
|
const matcher = typeof match === "string" ? makeNodeTitleStartMatcher(match) : match;
|
|
7000
|
-
startNode = startNode || firstNode;
|
|
7272
|
+
startNode = startNode || (reverse ? lastNode : firstNode);
|
|
7001
7273
|
function _checkNode(n) {
|
|
7002
7274
|
// console.log("_check " + n)
|
|
7003
7275
|
if (matcher(n)) {
|
|
@@ -7010,12 +7282,14 @@ class Wunderbaum {
|
|
|
7010
7282
|
this.visitRows(_checkNode, {
|
|
7011
7283
|
start: startNode,
|
|
7012
7284
|
includeSelf: false,
|
|
7285
|
+
reverse: reverse,
|
|
7013
7286
|
});
|
|
7014
7287
|
// Wrap around search
|
|
7015
7288
|
if (!res && startNode !== firstNode) {
|
|
7016
7289
|
this.visitRows(_checkNode, {
|
|
7017
|
-
start: firstNode,
|
|
7290
|
+
start: reverse ? lastNode : firstNode,
|
|
7018
7291
|
includeSelf: true,
|
|
7292
|
+
reverse: reverse,
|
|
7019
7293
|
});
|
|
7020
7294
|
}
|
|
7021
7295
|
return res;
|
|
@@ -7082,7 +7356,7 @@ class Wunderbaum {
|
|
|
7082
7356
|
// }
|
|
7083
7357
|
break;
|
|
7084
7358
|
case "up":
|
|
7085
|
-
res = this.
|
|
7359
|
+
res = this._getNextNodeInView(node, { reverse: true });
|
|
7086
7360
|
break;
|
|
7087
7361
|
case "down":
|
|
7088
7362
|
res = this._getNextNodeInView(node);
|
|
@@ -7095,7 +7369,10 @@ class Wunderbaum {
|
|
|
7095
7369
|
res = bottomNode;
|
|
7096
7370
|
}
|
|
7097
7371
|
else {
|
|
7098
|
-
res = this._getNextNodeInView(node,
|
|
7372
|
+
res = this._getNextNodeInView(node, {
|
|
7373
|
+
reverse: false,
|
|
7374
|
+
ofs: pageSize,
|
|
7375
|
+
});
|
|
7099
7376
|
}
|
|
7100
7377
|
}
|
|
7101
7378
|
break;
|
|
@@ -7110,10 +7387,23 @@ class Wunderbaum {
|
|
|
7110
7387
|
res = topNode;
|
|
7111
7388
|
}
|
|
7112
7389
|
else {
|
|
7113
|
-
res = this.
|
|
7390
|
+
res = this._getNextNodeInView(node, {
|
|
7391
|
+
reverse: true,
|
|
7392
|
+
ofs: pageSize,
|
|
7393
|
+
});
|
|
7114
7394
|
}
|
|
7115
7395
|
}
|
|
7116
7396
|
break;
|
|
7397
|
+
case "prevMatch":
|
|
7398
|
+
// fallthrough
|
|
7399
|
+
case "nextMatch":
|
|
7400
|
+
if (!this.isFilterActive) {
|
|
7401
|
+
this.logWarn(`${where}: Filter is not active.`);
|
|
7402
|
+
break;
|
|
7403
|
+
}
|
|
7404
|
+
res = this.findNextNode((n) => n.isMatched(), node, where === "prevMatch");
|
|
7405
|
+
res === null || res === void 0 ? void 0 : res.setActive();
|
|
7406
|
+
break;
|
|
7117
7407
|
default:
|
|
7118
7408
|
this.logWarn("Unknown relation '" + where + "'.");
|
|
7119
7409
|
}
|
|
@@ -7148,6 +7438,18 @@ class Wunderbaum {
|
|
|
7148
7438
|
format(name_cb, connectors) {
|
|
7149
7439
|
return this.root.format(name_cb, connectors);
|
|
7150
7440
|
}
|
|
7441
|
+
/**
|
|
7442
|
+
* Always returns null (so a tree instance behaves as `tree.root`).
|
|
7443
|
+
*/
|
|
7444
|
+
get parent() {
|
|
7445
|
+
return null;
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Return a list of top-level nodes.
|
|
7449
|
+
*/
|
|
7450
|
+
get children() {
|
|
7451
|
+
return this.root.children || [];
|
|
7452
|
+
}
|
|
7151
7453
|
/**
|
|
7152
7454
|
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
7153
7455
|
*/
|
|
@@ -7175,6 +7477,12 @@ class Wunderbaum {
|
|
|
7175
7477
|
getFirstChild() {
|
|
7176
7478
|
return this.root.getFirstChild();
|
|
7177
7479
|
}
|
|
7480
|
+
/**
|
|
7481
|
+
* Return the last top level node if any (not the invisible root node).
|
|
7482
|
+
*/
|
|
7483
|
+
getLastChild() {
|
|
7484
|
+
return this.root.getLastChild();
|
|
7485
|
+
}
|
|
7178
7486
|
/**
|
|
7179
7487
|
* Return the node that currently has keyboard focus or null.
|
|
7180
7488
|
* Alias for {@link Wunderbaum.focusNode}.
|
|
@@ -7260,7 +7568,7 @@ class Wunderbaum {
|
|
|
7260
7568
|
}
|
|
7261
7569
|
/** Return true if any node title or grid cell is currently beeing edited.
|
|
7262
7570
|
*
|
|
7263
|
-
* See also {@link
|
|
7571
|
+
* See also {@link isEditingTitle}.
|
|
7264
7572
|
*/
|
|
7265
7573
|
isEditing() {
|
|
7266
7574
|
const focusElem = this.nodeListElement.querySelector("input:focus,select:focus");
|
|
@@ -7268,7 +7576,7 @@ class Wunderbaum {
|
|
|
7268
7576
|
}
|
|
7269
7577
|
/** Return true if any node is currently in edit-title mode.
|
|
7270
7578
|
*
|
|
7271
|
-
* See also {@link WunderbaumNode.isEditingTitle} and {@link
|
|
7579
|
+
* See also {@link WunderbaumNode.isEditingTitle} and {@link isEditing}.
|
|
7272
7580
|
*/
|
|
7273
7581
|
isEditingTitle() {
|
|
7274
7582
|
return this._callMethod("edit.isEditingTitle");
|
|
@@ -7288,7 +7596,7 @@ class Wunderbaum {
|
|
|
7288
7596
|
return res;
|
|
7289
7597
|
}
|
|
7290
7598
|
/** Write to `console.log` with tree name as prefix if opts.debugLevel >= 4.
|
|
7291
|
-
* @see {@link
|
|
7599
|
+
* @see {@link logDebug}
|
|
7292
7600
|
*/
|
|
7293
7601
|
log(...args) {
|
|
7294
7602
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7297,7 +7605,7 @@ class Wunderbaum {
|
|
|
7297
7605
|
}
|
|
7298
7606
|
/** Write to `console.debug` with tree name as prefix if opts.debugLevel >= 4.
|
|
7299
7607
|
* and browser console level includes debug/verbose messages.
|
|
7300
|
-
* @see {@link
|
|
7608
|
+
* @see {@link log}
|
|
7301
7609
|
*/
|
|
7302
7610
|
logDebug(...args) {
|
|
7303
7611
|
if (this.options.debugLevel >= 4) {
|
|
@@ -7335,6 +7643,19 @@ class Wunderbaum {
|
|
|
7335
7643
|
console.warn(this.toString(), ...args); // eslint-disable-line no-console
|
|
7336
7644
|
}
|
|
7337
7645
|
}
|
|
7646
|
+
/** Emit a warning for deprecated methods. @internal */
|
|
7647
|
+
logDeprecate(method, options) {
|
|
7648
|
+
if (this.options.debugLevel >= 2) {
|
|
7649
|
+
let msg = `${this}: ${method} is deprecated`;
|
|
7650
|
+
if (options === null || options === void 0 ? void 0 : options.since) {
|
|
7651
|
+
msg += ` since ${options.since}`;
|
|
7652
|
+
}
|
|
7653
|
+
if (options === null || options === void 0 ? void 0 : options.hint) {
|
|
7654
|
+
msg += ` (${options.since})`;
|
|
7655
|
+
}
|
|
7656
|
+
console.warn(msg + "."); // eslint-disable-line no-console
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7338
7659
|
/** Reset column widths to default. @since 0.10.0 */
|
|
7339
7660
|
resetColumns() {
|
|
7340
7661
|
this.columns.forEach((col) => {
|
|
@@ -7502,6 +7823,69 @@ class Wunderbaum {
|
|
|
7502
7823
|
_setFocusNode(node) {
|
|
7503
7824
|
this._focusNode = node;
|
|
7504
7825
|
}
|
|
7826
|
+
/** Return the current selection/expansion/activation status. @experimental */
|
|
7827
|
+
getState(options = {}) {
|
|
7828
|
+
var _a, _b;
|
|
7829
|
+
const { activeKey = true, expandedKeys = false, selectedKeys = false, } = options;
|
|
7830
|
+
const expandSet = new Set();
|
|
7831
|
+
if (expandedKeys) {
|
|
7832
|
+
for (const node of this) {
|
|
7833
|
+
if (node.isExpanded() && node.hasChildren()) {
|
|
7834
|
+
expandSet.add(node.key);
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
// Parents of active node are always expanded
|
|
7839
|
+
if (activeKey && this.activeNode) {
|
|
7840
|
+
this.activeNode.visitParents((n) => {
|
|
7841
|
+
if (n.parent) {
|
|
7842
|
+
expandSet.add(n.key);
|
|
7843
|
+
}
|
|
7844
|
+
}, false);
|
|
7845
|
+
}
|
|
7846
|
+
const state = {
|
|
7847
|
+
expandedKeys: expandSet.size ? Array.from(expandSet) : undefined,
|
|
7848
|
+
activeKey: (_b = (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null,
|
|
7849
|
+
activeColIdx: this.activeColIdx,
|
|
7850
|
+
selectedKeys: selectedKeys
|
|
7851
|
+
? this.getSelectedNodes().flatMap((n) => n.key)
|
|
7852
|
+
: undefined,
|
|
7853
|
+
};
|
|
7854
|
+
return state;
|
|
7855
|
+
}
|
|
7856
|
+
/** Apply selection/expansion/activation status. @experimental */
|
|
7857
|
+
async setState(state, options = {}) {
|
|
7858
|
+
const { expandLazy = true } = options;
|
|
7859
|
+
return this.runWithDeferredUpdateAsync(async () => {
|
|
7860
|
+
var _a, _b;
|
|
7861
|
+
if (state.expandedKeys && state.expandedKeys.length) {
|
|
7862
|
+
if (expandLazy) {
|
|
7863
|
+
// Expand all keys recursively, even if they are not in the tree yet
|
|
7864
|
+
await this._loadLazyNodes(state.expandedKeys, {
|
|
7865
|
+
expand: true,
|
|
7866
|
+
noEvents: true,
|
|
7867
|
+
});
|
|
7868
|
+
}
|
|
7869
|
+
else {
|
|
7870
|
+
for (const key of state.expandedKeys) {
|
|
7871
|
+
(_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setExpanded(true);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
if (state.activeKey) {
|
|
7876
|
+
this.setActiveNode(state.activeKey);
|
|
7877
|
+
}
|
|
7878
|
+
if (state.selectedKeys) {
|
|
7879
|
+
this.selectAll(false);
|
|
7880
|
+
for (const key of state.selectedKeys) {
|
|
7881
|
+
(_b = this.findKey(key)) === null || _b === void 0 ? void 0 : _b.setSelected(true);
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7884
|
+
if (this.isCellNav() && state.activeColIdx != null) {
|
|
7885
|
+
this.setColumn(state.activeColIdx);
|
|
7886
|
+
}
|
|
7887
|
+
});
|
|
7888
|
+
}
|
|
7505
7889
|
update(change, node, options) {
|
|
7506
7890
|
// this.log(`update(${change}) node=${node}`);
|
|
7507
7891
|
if (!(node instanceof WunderbaumNode)) {
|
|
@@ -7658,18 +8042,33 @@ class Wunderbaum {
|
|
|
7658
8042
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
7659
8043
|
* (defaults to sorting by title).
|
|
7660
8044
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
8045
|
+
* @deprecated use {@link sort}
|
|
7661
8046
|
*/
|
|
7662
8047
|
sortChildren(cmp = nodeTitleSorter, deep = false) {
|
|
7663
|
-
this.
|
|
8048
|
+
this.logDeprecate("sortChildren()", { since: "0.14.0" });
|
|
8049
|
+
return this.sort({
|
|
8050
|
+
cmp: cmp ? cmp : undefined,
|
|
8051
|
+
deep: deep,
|
|
8052
|
+
propName: "title",
|
|
8053
|
+
});
|
|
7664
8054
|
}
|
|
7665
8055
|
/**
|
|
7666
8056
|
* Convenience method to implement column sorting.
|
|
7667
8057
|
* @see {@link WunderbaumNode.sortByProperty}.
|
|
7668
8058
|
* @since 0.11.0
|
|
8059
|
+
* @deprecated use {@link sort}
|
|
7669
8060
|
*/
|
|
7670
8061
|
sortByProperty(options) {
|
|
8062
|
+
this.logDeprecate("sortByProperty()", { since: "0.14.0" });
|
|
7671
8063
|
this.root.sortByProperty(options);
|
|
7672
8064
|
}
|
|
8065
|
+
/**
|
|
8066
|
+
* Sort nodes list by title or custom criteria.
|
|
8067
|
+
* @since 0.14.0
|
|
8068
|
+
*/
|
|
8069
|
+
sort(options) {
|
|
8070
|
+
this.root.sort(options);
|
|
8071
|
+
}
|
|
7673
8072
|
/** Convert tree to an array of plain objects.
|
|
7674
8073
|
*
|
|
7675
8074
|
* @param callback is called for every node, in order to allow
|
|
@@ -7783,11 +8182,11 @@ class Wunderbaum {
|
|
|
7783
8182
|
// }
|
|
7784
8183
|
return modified;
|
|
7785
8184
|
}
|
|
7786
|
-
_insertIcon(icon, elem) {
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
}
|
|
8185
|
+
// protected _insertIcon(icon: string, elem: HTMLElement) {
|
|
8186
|
+
// const iconElem = document.createElement("i");
|
|
8187
|
+
// iconElem.className = icon;
|
|
8188
|
+
// elem.appendChild(iconElem);
|
|
8189
|
+
// }
|
|
7791
8190
|
/** Create/update header markup from `this.columns` definition.
|
|
7792
8191
|
* @internal
|
|
7793
8192
|
*/
|
|
@@ -7885,6 +8284,102 @@ class Wunderbaum {
|
|
|
7885
8284
|
this._updateViewportImmediately();
|
|
7886
8285
|
}
|
|
7887
8286
|
}
|
|
8287
|
+
/** @internal */
|
|
8288
|
+
_createNodeIcon(node, showLoading, showBadge) {
|
|
8289
|
+
const iconMap = this.iconMap;
|
|
8290
|
+
let iconElem;
|
|
8291
|
+
let icon = node.getOption("icon");
|
|
8292
|
+
if (node._errorInfo) {
|
|
8293
|
+
icon = iconMap.error;
|
|
8294
|
+
}
|
|
8295
|
+
else if (node._isLoading && showLoading) {
|
|
8296
|
+
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
8297
|
+
// display the 'loading' status with the i.wb-icon span
|
|
8298
|
+
icon = iconMap.loading;
|
|
8299
|
+
}
|
|
8300
|
+
if (icon === false) {
|
|
8301
|
+
return null; // explicitly disabled: don't try default icons
|
|
8302
|
+
}
|
|
8303
|
+
if (typeof icon === "string") ;
|
|
8304
|
+
else if (node.statusNodeType) {
|
|
8305
|
+
icon = iconMap[node.statusNodeType];
|
|
8306
|
+
}
|
|
8307
|
+
else if (node.expanded) {
|
|
8308
|
+
icon = iconMap.folderOpen;
|
|
8309
|
+
}
|
|
8310
|
+
else if (node.children) {
|
|
8311
|
+
icon = iconMap.folder;
|
|
8312
|
+
}
|
|
8313
|
+
else if (node.lazy) {
|
|
8314
|
+
icon = iconMap.folderLazy;
|
|
8315
|
+
}
|
|
8316
|
+
else {
|
|
8317
|
+
icon = iconMap.doc;
|
|
8318
|
+
}
|
|
8319
|
+
if (!icon) {
|
|
8320
|
+
iconElem = document.createElement("i");
|
|
8321
|
+
iconElem.className = "wb-icon";
|
|
8322
|
+
}
|
|
8323
|
+
else if (TEST_HTML.test(icon)) {
|
|
8324
|
+
iconElem = elemFromHtml(icon);
|
|
8325
|
+
}
|
|
8326
|
+
else if (TEST_FILE_PATH.test(icon)) {
|
|
8327
|
+
iconElem = elemFromHtml(`<i class="wb-icon" style="background-image: url('${icon}');">`);
|
|
8328
|
+
}
|
|
8329
|
+
else {
|
|
8330
|
+
// Class name
|
|
8331
|
+
iconElem = document.createElement("i");
|
|
8332
|
+
iconElem.className = "wb-icon " + icon;
|
|
8333
|
+
}
|
|
8334
|
+
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
8335
|
+
const cbRes = showBadge && node._callEvent("iconBadge", { iconSpan: iconElem });
|
|
8336
|
+
let badge = null;
|
|
8337
|
+
if (cbRes != null && cbRes !== false) {
|
|
8338
|
+
let classes = "";
|
|
8339
|
+
let tooltip = "";
|
|
8340
|
+
if (isPlainObject(cbRes)) {
|
|
8341
|
+
badge = "" + cbRes.badge;
|
|
8342
|
+
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
8343
|
+
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
8344
|
+
}
|
|
8345
|
+
else if (typeof cbRes === "number") {
|
|
8346
|
+
badge = "" + cbRes;
|
|
8347
|
+
}
|
|
8348
|
+
else {
|
|
8349
|
+
badge = cbRes; // string or HTMLSpanElement
|
|
8350
|
+
}
|
|
8351
|
+
if (typeof badge === "string") {
|
|
8352
|
+
badge = elemFromHtml(`<span class="wb-badge${classes}"${tooltip}>${escapeHtml(badge)}</span>`);
|
|
8353
|
+
}
|
|
8354
|
+
if (badge) {
|
|
8355
|
+
iconElem.append(badge);
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8358
|
+
return iconElem;
|
|
8359
|
+
}
|
|
8360
|
+
_updateTopBreadcrumb() {
|
|
8361
|
+
const breadcrumb = this.breadcrumb;
|
|
8362
|
+
const topmost = this.getTopmostVpNode(true);
|
|
8363
|
+
const parentList = topmost === null || topmost === void 0 ? void 0 : topmost.getParentList(false, false);
|
|
8364
|
+
if (parentList === null || parentList === void 0 ? void 0 : parentList.length) {
|
|
8365
|
+
breadcrumb.innerHTML = "";
|
|
8366
|
+
for (const n of topmost.getParentList(false, false)) {
|
|
8367
|
+
const icon = this._createNodeIcon(n, false, false);
|
|
8368
|
+
if (icon) {
|
|
8369
|
+
breadcrumb.append(icon, " ");
|
|
8370
|
+
}
|
|
8371
|
+
const part = document.createElement("a");
|
|
8372
|
+
part.textContent = n.title;
|
|
8373
|
+
part.href = "#";
|
|
8374
|
+
part.classList.add("wb-breadcrumb");
|
|
8375
|
+
part.dataset.key = n.key;
|
|
8376
|
+
breadcrumb.append(part, this.options.strings.breadcrumbDelimiter);
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
else {
|
|
8380
|
+
breadcrumb.innerHTML = " ";
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
7888
8383
|
/**
|
|
7889
8384
|
* This is the actual update method, which is wrapped inside a throttle method.
|
|
7890
8385
|
* It calls `updateColumns()` and `_updateRows()`.
|
|
@@ -7895,7 +8390,6 @@ class Wunderbaum {
|
|
|
7895
8390
|
* @internal
|
|
7896
8391
|
*/
|
|
7897
8392
|
_updateViewportImmediately() {
|
|
7898
|
-
var _a;
|
|
7899
8393
|
if (this._disableUpdateCount) {
|
|
7900
8394
|
this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount}).`);
|
|
7901
8395
|
this._disableUpdateIgnoreCount++;
|
|
@@ -7942,11 +8436,8 @@ class Wunderbaum {
|
|
|
7942
8436
|
this._updateRows();
|
|
7943
8437
|
// console.profileEnd(`_updateViewportImmediately()`)
|
|
7944
8438
|
}
|
|
7945
|
-
if (this.
|
|
7946
|
-
|
|
7947
|
-
let path = (_a = this.getTopmostVpNode(true)) === null || _a === void 0 ? void 0 : _a.getPath(false, "title", " > ");
|
|
7948
|
-
path = path ? path + " >" : "";
|
|
7949
|
-
this.options.connectTopBreadcrumb.textContent = path;
|
|
8439
|
+
if (this.breadcrumb) {
|
|
8440
|
+
this._updateTopBreadcrumb();
|
|
7950
8441
|
}
|
|
7951
8442
|
this._callEvent("update");
|
|
7952
8443
|
}
|
|
@@ -8070,7 +8561,8 @@ class Wunderbaum {
|
|
|
8070
8561
|
}
|
|
8071
8562
|
/**
|
|
8072
8563
|
* Call `callback(node)` for all nodes in hierarchical order (depth-first, pre-order).
|
|
8073
|
-
* @see
|
|
8564
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
8565
|
+
* @see {@link WunderbaumNode.visit}.
|
|
8074
8566
|
*
|
|
8075
8567
|
* @param {function} callback the callback function.
|
|
8076
8568
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
@@ -8213,11 +8705,71 @@ class Wunderbaum {
|
|
|
8213
8705
|
*
|
|
8214
8706
|
* Previous data is cleared. Note that also column- and type defintions may
|
|
8215
8707
|
* be passed with the `source` object.
|
|
8708
|
+
* @see {@link Wunderbaum.reload} for a shortcut to reload the last ajax request
|
|
8709
|
+
* and restore the previous state.
|
|
8216
8710
|
*/
|
|
8217
|
-
load(source) {
|
|
8711
|
+
async load(source) {
|
|
8218
8712
|
this.clear();
|
|
8713
|
+
this._initialSource = source;
|
|
8219
8714
|
return this.root.load(source);
|
|
8220
8715
|
}
|
|
8716
|
+
/** Reload the tree and optionally restore state.
|
|
8717
|
+
* Source defaults to last ajax url if any.
|
|
8718
|
+
* Restoring the active node requires stable keys
|
|
8719
|
+
* @see {@link WunderbaumOptions.autoKeys}
|
|
8720
|
+
* @see {@link Wunderbaum.load}
|
|
8721
|
+
* @experimental
|
|
8722
|
+
*/
|
|
8723
|
+
async reload(options = {}) {
|
|
8724
|
+
const { source = this._initialSource, reactivate = true } = options;
|
|
8725
|
+
if (!source) {
|
|
8726
|
+
this.logWarn("No previous ajax source to reload.");
|
|
8727
|
+
return;
|
|
8728
|
+
}
|
|
8729
|
+
if (!reactivate) {
|
|
8730
|
+
return this.load(source);
|
|
8731
|
+
}
|
|
8732
|
+
const state = this.getState();
|
|
8733
|
+
await this.load(source);
|
|
8734
|
+
return this.setState(state);
|
|
8735
|
+
}
|
|
8736
|
+
/**
|
|
8737
|
+
* Make sure that all nodes in the given keyList are accessible.
|
|
8738
|
+
* This may include loading lazy parent nodes.
|
|
8739
|
+
* Recursively load (and optionally expand) all requested node paths.
|
|
8740
|
+
*/
|
|
8741
|
+
async _loadLazyNodes(keyList, options = {}) {
|
|
8742
|
+
const { expand = true } = options;
|
|
8743
|
+
const keySet = new Set(keyList);
|
|
8744
|
+
// Make sure that all parent nodes are loaded (and expand if requested)
|
|
8745
|
+
while (keySet.size > 0) {
|
|
8746
|
+
const pendingNodes = [];
|
|
8747
|
+
const curSet = new Set(keySet);
|
|
8748
|
+
for (const key of curSet) {
|
|
8749
|
+
const node = this.findKey(key);
|
|
8750
|
+
if (!node) {
|
|
8751
|
+
continue; // key not yet found (need to load lazy parent?)
|
|
8752
|
+
}
|
|
8753
|
+
keySet.delete(key);
|
|
8754
|
+
if (expand) {
|
|
8755
|
+
pendingNodes.push(node.setExpanded(true));
|
|
8756
|
+
}
|
|
8757
|
+
else if (node.isUnloaded()) {
|
|
8758
|
+
pendingNodes.push(node.loadLazy());
|
|
8759
|
+
}
|
|
8760
|
+
if (node._rowElem) {
|
|
8761
|
+
node._render(); // show spinner even is update is suppressed
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
if (pendingNodes.length === 0) {
|
|
8765
|
+
// will not load any more nodes, so if if there are still keys
|
|
8766
|
+
// left in the set, we will never find them
|
|
8767
|
+
this.logWarn(`Could not expand ${keySet.size} nodes:`, keySet);
|
|
8768
|
+
break;
|
|
8769
|
+
}
|
|
8770
|
+
await Promise.allSettled(pendingNodes);
|
|
8771
|
+
}
|
|
8772
|
+
}
|
|
8221
8773
|
/**
|
|
8222
8774
|
* Disable render requests during operations that would trigger many updates.
|
|
8223
8775
|
*
|
|
@@ -8315,8 +8867,20 @@ class Wunderbaum {
|
|
|
8315
8867
|
}
|
|
8316
8868
|
Wunderbaum.sequence = 0;
|
|
8317
8869
|
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
8318
|
-
Wunderbaum.version = "v0.
|
|
8870
|
+
Wunderbaum.version = "v0.14.0"; // Set to semver by 'grunt release'
|
|
8319
8871
|
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
8320
8872
|
Wunderbaum.util = util;
|
|
8873
|
+
/** A map of default iconMaps.
|
|
8874
|
+
* May be used as default, when passing partial icon definition maps:
|
|
8875
|
+
* ```js
|
|
8876
|
+
* const tree = new mar10.Wunderbaum({
|
|
8877
|
+
* ...
|
|
8878
|
+
* iconMap: Object.assign(Wunderbaum.iconMaps.bootstrap, {
|
|
8879
|
+
* folder: "bi bi-archive",
|
|
8880
|
+
* }),
|
|
8881
|
+
* });
|
|
8882
|
+
* ```
|
|
8883
|
+
*/
|
|
8884
|
+
Wunderbaum.iconMaps = defaultIconMaps;
|
|
8321
8885
|
|
|
8322
8886
|
export { Wunderbaum };
|