wunderbaum 0.12.0 → 0.13.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/dist/wunderbaum.css +10 -1
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +214 -59
- package/dist/wunderbaum.esm.js +417 -222
- package/dist/wunderbaum.esm.min.js +42 -42
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +417 -222
- package/dist/wunderbaum.umd.min.js +45 -45
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +30 -28
- package/src/common.ts +58 -16
- package/src/debounce.ts +5 -0
- package/src/deferred.ts +1 -1
- package/src/drag_observer.ts +1 -1
- package/src/types.ts +163 -21
- package/src/util.ts +1 -14
- package/src/wb_ext_dnd.ts +3 -3
- package/src/wb_ext_edit.ts +2 -2
- package/src/wb_ext_filter.ts +119 -44
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_ext_keynav.ts +1 -1
- package/src/wb_ext_logger.ts +1 -1
- package/src/wb_extension_base.ts +4 -3
- package/src/wb_node.ts +27 -98
- package/src/wb_options.ts +7 -5
- package/src/wunderbaum.scss +12 -4
- package/src/wunderbaum.ts +272 -48
package/src/wunderbaum.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A treegrid control.
|
|
5
5
|
*
|
|
6
|
-
* Copyright (c) 2021-
|
|
6
|
+
* Copyright (c) 2021-2025, Martin Wendt (https://wwWendt.de).
|
|
7
7
|
* https://github.com/mar10/wunderbaum
|
|
8
8
|
*
|
|
9
9
|
* Released under the MIT license.
|
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
ExpandAllOptions,
|
|
34
34
|
FilterModeType,
|
|
35
35
|
FilterNodesOptions,
|
|
36
|
+
IconMapType,
|
|
37
|
+
GetStateOptions,
|
|
36
38
|
MatcherCallback,
|
|
39
|
+
NavigationType,
|
|
37
40
|
NavModeEnum,
|
|
38
41
|
NodeFilterCallback,
|
|
39
42
|
NodeRegion,
|
|
@@ -46,10 +49,12 @@ import {
|
|
|
46
49
|
ScrollToOptions,
|
|
47
50
|
SetActiveOptions,
|
|
48
51
|
SetColumnOptions,
|
|
52
|
+
SetStateOptions,
|
|
49
53
|
SetStatusOptions,
|
|
50
54
|
SortByPropertyOptions,
|
|
51
55
|
SortCallback,
|
|
52
56
|
SourceType,
|
|
57
|
+
TreeStateDefinition,
|
|
53
58
|
UpdateOptions,
|
|
54
59
|
VisitRowsOptions,
|
|
55
60
|
WbEventInfo,
|
|
@@ -62,6 +67,7 @@ import {
|
|
|
62
67
|
nodeTitleSorter,
|
|
63
68
|
RENDER_MAX_PREFETCH,
|
|
64
69
|
DEFAULT_ROW_HEIGHT,
|
|
70
|
+
TEST_IMG,
|
|
65
71
|
} from "./common";
|
|
66
72
|
import { WunderbaumNode } from "./wb_node";
|
|
67
73
|
import { Deferred } from "./deferred";
|
|
@@ -125,14 +131,14 @@ export class Wunderbaum {
|
|
|
125
131
|
protected _focusNode: WunderbaumNode | null = null;
|
|
126
132
|
|
|
127
133
|
/** Currently active node if any.
|
|
128
|
-
* Use @link
|
|
134
|
+
* Use {@link WunderbaumNode.setActive|setActive} to modify.
|
|
129
135
|
*/
|
|
130
136
|
public get activeNode() {
|
|
131
137
|
// Check for deleted node, i.e. node.tree === null
|
|
132
138
|
return this._activeNode?.tree ? this._activeNode : null;
|
|
133
139
|
}
|
|
134
140
|
/** Current node hat has keyboard focus if any.
|
|
135
|
-
* Use @link
|
|
141
|
+
* Use {@link WunderbaumNode.setFocus|setFocus()} to modify.
|
|
136
142
|
*/
|
|
137
143
|
public get focusNode() {
|
|
138
144
|
// Check for deleted node, i.e. node.tree === null
|
|
@@ -171,6 +177,10 @@ export class Wunderbaum {
|
|
|
171
177
|
// /** @internal */
|
|
172
178
|
// public selectRangeAnchor: WunderbaumNode | null = null;
|
|
173
179
|
|
|
180
|
+
// --- BREADCRUMB ---
|
|
181
|
+
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
182
|
+
public breadcrumb: HTMLElement | null = null;
|
|
183
|
+
|
|
174
184
|
// --- FILTER ---
|
|
175
185
|
/** Filter options (used as defaults for calls to {@link Wunderbaum.filterNodes} ) */
|
|
176
186
|
public filterMode: FilterModeType = null;
|
|
@@ -210,10 +220,10 @@ export class Wunderbaum {
|
|
|
210
220
|
emptyChildListExpandable: false,
|
|
211
221
|
// updateThrottleWait: 200,
|
|
212
222
|
skeleton: false,
|
|
213
|
-
connectTopBreadcrumb: null,
|
|
223
|
+
connectTopBreadcrumb: null,
|
|
214
224
|
selectMode: "multi", // SelectModeType
|
|
215
225
|
// --- KeyNav ---
|
|
216
|
-
navigationModeOption: null, // NavModeEnum
|
|
226
|
+
navigationModeOption: null, // NavModeEnum,
|
|
217
227
|
quicksearch: true,
|
|
218
228
|
// --- Events ---
|
|
219
229
|
iconBadge: null,
|
|
@@ -225,8 +235,11 @@ export class Wunderbaum {
|
|
|
225
235
|
strings: {
|
|
226
236
|
loadError: "Error",
|
|
227
237
|
loading: "Loading...",
|
|
228
|
-
// loading: "Loading…",
|
|
229
238
|
noData: "No data",
|
|
239
|
+
breadcrumbDelimiter: " » ",
|
|
240
|
+
queryResult: "Found ${matches} of ${count}",
|
|
241
|
+
noMatch: "No results",
|
|
242
|
+
matchIndex: "${match} of ${matches}",
|
|
230
243
|
},
|
|
231
244
|
},
|
|
232
245
|
options
|
|
@@ -319,7 +332,7 @@ export class Wunderbaum {
|
|
|
319
332
|
// User existing header markup to define `this.columns`
|
|
320
333
|
util.assert(
|
|
321
334
|
!this.columns,
|
|
322
|
-
"`opts.columns` must not be set if markup already contains a header"
|
|
335
|
+
"`opts.columns` must not be set if table markup already contains a header"
|
|
323
336
|
);
|
|
324
337
|
this.columns = [];
|
|
325
338
|
const rowElement =
|
|
@@ -368,6 +381,24 @@ export class Wunderbaum {
|
|
|
368
381
|
|
|
369
382
|
this.element.classList.toggle("wb-grid", this.columns.length > 1);
|
|
370
383
|
|
|
384
|
+
if (this.options.connectTopBreadcrumb) {
|
|
385
|
+
this.breadcrumb = util.elemFromSelector(
|
|
386
|
+
this.options.connectTopBreadcrumb
|
|
387
|
+
)!;
|
|
388
|
+
util.assert(
|
|
389
|
+
!this.breadcrumb || this.breadcrumb.innerHTML != null,
|
|
390
|
+
`Invalid 'connectTopBreadcrumb' option: ${this.breadcrumb}.`
|
|
391
|
+
);
|
|
392
|
+
this.breadcrumb.addEventListener("click", (e) => {
|
|
393
|
+
// const node = Wunderbaum.getNode(e)!;
|
|
394
|
+
const elem = e.target as HTMLElement;
|
|
395
|
+
if (elem && elem.matches("a.wb-breadcrumb")) {
|
|
396
|
+
const node = this.keyMap.get(elem.dataset.key!);
|
|
397
|
+
node?.setActive();
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
371
402
|
this._initExtensions();
|
|
372
403
|
|
|
373
404
|
// --- apply initial options
|
|
@@ -380,8 +411,7 @@ export class Wunderbaum {
|
|
|
380
411
|
// --- Load initial data
|
|
381
412
|
if (opts.source) {
|
|
382
413
|
if (opts.showSpinner) {
|
|
383
|
-
this.nodeListElement.innerHTML =
|
|
384
|
-
"<progress class='spinner'>loading...</progress>";
|
|
414
|
+
this.nodeListElement.innerHTML = `<progress class='spinner'>${opts.strings.loading}</progress>`;
|
|
385
415
|
}
|
|
386
416
|
this.load(opts.source)
|
|
387
417
|
.then(() => {
|
|
@@ -598,7 +628,7 @@ export class Wunderbaum {
|
|
|
598
628
|
/**
|
|
599
629
|
* Return the icon-function -> icon-definition mapping.
|
|
600
630
|
*/
|
|
601
|
-
get iconMap():
|
|
631
|
+
get iconMap(): IconMapType {
|
|
602
632
|
const map = this.options.iconMap!;
|
|
603
633
|
if (typeof map === "string") {
|
|
604
634
|
return iconMaps[map];
|
|
@@ -772,7 +802,10 @@ export class Wunderbaum {
|
|
|
772
802
|
return <WunderbaumNode>node!;
|
|
773
803
|
}
|
|
774
804
|
|
|
775
|
-
/** Return the topmost visible node in the viewport.
|
|
805
|
+
/** Return the topmost visible node in the viewport.
|
|
806
|
+
* @param complete If `false`, the node is considered visible if at least one
|
|
807
|
+
* pixel is visible.
|
|
808
|
+
*/
|
|
776
809
|
getTopmostVpNode(complete = true) {
|
|
777
810
|
const rowHeight = this.options.rowHeightPx!;
|
|
778
811
|
const gracePx = 1; // ignore subpixel scrolling
|
|
@@ -807,30 +840,29 @@ export class Wunderbaum {
|
|
|
807
840
|
return this._getNodeByRowIdx(bottomIdx)!;
|
|
808
841
|
}
|
|
809
842
|
|
|
810
|
-
/** Return
|
|
811
|
-
protected
|
|
843
|
+
/** Return following visible node in the viewport. */
|
|
844
|
+
protected _getNextNodeInView(
|
|
845
|
+
node?: WunderbaumNode,
|
|
846
|
+
options?: {
|
|
847
|
+
ofs?: number;
|
|
848
|
+
reverse?: boolean;
|
|
849
|
+
cb?: (n: WunderbaumNode) => boolean;
|
|
850
|
+
}
|
|
851
|
+
) {
|
|
852
|
+
let ofs = options?.ofs || 1;
|
|
853
|
+
const reverse = !!options?.reverse;
|
|
854
|
+
|
|
812
855
|
this.visitRows(
|
|
813
856
|
(n) => {
|
|
814
857
|
node = n;
|
|
815
|
-
if (
|
|
858
|
+
if (options?.cb && options.cb(n)) {
|
|
816
859
|
return false;
|
|
817
860
|
}
|
|
818
|
-
},
|
|
819
|
-
{ reverse: true, start: node || this.getActiveNode() }
|
|
820
|
-
);
|
|
821
|
-
return node;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
/** Return following visible node in the viewport. */
|
|
825
|
-
protected _getNextNodeInView(node?: WunderbaumNode, ofs = 1) {
|
|
826
|
-
this.visitRows(
|
|
827
|
-
(n) => {
|
|
828
|
-
node = n;
|
|
829
861
|
if (ofs-- <= 0) {
|
|
830
862
|
return false;
|
|
831
863
|
}
|
|
832
864
|
},
|
|
833
|
-
{ reverse:
|
|
865
|
+
{ reverse: reverse, start: node || this.getActiveNode() }
|
|
834
866
|
);
|
|
835
867
|
return node;
|
|
836
868
|
}
|
|
@@ -973,9 +1005,11 @@ export class Wunderbaum {
|
|
|
973
1005
|
case "first":
|
|
974
1006
|
case "last":
|
|
975
1007
|
case "left":
|
|
1008
|
+
case "nextMatch":
|
|
976
1009
|
case "pageDown":
|
|
977
1010
|
case "pageUp":
|
|
978
1011
|
case "parent":
|
|
1012
|
+
case "prevMatch":
|
|
979
1013
|
case "right":
|
|
980
1014
|
case "up":
|
|
981
1015
|
return node.navigate(cmd);
|
|
@@ -1191,6 +1225,12 @@ export class Wunderbaum {
|
|
|
1191
1225
|
return visible ? this.treeRowCount : this.keyMap.size;
|
|
1192
1226
|
}
|
|
1193
1227
|
|
|
1228
|
+
/** Return the number of *unique* nodes in the data model, i.e. unique `node.refKey`.
|
|
1229
|
+
*/
|
|
1230
|
+
countUnique(): number {
|
|
1231
|
+
return this.refKeyMap.size;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1194
1234
|
/** @internal sanity check. */
|
|
1195
1235
|
_check() {
|
|
1196
1236
|
let i = 0;
|
|
@@ -1256,15 +1296,18 @@ export class Wunderbaum {
|
|
|
1256
1296
|
*/
|
|
1257
1297
|
findNextNode(
|
|
1258
1298
|
match: string | MatcherCallback,
|
|
1259
|
-
startNode?: WunderbaumNode | null
|
|
1299
|
+
startNode?: WunderbaumNode | null,
|
|
1300
|
+
reverse = false
|
|
1260
1301
|
): WunderbaumNode | null {
|
|
1261
1302
|
//, visibleOnly) {
|
|
1262
1303
|
let res: WunderbaumNode | null = null;
|
|
1263
1304
|
const firstNode = this.getFirstChild()!;
|
|
1305
|
+
// Last visible node (calculation is expensive, so do only if we need it):
|
|
1306
|
+
const lastNode = reverse ? this.findRelatedNode(firstNode, "last")! : null;
|
|
1264
1307
|
|
|
1265
1308
|
const matcher =
|
|
1266
1309
|
typeof match === "string" ? makeNodeTitleStartMatcher(match) : match;
|
|
1267
|
-
startNode = startNode || firstNode;
|
|
1310
|
+
startNode = startNode || (reverse ? lastNode : firstNode);
|
|
1268
1311
|
|
|
1269
1312
|
function _checkNode(n: WunderbaumNode) {
|
|
1270
1313
|
// console.log("_check " + n)
|
|
@@ -1278,12 +1321,14 @@ export class Wunderbaum {
|
|
|
1278
1321
|
this.visitRows(_checkNode, {
|
|
1279
1322
|
start: startNode,
|
|
1280
1323
|
includeSelf: false,
|
|
1324
|
+
reverse: reverse,
|
|
1281
1325
|
});
|
|
1282
1326
|
// Wrap around search
|
|
1283
1327
|
if (!res && startNode !== firstNode) {
|
|
1284
1328
|
this.visitRows(_checkNode, {
|
|
1285
|
-
start: firstNode,
|
|
1329
|
+
start: reverse ? lastNode : firstNode,
|
|
1286
1330
|
includeSelf: true,
|
|
1331
|
+
reverse: reverse,
|
|
1287
1332
|
});
|
|
1288
1333
|
}
|
|
1289
1334
|
return res;
|
|
@@ -1298,7 +1343,11 @@ export class Wunderbaum {
|
|
|
1298
1343
|
* e.g. `$.ui.keyCode.LEFT` = 'left'.
|
|
1299
1344
|
* @param includeHidden Not yet implemented
|
|
1300
1345
|
*/
|
|
1301
|
-
findRelatedNode(
|
|
1346
|
+
findRelatedNode(
|
|
1347
|
+
node: WunderbaumNode,
|
|
1348
|
+
where: NavigationType,
|
|
1349
|
+
includeHidden = false
|
|
1350
|
+
) {
|
|
1302
1351
|
const rowHeight = this.options.rowHeightPx!;
|
|
1303
1352
|
let res = null;
|
|
1304
1353
|
const pageSize = Math.floor(
|
|
@@ -1354,7 +1403,7 @@ export class Wunderbaum {
|
|
|
1354
1403
|
// }
|
|
1355
1404
|
break;
|
|
1356
1405
|
case "up":
|
|
1357
|
-
res = this.
|
|
1406
|
+
res = this._getNextNodeInView(node, { reverse: true });
|
|
1358
1407
|
break;
|
|
1359
1408
|
case "down":
|
|
1360
1409
|
res = this._getNextNodeInView(node);
|
|
@@ -1367,7 +1416,10 @@ export class Wunderbaum {
|
|
|
1367
1416
|
if (node._rowIdx! < bottomNode._rowIdx!) {
|
|
1368
1417
|
res = bottomNode;
|
|
1369
1418
|
} else {
|
|
1370
|
-
res = this._getNextNodeInView(node,
|
|
1419
|
+
res = this._getNextNodeInView(node, {
|
|
1420
|
+
reverse: false,
|
|
1421
|
+
ofs: pageSize,
|
|
1422
|
+
});
|
|
1371
1423
|
}
|
|
1372
1424
|
}
|
|
1373
1425
|
break;
|
|
@@ -1381,10 +1433,28 @@ export class Wunderbaum {
|
|
|
1381
1433
|
if (node._rowIdx! > topNode._rowIdx!) {
|
|
1382
1434
|
res = topNode;
|
|
1383
1435
|
} else {
|
|
1384
|
-
res = this.
|
|
1436
|
+
res = this._getNextNodeInView(node, {
|
|
1437
|
+
reverse: true,
|
|
1438
|
+
ofs: pageSize,
|
|
1439
|
+
});
|
|
1385
1440
|
}
|
|
1386
1441
|
}
|
|
1387
1442
|
break;
|
|
1443
|
+
|
|
1444
|
+
case "prevMatch":
|
|
1445
|
+
// fallthrough
|
|
1446
|
+
case "nextMatch":
|
|
1447
|
+
if (!this.isFilterActive) {
|
|
1448
|
+
this.logWarn(`${where}: Filter is not active.`);
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
res = this.findNextNode(
|
|
1452
|
+
(n) => n.isMatched(),
|
|
1453
|
+
node,
|
|
1454
|
+
where === "prevMatch"
|
|
1455
|
+
);
|
|
1456
|
+
res?.setActive();
|
|
1457
|
+
break;
|
|
1388
1458
|
default:
|
|
1389
1459
|
this.logWarn("Unknown relation '" + where + "'.");
|
|
1390
1460
|
}
|
|
@@ -1455,6 +1525,13 @@ export class Wunderbaum {
|
|
|
1455
1525
|
return this.root.getFirstChild();
|
|
1456
1526
|
}
|
|
1457
1527
|
|
|
1528
|
+
/**
|
|
1529
|
+
* Return the last top level node if any (not the invisible root node).
|
|
1530
|
+
*/
|
|
1531
|
+
getLastChild() {
|
|
1532
|
+
return this.root.getLastChild();
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1458
1535
|
/**
|
|
1459
1536
|
* Return the node that currently has keyboard focus or null.
|
|
1460
1537
|
* Alias for {@link Wunderbaum.focusNode}.
|
|
@@ -1817,6 +1894,53 @@ export class Wunderbaum {
|
|
|
1817
1894
|
this._focusNode = node;
|
|
1818
1895
|
}
|
|
1819
1896
|
|
|
1897
|
+
/** Return the current selection/expansion/activation status. @experimental */
|
|
1898
|
+
getState(options: GetStateOptions): TreeStateDefinition {
|
|
1899
|
+
let expandedKeys = undefined;
|
|
1900
|
+
if (options.expandedKeys !== false) {
|
|
1901
|
+
expandedKeys = [];
|
|
1902
|
+
for (const node of this) {
|
|
1903
|
+
if (node.expanded) {
|
|
1904
|
+
expandedKeys.push(node.key);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const state: TreeStateDefinition = {
|
|
1910
|
+
activeKey: this.activeNode?.key ?? null,
|
|
1911
|
+
activeColIdx: this.activeColIdx,
|
|
1912
|
+
selectedKeys:
|
|
1913
|
+
options.selectedKeys === false
|
|
1914
|
+
? undefined
|
|
1915
|
+
: this.getSelectedNodes().flatMap((n) => n.key),
|
|
1916
|
+
expandedKeys: expandedKeys,
|
|
1917
|
+
};
|
|
1918
|
+
return state;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/** Apply selection/expansion/activation status. @experimental */
|
|
1922
|
+
setState(state: TreeStateDefinition, options: SetStateOptions) {
|
|
1923
|
+
this.runWithDeferredUpdate(() => {
|
|
1924
|
+
if (state.selectedKeys) {
|
|
1925
|
+
this.selectAll(false);
|
|
1926
|
+
for (const key of state.selectedKeys) {
|
|
1927
|
+
this.findKey(key)?.setSelected(true);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (state.expandedKeys) {
|
|
1931
|
+
for (const key of state.expandedKeys) {
|
|
1932
|
+
this.findKey(key)?.setExpanded(true);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
if (state.activeKey) {
|
|
1936
|
+
this.setActiveNode(state.activeKey);
|
|
1937
|
+
}
|
|
1938
|
+
if (state.activeColIdx != null) {
|
|
1939
|
+
this.setColumn(state.activeColIdx);
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1820
1944
|
/**
|
|
1821
1945
|
* Schedule an update request to reflect a tree change.
|
|
1822
1946
|
* The render operation is async and debounced unless the `immediate` option
|
|
@@ -2150,11 +2274,11 @@ export class Wunderbaum {
|
|
|
2150
2274
|
return modified;
|
|
2151
2275
|
}
|
|
2152
2276
|
|
|
2153
|
-
protected _insertIcon(icon: string, elem: HTMLElement) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
}
|
|
2277
|
+
// protected _insertIcon(icon: string, elem: HTMLElement) {
|
|
2278
|
+
// const iconElem = document.createElement("i");
|
|
2279
|
+
// iconElem.className = icon;
|
|
2280
|
+
// elem.appendChild(iconElem);
|
|
2281
|
+
// }
|
|
2158
2282
|
|
|
2159
2283
|
/** Create/update header markup from `this.columns` definition.
|
|
2160
2284
|
* @internal
|
|
@@ -2258,6 +2382,111 @@ export class Wunderbaum {
|
|
|
2258
2382
|
}
|
|
2259
2383
|
}
|
|
2260
2384
|
|
|
2385
|
+
/** @internal */
|
|
2386
|
+
public _createNodeIcon(
|
|
2387
|
+
node: WunderbaumNode,
|
|
2388
|
+
showLoading: boolean,
|
|
2389
|
+
showBadge: boolean
|
|
2390
|
+
): HTMLElement | null {
|
|
2391
|
+
const iconMap = this.iconMap;
|
|
2392
|
+
let iconElem;
|
|
2393
|
+
let icon = node.getOption("icon");
|
|
2394
|
+
if (node._errorInfo) {
|
|
2395
|
+
icon = iconMap.error;
|
|
2396
|
+
} else if (node._isLoading && showLoading) {
|
|
2397
|
+
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
2398
|
+
// display the 'loading' status with the i.wb-icon span
|
|
2399
|
+
icon = iconMap.loading;
|
|
2400
|
+
}
|
|
2401
|
+
if (icon === false) {
|
|
2402
|
+
return null; // explicitly disabled: don't try default icons
|
|
2403
|
+
}
|
|
2404
|
+
if (typeof icon === "string") {
|
|
2405
|
+
// Callback returned an icon definition
|
|
2406
|
+
// icon = icon.trim()
|
|
2407
|
+
} else if (node.statusNodeType) {
|
|
2408
|
+
icon = (<any>iconMap)[node.statusNodeType];
|
|
2409
|
+
} else if (node.expanded) {
|
|
2410
|
+
icon = iconMap.folderOpen;
|
|
2411
|
+
} else if (node.children) {
|
|
2412
|
+
icon = iconMap.folder;
|
|
2413
|
+
} else if (node.lazy) {
|
|
2414
|
+
icon = iconMap.folderLazy;
|
|
2415
|
+
} else {
|
|
2416
|
+
icon = iconMap.doc;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
if (!icon) {
|
|
2420
|
+
iconElem = document.createElement("i");
|
|
2421
|
+
iconElem.className = "wb-icon";
|
|
2422
|
+
} else if (icon.indexOf("<") >= 0) {
|
|
2423
|
+
// HTML
|
|
2424
|
+
iconElem = util.elemFromHtml(icon);
|
|
2425
|
+
} else if (TEST_IMG.test(icon)) {
|
|
2426
|
+
// Image URL
|
|
2427
|
+
iconElem = util.elemFromHtml(
|
|
2428
|
+
`<i class="wb-icon" style="background-image: url('${icon}');">`
|
|
2429
|
+
);
|
|
2430
|
+
} else {
|
|
2431
|
+
// Class name
|
|
2432
|
+
iconElem = document.createElement("i");
|
|
2433
|
+
iconElem.className = "wb-icon " + icon;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
2437
|
+
const cbRes =
|
|
2438
|
+
showBadge && node._callEvent("iconBadge", { iconSpan: iconElem });
|
|
2439
|
+
|
|
2440
|
+
let badge = null;
|
|
2441
|
+
if (cbRes != null && cbRes !== false) {
|
|
2442
|
+
let classes = "";
|
|
2443
|
+
let tooltip = "";
|
|
2444
|
+
if (util.isPlainObject(cbRes)) {
|
|
2445
|
+
badge = "" + cbRes.badge;
|
|
2446
|
+
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
2447
|
+
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
2448
|
+
} else if (typeof cbRes === "number") {
|
|
2449
|
+
badge = "" + cbRes;
|
|
2450
|
+
} else {
|
|
2451
|
+
badge = cbRes; // string or HTMLSpanElement
|
|
2452
|
+
}
|
|
2453
|
+
if (typeof badge === "string") {
|
|
2454
|
+
badge = util.elemFromHtml(
|
|
2455
|
+
`<span class="wb-badge${classes}"${tooltip}>${util.escapeHtml(
|
|
2456
|
+
badge
|
|
2457
|
+
)}</span>`
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
if (badge) {
|
|
2461
|
+
iconElem.append(<HTMLSpanElement>badge);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return iconElem;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
private _updateTopBreadcrumb() {
|
|
2468
|
+
const breadcrumb = this.breadcrumb!;
|
|
2469
|
+
const topmost = this.getTopmostVpNode(true);
|
|
2470
|
+
const parentList = topmost?.getParentList(false, false);
|
|
2471
|
+
if (parentList?.length) {
|
|
2472
|
+
breadcrumb.innerHTML = "";
|
|
2473
|
+
for (const n of topmost.getParentList(false, false)) {
|
|
2474
|
+
const icon = this._createNodeIcon(n, false, false);
|
|
2475
|
+
if (icon) {
|
|
2476
|
+
breadcrumb.append(icon, " ");
|
|
2477
|
+
}
|
|
2478
|
+
const part = document.createElement("a");
|
|
2479
|
+
part.textContent = n.title;
|
|
2480
|
+
part.href = "#";
|
|
2481
|
+
part.classList.add("wb-breadcrumb");
|
|
2482
|
+
part.dataset.key = n.key;
|
|
2483
|
+
breadcrumb.append(part, this.options.strings!.breadcrumbDelimiter);
|
|
2484
|
+
}
|
|
2485
|
+
} else {
|
|
2486
|
+
breadcrumb.innerHTML = " ";
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2261
2490
|
/**
|
|
2262
2491
|
* This is the actual update method, which is wrapped inside a throttle method.
|
|
2263
2492
|
* It calls `updateColumns()` and `_updateRows()`.
|
|
@@ -2322,14 +2551,8 @@ export class Wunderbaum {
|
|
|
2322
2551
|
// console.profileEnd(`_updateViewportImmediately()`)
|
|
2323
2552
|
}
|
|
2324
2553
|
|
|
2325
|
-
if (this.
|
|
2326
|
-
|
|
2327
|
-
this.options.connectTopBreadcrumb.textContent != null,
|
|
2328
|
-
`Invalid 'connectTopBreadcrumb' option (input element expected).`
|
|
2329
|
-
);
|
|
2330
|
-
let path = this.getTopmostVpNode(true)?.getPath(false, "title", " > ");
|
|
2331
|
-
path = path ? path + " >" : "";
|
|
2332
|
-
this.options.connectTopBreadcrumb.textContent = path;
|
|
2554
|
+
if (this.breadcrumb) {
|
|
2555
|
+
this._updateTopBreadcrumb();
|
|
2333
2556
|
}
|
|
2334
2557
|
this._callEvent("update");
|
|
2335
2558
|
}
|
|
@@ -2400,8 +2623,9 @@ export class Wunderbaum {
|
|
|
2400
2623
|
// this.debug("render", opts);
|
|
2401
2624
|
const obsoleteNodes = new Set<WunderbaumNode>();
|
|
2402
2625
|
this.nodeListElement.childNodes.forEach((elem) => {
|
|
2403
|
-
|
|
2404
|
-
|
|
2626
|
+
if ((<any>elem)._wb_node) {
|
|
2627
|
+
obsoleteNodes.add((<any>elem)._wb_node);
|
|
2628
|
+
}
|
|
2405
2629
|
});
|
|
2406
2630
|
|
|
2407
2631
|
let idx = 0;
|