wunderbaum 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/wunderbaum.css +1 -1
- package/dist/wunderbaum.d.ts +738 -174
- package/dist/wunderbaum.esm.js +1123 -647
- package/dist/wunderbaum.esm.min.js +30 -20
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +1125 -649
- package/dist/wunderbaum.umd.min.js +34 -25
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +29 -30
- package/src/common.ts +46 -6
- package/src/deferred.ts +12 -2
- package/src/drag_observer.ts +169 -0
- package/src/util.ts +129 -16
- package/src/wb_ext_dnd.ts +144 -3
- package/src/wb_ext_edit.ts +10 -1
- package/src/wb_ext_filter.ts +35 -35
- package/src/wb_ext_grid.ts +45 -0
- package/src/wb_ext_keynav.ts +8 -4
- package/src/wb_extension_base.ts +3 -3
- package/src/wb_node.ts +359 -214
- package/src/wb_options.ts +173 -26
- package/src/wunderbaum.scss +114 -28
- package/src/wunderbaum.ts +504 -338
package/dist/wunderbaum.esm.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - util
|
|
3
3
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
4
|
-
* v0.0.
|
|
4
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
|
+
/** @module util */
|
|
6
7
|
/** Readable names for `MouseEvent.button` */
|
|
7
8
|
const MOUSE_BUTTONS = {
|
|
8
9
|
0: "",
|
|
@@ -14,7 +15,7 @@ const MOUSE_BUTTONS = {
|
|
|
14
15
|
};
|
|
15
16
|
const MAX_INT = 9007199254740991;
|
|
16
17
|
const userInfo = _getUserInfo();
|
|
17
|
-
/**True if the
|
|
18
|
+
/**True if the client is using a macOS platform. */
|
|
18
19
|
const isMac = userInfo.isMac;
|
|
19
20
|
const REX_HTML = /[&<>"'/]/g; // Escape those characters
|
|
20
21
|
const REX_TOOLTIP = /[<>"'/]/g; // Don't escape `&` in tooltips
|
|
@@ -162,7 +163,7 @@ function escapeTooltip(s) {
|
|
|
162
163
|
/** TODO */
|
|
163
164
|
function extractHtmlText(s) {
|
|
164
165
|
if (s.indexOf(">") >= 0) {
|
|
165
|
-
error("
|
|
166
|
+
error("Not implemented");
|
|
166
167
|
// return $("<div/>").html(s).text();
|
|
167
168
|
}
|
|
168
169
|
return s;
|
|
@@ -284,7 +285,7 @@ function setValueToElem(elem, value) {
|
|
|
284
285
|
input.valueAsNumber = value;
|
|
285
286
|
break;
|
|
286
287
|
case "radio":
|
|
287
|
-
|
|
288
|
+
error("Not implemented");
|
|
288
289
|
// const name = input.name;
|
|
289
290
|
// const checked = input.parentElement!.querySelector(
|
|
290
291
|
// `input[name="${name}"]:checked`
|
|
@@ -298,16 +299,15 @@ function setValueToElem(elem, value) {
|
|
|
298
299
|
break;
|
|
299
300
|
case "text":
|
|
300
301
|
default:
|
|
301
|
-
input.
|
|
302
|
+
input.value = value || "";
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
else if (tag === "SELECT") {
|
|
305
306
|
const select = elem;
|
|
306
307
|
select.value = value;
|
|
307
308
|
}
|
|
308
|
-
// return value;
|
|
309
309
|
}
|
|
310
|
-
/**
|
|
310
|
+
/** Create and return an unconnected `HTMLElement` from a HTML string. */
|
|
311
311
|
function elemFromHtml(html) {
|
|
312
312
|
const t = document.createElement("template");
|
|
313
313
|
t.innerHTML = html.trim();
|
|
@@ -324,11 +324,23 @@ function elemFromSelector(obj) {
|
|
|
324
324
|
}
|
|
325
325
|
return obj;
|
|
326
326
|
}
|
|
327
|
+
/** Return a EventTarget from selector or cast an existing element. */
|
|
328
|
+
function eventTargetFromSelector(obj) {
|
|
329
|
+
if (!obj) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
if (typeof obj === "string") {
|
|
333
|
+
return document.querySelector(obj);
|
|
334
|
+
}
|
|
335
|
+
return obj;
|
|
336
|
+
}
|
|
327
337
|
/**
|
|
328
|
-
* Return a descriptive string for a keyboard or mouse event.
|
|
338
|
+
* Return a canonical descriptive string for a keyboard or mouse event.
|
|
329
339
|
*
|
|
330
340
|
* The result also contains a prefix for modifiers if any, for example
|
|
331
341
|
* `"x"`, `"F2"`, `"Control+Home"`, or `"Shift+clickright"`.
|
|
342
|
+
* This is especially useful in `switch` statements, to make sure that modifier
|
|
343
|
+
* keys are considered and handled correctly.
|
|
332
344
|
*/
|
|
333
345
|
function eventToString(event) {
|
|
334
346
|
let key = event.key, et = event.type, s = [];
|
|
@@ -360,6 +372,13 @@ function eventToString(event) {
|
|
|
360
372
|
}
|
|
361
373
|
return s.join("+");
|
|
362
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Copy allproperties from one or more source objects to a target object.
|
|
377
|
+
*
|
|
378
|
+
* @returns the modified target object.
|
|
379
|
+
*/
|
|
380
|
+
// TODO: use Object.assign()? --> https://stackoverflow.com/a/42740894
|
|
381
|
+
// TODO: support deep merge --> https://stackoverflow.com/a/42740894
|
|
363
382
|
function extend(...args) {
|
|
364
383
|
for (let i = 1; i < args.length; i++) {
|
|
365
384
|
let arg = args[i];
|
|
@@ -374,23 +393,27 @@ function extend(...args) {
|
|
|
374
393
|
}
|
|
375
394
|
return args[0];
|
|
376
395
|
}
|
|
396
|
+
/** Return true if `obj` is of type `array`. */
|
|
377
397
|
function isArray(obj) {
|
|
378
398
|
return Array.isArray(obj);
|
|
379
399
|
}
|
|
400
|
+
/** Return true if `obj` is of type `Object` and has no propertied. */
|
|
380
401
|
function isEmptyObject(obj) {
|
|
381
402
|
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
|
382
403
|
}
|
|
404
|
+
/** Return true if `obj` is of type `function`. */
|
|
383
405
|
function isFunction(obj) {
|
|
384
406
|
return typeof obj === "function";
|
|
385
407
|
}
|
|
408
|
+
/** Return true if `obj` is of type `Object`. */
|
|
386
409
|
function isPlainObject(obj) {
|
|
387
410
|
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
388
411
|
}
|
|
389
412
|
/** A dummy function that does nothing ('no operation'). */
|
|
390
413
|
function noop(...args) { }
|
|
391
|
-
function onEvent(
|
|
414
|
+
function onEvent(rootTarget, eventNames, selectorOrHandler, handlerOrNone) {
|
|
392
415
|
let selector, handler;
|
|
393
|
-
|
|
416
|
+
rootTarget = eventTargetFromSelector(rootTarget);
|
|
394
417
|
if (handlerOrNone) {
|
|
395
418
|
selector = selectorOrHandler;
|
|
396
419
|
handler = handlerOrNone;
|
|
@@ -400,7 +423,7 @@ function onEvent(rootElem, eventNames, selectorOrHandler, handlerOrNone) {
|
|
|
400
423
|
handler = selectorOrHandler;
|
|
401
424
|
}
|
|
402
425
|
eventNames.split(" ").forEach((evn) => {
|
|
403
|
-
|
|
426
|
+
rootTarget.addEventListener(evn, function (e) {
|
|
404
427
|
if (!selector) {
|
|
405
428
|
return handler(e); // no event delegation
|
|
406
429
|
}
|
|
@@ -455,7 +478,7 @@ function setTimeoutPromise(callback, ms) {
|
|
|
455
478
|
return new Promise((resolve, reject) => {
|
|
456
479
|
setTimeout(() => {
|
|
457
480
|
try {
|
|
458
|
-
resolve(callback.apply(
|
|
481
|
+
resolve(callback.apply(this));
|
|
459
482
|
}
|
|
460
483
|
catch (err) {
|
|
461
484
|
reject(err);
|
|
@@ -478,6 +501,13 @@ async function sleep(ms) {
|
|
|
478
501
|
}
|
|
479
502
|
/**
|
|
480
503
|
* Set or rotate checkbox status with support for tri-state.
|
|
504
|
+
*
|
|
505
|
+
* An initial 'indeterminate' state becomes 'checked' on the first call.
|
|
506
|
+
*
|
|
507
|
+
* If the input element has the class 'wb-tristate' assigned, the sequence is:<br>
|
|
508
|
+
* 'indeterminate' -> 'checked' -> 'unchecked' -> 'indeterminate' -> ...<br>
|
|
509
|
+
* Otherwise we toggle like <br>
|
|
510
|
+
* 'checked' -> 'unchecked' -> 'checked' -> ...
|
|
481
511
|
*/
|
|
482
512
|
function toggleCheckbox(element, value, tristate) {
|
|
483
513
|
const input = elemFromSelector(element);
|
|
@@ -535,12 +565,79 @@ function toSet(val) {
|
|
|
535
565
|
}
|
|
536
566
|
throw new Error("Cannot convert to Set<string>: " + val);
|
|
537
567
|
}
|
|
568
|
+
/**Return a canonical string representation for an object's type (e.g. 'array', 'number', ...) */
|
|
538
569
|
function type(obj) {
|
|
539
570
|
return Object.prototype.toString
|
|
540
571
|
.call(obj)
|
|
541
572
|
.replace(/^\[object (.+)\]$/, "$1")
|
|
542
573
|
.toLowerCase();
|
|
543
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Return a function that can be called instead of `callback`, but guarantees
|
|
577
|
+
* a limited execution rate.
|
|
578
|
+
* The execution rate is calculated based on the runtime duration of the
|
|
579
|
+
* previous call.
|
|
580
|
+
* Example:
|
|
581
|
+
* ```js
|
|
582
|
+
* throttledFoo = util.addaptiveThrottle(foo.bind(this), {});
|
|
583
|
+
* throttledFoo();
|
|
584
|
+
* throttledFoo();
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
function addaptiveThrottle(callback, options) {
|
|
588
|
+
let waiting = 0; // Initially, we're not waiting
|
|
589
|
+
let pendingArgs = null;
|
|
590
|
+
const opts = Object.assign({
|
|
591
|
+
minDelay: 16,
|
|
592
|
+
defaultDelay: 200,
|
|
593
|
+
maxDelay: 5000,
|
|
594
|
+
delayFactor: 2.0,
|
|
595
|
+
}, options);
|
|
596
|
+
const minDelay = Math.max(16, +opts.minDelay);
|
|
597
|
+
const maxDelay = +opts.maxDelay;
|
|
598
|
+
const throttledFn = (...args) => {
|
|
599
|
+
if (waiting) {
|
|
600
|
+
pendingArgs = args;
|
|
601
|
+
// console.log(`addaptiveThrottle() queing request #${waiting}...`, args);
|
|
602
|
+
waiting += 1;
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
// Prevent invocations while running or blocking
|
|
606
|
+
waiting = 1;
|
|
607
|
+
const useArgs = args; // pendingArgs || args;
|
|
608
|
+
pendingArgs = null;
|
|
609
|
+
// console.log(`addaptiveThrottle() execute...`, useArgs);
|
|
610
|
+
const start = Date.now();
|
|
611
|
+
try {
|
|
612
|
+
callback.apply(this, useArgs);
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
console.error(error);
|
|
616
|
+
}
|
|
617
|
+
const elap = Date.now() - start;
|
|
618
|
+
const curDelay = Math.min(Math.max(minDelay, elap * opts.delayFactor), maxDelay);
|
|
619
|
+
const useDelay = Math.max(minDelay, curDelay - elap);
|
|
620
|
+
// console.log(
|
|
621
|
+
// `addaptiveThrottle() calling worker took ${elap}ms. delay = ${curDelay}ms, using ${useDelay}ms`,
|
|
622
|
+
// pendingArgs
|
|
623
|
+
// );
|
|
624
|
+
setTimeout(() => {
|
|
625
|
+
// Unblock, and trigger pending requests if any
|
|
626
|
+
// const skipped = waiting - 1;
|
|
627
|
+
waiting = 0; // And allow future invocations
|
|
628
|
+
if (pendingArgs != null) {
|
|
629
|
+
// There was another request while running or waiting
|
|
630
|
+
// console.log(
|
|
631
|
+
// `addaptiveThrottle() re-trigger (missed ${skipped})...`,
|
|
632
|
+
// pendingArgs
|
|
633
|
+
// );
|
|
634
|
+
throttledFn.apply(this, pendingArgs);
|
|
635
|
+
}
|
|
636
|
+
}, useDelay);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
return throttledFn;
|
|
640
|
+
}
|
|
544
641
|
|
|
545
642
|
var util = /*#__PURE__*/Object.freeze({
|
|
546
643
|
__proto__: null,
|
|
@@ -561,6 +658,7 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
561
658
|
setValueToElem: setValueToElem,
|
|
562
659
|
elemFromHtml: elemFromHtml,
|
|
563
660
|
elemFromSelector: elemFromSelector,
|
|
661
|
+
eventTargetFromSelector: eventTargetFromSelector,
|
|
564
662
|
eventToString: eventToString,
|
|
565
663
|
extend: extend,
|
|
566
664
|
isArray: isArray,
|
|
@@ -575,13 +673,14 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
575
673
|
toggleCheckbox: toggleCheckbox,
|
|
576
674
|
getOption: getOption,
|
|
577
675
|
toSet: toSet,
|
|
578
|
-
type: type
|
|
676
|
+
type: type,
|
|
677
|
+
addaptiveThrottle: addaptiveThrottle
|
|
579
678
|
});
|
|
580
679
|
|
|
581
680
|
/*!
|
|
582
681
|
* Wunderbaum - common
|
|
583
682
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
584
|
-
* v0.0.
|
|
683
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
585
684
|
*/
|
|
586
685
|
const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
|
|
587
686
|
const ROW_HEIGHT = 22;
|
|
@@ -591,10 +690,20 @@ const RENDER_MAX_PREFETCH = 5;
|
|
|
591
690
|
const TEST_IMG = new RegExp(/\.|\//); // strings are considered image urls if they contain '.' or '/'
|
|
592
691
|
var ChangeType;
|
|
593
692
|
(function (ChangeType) {
|
|
693
|
+
/** Re-render the whole viewport, headers, and all rows. */
|
|
594
694
|
ChangeType["any"] = "any";
|
|
695
|
+
/** Update current row title, icon, columns, and status. */
|
|
696
|
+
ChangeType["data"] = "data";
|
|
697
|
+
/** Redraw the header and update the width of all row columns. */
|
|
698
|
+
ChangeType["header"] = "header";
|
|
699
|
+
/** Re-render the whole current row. */
|
|
595
700
|
ChangeType["row"] = "row";
|
|
701
|
+
/** Alias for 'any'. */
|
|
596
702
|
ChangeType["structure"] = "structure";
|
|
703
|
+
/** Update current row's classes, to reflect active, selected, ... */
|
|
597
704
|
ChangeType["status"] = "status";
|
|
705
|
+
/** Update the 'top' property of all rows. */
|
|
706
|
+
ChangeType["vscroll"] = "vscroll";
|
|
598
707
|
})(ChangeType || (ChangeType = {}));
|
|
599
708
|
var NodeStatusType;
|
|
600
709
|
(function (NodeStatusType) {
|
|
@@ -667,6 +776,8 @@ const KEY_TO_ACTION_DICT = {
|
|
|
667
776
|
Home: "firstCol",
|
|
668
777
|
"Control+End": "last",
|
|
669
778
|
"Control+Home": "first",
|
|
779
|
+
"Meta+ArrowDown": "last",
|
|
780
|
+
"Meta+ArrowUp": "first",
|
|
670
781
|
"*": "expandAll",
|
|
671
782
|
Multiply: "expandAll",
|
|
672
783
|
PageDown: "pageDown",
|
|
@@ -693,7 +804,7 @@ function makeNodeTitleStartMatcher(s) {
|
|
|
693
804
|
/*!
|
|
694
805
|
* Wunderbaum - wb_extension_base
|
|
695
806
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
696
|
-
* v0.0.
|
|
807
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
697
808
|
*/
|
|
698
809
|
class WunderbaumExtension {
|
|
699
810
|
constructor(tree, id, defaults) {
|
|
@@ -716,14 +827,14 @@ class WunderbaumExtension {
|
|
|
716
827
|
init() {
|
|
717
828
|
this.tree.element.classList.add("wb-ext-" + this.id);
|
|
718
829
|
}
|
|
719
|
-
// protected callEvent(
|
|
720
|
-
// let func = this.extensionOpts[
|
|
830
|
+
// protected callEvent(type: string, extra?: any): any {
|
|
831
|
+
// let func = this.extensionOpts[type];
|
|
721
832
|
// if (func) {
|
|
722
833
|
// return func.call(
|
|
723
834
|
// this.tree,
|
|
724
835
|
// util.extend(
|
|
725
836
|
// {
|
|
726
|
-
// event: this.id + "." +
|
|
837
|
+
// event: this.id + "." + type,
|
|
727
838
|
// },
|
|
728
839
|
// extra
|
|
729
840
|
// )
|
|
@@ -980,75 +1091,11 @@ function debounce(func, wait = 0, options = {}) {
|
|
|
980
1091
|
debounced.pending = pending;
|
|
981
1092
|
return debounced;
|
|
982
1093
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Creates a throttled function that only invokes `func` at most once per
|
|
985
|
-
* every `wait` milliseconds (or once per browser frame). The throttled function
|
|
986
|
-
* comes with a `cancel` method to cancel delayed `func` invocations and a
|
|
987
|
-
* `flush` method to immediately invoke them. Provide `options` to indicate
|
|
988
|
-
* whether `func` should be invoked on the leading and/or trailing edge of the
|
|
989
|
-
* `wait` timeout. The `func` is invoked with the last arguments provided to the
|
|
990
|
-
* throttled function. Subsequent calls to the throttled function return the
|
|
991
|
-
* result of the last `func` invocation.
|
|
992
|
-
*
|
|
993
|
-
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
994
|
-
* invoked on the trailing edge of the timeout only if the throttled function
|
|
995
|
-
* is invoked more than once during the `wait` timeout.
|
|
996
|
-
*
|
|
997
|
-
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
998
|
-
* until the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
999
|
-
*
|
|
1000
|
-
* If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
|
|
1001
|
-
* invocation will be deferred until the next frame is drawn (typically about
|
|
1002
|
-
* 16ms).
|
|
1003
|
-
*
|
|
1004
|
-
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
|
1005
|
-
* for details over the differences between `throttle` and `debounce`.
|
|
1006
|
-
*
|
|
1007
|
-
* @since 0.1.0
|
|
1008
|
-
* @category Function
|
|
1009
|
-
* @param {Function} func The function to throttle.
|
|
1010
|
-
* @param {number} [wait=0]
|
|
1011
|
-
* The number of milliseconds to throttle invocations to; if omitted,
|
|
1012
|
-
* `requestAnimationFrame` is used (if available).
|
|
1013
|
-
* @param {Object} [options={}] The options object.
|
|
1014
|
-
* @param {boolean} [options.leading=true]
|
|
1015
|
-
* Specify invoking on the leading edge of the timeout.
|
|
1016
|
-
* @param {boolean} [options.trailing=true]
|
|
1017
|
-
* Specify invoking on the trailing edge of the timeout.
|
|
1018
|
-
* @returns {Function} Returns the new throttled function.
|
|
1019
|
-
* @example
|
|
1020
|
-
*
|
|
1021
|
-
* // Avoid excessively updating the position while scrolling.
|
|
1022
|
-
* jQuery(window).on('scroll', throttle(updatePosition, 100))
|
|
1023
|
-
*
|
|
1024
|
-
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
|
|
1025
|
-
* const throttled = throttle(renewToken, 300000, { 'trailing': false })
|
|
1026
|
-
* jQuery(element).on('click', throttled)
|
|
1027
|
-
*
|
|
1028
|
-
* // Cancel the trailing throttled invocation.
|
|
1029
|
-
* jQuery(window).on('popstate', throttled.cancel)
|
|
1030
|
-
*/
|
|
1031
|
-
function throttle(func, wait = 0, options = {}) {
|
|
1032
|
-
let leading = true;
|
|
1033
|
-
let trailing = true;
|
|
1034
|
-
if (typeof func !== "function") {
|
|
1035
|
-
throw new TypeError("Expected a function");
|
|
1036
|
-
}
|
|
1037
|
-
if (isObject(options)) {
|
|
1038
|
-
leading = "leading" in options ? !!options.leading : leading;
|
|
1039
|
-
trailing = "trailing" in options ? !!options.trailing : trailing;
|
|
1040
|
-
}
|
|
1041
|
-
return debounce(func, wait, {
|
|
1042
|
-
leading,
|
|
1043
|
-
trailing,
|
|
1044
|
-
maxWait: wait,
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
1094
|
|
|
1048
1095
|
/*!
|
|
1049
1096
|
* Wunderbaum - ext-filter
|
|
1050
1097
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1051
|
-
* v0.0.
|
|
1098
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1052
1099
|
*/
|
|
1053
1100
|
const START_MARKER = "\uFFF7";
|
|
1054
1101
|
const END_MARKER = "\uFFF8";
|
|
@@ -1087,7 +1134,9 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1087
1134
|
});
|
|
1088
1135
|
}
|
|
1089
1136
|
_applyFilterImpl(filter, branchMode, _opts) {
|
|
1090
|
-
let match, temp, start = Date.now(), count = 0, tree = this.tree, treeOpts = tree.options,
|
|
1137
|
+
let match, temp, start = Date.now(), count = 0, tree = this.tree, treeOpts = tree.options,
|
|
1138
|
+
// escapeTitles = treeOpts.escapeTitles,
|
|
1139
|
+
prevAutoCollapse = treeOpts.autoCollapse, opts = extend({}, treeOpts.filter, _opts), hideMode = opts.mode === "hide", leavesOnly = !!opts.leavesOnly && !branchMode;
|
|
1091
1140
|
// Default to 'match title substring (case insensitive)'
|
|
1092
1141
|
if (typeof filter === "string") {
|
|
1093
1142
|
if (filter === "") {
|
|
@@ -1120,37 +1169,36 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1120
1169
|
if (!node.title) {
|
|
1121
1170
|
return false;
|
|
1122
1171
|
}
|
|
1123
|
-
let text = escapeTitles ? node.title : extractHtmlText(node.title);
|
|
1172
|
+
// let text = escapeTitles ? node.title : extractHtmlText(node.title);
|
|
1173
|
+
let text = node.title;
|
|
1124
1174
|
// `.match` instead of `.test` to get the capture groups
|
|
1125
1175
|
let res = text.match(re);
|
|
1126
1176
|
if (res && opts.highlight) {
|
|
1127
|
-
if (escapeTitles) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1131
|
-
else {
|
|
1132
|
-
// #740: we must not apply the marks to escaped entity names, e.g. `"`
|
|
1133
|
-
// Use some exotic characters to mark matches:
|
|
1134
|
-
temp = text.replace(reHighlight, function (s) {
|
|
1135
|
-
return START_MARKER + s + END_MARKER;
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
// now we can escape the title...
|
|
1139
|
-
node.titleWithHighlight = escapeHtml(temp)
|
|
1140
|
-
// ... and finally insert the desired `<mark>` tags
|
|
1141
|
-
.replace(RE_START_MARKER, "<mark>")
|
|
1142
|
-
.replace(RE_END_MARTKER, "</mark>");
|
|
1177
|
+
// if (escapeTitles) {
|
|
1178
|
+
if (opts.fuzzy) {
|
|
1179
|
+
temp = _markFuzzyMatchedChars(text, res, true);
|
|
1143
1180
|
}
|
|
1144
1181
|
else {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
return "<mark>" + s + "</mark>";
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1182
|
+
// #740: we must not apply the marks to escaped entity names, e.g. `"`
|
|
1183
|
+
// Use some exotic characters to mark matches:
|
|
1184
|
+
temp = text.replace(reHighlight, function (s) {
|
|
1185
|
+
return START_MARKER + s + END_MARKER;
|
|
1186
|
+
});
|
|
1153
1187
|
}
|
|
1188
|
+
// now we can escape the title...
|
|
1189
|
+
node.titleWithHighlight = escapeHtml(temp)
|
|
1190
|
+
// ... and finally insert the desired `<mark>` tags
|
|
1191
|
+
.replace(RE_START_MARKER, "<mark>")
|
|
1192
|
+
.replace(RE_END_MARTKER, "</mark>");
|
|
1193
|
+
// } else {
|
|
1194
|
+
// if (opts.fuzzy) {
|
|
1195
|
+
// node.titleWithHighlight = _markFuzzyMatchedChars(text, res);
|
|
1196
|
+
// } else {
|
|
1197
|
+
// node.titleWithHighlight = text.replace(reHighlight, function (s) {
|
|
1198
|
+
// return "<mark>" + s + "</mark>";
|
|
1199
|
+
// });
|
|
1200
|
+
// }
|
|
1201
|
+
// }
|
|
1154
1202
|
// node.debug("filter", escapeTitles, text, node.titleWithHighlight);
|
|
1155
1203
|
}
|
|
1156
1204
|
return !!res;
|
|
@@ -1252,9 +1300,9 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1252
1300
|
* [ext-filter] Reset the filter.
|
|
1253
1301
|
*/
|
|
1254
1302
|
clearFilter() {
|
|
1255
|
-
let tree = this.tree
|
|
1303
|
+
let tree = this.tree;
|
|
1256
1304
|
// statusNode = tree.root.findDirectChild(KEY_NODATA),
|
|
1257
|
-
escapeTitles = tree.options.escapeTitles;
|
|
1305
|
+
// escapeTitles = tree.options.escapeTitles;
|
|
1258
1306
|
// enhanceTitle = tree.options.enhanceTitle,
|
|
1259
1307
|
tree.enableUpdate(false);
|
|
1260
1308
|
// if (statusNode) {
|
|
@@ -1268,12 +1316,11 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1268
1316
|
if (node.match && node._rowElem) {
|
|
1269
1317
|
// #491, #601
|
|
1270
1318
|
let titleElem = node._rowElem.querySelector("span.wb-title");
|
|
1271
|
-
if (escapeTitles) {
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1319
|
+
// if (escapeTitles) {
|
|
1320
|
+
titleElem.textContent = node.title;
|
|
1321
|
+
// } else {
|
|
1322
|
+
// titleElem.innerHTML = node.title;
|
|
1323
|
+
// }
|
|
1277
1324
|
node._callEvent("enhanceTitle", { titleElem: titleElem });
|
|
1278
1325
|
}
|
|
1279
1326
|
delete node.match;
|
|
@@ -1309,7 +1356,7 @@ class FilterExtension extends WunderbaumExtension {
|
|
|
1309
1356
|
* @param {string} text
|
|
1310
1357
|
* @param {RegExpMatchArray} matches
|
|
1311
1358
|
*/
|
|
1312
|
-
function _markFuzzyMatchedChars(text, matches, escapeTitles =
|
|
1359
|
+
function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
|
|
1313
1360
|
let matchingIndices = [];
|
|
1314
1361
|
// get the indices of matched characters (Iterate through `RegExpMatchArray`)
|
|
1315
1362
|
for (let _matchingArrIdx = 1; _matchingArrIdx < matches.length; _matchingArrIdx++) {
|
|
@@ -1324,7 +1371,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = false) {
|
|
|
1324
1371
|
// Map each `text` char to its position and store in `textPoses`.
|
|
1325
1372
|
let textPoses = text.split("");
|
|
1326
1373
|
if (escapeTitles) {
|
|
1327
|
-
// If escaping the title, then wrap the
|
|
1374
|
+
// If escaping the title, then wrap the matching char within exotic chars
|
|
1328
1375
|
matchingIndices.forEach(function (v) {
|
|
1329
1376
|
textPoses[v] = START_MARKER + textPoses[v] + END_MARKER;
|
|
1330
1377
|
});
|
|
@@ -1342,7 +1389,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = false) {
|
|
|
1342
1389
|
/*!
|
|
1343
1390
|
* Wunderbaum - ext-keynav
|
|
1344
1391
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1345
|
-
* v0.0.
|
|
1392
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1346
1393
|
*/
|
|
1347
1394
|
class KeynavExtension extends WunderbaumExtension {
|
|
1348
1395
|
constructor(tree) {
|
|
@@ -1409,7 +1456,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1409
1456
|
eventName = "Add"; // expand
|
|
1410
1457
|
}
|
|
1411
1458
|
else if (navModeOption === NavigationModeOption.startRow) {
|
|
1412
|
-
tree.
|
|
1459
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1413
1460
|
return;
|
|
1414
1461
|
}
|
|
1415
1462
|
break;
|
|
@@ -1448,6 +1495,8 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1448
1495
|
case "Home":
|
|
1449
1496
|
case "Control+End":
|
|
1450
1497
|
case "Control+Home":
|
|
1498
|
+
case "Meta+ArrowDown":
|
|
1499
|
+
case "Meta+ArrowUp":
|
|
1451
1500
|
case "PageDown":
|
|
1452
1501
|
case "PageUp":
|
|
1453
1502
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1479,11 +1528,11 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1479
1528
|
break;
|
|
1480
1529
|
case "Escape":
|
|
1481
1530
|
if (tree.navMode === NavigationMode.cellEdit) {
|
|
1482
|
-
tree.
|
|
1531
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1483
1532
|
handled = true;
|
|
1484
1533
|
}
|
|
1485
1534
|
else if (tree.navMode === NavigationMode.cellNav) {
|
|
1486
|
-
tree.
|
|
1535
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1487
1536
|
handled = true;
|
|
1488
1537
|
}
|
|
1489
1538
|
break;
|
|
@@ -1493,7 +1542,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1493
1542
|
handled = true;
|
|
1494
1543
|
}
|
|
1495
1544
|
else if (navModeOption !== NavigationModeOption.cell) {
|
|
1496
|
-
tree.
|
|
1545
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1497
1546
|
handled = true;
|
|
1498
1547
|
}
|
|
1499
1548
|
break;
|
|
@@ -1510,6 +1559,8 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1510
1559
|
case "Home":
|
|
1511
1560
|
case "Control+End":
|
|
1512
1561
|
case "Control+Home":
|
|
1562
|
+
case "Meta+ArrowDown":
|
|
1563
|
+
case "Meta+ArrowUp":
|
|
1513
1564
|
case "PageDown":
|
|
1514
1565
|
case "PageUp":
|
|
1515
1566
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1528,7 +1579,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1528
1579
|
/*!
|
|
1529
1580
|
* Wunderbaum - ext-logger
|
|
1530
1581
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1531
|
-
* v0.0.
|
|
1582
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1532
1583
|
*/
|
|
1533
1584
|
class LoggerExtension extends WunderbaumExtension {
|
|
1534
1585
|
constructor(tree) {
|
|
@@ -1568,19 +1619,9 @@ class LoggerExtension extends WunderbaumExtension {
|
|
|
1568
1619
|
/*!
|
|
1569
1620
|
* Wunderbaum - ext-dnd
|
|
1570
1621
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1571
|
-
* v0.0.
|
|
1622
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1572
1623
|
*/
|
|
1573
1624
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
1574
|
-
// type AllowedDropRegionType =
|
|
1575
|
-
// | "after"
|
|
1576
|
-
// | "afterBefore"
|
|
1577
|
-
// // | "afterBeforeOver" // == all == true
|
|
1578
|
-
// | "afterOver"
|
|
1579
|
-
// | "all" // == true
|
|
1580
|
-
// | "before"
|
|
1581
|
-
// | "beforeOver"
|
|
1582
|
-
// | "none" // == false == "" == null
|
|
1583
|
-
// | "over"; // == "child"
|
|
1584
1625
|
class DndExtension extends WunderbaumExtension {
|
|
1585
1626
|
constructor(tree) {
|
|
1586
1627
|
super(tree, "dnd", {
|
|
@@ -1843,15 +1884,196 @@ class DndExtension extends WunderbaumExtension {
|
|
|
1843
1884
|
}
|
|
1844
1885
|
}
|
|
1845
1886
|
|
|
1887
|
+
/*!
|
|
1888
|
+
* Wunderbaum - drag_observer
|
|
1889
|
+
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1890
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1891
|
+
*/
|
|
1892
|
+
/**
|
|
1893
|
+
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
1894
|
+
*/
|
|
1895
|
+
class DragObserver {
|
|
1896
|
+
constructor(opts) {
|
|
1897
|
+
this.start = {
|
|
1898
|
+
x: 0,
|
|
1899
|
+
y: 0,
|
|
1900
|
+
altKey: false,
|
|
1901
|
+
ctrlKey: false,
|
|
1902
|
+
metaKey: false,
|
|
1903
|
+
shiftKey: false,
|
|
1904
|
+
};
|
|
1905
|
+
this.dragElem = null;
|
|
1906
|
+
this.dragging = false;
|
|
1907
|
+
// TODO: touch events
|
|
1908
|
+
this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
|
|
1909
|
+
assert(opts.root);
|
|
1910
|
+
this.opts = extend({ thresh: 5 }, opts);
|
|
1911
|
+
this.root = opts.root;
|
|
1912
|
+
this._handler = this.handleEvent.bind(this);
|
|
1913
|
+
this.events.forEach((type) => {
|
|
1914
|
+
this.root.addEventListener(type, this._handler);
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
/** Unregister all event listeners. */
|
|
1918
|
+
disconnect() {
|
|
1919
|
+
this.events.forEach((type) => {
|
|
1920
|
+
this.root.removeEventListener(type, this._handler);
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
getDragElem() {
|
|
1924
|
+
return this.dragElem;
|
|
1925
|
+
}
|
|
1926
|
+
isDragging() {
|
|
1927
|
+
return this.dragging;
|
|
1928
|
+
}
|
|
1929
|
+
stopDrag(cb_event) {
|
|
1930
|
+
if (this.dragging && this.opts.dragstop && cb_event) {
|
|
1931
|
+
cb_event.type = "dragstop";
|
|
1932
|
+
this.opts.dragstop(cb_event);
|
|
1933
|
+
}
|
|
1934
|
+
this.dragElem = null;
|
|
1935
|
+
this.dragging = false;
|
|
1936
|
+
}
|
|
1937
|
+
handleEvent(e) {
|
|
1938
|
+
const type = e.type;
|
|
1939
|
+
const opts = this.opts;
|
|
1940
|
+
const cb_event = {
|
|
1941
|
+
type: e.type,
|
|
1942
|
+
event: e,
|
|
1943
|
+
dragElem: this.dragElem,
|
|
1944
|
+
dx: e.pageX - this.start.x,
|
|
1945
|
+
dy: e.pageY - this.start.y,
|
|
1946
|
+
apply: undefined,
|
|
1947
|
+
};
|
|
1948
|
+
switch (type) {
|
|
1949
|
+
case "keydown":
|
|
1950
|
+
this.stopDrag(cb_event);
|
|
1951
|
+
break;
|
|
1952
|
+
case "mousedown":
|
|
1953
|
+
if (this.dragElem) {
|
|
1954
|
+
this.stopDrag(cb_event);
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
if (opts.selector) {
|
|
1958
|
+
let elem = e.target;
|
|
1959
|
+
if (elem.matches(opts.selector)) {
|
|
1960
|
+
this.dragElem = elem;
|
|
1961
|
+
}
|
|
1962
|
+
else {
|
|
1963
|
+
elem = elem.closest(opts.selector);
|
|
1964
|
+
if (elem) {
|
|
1965
|
+
this.dragElem = elem;
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
break; // no event delegation selector matched
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
this.start.x = e.pageX;
|
|
1973
|
+
this.start.y = e.pageY;
|
|
1974
|
+
this.start.altKey = e.altKey;
|
|
1975
|
+
this.start.ctrlKey = e.ctrlKey;
|
|
1976
|
+
this.start.metaKey = e.metaKey;
|
|
1977
|
+
this.start.shiftKey = e.shiftKey;
|
|
1978
|
+
break;
|
|
1979
|
+
case "mousemove":
|
|
1980
|
+
// TODO: debounce/throttle?
|
|
1981
|
+
// TODO: horizontal mode: ignore if dx unchanged
|
|
1982
|
+
if (!this.dragElem) {
|
|
1983
|
+
break;
|
|
1984
|
+
}
|
|
1985
|
+
if (!this.dragging) {
|
|
1986
|
+
if (opts.thresh) {
|
|
1987
|
+
const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
|
|
1988
|
+
if (dist2 < opts.thresh * opts.thresh) {
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
cb_event.type = "dragstart";
|
|
1993
|
+
if (opts.dragstart(cb_event) === false) {
|
|
1994
|
+
this.stopDrag(cb_event);
|
|
1995
|
+
break;
|
|
1996
|
+
}
|
|
1997
|
+
this.dragging = true;
|
|
1998
|
+
}
|
|
1999
|
+
if (this.dragging && this.opts.drag) {
|
|
2000
|
+
cb_event.type = "drag";
|
|
2001
|
+
this.opts.drag(cb_event);
|
|
2002
|
+
}
|
|
2003
|
+
break;
|
|
2004
|
+
case "mouseup":
|
|
2005
|
+
if (!this.dragging) {
|
|
2006
|
+
this.stopDrag(cb_event);
|
|
2007
|
+
break;
|
|
2008
|
+
}
|
|
2009
|
+
if (e.button === 0) {
|
|
2010
|
+
cb_event.apply = true;
|
|
2011
|
+
}
|
|
2012
|
+
else {
|
|
2013
|
+
cb_event.apply = false;
|
|
2014
|
+
}
|
|
2015
|
+
this.stopDrag(cb_event);
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/*!
|
|
2022
|
+
* Wunderbaum - ext-grid
|
|
2023
|
+
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2024
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
2025
|
+
*/
|
|
2026
|
+
class GridExtension extends WunderbaumExtension {
|
|
2027
|
+
constructor(tree) {
|
|
2028
|
+
super(tree, "grid", {
|
|
2029
|
+
// throttle: 200,
|
|
2030
|
+
});
|
|
2031
|
+
this.observer = new DragObserver({
|
|
2032
|
+
root: window.document,
|
|
2033
|
+
selector: "span.wb-col-resizer",
|
|
2034
|
+
thresh: 4,
|
|
2035
|
+
// throttle: 400,
|
|
2036
|
+
dragstart: (e) => {
|
|
2037
|
+
return this.tree.element.contains(e.dragElem);
|
|
2038
|
+
},
|
|
2039
|
+
drag: (e) => {
|
|
2040
|
+
// TODO: throttle
|
|
2041
|
+
return this.handleDrag(e);
|
|
2042
|
+
},
|
|
2043
|
+
dragstop: (e) => {
|
|
2044
|
+
return this.handleDrag(e);
|
|
2045
|
+
},
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
init() {
|
|
2049
|
+
super.init();
|
|
2050
|
+
}
|
|
2051
|
+
handleDrag(e) {
|
|
2052
|
+
const info = Wunderbaum.getEventInfo(e.event);
|
|
2053
|
+
// this.tree.options.
|
|
2054
|
+
this.tree.log(`${e.type}(${e.dx})`, e, info);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
1846
2058
|
/*!
|
|
1847
2059
|
* Wunderbaum - deferred
|
|
1848
2060
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1849
|
-
* v0.0.
|
|
2061
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1850
2062
|
*/
|
|
1851
2063
|
/**
|
|
1852
|
-
*
|
|
2064
|
+
* Implement a ES6 Promise, that exposes a resolve() and reject() method.
|
|
1853
2065
|
*
|
|
1854
|
-
* Loosely mimics
|
|
2066
|
+
* Loosely mimics {@link https://api.jquery.com/category/deferred-object/ | jQuery.Deferred}.
|
|
2067
|
+
* Example:
|
|
2068
|
+
* ```js
|
|
2069
|
+
* function foo() {
|
|
2070
|
+
* let dfd = new Deferred(),
|
|
2071
|
+
* ...
|
|
2072
|
+
* dfd.resolve('foo')
|
|
2073
|
+
* ...
|
|
2074
|
+
* return dfd.promise();
|
|
2075
|
+
* }
|
|
2076
|
+
* ```
|
|
1855
2077
|
*/
|
|
1856
2078
|
class Deferred {
|
|
1857
2079
|
constructor() {
|
|
@@ -1889,7 +2111,7 @@ class Deferred {
|
|
|
1889
2111
|
/*!
|
|
1890
2112
|
* Wunderbaum - wunderbaum_node
|
|
1891
2113
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1892
|
-
* v0.0.
|
|
2114
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
1893
2115
|
*/
|
|
1894
2116
|
/** Top-level properties that can be passed with `data`. */
|
|
1895
2117
|
const NODE_PROPS = new Set([
|
|
@@ -1926,15 +2148,31 @@ const NODE_ATTRS = new Set([
|
|
|
1926
2148
|
"unselectableIgnore",
|
|
1927
2149
|
"unselectableStatus",
|
|
1928
2150
|
]);
|
|
2151
|
+
/**
|
|
2152
|
+
* A single tree node.
|
|
2153
|
+
*
|
|
2154
|
+
* **NOTE:** <br>
|
|
2155
|
+
* Generally you should not modify properties directly, since this may break
|
|
2156
|
+
* the internal bookkeeping.
|
|
2157
|
+
*/
|
|
1929
2158
|
class WunderbaumNode {
|
|
1930
2159
|
constructor(tree, parent, data) {
|
|
1931
2160
|
var _a, _b;
|
|
2161
|
+
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
2162
|
+
* times within a tree (in this case we have 'clone nodes').
|
|
2163
|
+
* @see Use {@link setKey} to modify.
|
|
2164
|
+
*/
|
|
1932
2165
|
this.refKey = undefined;
|
|
1933
2166
|
this.children = null;
|
|
1934
2167
|
this.lazy = false;
|
|
2168
|
+
/** Expansion state.
|
|
2169
|
+
* @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
|
|
1935
2170
|
this.expanded = false;
|
|
2171
|
+
/** Selection state.
|
|
2172
|
+
* @see {@link isSelected}, {@link setSelected}. */
|
|
1936
2173
|
this.selected = false;
|
|
1937
|
-
/** Additional classes added to `div.wb-row`.
|
|
2174
|
+
/** Additional classes added to `div.wb-row`.
|
|
2175
|
+
* @see {@link addClass}, {@link removeClass}, {@link toggleClass}. */
|
|
1938
2176
|
this.extraClasses = new Set();
|
|
1939
2177
|
/** Custom data that was passed to the constructor */
|
|
1940
2178
|
this.data = {};
|
|
@@ -2007,8 +2245,8 @@ class WunderbaumNode {
|
|
|
2007
2245
|
* node._callEvent("edit.beforeEdit", {foo: 42})
|
|
2008
2246
|
* ```
|
|
2009
2247
|
*/
|
|
2010
|
-
_callEvent(
|
|
2011
|
-
return this.tree._callEvent(
|
|
2248
|
+
_callEvent(type, extra) {
|
|
2249
|
+
return this.tree._callEvent(type, extend({
|
|
2012
2250
|
node: this,
|
|
2013
2251
|
typeInfo: this.type ? this.tree.types[this.type] : {},
|
|
2014
2252
|
}, extra));
|
|
@@ -2061,7 +2299,7 @@ class WunderbaumNode {
|
|
|
2061
2299
|
// this.fixSelection3FromEndNodes();
|
|
2062
2300
|
// }
|
|
2063
2301
|
// this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
|
|
2064
|
-
this.tree.setModified(ChangeType.structure
|
|
2302
|
+
this.tree.setModified(ChangeType.structure);
|
|
2065
2303
|
return nodeList[0];
|
|
2066
2304
|
}
|
|
2067
2305
|
finally {
|
|
@@ -2100,7 +2338,8 @@ class WunderbaumNode {
|
|
|
2100
2338
|
}
|
|
2101
2339
|
/**
|
|
2102
2340
|
* Apply a modification (or navigation) operation.
|
|
2103
|
-
*
|
|
2341
|
+
*
|
|
2342
|
+
* @see {@link Wunderbaum.applyCommand}
|
|
2104
2343
|
*/
|
|
2105
2344
|
applyCommand(cmd, opts) {
|
|
2106
2345
|
return this.tree.applyCommand(cmd, this, opts);
|
|
@@ -2193,8 +2432,7 @@ class WunderbaumNode {
|
|
|
2193
2432
|
}
|
|
2194
2433
|
/** Find a node relative to self.
|
|
2195
2434
|
*
|
|
2196
|
-
* @
|
|
2197
|
-
* or a keyword ('down', 'first', 'last', 'left', 'parent', 'right', 'up').
|
|
2435
|
+
* @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
|
|
2198
2436
|
*/
|
|
2199
2437
|
findRelatedNode(where, includeHidden = false) {
|
|
2200
2438
|
return this.tree.findRelatedNode(this, where, includeHidden);
|
|
@@ -2495,7 +2733,7 @@ class WunderbaumNode {
|
|
|
2495
2733
|
assert(!this.parent);
|
|
2496
2734
|
tree.columns = data.columns;
|
|
2497
2735
|
delete data.columns;
|
|
2498
|
-
tree.
|
|
2736
|
+
tree.updateColumns({ calculateCols: false });
|
|
2499
2737
|
}
|
|
2500
2738
|
this._loadSourceObject(data);
|
|
2501
2739
|
}
|
|
@@ -2531,19 +2769,20 @@ class WunderbaumNode {
|
|
|
2531
2769
|
this.setStatus(NodeStatusType.ok);
|
|
2532
2770
|
return;
|
|
2533
2771
|
}
|
|
2534
|
-
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}
|
|
2772
|
+
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}`, or false.");
|
|
2535
2773
|
await this.load(source); // also calls setStatus('ok')
|
|
2536
2774
|
if (wasExpanded) {
|
|
2537
2775
|
this.expanded = true;
|
|
2538
|
-
this.tree.
|
|
2776
|
+
this.tree.setModified(ChangeType.structure);
|
|
2539
2777
|
}
|
|
2540
2778
|
else {
|
|
2541
|
-
this.
|
|
2779
|
+
this.setModified(); // Fix expander icon to 'loaded'
|
|
2542
2780
|
}
|
|
2543
2781
|
}
|
|
2544
2782
|
catch (e) {
|
|
2783
|
+
this.logError("Error during loadLazy()", e);
|
|
2784
|
+
this._callEvent("error", { error: e });
|
|
2545
2785
|
this.setStatus(NodeStatusType.error, "" + e);
|
|
2546
|
-
// } finally {
|
|
2547
2786
|
}
|
|
2548
2787
|
return;
|
|
2549
2788
|
}
|
|
@@ -2691,7 +2930,7 @@ class WunderbaumNode {
|
|
|
2691
2930
|
n.tree = targetNode.tree;
|
|
2692
2931
|
}, true);
|
|
2693
2932
|
}
|
|
2694
|
-
tree.
|
|
2933
|
+
tree.setModified(ChangeType.structure);
|
|
2695
2934
|
// TODO: fix selection state
|
|
2696
2935
|
// TODO: fix active state
|
|
2697
2936
|
}
|
|
@@ -2711,31 +2950,33 @@ class WunderbaumNode {
|
|
|
2711
2950
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
2712
2951
|
where = KEY_TO_ACTION_DICT[where] || where;
|
|
2713
2952
|
// Otherwise activate or focus the related node
|
|
2714
|
-
|
|
2715
|
-
if (node) {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
node.
|
|
2722
|
-
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2723
|
-
return Promise.resolve(this);
|
|
2724
|
-
}
|
|
2725
|
-
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2953
|
+
const node = this.findRelatedNode(where);
|
|
2954
|
+
if (!node) {
|
|
2955
|
+
this.logWarn(`Could not find related node '${where}'.`);
|
|
2956
|
+
return Promise.resolve(this);
|
|
2957
|
+
}
|
|
2958
|
+
// setFocus/setActive will scroll later (if autoScroll is specified)
|
|
2959
|
+
try {
|
|
2960
|
+
node.makeVisible({ scrollIntoView: false });
|
|
2726
2961
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2962
|
+
catch (e) { } // #272
|
|
2963
|
+
node.setFocus();
|
|
2964
|
+
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2965
|
+
return Promise.resolve(this);
|
|
2966
|
+
}
|
|
2967
|
+
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2729
2968
|
}
|
|
2730
2969
|
/** Delete this node and all descendants. */
|
|
2731
2970
|
remove() {
|
|
2732
2971
|
const tree = this.tree;
|
|
2733
2972
|
const pos = this.parent.children.indexOf(this);
|
|
2973
|
+
this.triggerModify("remove");
|
|
2734
2974
|
this.parent.children.splice(pos, 1);
|
|
2735
2975
|
this.visit((n) => {
|
|
2736
2976
|
n.removeMarkup();
|
|
2737
2977
|
tree._unregisterNode(n);
|
|
2738
2978
|
}, true);
|
|
2979
|
+
tree.setModified(ChangeType.structure);
|
|
2739
2980
|
}
|
|
2740
2981
|
/** Remove all descendants of this node. */
|
|
2741
2982
|
removeChildren() {
|
|
@@ -2767,7 +3008,7 @@ class WunderbaumNode {
|
|
|
2767
3008
|
if (!this.isRootNode()) {
|
|
2768
3009
|
this.expanded = false;
|
|
2769
3010
|
}
|
|
2770
|
-
this.tree.
|
|
3011
|
+
this.tree.setModified(ChangeType.structure);
|
|
2771
3012
|
}
|
|
2772
3013
|
/** Remove all HTML markup from the DOM. */
|
|
2773
3014
|
removeMarkup() {
|
|
@@ -2842,13 +3083,15 @@ class WunderbaumNode {
|
|
|
2842
3083
|
// this.log("_createIcon: ", iconSpan);
|
|
2843
3084
|
return iconSpan;
|
|
2844
3085
|
}
|
|
2845
|
-
/**
|
|
2846
|
-
|
|
3086
|
+
/**
|
|
3087
|
+
* Create a whole new `<div class="wb-row">` element.
|
|
3088
|
+
* @see {@link Wunderbaumode.render}
|
|
3089
|
+
*/
|
|
3090
|
+
_render_markup(opts) {
|
|
2847
3091
|
const tree = this.tree;
|
|
2848
3092
|
const treeOptions = tree.options;
|
|
2849
3093
|
const checkbox = this.getOption("checkbox") !== false;
|
|
2850
3094
|
const columns = tree.columns;
|
|
2851
|
-
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
2852
3095
|
const level = this.getLevel();
|
|
2853
3096
|
let elem;
|
|
2854
3097
|
let nodeElem;
|
|
@@ -2858,10 +3101,171 @@ class WunderbaumNode {
|
|
|
2858
3101
|
let iconSpan;
|
|
2859
3102
|
let expanderSpan = null;
|
|
2860
3103
|
const activeColIdx = tree.navMode === NavigationMode.row ? null : tree.activeColIdx;
|
|
2861
|
-
// let colElems: HTMLElement[];
|
|
2862
3104
|
const isNew = !rowDiv;
|
|
3105
|
+
assert(isNew);
|
|
3106
|
+
assert(!isNew || (opts && opts.after), "opts.after expected, unless updating");
|
|
2863
3107
|
assert(!this.isRootNode());
|
|
2864
|
-
|
|
3108
|
+
rowDiv = document.createElement("div");
|
|
3109
|
+
rowDiv.classList.add("wb-row");
|
|
3110
|
+
rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
|
|
3111
|
+
this._rowElem = rowDiv;
|
|
3112
|
+
// Attach a node reference to the DOM Element:
|
|
3113
|
+
rowDiv._wb_node = this;
|
|
3114
|
+
nodeElem = document.createElement("span");
|
|
3115
|
+
nodeElem.classList.add("wb-node", "wb-col");
|
|
3116
|
+
rowDiv.appendChild(nodeElem);
|
|
3117
|
+
let ofsTitlePx = 0;
|
|
3118
|
+
if (checkbox) {
|
|
3119
|
+
checkboxSpan = document.createElement("i");
|
|
3120
|
+
checkboxSpan.classList.add("wb-checkbox");
|
|
3121
|
+
nodeElem.appendChild(checkboxSpan);
|
|
3122
|
+
ofsTitlePx += ICON_WIDTH;
|
|
3123
|
+
}
|
|
3124
|
+
for (let i = level - 1; i > 0; i--) {
|
|
3125
|
+
elem = document.createElement("i");
|
|
3126
|
+
elem.classList.add("wb-indent");
|
|
3127
|
+
nodeElem.appendChild(elem);
|
|
3128
|
+
ofsTitlePx += ICON_WIDTH;
|
|
3129
|
+
}
|
|
3130
|
+
if (!treeOptions.minExpandLevel || level > treeOptions.minExpandLevel) {
|
|
3131
|
+
expanderSpan = document.createElement("i");
|
|
3132
|
+
expanderSpan.classList.add("wb-expander");
|
|
3133
|
+
nodeElem.appendChild(expanderSpan);
|
|
3134
|
+
ofsTitlePx += ICON_WIDTH;
|
|
3135
|
+
}
|
|
3136
|
+
iconSpan = this._createIcon(nodeElem);
|
|
3137
|
+
if (iconSpan) {
|
|
3138
|
+
ofsTitlePx += ICON_WIDTH;
|
|
3139
|
+
}
|
|
3140
|
+
titleSpan = document.createElement("span");
|
|
3141
|
+
titleSpan.classList.add("wb-title");
|
|
3142
|
+
nodeElem.appendChild(titleSpan);
|
|
3143
|
+
this._callEvent("enhanceTitle", { titleSpan: titleSpan });
|
|
3144
|
+
// Store the width of leading icons with the node, so we can calculate
|
|
3145
|
+
// the width of the embedded title span later
|
|
3146
|
+
nodeElem._ofsTitlePx = ofsTitlePx;
|
|
3147
|
+
// Support HTML5 drag-n-drop
|
|
3148
|
+
if (tree.options.dnd.dragStart) {
|
|
3149
|
+
nodeElem.draggable = true;
|
|
3150
|
+
}
|
|
3151
|
+
// Render columns
|
|
3152
|
+
if (!this.colspan && columns.length > 1) {
|
|
3153
|
+
let colIdx = 0;
|
|
3154
|
+
for (let col of columns) {
|
|
3155
|
+
colIdx++;
|
|
3156
|
+
let colElem;
|
|
3157
|
+
if (col.id === "*") {
|
|
3158
|
+
colElem = nodeElem;
|
|
3159
|
+
}
|
|
3160
|
+
else {
|
|
3161
|
+
colElem = document.createElement("span");
|
|
3162
|
+
colElem.classList.add("wb-col");
|
|
3163
|
+
rowDiv.appendChild(colElem);
|
|
3164
|
+
}
|
|
3165
|
+
if (colIdx === activeColIdx) {
|
|
3166
|
+
colElem.classList.add("wb-active");
|
|
3167
|
+
}
|
|
3168
|
+
// Add classes from `columns` definition to `<div.wb-col>` cells
|
|
3169
|
+
col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
|
|
3170
|
+
colElem.style.left = col._ofsPx + "px";
|
|
3171
|
+
colElem.style.width = col._widthPx + "px";
|
|
3172
|
+
if (isNew && col.html) {
|
|
3173
|
+
if (typeof col.html === "string") {
|
|
3174
|
+
colElem.innerHTML = col.html;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
// Now go on and fill in data and update classes
|
|
3180
|
+
opts.isNew = true;
|
|
3181
|
+
this._render_data(opts);
|
|
3182
|
+
// Attach to DOM as late as possible
|
|
3183
|
+
const after = opts ? opts.after : "last";
|
|
3184
|
+
switch (after) {
|
|
3185
|
+
case "first":
|
|
3186
|
+
tree.nodeListElement.prepend(rowDiv);
|
|
3187
|
+
break;
|
|
3188
|
+
case "last":
|
|
3189
|
+
tree.nodeListElement.appendChild(rowDiv);
|
|
3190
|
+
break;
|
|
3191
|
+
default:
|
|
3192
|
+
opts.after.after(rowDiv);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Render `node.title`, `.icon` into an existing row.
|
|
3197
|
+
*
|
|
3198
|
+
* @see {@link Wunderbaumode.render}
|
|
3199
|
+
*/
|
|
3200
|
+
_render_data(opts) {
|
|
3201
|
+
assert(this._rowElem);
|
|
3202
|
+
const tree = this.tree;
|
|
3203
|
+
const treeOptions = tree.options;
|
|
3204
|
+
const rowDiv = this._rowElem;
|
|
3205
|
+
const isNew = !!opts.isNew; // Called by _render_markup()?
|
|
3206
|
+
const columns = tree.columns;
|
|
3207
|
+
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
3208
|
+
// Row markup already exists
|
|
3209
|
+
const nodeElem = rowDiv.querySelector("span.wb-node");
|
|
3210
|
+
const titleSpan = nodeElem.querySelector("span.wb-title");
|
|
3211
|
+
if (this.titleWithHighlight) {
|
|
3212
|
+
titleSpan.innerHTML = this.titleWithHighlight;
|
|
3213
|
+
}
|
|
3214
|
+
else {
|
|
3215
|
+
titleSpan.textContent = this.title;
|
|
3216
|
+
}
|
|
3217
|
+
// Set the width of the title span, so overflow ellipsis work
|
|
3218
|
+
if (!treeOptions.skeleton) {
|
|
3219
|
+
if (this.colspan) {
|
|
3220
|
+
let vpWidth = tree.element.clientWidth;
|
|
3221
|
+
titleSpan.style.width =
|
|
3222
|
+
vpWidth - nodeElem._ofsTitlePx - ROW_EXTRA_PAD + "px";
|
|
3223
|
+
}
|
|
3224
|
+
else {
|
|
3225
|
+
titleSpan.style.width =
|
|
3226
|
+
columns[0]._widthPx -
|
|
3227
|
+
nodeElem._ofsTitlePx -
|
|
3228
|
+
ROW_EXTRA_PAD +
|
|
3229
|
+
"px";
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
// Update row classes
|
|
3233
|
+
opts.isDataChange = true;
|
|
3234
|
+
this._render_status(opts);
|
|
3235
|
+
// Let user modify the result
|
|
3236
|
+
if (this.statusNodeType) {
|
|
3237
|
+
this._callEvent("renderStatusNode", {
|
|
3238
|
+
isNew: isNew,
|
|
3239
|
+
nodeElem: nodeElem,
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
else if (this.parent) {
|
|
3243
|
+
// Skip root node
|
|
3244
|
+
this._callEvent("render", {
|
|
3245
|
+
isNew: isNew,
|
|
3246
|
+
isDataChange: true,
|
|
3247
|
+
nodeElem: nodeElem,
|
|
3248
|
+
typeInfo: typeInfo,
|
|
3249
|
+
colInfosById: this._getRenderInfo(),
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
/**
|
|
3254
|
+
* Update row classes to reflect active, focuses, etc.
|
|
3255
|
+
* @see {@link Wunderbaumode.render}
|
|
3256
|
+
*/
|
|
3257
|
+
_render_status(opts) {
|
|
3258
|
+
// this.log("_render_status", opts);
|
|
3259
|
+
const tree = this.tree;
|
|
3260
|
+
const treeOptions = tree.options;
|
|
3261
|
+
const typeInfo = this.type ? tree.types[this.type] : null;
|
|
3262
|
+
const rowDiv = this._rowElem;
|
|
3263
|
+
// Row markup already exists
|
|
3264
|
+
const nodeElem = rowDiv.querySelector("span.wb-node");
|
|
3265
|
+
const expanderSpan = nodeElem.querySelector("i.wb-expander");
|
|
3266
|
+
const checkboxSpan = nodeElem.querySelector("i.wb-checkbox");
|
|
3267
|
+
// TODO: update icon (if not opts.isNew)
|
|
3268
|
+
// const iconSpan = nodeElem.querySelector("i.wb-icon") as HTMLElement;
|
|
2865
3269
|
let rowClasses = ["wb-row"];
|
|
2866
3270
|
this.expanded ? rowClasses.push("wb-expanded") : 0;
|
|
2867
3271
|
this.lazy ? rowClasses.push("wb-lazy") : 0;
|
|
@@ -2876,102 +3280,14 @@ class WunderbaumNode {
|
|
|
2876
3280
|
this.match ? rowClasses.push("wb-match") : 0;
|
|
2877
3281
|
this.subMatchCount ? rowClasses.push("wb-submatch") : 0;
|
|
2878
3282
|
treeOptions.skeleton ? rowClasses.push("wb-skeleton") : 0;
|
|
2879
|
-
//
|
|
2880
|
-
|
|
2881
|
-
if (rowDiv) {
|
|
2882
|
-
// Row markup already exists
|
|
2883
|
-
nodeElem = rowDiv.querySelector("span.wb-node");
|
|
2884
|
-
titleSpan = nodeElem.querySelector("span.wb-title");
|
|
2885
|
-
expanderSpan = nodeElem.querySelector("i.wb-expander");
|
|
2886
|
-
checkboxSpan = nodeElem.querySelector("i.wb-checkbox");
|
|
2887
|
-
iconSpan = nodeElem.querySelector("i.wb-icon");
|
|
2888
|
-
// TODO: we need this, when icons should be replacable
|
|
2889
|
-
// iconSpan = this._createIcon(nodeElem, iconSpan);
|
|
2890
|
-
// colElems = (<unknown>(
|
|
2891
|
-
// rowDiv.querySelectorAll("span.wb-col")
|
|
2892
|
-
// )) as HTMLElement[];
|
|
2893
|
-
}
|
|
2894
|
-
else {
|
|
2895
|
-
rowDiv = document.createElement("div");
|
|
2896
|
-
// rowDiv.classList.add("wb-row");
|
|
2897
|
-
// Attach a node reference to the DOM Element:
|
|
2898
|
-
rowDiv._wb_node = this;
|
|
2899
|
-
nodeElem = document.createElement("span");
|
|
2900
|
-
nodeElem.classList.add("wb-node", "wb-col");
|
|
2901
|
-
rowDiv.appendChild(nodeElem);
|
|
2902
|
-
let ofsTitlePx = 0;
|
|
2903
|
-
if (checkbox) {
|
|
2904
|
-
checkboxSpan = document.createElement("i");
|
|
2905
|
-
nodeElem.appendChild(checkboxSpan);
|
|
2906
|
-
ofsTitlePx += ICON_WIDTH;
|
|
2907
|
-
}
|
|
2908
|
-
for (let i = level - 1; i > 0; i--) {
|
|
2909
|
-
elem = document.createElement("i");
|
|
2910
|
-
elem.classList.add("wb-indent");
|
|
2911
|
-
nodeElem.appendChild(elem);
|
|
2912
|
-
ofsTitlePx += ICON_WIDTH;
|
|
2913
|
-
}
|
|
2914
|
-
if (level > treeOptions.minExpandLevel) {
|
|
2915
|
-
expanderSpan = document.createElement("i");
|
|
2916
|
-
nodeElem.appendChild(expanderSpan);
|
|
2917
|
-
ofsTitlePx += ICON_WIDTH;
|
|
2918
|
-
}
|
|
2919
|
-
iconSpan = this._createIcon(nodeElem);
|
|
2920
|
-
if (iconSpan) {
|
|
2921
|
-
ofsTitlePx += ICON_WIDTH;
|
|
2922
|
-
}
|
|
2923
|
-
titleSpan = document.createElement("span");
|
|
2924
|
-
titleSpan.classList.add("wb-title");
|
|
2925
|
-
nodeElem.appendChild(titleSpan);
|
|
2926
|
-
this._callEvent("enhanceTitle", { titleSpan: titleSpan });
|
|
2927
|
-
// Store the width of leading icons with the node, so we can calculate
|
|
2928
|
-
// the width of the embedded title span later
|
|
2929
|
-
nodeElem._ofsTitlePx = ofsTitlePx;
|
|
2930
|
-
if (tree.options.dnd.dragStart) {
|
|
2931
|
-
nodeElem.draggable = true;
|
|
2932
|
-
}
|
|
2933
|
-
// Render columns
|
|
2934
|
-
// colElems = [];
|
|
2935
|
-
if (!this.colspan && columns.length > 1) {
|
|
2936
|
-
let colIdx = 0;
|
|
2937
|
-
for (let col of columns) {
|
|
2938
|
-
colIdx++;
|
|
2939
|
-
let colElem;
|
|
2940
|
-
if (col.id === "*") {
|
|
2941
|
-
colElem = nodeElem;
|
|
2942
|
-
}
|
|
2943
|
-
else {
|
|
2944
|
-
colElem = document.createElement("span");
|
|
2945
|
-
colElem.classList.add("wb-col");
|
|
2946
|
-
// colElem.textContent = "" + col.id;
|
|
2947
|
-
rowDiv.appendChild(colElem);
|
|
2948
|
-
}
|
|
2949
|
-
if (colIdx === activeColIdx) {
|
|
2950
|
-
colElem.classList.add("wb-active");
|
|
2951
|
-
}
|
|
2952
|
-
// Add classes from `columns` definition to `<div.wb-col>` cells
|
|
2953
|
-
col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
|
|
2954
|
-
colElem.style.left = col._ofsPx + "px";
|
|
2955
|
-
colElem.style.width = col._widthPx + "px";
|
|
2956
|
-
// colElems.push(colElem);
|
|
2957
|
-
if (isNew && col.html) {
|
|
2958
|
-
if (typeof col.html === "string") {
|
|
2959
|
-
colElem.innerHTML = col.html;
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
// --- From here common code starts (either new or existing markup):
|
|
2966
|
-
rowDiv.className = rowClasses.join(" "); // Reset prev. classes
|
|
3283
|
+
// Replace previous classes:
|
|
3284
|
+
rowDiv.className = rowClasses.join(" ");
|
|
2967
3285
|
// Add classes from `node.extraClasses`
|
|
2968
3286
|
rowDiv.classList.add(...this.extraClasses);
|
|
2969
3287
|
// Add classes from `tree.types[node.type]`
|
|
2970
3288
|
if (typeInfo && typeInfo.classes) {
|
|
2971
3289
|
rowDiv.classList.add(...typeInfo.classes);
|
|
2972
3290
|
}
|
|
2973
|
-
// rowDiv.style.top = (this._rowIdx! * 1.1) + "em";
|
|
2974
|
-
rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
|
|
2975
3291
|
if (expanderSpan) {
|
|
2976
3292
|
if (this.isExpandable(false)) {
|
|
2977
3293
|
if (this.expanded) {
|
|
@@ -2999,50 +3315,42 @@ class WunderbaumNode {
|
|
|
2999
3315
|
checkboxSpan.className = "wb-checkbox " + iconMap.checkUnchecked;
|
|
3000
3316
|
}
|
|
3001
3317
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3318
|
+
// Fix active cell in cell-nav mode
|
|
3319
|
+
if (!opts.isNew) {
|
|
3320
|
+
let i = 0;
|
|
3321
|
+
for (let colSpan of rowDiv.children) {
|
|
3322
|
+
colSpan.classList.toggle("wb-active", i++ === tree.activeColIdx);
|
|
3323
|
+
}
|
|
3007
3324
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Create or update node's markup.
|
|
3328
|
+
*
|
|
3329
|
+
* `options.change` defaults to ChangeType.data, which updates the title,
|
|
3330
|
+
* icon, and status. It also triggers the `render` event, that lets the user
|
|
3331
|
+
* create or update the content of embeded cell elements.<br>
|
|
3332
|
+
*
|
|
3333
|
+
* If only the status or other class-only modifications have changed,
|
|
3334
|
+
* `options.change` should be set to ChangeType.status instead for best
|
|
3335
|
+
* efficiency.
|
|
3336
|
+
*/
|
|
3337
|
+
render(options) {
|
|
3338
|
+
// this.log("render", options);
|
|
3339
|
+
const opts = Object.assign({ change: ChangeType.data }, options);
|
|
3340
|
+
if (!this._rowElem) {
|
|
3341
|
+
opts.change = "row";
|
|
3342
|
+
}
|
|
3343
|
+
switch (opts.change) {
|
|
3344
|
+
case "status":
|
|
3345
|
+
this._render_status(opts);
|
|
3346
|
+
break;
|
|
3347
|
+
case "data":
|
|
3348
|
+
this._render_data(opts);
|
|
3349
|
+
break;
|
|
3350
|
+
default:
|
|
3351
|
+
this._render_markup(opts);
|
|
3352
|
+
break;
|
|
3010
3353
|
}
|
|
3011
|
-
// Set the width of the title span, so overflow ellipsis work
|
|
3012
|
-
if (!treeOptions.skeleton) {
|
|
3013
|
-
if (this.colspan) {
|
|
3014
|
-
let vpWidth = tree.element.clientWidth;
|
|
3015
|
-
titleSpan.style.width =
|
|
3016
|
-
vpWidth - nodeElem._ofsTitlePx - ROW_EXTRA_PAD + "px";
|
|
3017
|
-
}
|
|
3018
|
-
else {
|
|
3019
|
-
titleSpan.style.width =
|
|
3020
|
-
columns[0]._widthPx -
|
|
3021
|
-
nodeElem._ofsTitlePx -
|
|
3022
|
-
ROW_EXTRA_PAD +
|
|
3023
|
-
"px";
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
this._rowElem = rowDiv;
|
|
3027
|
-
if (this.statusNodeType) {
|
|
3028
|
-
this._callEvent("renderStatusNode", {
|
|
3029
|
-
isNew: isNew,
|
|
3030
|
-
nodeElem: nodeElem,
|
|
3031
|
-
});
|
|
3032
|
-
}
|
|
3033
|
-
else if (this.parent) {
|
|
3034
|
-
// Skip root node
|
|
3035
|
-
this._callEvent("render", {
|
|
3036
|
-
isNew: isNew,
|
|
3037
|
-
nodeElem: nodeElem,
|
|
3038
|
-
typeInfo: typeInfo,
|
|
3039
|
-
colInfosById: this._getRenderInfo(),
|
|
3040
|
-
});
|
|
3041
|
-
}
|
|
3042
|
-
// Attach to DOM as late as possible
|
|
3043
|
-
// if (!this._rowElem) {
|
|
3044
|
-
tree.nodeListElement.appendChild(rowDiv);
|
|
3045
|
-
// }
|
|
3046
3354
|
}
|
|
3047
3355
|
/**
|
|
3048
3356
|
* Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
|
|
@@ -3053,7 +3361,7 @@ class WunderbaumNode {
|
|
|
3053
3361
|
this.expanded = false;
|
|
3054
3362
|
this.lazy = true;
|
|
3055
3363
|
this.children = null;
|
|
3056
|
-
this.tree.
|
|
3364
|
+
this.tree.setModified(ChangeType.structure);
|
|
3057
3365
|
}
|
|
3058
3366
|
/** Convert node (or whole branch) into a plain object.
|
|
3059
3367
|
*
|
|
@@ -3116,14 +3424,15 @@ class WunderbaumNode {
|
|
|
3116
3424
|
*
|
|
3117
3425
|
* Evaluation sequence:
|
|
3118
3426
|
*
|
|
3119
|
-
* If `tree.options.<name>` is a callback that returns something, use that.
|
|
3120
|
-
* Else if `node.<name>` is defined, use that.
|
|
3121
|
-
* Else if `tree.types[<node.type>]` is a value, use that.
|
|
3122
|
-
* Else if `tree.options.<name>` is a value, use that.
|
|
3123
|
-
* Else use `defaultValue`.
|
|
3427
|
+
* - If `tree.options.<name>` is a callback that returns something, use that.
|
|
3428
|
+
* - Else if `node.<name>` is defined, use that.
|
|
3429
|
+
* - Else if `tree.types[<node.type>]` is a value, use that.
|
|
3430
|
+
* - Else if `tree.options.<name>` is a value, use that.
|
|
3431
|
+
* - Else use `defaultValue`.
|
|
3124
3432
|
*
|
|
3125
3433
|
* @param name name of the option property (on node and tree)
|
|
3126
3434
|
* @param defaultValue return this if nothing else matched
|
|
3435
|
+
* {@link Wunderbaum.getOption|Wunderbaum.getOption()}
|
|
3127
3436
|
*/
|
|
3128
3437
|
getOption(name, defaultValue) {
|
|
3129
3438
|
let tree = this.tree;
|
|
@@ -3158,15 +3467,21 @@ class WunderbaumNode {
|
|
|
3158
3467
|
// Use value from value options dict, fallback do default
|
|
3159
3468
|
return value !== null && value !== void 0 ? value : defaultValue;
|
|
3160
3469
|
}
|
|
3470
|
+
/** Make sure that this node is visible in the viewport.
|
|
3471
|
+
* @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
|
|
3472
|
+
*/
|
|
3161
3473
|
async scrollIntoView(options) {
|
|
3162
3474
|
return this.tree.scrollTo(this);
|
|
3163
3475
|
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Activate this node, deactivate previous, send events, activate column and scroll int viewport.
|
|
3478
|
+
*/
|
|
3164
3479
|
async setActive(flag = true, options) {
|
|
3165
3480
|
const tree = this.tree;
|
|
3166
3481
|
const prev = tree.activeNode;
|
|
3167
3482
|
const retrigger = options === null || options === void 0 ? void 0 : options.retrigger;
|
|
3168
|
-
const
|
|
3169
|
-
if (!
|
|
3483
|
+
const noEvents = options === null || options === void 0 ? void 0 : options.noEvents;
|
|
3484
|
+
if (!noEvents) {
|
|
3170
3485
|
let orgEvent = options === null || options === void 0 ? void 0 : options.event;
|
|
3171
3486
|
if (flag) {
|
|
3172
3487
|
if (prev !== this || retrigger) {
|
|
@@ -3181,7 +3496,7 @@ class WunderbaumNode {
|
|
|
3181
3496
|
orgEvent: orgEvent,
|
|
3182
3497
|
}) === false) {
|
|
3183
3498
|
tree.activeNode = null;
|
|
3184
|
-
prev === null || prev === void 0 ? void 0 : prev.
|
|
3499
|
+
prev === null || prev === void 0 ? void 0 : prev.setModified();
|
|
3185
3500
|
return;
|
|
3186
3501
|
}
|
|
3187
3502
|
}
|
|
@@ -3192,8 +3507,8 @@ class WunderbaumNode {
|
|
|
3192
3507
|
}
|
|
3193
3508
|
if (prev !== this) {
|
|
3194
3509
|
tree.activeNode = this;
|
|
3195
|
-
prev === null || prev === void 0 ? void 0 : prev.
|
|
3196
|
-
this.
|
|
3510
|
+
prev === null || prev === void 0 ? void 0 : prev.setModified(ChangeType.status);
|
|
3511
|
+
this.setModified(ChangeType.status);
|
|
3197
3512
|
}
|
|
3198
3513
|
if (options &&
|
|
3199
3514
|
options.colIdx != null &&
|
|
@@ -3204,57 +3519,68 @@ class WunderbaumNode {
|
|
|
3204
3519
|
// requestAnimationFrame(() => {
|
|
3205
3520
|
// this.scrollIntoView();
|
|
3206
3521
|
// })
|
|
3207
|
-
this.scrollIntoView();
|
|
3208
|
-
}
|
|
3209
|
-
setDirty(type) {
|
|
3210
|
-
if (this.tree._disableUpdate) {
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
if (type === ChangeType.structure) {
|
|
3214
|
-
this.tree.updateViewport();
|
|
3215
|
-
}
|
|
3216
|
-
else if (this._rowElem) {
|
|
3217
|
-
// otherwise not in viewport, so no need to render
|
|
3218
|
-
this.render();
|
|
3219
|
-
}
|
|
3522
|
+
return this.scrollIntoView();
|
|
3220
3523
|
}
|
|
3524
|
+
/**
|
|
3525
|
+
* Expand or collapse this node.
|
|
3526
|
+
*/
|
|
3221
3527
|
async setExpanded(flag = true, options) {
|
|
3222
3528
|
// alert("" + this.getLevel() + ", "+ this.getOption("minExpandLevel");
|
|
3223
3529
|
if (!flag &&
|
|
3224
3530
|
this.isExpanded() &&
|
|
3225
3531
|
this.getLevel() < this.getOption("minExpandLevel") &&
|
|
3226
3532
|
!getOption(options, "force")) {
|
|
3227
|
-
this.logDebug("Ignored collapse request.");
|
|
3533
|
+
this.logDebug("Ignored collapse request below expandLevel.");
|
|
3228
3534
|
return;
|
|
3229
3535
|
}
|
|
3230
3536
|
if (flag && this.lazy && this.children == null) {
|
|
3231
3537
|
await this.loadLazy();
|
|
3232
3538
|
}
|
|
3233
3539
|
this.expanded = flag;
|
|
3234
|
-
this.
|
|
3235
|
-
}
|
|
3236
|
-
setIcon() {
|
|
3237
|
-
throw new Error("Not yet implemented");
|
|
3238
|
-
// this.setDirty(ChangeType.status);
|
|
3540
|
+
this.tree.setModified(ChangeType.structure);
|
|
3239
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Set keyboard focus here.
|
|
3544
|
+
* @see {@link setActive}
|
|
3545
|
+
*/
|
|
3240
3546
|
setFocus(flag = true, options) {
|
|
3241
3547
|
const prev = this.tree.focusNode;
|
|
3242
3548
|
this.tree.focusNode = this;
|
|
3243
|
-
prev === null || prev === void 0 ? void 0 : prev.
|
|
3244
|
-
this.
|
|
3549
|
+
prev === null || prev === void 0 ? void 0 : prev.setModified();
|
|
3550
|
+
this.setModified();
|
|
3245
3551
|
}
|
|
3552
|
+
/** Set a new icon path or class. */
|
|
3553
|
+
setIcon() {
|
|
3554
|
+
throw new Error("Not yet implemented");
|
|
3555
|
+
// this.setModified();
|
|
3556
|
+
}
|
|
3557
|
+
/** Change node's {@link key} and/or {@link refKey}. */
|
|
3558
|
+
setKey(key, refKey) {
|
|
3559
|
+
throw new Error("Not yet implemented");
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Schedule a render, typically called to update after a status or data change.
|
|
3563
|
+
*
|
|
3564
|
+
* `change` defaults to 'data', which handles modifcations of title, icon,
|
|
3565
|
+
* and column content. It can be reduced to 'ChangeType.status' if only
|
|
3566
|
+
* active/focus/selected state has changed.
|
|
3567
|
+
*/
|
|
3568
|
+
setModified(change = ChangeType.data) {
|
|
3569
|
+
assert(change === ChangeType.status || change === ChangeType.data);
|
|
3570
|
+
this.tree.setModified(change, this);
|
|
3571
|
+
}
|
|
3572
|
+
/** Modify the check/uncheck state. */
|
|
3246
3573
|
setSelected(flag = true, options) {
|
|
3247
3574
|
const prev = this.selected;
|
|
3248
3575
|
if (!!flag !== prev) {
|
|
3249
3576
|
this._callEvent("select", { flag: flag });
|
|
3250
3577
|
}
|
|
3251
3578
|
this.selected = !!flag;
|
|
3252
|
-
this.
|
|
3579
|
+
this.setModified();
|
|
3253
3580
|
}
|
|
3254
|
-
/**
|
|
3255
|
-
*/
|
|
3581
|
+
/** Display node status (ok, loading, error, noData) using styles and a dummy child node. */
|
|
3256
3582
|
setStatus(status, message, details) {
|
|
3257
|
-
|
|
3583
|
+
const tree = this.tree;
|
|
3258
3584
|
let statusNode = null;
|
|
3259
3585
|
const _clearStatusNode = () => {
|
|
3260
3586
|
// Remove dedicated dummy node, if any
|
|
@@ -3325,12 +3651,13 @@ class WunderbaumNode {
|
|
|
3325
3651
|
default:
|
|
3326
3652
|
error("invalid node status " + status);
|
|
3327
3653
|
}
|
|
3328
|
-
tree.
|
|
3654
|
+
tree.setModified(ChangeType.structure);
|
|
3329
3655
|
return statusNode;
|
|
3330
3656
|
}
|
|
3657
|
+
/** Rename this node. */
|
|
3331
3658
|
setTitle(title) {
|
|
3332
3659
|
this.title = title;
|
|
3333
|
-
this.
|
|
3660
|
+
this.setModified();
|
|
3334
3661
|
// this.triggerModify("rename"); // TODO
|
|
3335
3662
|
}
|
|
3336
3663
|
/**
|
|
@@ -3351,10 +3678,16 @@ class WunderbaumNode {
|
|
|
3351
3678
|
* @param {object} [extra]
|
|
3352
3679
|
*/
|
|
3353
3680
|
triggerModify(operation, extra) {
|
|
3681
|
+
if (!this.parent) {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3354
3684
|
this.parent.triggerModifyChild(operation, this, extra);
|
|
3355
3685
|
}
|
|
3356
|
-
/**
|
|
3357
|
-
*
|
|
3686
|
+
/**
|
|
3687
|
+
* Call fn(node) for all child nodes in hierarchical order (depth-first).
|
|
3688
|
+
*
|
|
3689
|
+
* Stop iteration, if fn() returns false. Skip current branch, if fn()
|
|
3690
|
+
* returns "skip".<br>
|
|
3358
3691
|
* Return false if iteration was stopped.
|
|
3359
3692
|
*
|
|
3360
3693
|
* @param {function} callback the callback function.
|
|
@@ -3398,7 +3731,8 @@ class WunderbaumNode {
|
|
|
3398
3731
|
}
|
|
3399
3732
|
return true;
|
|
3400
3733
|
}
|
|
3401
|
-
/**
|
|
3734
|
+
/**
|
|
3735
|
+
* Call fn(node) for all sibling nodes.<br>
|
|
3402
3736
|
* Stop iteration, if fn() returns false.<br>
|
|
3403
3737
|
* Return false if iteration was stopped.
|
|
3404
3738
|
*
|
|
@@ -3429,7 +3763,7 @@ WunderbaumNode.sequence = 0;
|
|
|
3429
3763
|
/*!
|
|
3430
3764
|
* Wunderbaum - ext-edit
|
|
3431
3765
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
3432
|
-
* v0.0.
|
|
3766
|
+
* v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
|
|
3433
3767
|
*/
|
|
3434
3768
|
// const START_MARKER = "\uFFF7";
|
|
3435
3769
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -3547,7 +3881,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3547
3881
|
break;
|
|
3548
3882
|
case "F2":
|
|
3549
3883
|
if (trigger.indexOf("F2") >= 0) {
|
|
3550
|
-
// tree.
|
|
3884
|
+
// tree.setNavigationMode(NavigationMode.cellEdit);
|
|
3551
3885
|
this.startEditTitle();
|
|
3552
3886
|
return false;
|
|
3553
3887
|
}
|
|
@@ -3588,6 +3922,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3588
3922
|
if (validity) {
|
|
3589
3923
|
// Permanently apply input validations (CSS and tooltip)
|
|
3590
3924
|
inputElem.addEventListener("keydown", (e) => {
|
|
3925
|
+
inputElem.setCustomValidity("");
|
|
3591
3926
|
if (!inputElem.reportValidity()) ;
|
|
3592
3927
|
});
|
|
3593
3928
|
}
|
|
@@ -3628,6 +3963,11 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3628
3963
|
}
|
|
3629
3964
|
node.logDebug(`stopEditTitle(${apply})`, opts, focusElem, newValue);
|
|
3630
3965
|
if (apply && newValue !== null && newValue !== node.title) {
|
|
3966
|
+
const errMsg = focusElem.validationMessage;
|
|
3967
|
+
if (errMsg) {
|
|
3968
|
+
// input element's native validation failed
|
|
3969
|
+
throw new Error(`Input validation failed for "${newValue}": ${errMsg}.`);
|
|
3970
|
+
}
|
|
3631
3971
|
const colElem = node.getColElem(0);
|
|
3632
3972
|
this._applyChange("edit.apply", node, colElem, {
|
|
3633
3973
|
oldValue: node.title,
|
|
@@ -3714,12 +4054,12 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3714
4054
|
* Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
|
|
3715
4055
|
* Released under the MIT license.
|
|
3716
4056
|
*
|
|
3717
|
-
* @version v0.0.
|
|
3718
|
-
* @date
|
|
4057
|
+
* @version v0.0.4
|
|
4058
|
+
* @date Tue, 03 May 2022 06:21:31 GMT
|
|
3719
4059
|
*/
|
|
3720
4060
|
// const class_prefix = "wb-";
|
|
3721
4061
|
// const node_props: string[] = ["title", "key", "refKey"];
|
|
3722
|
-
const MAX_CHANGED_NODES = 10;
|
|
4062
|
+
// const MAX_CHANGED_NODES = 10;
|
|
3723
4063
|
/**
|
|
3724
4064
|
* A persistent plain object or array.
|
|
3725
4065
|
*
|
|
@@ -3731,36 +4071,43 @@ class Wunderbaum {
|
|
|
3731
4071
|
this.extensions = {};
|
|
3732
4072
|
this.keyMap = new Map();
|
|
3733
4073
|
this.refKeyMap = new Map();
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
4074
|
+
// protected viewNodes = new Set<WunderbaumNode>();
|
|
4075
|
+
this.treeRowCount = 0;
|
|
4076
|
+
this._disableUpdateCount = 0;
|
|
3737
4077
|
// protected eventHandlers : Array<function> = [];
|
|
4078
|
+
/** Currently active node if any. */
|
|
3738
4079
|
this.activeNode = null;
|
|
4080
|
+
/** Current node hat has keyboard focus if any. */
|
|
3739
4081
|
this.focusNode = null;
|
|
3740
|
-
this._disableUpdate = 0;
|
|
3741
|
-
this._disableUpdateCount = 0;
|
|
3742
4082
|
/** Shared properties, referenced by `node.type`. */
|
|
3743
4083
|
this.types = {};
|
|
3744
4084
|
/** List of column definitions. */
|
|
3745
4085
|
this.columns = [];
|
|
3746
4086
|
this._columnsById = {};
|
|
3747
4087
|
// Modification Status
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
4088
|
+
// protected changedSince = 0;
|
|
4089
|
+
// protected changes = new Set<ChangeType>();
|
|
4090
|
+
// protected changedNodes = new Set<WunderbaumNode>();
|
|
4091
|
+
this.changeRedrawRequestPending = false;
|
|
4092
|
+
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
4093
|
+
this._util = util;
|
|
3751
4094
|
// --- FILTER ---
|
|
3752
4095
|
this.filterMode = null;
|
|
3753
4096
|
// --- KEYNAV ---
|
|
4097
|
+
/** @internal Use `setColumn()`/`getActiveColElem()`*/
|
|
3754
4098
|
this.activeColIdx = 0;
|
|
4099
|
+
/** @internal */
|
|
3755
4100
|
this.navMode = NavigationMode.row;
|
|
4101
|
+
/** @internal */
|
|
3756
4102
|
this.lastQuicksearchTime = 0;
|
|
4103
|
+
/** @internal */
|
|
3757
4104
|
this.lastQuicksearchTerm = "";
|
|
3758
4105
|
// --- EDIT ---
|
|
3759
4106
|
this.lastClickTime = 0;
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
this.log = this.logDebug;
|
|
4107
|
+
/** Alias for {@link Wunderbaum.logDebug}.
|
|
4108
|
+
* @alias Wunderbaum.logDebug
|
|
4109
|
+
*/
|
|
4110
|
+
this.log = this.logDebug;
|
|
3764
4111
|
let opts = (this.options = extend({
|
|
3765
4112
|
id: null,
|
|
3766
4113
|
source: null,
|
|
@@ -3771,7 +4118,7 @@ class Wunderbaum {
|
|
|
3771
4118
|
rowHeightPx: ROW_HEIGHT,
|
|
3772
4119
|
columns: null,
|
|
3773
4120
|
types: null,
|
|
3774
|
-
escapeTitles: true,
|
|
4121
|
+
// escapeTitles: true,
|
|
3775
4122
|
showSpinner: false,
|
|
3776
4123
|
checkbox: true,
|
|
3777
4124
|
minExpandLevel: 0,
|
|
@@ -3828,6 +4175,7 @@ class Wunderbaum {
|
|
|
3828
4175
|
this._registerExtension(new EditExtension(this));
|
|
3829
4176
|
this._registerExtension(new FilterExtension(this));
|
|
3830
4177
|
this._registerExtension(new DndExtension(this));
|
|
4178
|
+
this._registerExtension(new GridExtension(this));
|
|
3831
4179
|
this._registerExtension(new LoggerExtension(this));
|
|
3832
4180
|
// --- Evaluate options
|
|
3833
4181
|
this.columns = opts.columns;
|
|
@@ -3851,9 +4199,7 @@ class Wunderbaum {
|
|
|
3851
4199
|
opts.navigationMode === NavigationModeOption.startCell) {
|
|
3852
4200
|
this.navMode = NavigationMode.cellNav;
|
|
3853
4201
|
}
|
|
3854
|
-
this._updateViewportThrottled =
|
|
3855
|
-
this._updateViewport();
|
|
3856
|
-
}, opts.updateThrottleWait, { leading: true, trailing: true });
|
|
4202
|
+
this._updateViewportThrottled = addaptiveThrottle(this._updateViewport.bind(this), {});
|
|
3857
4203
|
// --- Create Markup
|
|
3858
4204
|
this.element = elemFromSelector(opts.element);
|
|
3859
4205
|
assert(!!this.element, `Invalid 'element' option: ${opts.element}`);
|
|
@@ -3921,11 +4267,9 @@ class Wunderbaum {
|
|
|
3921
4267
|
var _a;
|
|
3922
4268
|
(_a = this.element.querySelector("progress.spinner")) === null || _a === void 0 ? void 0 : _a.remove();
|
|
3923
4269
|
this.element.classList.remove("wb-initializing");
|
|
3924
|
-
// this.updateViewport();
|
|
3925
4270
|
});
|
|
3926
4271
|
}
|
|
3927
4272
|
else {
|
|
3928
|
-
// this.updateViewport();
|
|
3929
4273
|
readyDeferred.resolve();
|
|
3930
4274
|
}
|
|
3931
4275
|
// TODO: This is sometimes required, because this.element.clientWidth
|
|
@@ -3935,19 +4279,17 @@ class Wunderbaum {
|
|
|
3935
4279
|
}, 50);
|
|
3936
4280
|
// --- Bind listeners
|
|
3937
4281
|
this.scrollContainer.addEventListener("scroll", (e) => {
|
|
3938
|
-
this.
|
|
4282
|
+
this.setModified(ChangeType.vscroll);
|
|
3939
4283
|
});
|
|
3940
|
-
// window.addEventListener("resize", (e: Event) => {
|
|
3941
|
-
// this.updateViewport();
|
|
3942
|
-
// });
|
|
3943
4284
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
3944
|
-
this.
|
|
3945
|
-
|
|
4285
|
+
this.setModified(ChangeType.vscroll);
|
|
4286
|
+
// this.log("ResizeObserver: Size changed", entries);
|
|
3946
4287
|
});
|
|
3947
4288
|
this.resizeObserver.observe(this.element);
|
|
3948
4289
|
onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
|
|
3949
4290
|
const info = Wunderbaum.getEventInfo(e);
|
|
3950
4291
|
const node = info.node;
|
|
4292
|
+
// this.log("click", info, e);
|
|
3951
4293
|
if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
|
|
3952
4294
|
this.lastClickTime = Date.now();
|
|
3953
4295
|
return false;
|
|
@@ -3975,8 +4317,6 @@ class Wunderbaum {
|
|
|
3975
4317
|
node.setSelected(!node.isSelected());
|
|
3976
4318
|
}
|
|
3977
4319
|
}
|
|
3978
|
-
// if(e.target.classList.)
|
|
3979
|
-
// this.log("click", info);
|
|
3980
4320
|
this.lastClickTime = Date.now();
|
|
3981
4321
|
});
|
|
3982
4322
|
onEvent(this.element, "keydown", (e) => {
|
|
@@ -3998,37 +4338,18 @@ class Wunderbaum {
|
|
|
3998
4338
|
forceClose: true,
|
|
3999
4339
|
});
|
|
4000
4340
|
}
|
|
4001
|
-
// if (flag && !this.activeNode ) {
|
|
4002
|
-
// setTimeout(() => {
|
|
4003
|
-
// if (!this.activeNode) {
|
|
4004
|
-
// const firstNode = this.getFirstChild();
|
|
4005
|
-
// if (firstNode && !firstNode?.isStatusNode()) {
|
|
4006
|
-
// firstNode.logInfo("Activate on focus", e);
|
|
4007
|
-
// firstNode.setActive(true, { event: e });
|
|
4008
|
-
// }
|
|
4009
|
-
// }
|
|
4010
|
-
// }, 10);
|
|
4011
|
-
// }
|
|
4012
4341
|
});
|
|
4013
4342
|
}
|
|
4014
|
-
/**
|
|
4015
|
-
|
|
4016
|
-
// const coldivs = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
4017
|
-
// this.element.innerHTML = `
|
|
4018
|
-
// <div class='wb-header'>
|
|
4019
|
-
// <div class='wb-row'>
|
|
4020
|
-
// ${coldivs}
|
|
4021
|
-
// </div>
|
|
4022
|
-
// </div>`;
|
|
4023
|
-
// }
|
|
4024
|
-
/** Return a Wunderbaum instance, from element, index, or event.
|
|
4343
|
+
/**
|
|
4344
|
+
* Return a Wunderbaum instance, from element, id, index, or event.
|
|
4025
4345
|
*
|
|
4026
|
-
*
|
|
4027
|
-
* getTree();
|
|
4028
|
-
* getTree(1);
|
|
4029
|
-
* getTree(event);
|
|
4030
|
-
* getTree("foo");
|
|
4346
|
+
* ```js
|
|
4347
|
+
* getTree(); // Get first Wunderbaum instance on page
|
|
4348
|
+
* getTree(1); // Get second Wunderbaum instance on page
|
|
4349
|
+
* getTree(event); // Get tree for this mouse- or keyboard event
|
|
4350
|
+
* getTree("foo"); // Get tree for this `tree.options.id`
|
|
4031
4351
|
* getTree("#tree"); // Get tree for this matching element
|
|
4352
|
+
* ```
|
|
4032
4353
|
*/
|
|
4033
4354
|
static getTree(el) {
|
|
4034
4355
|
if (el instanceof Wunderbaum) {
|
|
@@ -4069,9 +4390,8 @@ class Wunderbaum {
|
|
|
4069
4390
|
}
|
|
4070
4391
|
return null;
|
|
4071
4392
|
}
|
|
4072
|
-
/**
|
|
4073
|
-
*
|
|
4074
|
-
* @param el
|
|
4393
|
+
/**
|
|
4394
|
+
* Return a WunderbaumNode instance from element or event.
|
|
4075
4395
|
*/
|
|
4076
4396
|
static getNode(el) {
|
|
4077
4397
|
if (!el) {
|
|
@@ -4093,7 +4413,7 @@ class Wunderbaum {
|
|
|
4093
4413
|
}
|
|
4094
4414
|
return null;
|
|
4095
4415
|
}
|
|
4096
|
-
/** */
|
|
4416
|
+
/** @internal */
|
|
4097
4417
|
_registerExtension(extension) {
|
|
4098
4418
|
this.extensionList.push(extension);
|
|
4099
4419
|
this.extensions[extension.id] = extension;
|
|
@@ -4135,7 +4455,7 @@ class Wunderbaum {
|
|
|
4135
4455
|
node.tree = null;
|
|
4136
4456
|
node.parent = null;
|
|
4137
4457
|
// node.title = "DISPOSED: " + node.title
|
|
4138
|
-
this.viewNodes.delete(node);
|
|
4458
|
+
// this.viewNodes.delete(node);
|
|
4139
4459
|
node.removeMarkup();
|
|
4140
4460
|
}
|
|
4141
4461
|
/** Call all hook methods of all registered extensions.*/
|
|
@@ -4153,7 +4473,9 @@ class Wunderbaum {
|
|
|
4153
4473
|
}
|
|
4154
4474
|
return res;
|
|
4155
4475
|
}
|
|
4156
|
-
/**
|
|
4476
|
+
/**
|
|
4477
|
+
* Call tree method or extension method if defined.
|
|
4478
|
+
*
|
|
4157
4479
|
* Example:
|
|
4158
4480
|
* ```js
|
|
4159
4481
|
* tree._callMethod("edit.startEdit", "arg1", "arg2")
|
|
@@ -4170,43 +4492,51 @@ class Wunderbaum {
|
|
|
4170
4492
|
this.logError(`Calling undefined method '${name}()'.`);
|
|
4171
4493
|
}
|
|
4172
4494
|
}
|
|
4173
|
-
/**
|
|
4495
|
+
/**
|
|
4496
|
+
* Call event handler if defined in tree or tree.EXTENSION options.
|
|
4497
|
+
*
|
|
4174
4498
|
* Example:
|
|
4175
4499
|
* ```js
|
|
4176
4500
|
* tree._callEvent("edit.beforeEdit", {foo: 42})
|
|
4177
4501
|
* ```
|
|
4178
4502
|
*/
|
|
4179
|
-
_callEvent(
|
|
4180
|
-
const [p, n] =
|
|
4503
|
+
_callEvent(type, extra) {
|
|
4504
|
+
const [p, n] = type.split(".");
|
|
4181
4505
|
const opts = this.options;
|
|
4182
4506
|
const func = n ? opts[p][n] : opts[p];
|
|
4183
4507
|
if (func) {
|
|
4184
|
-
return func.call(this, extend({
|
|
4508
|
+
return func.call(this, extend({ type: type, tree: this, util: this._util }, extra));
|
|
4185
4509
|
// } else {
|
|
4186
|
-
// this.logError(`Triggering undefined event '${
|
|
4510
|
+
// this.logError(`Triggering undefined event '${type}'.`)
|
|
4187
4511
|
}
|
|
4188
4512
|
}
|
|
4189
|
-
/** Return the
|
|
4190
|
-
|
|
4191
|
-
let topIdx, node;
|
|
4192
|
-
if (complete) {
|
|
4193
|
-
topIdx = Math.ceil(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4194
|
-
}
|
|
4195
|
-
else {
|
|
4196
|
-
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4197
|
-
}
|
|
4513
|
+
/** Return the node for given row index. */
|
|
4514
|
+
_getNodeByRowIdx(idx) {
|
|
4198
4515
|
// TODO: start searching from active node (reverse)
|
|
4516
|
+
let node = null;
|
|
4199
4517
|
this.visitRows((n) => {
|
|
4200
|
-
if (n._rowIdx ===
|
|
4518
|
+
if (n._rowIdx === idx) {
|
|
4201
4519
|
node = n;
|
|
4202
4520
|
return false;
|
|
4203
4521
|
}
|
|
4204
4522
|
});
|
|
4205
4523
|
return node;
|
|
4206
4524
|
}
|
|
4207
|
-
/** Return the
|
|
4208
|
-
|
|
4209
|
-
let
|
|
4525
|
+
/** Return the topmost visible node in the viewport. */
|
|
4526
|
+
getTopmostVpNode(complete = true) {
|
|
4527
|
+
let topIdx;
|
|
4528
|
+
const gracePy = 1; // ignore subpixel scrolling
|
|
4529
|
+
if (complete) {
|
|
4530
|
+
topIdx = Math.ceil((this.scrollContainer.scrollTop - gracePy) / ROW_HEIGHT);
|
|
4531
|
+
}
|
|
4532
|
+
else {
|
|
4533
|
+
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4534
|
+
}
|
|
4535
|
+
return this._getNodeByRowIdx(topIdx);
|
|
4536
|
+
}
|
|
4537
|
+
/** Return the lowest visible node in the viewport. */
|
|
4538
|
+
getLowestVpNode(complete = true) {
|
|
4539
|
+
let bottomIdx;
|
|
4210
4540
|
if (complete) {
|
|
4211
4541
|
bottomIdx =
|
|
4212
4542
|
Math.floor((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
@@ -4217,16 +4547,10 @@ class Wunderbaum {
|
|
|
4217
4547
|
Math.ceil((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
4218
4548
|
ROW_HEIGHT) - 1;
|
|
4219
4549
|
}
|
|
4220
|
-
|
|
4221
|
-
this.
|
|
4222
|
-
if (n._rowIdx === bottomIdx) {
|
|
4223
|
-
node = n;
|
|
4224
|
-
return false;
|
|
4225
|
-
}
|
|
4226
|
-
});
|
|
4227
|
-
return node;
|
|
4550
|
+
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
4551
|
+
return this._getNodeByRowIdx(bottomIdx);
|
|
4228
4552
|
}
|
|
4229
|
-
/** Return preceeding visible node in the viewport */
|
|
4553
|
+
/** Return preceeding visible node in the viewport. */
|
|
4230
4554
|
_getPrevNodeInView(node, ofs = 1) {
|
|
4231
4555
|
this.visitRows((n) => {
|
|
4232
4556
|
node = n;
|
|
@@ -4236,7 +4560,7 @@ class Wunderbaum {
|
|
|
4236
4560
|
}, { reverse: true, start: node || this.getActiveNode() });
|
|
4237
4561
|
return node;
|
|
4238
4562
|
}
|
|
4239
|
-
/** Return following visible node in the viewport */
|
|
4563
|
+
/** Return following visible node in the viewport. */
|
|
4240
4564
|
_getNextNodeInView(node, ofs = 1) {
|
|
4241
4565
|
this.visitRows((n) => {
|
|
4242
4566
|
node = n;
|
|
@@ -4246,10 +4570,15 @@ class Wunderbaum {
|
|
|
4246
4570
|
}, { reverse: false, start: node || this.getActiveNode() });
|
|
4247
4571
|
return node;
|
|
4248
4572
|
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Append (or insert) a list of toplevel nodes.
|
|
4575
|
+
*
|
|
4576
|
+
* @see {@link WunderbaumNode.addChildren}
|
|
4577
|
+
*/
|
|
4249
4578
|
addChildren(nodeData, options) {
|
|
4250
4579
|
return this.root.addChildren(nodeData, options);
|
|
4251
4580
|
}
|
|
4252
|
-
|
|
4581
|
+
/**
|
|
4253
4582
|
* Apply a modification or navigation operation.
|
|
4254
4583
|
*
|
|
4255
4584
|
* Most of these commands simply map to a node or tree method.
|
|
@@ -4374,16 +4703,17 @@ class Wunderbaum {
|
|
|
4374
4703
|
this.root.children = null;
|
|
4375
4704
|
this.keyMap.clear();
|
|
4376
4705
|
this.refKeyMap.clear();
|
|
4377
|
-
this.viewNodes.clear();
|
|
4706
|
+
// this.viewNodes.clear();
|
|
4707
|
+
this.treeRowCount = 0;
|
|
4378
4708
|
this.activeNode = null;
|
|
4379
4709
|
this.focusNode = null;
|
|
4380
4710
|
// this.types = {};
|
|
4381
4711
|
// this. columns =[];
|
|
4382
4712
|
// this._columnsById = {};
|
|
4383
4713
|
// Modification Status
|
|
4384
|
-
this.changedSince = 0;
|
|
4385
|
-
this.changes.clear();
|
|
4386
|
-
this.changedNodes.clear();
|
|
4714
|
+
// this.changedSince = 0;
|
|
4715
|
+
// this.changes.clear();
|
|
4716
|
+
// this.changedNodes.clear();
|
|
4387
4717
|
// // --- FILTER ---
|
|
4388
4718
|
// public filterMode: FilterModeType = null;
|
|
4389
4719
|
// // --- KEYNAV ---
|
|
@@ -4391,7 +4721,7 @@ class Wunderbaum {
|
|
|
4391
4721
|
// public cellNavMode = false;
|
|
4392
4722
|
// public lastQuicksearchTime = 0;
|
|
4393
4723
|
// public lastQuicksearchTerm = "";
|
|
4394
|
-
this.
|
|
4724
|
+
this.setModified(ChangeType.structure);
|
|
4395
4725
|
}
|
|
4396
4726
|
/**
|
|
4397
4727
|
* Clear nodes and markup and detach events and observers.
|
|
@@ -4411,10 +4741,11 @@ class Wunderbaum {
|
|
|
4411
4741
|
/**
|
|
4412
4742
|
* Return `tree.option.NAME` (also resolving if this is a callback).
|
|
4413
4743
|
*
|
|
4414
|
-
* See also
|
|
4415
|
-
* `tree.types[node.type].NAME`.
|
|
4744
|
+
* See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
|
|
4745
|
+
* to consider `node.NAME` setting and `tree.types[node.type].NAME`.
|
|
4416
4746
|
*
|
|
4417
|
-
* @param name option name (use dot notation to access extension option, e.g.
|
|
4747
|
+
* @param name option name (use dot notation to access extension option, e.g.
|
|
4748
|
+
* `filter.mode`)
|
|
4418
4749
|
*/
|
|
4419
4750
|
getOption(name, defaultValue) {
|
|
4420
4751
|
let ext;
|
|
@@ -4458,18 +4789,14 @@ class Wunderbaum {
|
|
|
4458
4789
|
}
|
|
4459
4790
|
/** Run code, but defer `updateViewport()` until done. */
|
|
4460
4791
|
runWithoutUpdate(func, hint = null) {
|
|
4461
|
-
// const prev = this._disableUpdate;
|
|
4462
|
-
// const start = Date.now();
|
|
4463
|
-
// this._disableUpdate = Date.now();
|
|
4464
4792
|
try {
|
|
4465
4793
|
this.enableUpdate(false);
|
|
4466
|
-
|
|
4794
|
+
const res = func();
|
|
4795
|
+
assert(!(res instanceof Promise));
|
|
4796
|
+
return res;
|
|
4467
4797
|
}
|
|
4468
4798
|
finally {
|
|
4469
4799
|
this.enableUpdate(true);
|
|
4470
|
-
// if (!prev && this._disableUpdate === start) {
|
|
4471
|
-
// this._disableUpdate = 0;
|
|
4472
|
-
// }
|
|
4473
4800
|
}
|
|
4474
4801
|
}
|
|
4475
4802
|
/** Recursively expand all expandable nodes (triggers lazy load id needed). */
|
|
@@ -4487,11 +4814,12 @@ class Wunderbaum {
|
|
|
4487
4814
|
/** Return the number of nodes in the data model.*/
|
|
4488
4815
|
count(visible = false) {
|
|
4489
4816
|
if (visible) {
|
|
4490
|
-
return this.
|
|
4817
|
+
return this.treeRowCount;
|
|
4818
|
+
// return this.viewNodes.size;
|
|
4491
4819
|
}
|
|
4492
4820
|
return this.keyMap.size;
|
|
4493
4821
|
}
|
|
4494
|
-
|
|
4822
|
+
/** @internal sanity check. */
|
|
4495
4823
|
_check() {
|
|
4496
4824
|
let i = 0;
|
|
4497
4825
|
this.visit((n) => {
|
|
@@ -4502,25 +4830,30 @@ class Wunderbaum {
|
|
|
4502
4830
|
}
|
|
4503
4831
|
// util.assert(this.keyMap.size === i);
|
|
4504
4832
|
}
|
|
4505
|
-
/**
|
|
4833
|
+
/**
|
|
4834
|
+
* Find all nodes that matches condition.
|
|
4506
4835
|
*
|
|
4507
4836
|
* @param match title string to search for, or a
|
|
4508
4837
|
* callback function that returns `true` if a node is matched.
|
|
4509
|
-
*
|
|
4838
|
+
*
|
|
4839
|
+
* @see {@link WunderbaumNode.findAll}
|
|
4510
4840
|
*/
|
|
4511
4841
|
findAll(match) {
|
|
4512
4842
|
return this.root.findAll(match);
|
|
4513
4843
|
}
|
|
4514
|
-
/**
|
|
4844
|
+
/**
|
|
4845
|
+
* Find first node that matches condition.
|
|
4515
4846
|
*
|
|
4516
4847
|
* @param match title string to search for, or a
|
|
4517
4848
|
* callback function that returns `true` if a node is matched.
|
|
4518
|
-
* @see
|
|
4849
|
+
* @see {@link WunderbaumNode.findFirst}
|
|
4850
|
+
*
|
|
4519
4851
|
*/
|
|
4520
4852
|
findFirst(match) {
|
|
4521
4853
|
return this.root.findFirst(match);
|
|
4522
4854
|
}
|
|
4523
|
-
/**
|
|
4855
|
+
/**
|
|
4856
|
+
* Find the next visible node that starts with `match`, starting at `startNode`
|
|
4524
4857
|
* and wrap-around at the end.
|
|
4525
4858
|
*/
|
|
4526
4859
|
findNextNode(match, startNode) {
|
|
@@ -4550,7 +4883,8 @@ class Wunderbaum {
|
|
|
4550
4883
|
}
|
|
4551
4884
|
return res;
|
|
4552
4885
|
}
|
|
4553
|
-
/**
|
|
4886
|
+
/**
|
|
4887
|
+
* Find a node relative to another node.
|
|
4554
4888
|
*
|
|
4555
4889
|
* @param node
|
|
4556
4890
|
* @param where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
|
|
@@ -4560,7 +4894,7 @@ class Wunderbaum {
|
|
|
4560
4894
|
*/
|
|
4561
4895
|
findRelatedNode(node, where, includeHidden = false) {
|
|
4562
4896
|
let res = null;
|
|
4563
|
-
|
|
4897
|
+
const pageSize = Math.floor(this.scrollContainer.clientHeight / ROW_HEIGHT);
|
|
4564
4898
|
switch (where) {
|
|
4565
4899
|
case "parent":
|
|
4566
4900
|
if (node.parent && node.parent.parent) {
|
|
@@ -4616,9 +4950,9 @@ class Wunderbaum {
|
|
|
4616
4950
|
res = this._getNextNodeInView(node);
|
|
4617
4951
|
break;
|
|
4618
4952
|
case "pageDown":
|
|
4619
|
-
|
|
4620
|
-
// this.logDebug(where
|
|
4621
|
-
if (
|
|
4953
|
+
const bottomNode = this.getLowestVpNode();
|
|
4954
|
+
// this.logDebug(`${where}(${node}) -> ${bottomNode}`);
|
|
4955
|
+
if (node._rowIdx < bottomNode._rowIdx) {
|
|
4622
4956
|
res = bottomNode;
|
|
4623
4957
|
}
|
|
4624
4958
|
else {
|
|
@@ -4626,12 +4960,13 @@ class Wunderbaum {
|
|
|
4626
4960
|
}
|
|
4627
4961
|
break;
|
|
4628
4962
|
case "pageUp":
|
|
4629
|
-
if (
|
|
4630
|
-
res =
|
|
4963
|
+
if (node._rowIdx === 0) {
|
|
4964
|
+
res = node;
|
|
4631
4965
|
}
|
|
4632
4966
|
else {
|
|
4633
|
-
|
|
4634
|
-
|
|
4967
|
+
const topNode = this.getTopmostVpNode();
|
|
4968
|
+
// this.logDebug(`${where}(${node}) -> ${topNode}`);
|
|
4969
|
+
if (node._rowIdx > topNode._rowIdx) {
|
|
4635
4970
|
res = topNode;
|
|
4636
4971
|
}
|
|
4637
4972
|
else {
|
|
@@ -4645,7 +4980,7 @@ class Wunderbaum {
|
|
|
4645
4980
|
return res;
|
|
4646
4981
|
}
|
|
4647
4982
|
/**
|
|
4648
|
-
* Return the active cell of the currently active node or null.
|
|
4983
|
+
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
4649
4984
|
*/
|
|
4650
4985
|
getActiveColElem() {
|
|
4651
4986
|
if (this.activeNode && this.activeColIdx >= 0) {
|
|
@@ -4678,7 +5013,8 @@ class Wunderbaum {
|
|
|
4678
5013
|
* TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
|
|
4679
5014
|
*/
|
|
4680
5015
|
static getEventInfo(event) {
|
|
4681
|
-
let target = event.target, cl = target.classList, parentCol = target.closest(".wb-col"), node = Wunderbaum.getNode(target), res = {
|
|
5016
|
+
let target = event.target, cl = target.classList, parentCol = target.closest("span.wb-col"), node = Wunderbaum.getNode(target), tree = node ? node.tree : Wunderbaum.getTree(event), res = {
|
|
5017
|
+
tree: tree,
|
|
4682
5018
|
node: node,
|
|
4683
5019
|
region: TargetType.unknown,
|
|
4684
5020
|
colDef: undefined,
|
|
@@ -4710,13 +5046,15 @@ class Wunderbaum {
|
|
|
4710
5046
|
}
|
|
4711
5047
|
else {
|
|
4712
5048
|
// Somewhere near the title
|
|
4713
|
-
|
|
5049
|
+
if (event.type !== "mousemove" && !(event instanceof KeyboardEvent)) {
|
|
5050
|
+
console.warn("getEventInfo(): not found", event, res);
|
|
5051
|
+
}
|
|
4714
5052
|
return res;
|
|
4715
5053
|
}
|
|
4716
5054
|
if (res.colIdx === -1) {
|
|
4717
5055
|
res.colIdx = 0;
|
|
4718
5056
|
}
|
|
4719
|
-
res.colDef =
|
|
5057
|
+
res.colDef = tree === null || tree === void 0 ? void 0 : tree.columns[res.colIdx];
|
|
4720
5058
|
res.colDef != null ? (res.colId = res.colDef.id) : 0;
|
|
4721
5059
|
// this.log("Event", event, res);
|
|
4722
5060
|
return res;
|
|
@@ -4740,7 +5078,8 @@ class Wunderbaum {
|
|
|
4740
5078
|
isEditing() {
|
|
4741
5079
|
return this._callMethod("edit.isEditingTitle");
|
|
4742
5080
|
}
|
|
4743
|
-
/**
|
|
5081
|
+
/**
|
|
5082
|
+
* Return true if any node is currently beeing loaded, i.e. a Ajax request is pending.
|
|
4744
5083
|
*/
|
|
4745
5084
|
isLoading() {
|
|
4746
5085
|
var res = false;
|
|
@@ -4767,7 +5106,7 @@ class Wunderbaum {
|
|
|
4767
5106
|
console.error.apply(console, args);
|
|
4768
5107
|
}
|
|
4769
5108
|
}
|
|
4770
|
-
|
|
5109
|
+
/** Log to console if opts.debugLevel >= 3 */
|
|
4771
5110
|
logInfo(...args) {
|
|
4772
5111
|
if (this.options.debugLevel >= 3) {
|
|
4773
5112
|
Array.prototype.unshift.call(args, this.toString());
|
|
@@ -4794,77 +5133,13 @@ class Wunderbaum {
|
|
|
4794
5133
|
console.warn.apply(console, args);
|
|
4795
5134
|
}
|
|
4796
5135
|
}
|
|
4797
|
-
/** */
|
|
4798
|
-
render(opts) {
|
|
4799
|
-
const label = this.logTime("render");
|
|
4800
|
-
let idx = 0;
|
|
4801
|
-
let top = 0;
|
|
4802
|
-
const height = ROW_HEIGHT;
|
|
4803
|
-
let modified = false;
|
|
4804
|
-
let start = opts === null || opts === void 0 ? void 0 : opts.startIdx;
|
|
4805
|
-
let end = opts === null || opts === void 0 ? void 0 : opts.endIdx;
|
|
4806
|
-
const obsoleteViewNodes = this.viewNodes;
|
|
4807
|
-
this.viewNodes = new Set();
|
|
4808
|
-
let viewNodes = this.viewNodes;
|
|
4809
|
-
// this.debug("render", opts);
|
|
4810
|
-
assert(start != null && end != null);
|
|
4811
|
-
// Make sure start is always even, so the alternating row colors don't
|
|
4812
|
-
// change when scrolling:
|
|
4813
|
-
if (start % 2) {
|
|
4814
|
-
start--;
|
|
4815
|
-
}
|
|
4816
|
-
this.visitRows(function (node) {
|
|
4817
|
-
const prevIdx = node._rowIdx;
|
|
4818
|
-
viewNodes.add(node);
|
|
4819
|
-
obsoleteViewNodes.delete(node);
|
|
4820
|
-
if (prevIdx !== idx) {
|
|
4821
|
-
node._rowIdx = idx;
|
|
4822
|
-
modified = true;
|
|
4823
|
-
}
|
|
4824
|
-
if (idx < start || idx > end) {
|
|
4825
|
-
node._callEvent("discard");
|
|
4826
|
-
node.removeMarkup();
|
|
4827
|
-
}
|
|
4828
|
-
else {
|
|
4829
|
-
// if (!node._rowElem || prevIdx != idx) {
|
|
4830
|
-
node.render({ top: top });
|
|
4831
|
-
}
|
|
4832
|
-
idx++;
|
|
4833
|
-
top += height;
|
|
4834
|
-
});
|
|
4835
|
-
for (const prevNode of obsoleteViewNodes) {
|
|
4836
|
-
prevNode._callEvent("discard");
|
|
4837
|
-
prevNode.removeMarkup();
|
|
4838
|
-
}
|
|
4839
|
-
// Resize tree container
|
|
4840
|
-
this.nodeListElement.style.height = "" + top + "px";
|
|
4841
|
-
// this.log("render()", this.nodeListElement.style.height);
|
|
4842
|
-
this.logTimeEnd(label);
|
|
4843
|
-
return modified;
|
|
4844
|
-
}
|
|
4845
|
-
/**Recalc and apply header columns from `this.columns`. */
|
|
4846
|
-
renderHeader() {
|
|
4847
|
-
if (!this.headerElement) {
|
|
4848
|
-
return;
|
|
4849
|
-
}
|
|
4850
|
-
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
4851
|
-
assert(headerRow);
|
|
4852
|
-
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
4853
|
-
for (let i = 0; i < this.columns.length; i++) {
|
|
4854
|
-
let col = this.columns[i];
|
|
4855
|
-
let colElem = headerRow.children[i];
|
|
4856
|
-
colElem.style.left = col._ofsPx + "px";
|
|
4857
|
-
colElem.style.width = col._widthPx + "px";
|
|
4858
|
-
colElem.textContent = col.title || col.id;
|
|
4859
|
-
}
|
|
4860
|
-
}
|
|
4861
5136
|
/**
|
|
5137
|
+
* Make sure that this node is scrolled into the viewport.
|
|
4862
5138
|
*
|
|
4863
5139
|
* @param {boolean | PlainObject} [effects=false] animation options.
|
|
4864
5140
|
* @param {object} [options=null] {topNode: null, effects: ..., parent: ...}
|
|
4865
5141
|
* this node will remain visible in
|
|
4866
5142
|
* any case, even if `this` is outside the scroll pane.
|
|
4867
|
-
* Make sure that a node is scrolled into the viewport.
|
|
4868
5143
|
*/
|
|
4869
5144
|
scrollTo(opts) {
|
|
4870
5145
|
const MARGIN = 1;
|
|
@@ -4885,47 +5160,34 @@ class Wunderbaum {
|
|
|
4885
5160
|
// Node is above viewport
|
|
4886
5161
|
newTop = nodeOfs + MARGIN;
|
|
4887
5162
|
}
|
|
4888
|
-
this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
|
|
4889
5163
|
if (newTop != null) {
|
|
5164
|
+
this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
|
|
4890
5165
|
this.scrollContainer.scrollTop = newTop;
|
|
4891
|
-
this.
|
|
5166
|
+
this.setModified(ChangeType.vscroll);
|
|
4892
5167
|
}
|
|
4893
5168
|
}
|
|
4894
|
-
/**
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
}
|
|
4901
|
-
const prevMode = this.navMode;
|
|
4902
|
-
const cellMode = mode !== NavigationMode.row;
|
|
4903
|
-
this.navMode = mode;
|
|
4904
|
-
if (cellMode && prevMode === NavigationMode.row) {
|
|
4905
|
-
this.setColumn(0);
|
|
4906
|
-
}
|
|
4907
|
-
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
4908
|
-
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
4909
|
-
this.setModified(ChangeType.row, this.activeNode);
|
|
4910
|
-
}
|
|
4911
|
-
/** */
|
|
5169
|
+
/**
|
|
5170
|
+
* Set column #colIdx to 'active'.
|
|
5171
|
+
*
|
|
5172
|
+
* This higlights the column header and -cells by adding the `wb-active` class.
|
|
5173
|
+
* Available in cell-nav and cell-edit mode, not in row-mode.
|
|
5174
|
+
*/
|
|
4912
5175
|
setColumn(colIdx) {
|
|
5176
|
+
var _a;
|
|
4913
5177
|
assert(this.navMode !== NavigationMode.row);
|
|
4914
5178
|
assert(0 <= colIdx && colIdx < this.columns.length);
|
|
4915
5179
|
this.activeColIdx = colIdx;
|
|
4916
|
-
// node.setActive(true, { column: tree.activeColIdx + 1 });
|
|
4917
|
-
this.setModified(ChangeType.row, this.activeNode);
|
|
4918
5180
|
// Update `wb-active` class for all headers
|
|
4919
5181
|
if (this.headerElement) {
|
|
4920
5182
|
for (let rowDiv of this.headerElement.children) {
|
|
4921
|
-
// for (let rowDiv of document.querySelector("div.wb-header").children) {
|
|
4922
5183
|
let i = 0;
|
|
4923
5184
|
for (let colDiv of rowDiv.children) {
|
|
4924
5185
|
colDiv.classList.toggle("wb-active", i++ === colIdx);
|
|
4925
5186
|
}
|
|
4926
5187
|
}
|
|
4927
5188
|
}
|
|
4928
|
-
|
|
5189
|
+
(_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
|
|
5190
|
+
// Update `wb-active` class for all cell spans
|
|
4929
5191
|
for (let rowDiv of this.nodeListElement.children) {
|
|
4930
5192
|
let i = 0;
|
|
4931
5193
|
for (let colDiv of rowDiv.children) {
|
|
@@ -4933,7 +5195,7 @@ class Wunderbaum {
|
|
|
4933
5195
|
}
|
|
4934
5196
|
}
|
|
4935
5197
|
}
|
|
4936
|
-
/** */
|
|
5198
|
+
/** Set or remove keybaord focus to the tree container. */
|
|
4937
5199
|
setFocus(flag = true) {
|
|
4938
5200
|
if (flag) {
|
|
4939
5201
|
this.element.focus();
|
|
@@ -4942,129 +5204,321 @@ class Wunderbaum {
|
|
|
4942
5204
|
this.element.blur();
|
|
4943
5205
|
}
|
|
4944
5206
|
}
|
|
4945
|
-
/** */
|
|
4946
5207
|
setModified(change, node, options) {
|
|
4947
|
-
if (
|
|
4948
|
-
|
|
5208
|
+
if (this._disableUpdateCount) {
|
|
5209
|
+
// Assuming that we redraw all when enableUpdate() is re-enabled.
|
|
5210
|
+
// this.log(
|
|
5211
|
+
// `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
|
|
5212
|
+
// );
|
|
5213
|
+
return;
|
|
4949
5214
|
}
|
|
4950
|
-
this.
|
|
4951
|
-
if (
|
|
4952
|
-
|
|
5215
|
+
// this.log(`setModified(${change}) node=${node}`);
|
|
5216
|
+
if (!(node instanceof WunderbaumNode)) {
|
|
5217
|
+
options = node;
|
|
5218
|
+
}
|
|
5219
|
+
const immediate = !!getOption(options, "immediate");
|
|
5220
|
+
switch (change) {
|
|
5221
|
+
case ChangeType.any:
|
|
5222
|
+
case ChangeType.structure:
|
|
5223
|
+
case ChangeType.header:
|
|
5224
|
+
this.changeRedrawRequestPending = true;
|
|
5225
|
+
this.updateViewport(immediate);
|
|
5226
|
+
break;
|
|
5227
|
+
case ChangeType.vscroll:
|
|
5228
|
+
this.updateViewport(immediate);
|
|
5229
|
+
break;
|
|
5230
|
+
case ChangeType.row:
|
|
5231
|
+
case ChangeType.data:
|
|
5232
|
+
case ChangeType.status:
|
|
5233
|
+
// Single nodes are immedialtely updated if already inside the viewport
|
|
5234
|
+
// (otherwise we can ignore)
|
|
5235
|
+
if (node._rowElem) {
|
|
5236
|
+
node.render({ change: change });
|
|
5237
|
+
}
|
|
5238
|
+
break;
|
|
5239
|
+
default:
|
|
5240
|
+
error(`Invalid change type ${change}`);
|
|
4953
5241
|
}
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
5242
|
+
}
|
|
5243
|
+
/** Set the tree's navigation mode. */
|
|
5244
|
+
setNavigationMode(mode) {
|
|
5245
|
+
var _a;
|
|
5246
|
+
// util.assert(this.cellNavMode);
|
|
5247
|
+
// util.assert(0 <= colIdx && colIdx < this.columns.length);
|
|
5248
|
+
if (mode === this.navMode) {
|
|
5249
|
+
return;
|
|
4962
5250
|
}
|
|
4963
|
-
|
|
5251
|
+
const prevMode = this.navMode;
|
|
5252
|
+
const cellMode = mode !== NavigationMode.row;
|
|
5253
|
+
this.navMode = mode;
|
|
5254
|
+
if (cellMode && prevMode === NavigationMode.row) {
|
|
5255
|
+
this.setColumn(0);
|
|
5256
|
+
}
|
|
5257
|
+
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
5258
|
+
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
5259
|
+
// this.setModified(ChangeType.row, this.activeNode);
|
|
5260
|
+
(_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
|
|
4964
5261
|
}
|
|
5262
|
+
/** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
|
|
4965
5263
|
setStatus(status, message, details) {
|
|
4966
5264
|
return this.root.setStatus(status, message, details);
|
|
4967
5265
|
}
|
|
4968
5266
|
/** Update column headers and width. */
|
|
4969
5267
|
updateColumns(opts) {
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
5268
|
+
opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
|
|
5269
|
+
const minWidth = 4;
|
|
5270
|
+
const vpWidth = this.element.clientWidth;
|
|
4973
5271
|
let totalWeight = 0;
|
|
4974
5272
|
let fixedWidth = 0;
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
this._columnsById
|
|
4979
|
-
let
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
col._widthPx
|
|
5273
|
+
let modified = false;
|
|
5274
|
+
if (opts.calculateCols) {
|
|
5275
|
+
// Gather width requests
|
|
5276
|
+
this._columnsById = {};
|
|
5277
|
+
for (let col of this.columns) {
|
|
5278
|
+
this._columnsById[col.id] = col;
|
|
5279
|
+
let cw = col.width;
|
|
5280
|
+
if (!cw || cw === "*") {
|
|
5281
|
+
col._weight = 1.0;
|
|
5282
|
+
totalWeight += 1.0;
|
|
5283
|
+
}
|
|
5284
|
+
else if (typeof cw === "number") {
|
|
5285
|
+
col._weight = cw;
|
|
5286
|
+
totalWeight += cw;
|
|
5287
|
+
}
|
|
5288
|
+
else if (typeof cw === "string" && cw.endsWith("px")) {
|
|
5289
|
+
col._weight = 0;
|
|
5290
|
+
let px = parseFloat(cw.slice(0, -2));
|
|
5291
|
+
if (col._widthPx != px) {
|
|
5292
|
+
modified = true;
|
|
5293
|
+
col._widthPx = px;
|
|
5294
|
+
}
|
|
5295
|
+
fixedWidth += px;
|
|
5296
|
+
}
|
|
5297
|
+
else {
|
|
5298
|
+
error("Invalid column width: " + cw);
|
|
4994
5299
|
}
|
|
4995
|
-
fixedWidth += px;
|
|
4996
5300
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
if (col._widthPx != px) {
|
|
5008
|
-
modified = true;
|
|
5009
|
-
col._widthPx = px;
|
|
5301
|
+
// Share remaining space between non-fixed columns
|
|
5302
|
+
const restPx = Math.max(0, vpWidth - fixedWidth);
|
|
5303
|
+
let ofsPx = 0;
|
|
5304
|
+
for (let col of this.columns) {
|
|
5305
|
+
if (col._weight) {
|
|
5306
|
+
const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
|
|
5307
|
+
if (col._widthPx != px) {
|
|
5308
|
+
modified = true;
|
|
5309
|
+
col._widthPx = px;
|
|
5310
|
+
}
|
|
5010
5311
|
}
|
|
5312
|
+
col._ofsPx = ofsPx;
|
|
5313
|
+
ofsPx += col._widthPx;
|
|
5011
5314
|
}
|
|
5012
|
-
col._ofsPx = ofsPx;
|
|
5013
|
-
ofsPx += col._widthPx;
|
|
5014
5315
|
}
|
|
5015
5316
|
// Every column has now a calculated `_ofsPx` and `_widthPx`
|
|
5016
5317
|
// this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
|
|
5017
5318
|
// console.trace();
|
|
5018
5319
|
// util.error("BREAK");
|
|
5019
5320
|
if (modified) {
|
|
5020
|
-
this.
|
|
5021
|
-
if (opts.
|
|
5022
|
-
this.
|
|
5321
|
+
this._renderHeaderMarkup();
|
|
5322
|
+
if (opts.updateRows) {
|
|
5323
|
+
this._updateRows();
|
|
5023
5324
|
}
|
|
5024
5325
|
}
|
|
5025
5326
|
}
|
|
5026
|
-
/**
|
|
5327
|
+
/** Create/update header markup from `this.columns` definition.
|
|
5328
|
+
* @internal
|
|
5329
|
+
*/
|
|
5330
|
+
_renderHeaderMarkup() {
|
|
5331
|
+
if (!this.headerElement) {
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5334
|
+
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
5335
|
+
assert(headerRow);
|
|
5336
|
+
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
5337
|
+
for (let i = 0; i < this.columns.length; i++) {
|
|
5338
|
+
const col = this.columns[i];
|
|
5339
|
+
const colElem = headerRow.children[i];
|
|
5340
|
+
colElem.style.left = col._ofsPx + "px";
|
|
5341
|
+
colElem.style.width = col._widthPx + "px";
|
|
5342
|
+
// colElem.textContent = col.title || col.id;
|
|
5343
|
+
const title = escapeHtml(col.title || col.id);
|
|
5344
|
+
colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
|
|
5345
|
+
// colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
/** Render header and all rows that are visible in the viewport (async, throttled). */
|
|
5027
5349
|
updateViewport(immediate = false) {
|
|
5028
5350
|
// Call the `throttle` wrapper for `this._updateViewport()` which will
|
|
5029
5351
|
// execute immediately on the leading edge of a sequence:
|
|
5030
|
-
this._updateViewportThrottled();
|
|
5031
5352
|
if (immediate) {
|
|
5032
|
-
this.
|
|
5353
|
+
this._updateViewport();
|
|
5354
|
+
}
|
|
5355
|
+
else {
|
|
5356
|
+
this._updateViewportThrottled();
|
|
5033
5357
|
}
|
|
5034
5358
|
}
|
|
5359
|
+
/**
|
|
5360
|
+
* This is the actual update method, which is wrapped inside a throttle method.
|
|
5361
|
+
* This protected method should not be called directly but via
|
|
5362
|
+
* `tree.updateViewport()` or `tree.setModified()`.
|
|
5363
|
+
* It calls `updateColumns()` and `_updateRows()`.
|
|
5364
|
+
* @internal
|
|
5365
|
+
*/
|
|
5035
5366
|
_updateViewport() {
|
|
5036
|
-
if (this.
|
|
5367
|
+
if (this._disableUpdateCount) {
|
|
5368
|
+
this.log(`IGNORED _updateViewport() disable level: ${this._disableUpdateCount}`);
|
|
5037
5369
|
return;
|
|
5038
5370
|
}
|
|
5371
|
+
const newNodesOnly = !this.changeRedrawRequestPending;
|
|
5372
|
+
this.changeRedrawRequestPending = false;
|
|
5039
5373
|
let height = this.scrollContainer.clientHeight;
|
|
5040
|
-
// We cannot get the height for
|
|
5374
|
+
// We cannot get the height for absolute positioned parent, so look at first col
|
|
5041
5375
|
// let headerHeight = this.headerElement.clientHeight
|
|
5042
5376
|
// let headerHeight = this.headerElement.children[0].children[0].clientHeight;
|
|
5043
5377
|
const headerHeight = this.options.headerHeightPx;
|
|
5044
|
-
|
|
5045
|
-
let ofs = this.scrollContainer.scrollTop;
|
|
5378
|
+
const wantHeight = this.element.clientHeight - headerHeight;
|
|
5046
5379
|
if (Math.abs(height - wantHeight) > 1.0) {
|
|
5047
5380
|
// this.log("resize", height, wantHeight);
|
|
5048
5381
|
this.scrollContainer.style.height = wantHeight + "px";
|
|
5049
5382
|
height = wantHeight;
|
|
5050
5383
|
}
|
|
5051
|
-
|
|
5052
|
-
this.
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
});
|
|
5384
|
+
// console.profile(`_updateViewport()`)
|
|
5385
|
+
this.updateColumns({ updateRows: false });
|
|
5386
|
+
this._updateRows({ newNodesOnly: newNodesOnly });
|
|
5387
|
+
// console.profileEnd(`_updateViewport()`)
|
|
5056
5388
|
this._callEvent("update");
|
|
5057
5389
|
}
|
|
5058
|
-
/**
|
|
5390
|
+
/**
|
|
5391
|
+
* Assert that TR order matches the natural node order
|
|
5392
|
+
* @internal
|
|
5393
|
+
*/
|
|
5394
|
+
_validateRows() {
|
|
5395
|
+
let trs = this.nodeListElement.childNodes;
|
|
5396
|
+
let i = 0;
|
|
5397
|
+
let prev = -1;
|
|
5398
|
+
let ok = true;
|
|
5399
|
+
trs.forEach((element) => {
|
|
5400
|
+
const tr = element;
|
|
5401
|
+
const top = Number.parseInt(tr.style.top);
|
|
5402
|
+
const n = tr._wb_node;
|
|
5403
|
+
// if (i < 4) {
|
|
5404
|
+
// console.info(
|
|
5405
|
+
// `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
|
|
5406
|
+
// );
|
|
5407
|
+
// }
|
|
5408
|
+
if (prev >= 0 && top !== prev + ROW_HEIGHT) {
|
|
5409
|
+
n.logWarn(`TR order mismatch at index ${i}: top=${top}px != ${prev + ROW_HEIGHT}`);
|
|
5410
|
+
// throw new Error("fault");
|
|
5411
|
+
ok = false;
|
|
5412
|
+
}
|
|
5413
|
+
prev = top;
|
|
5414
|
+
i++;
|
|
5415
|
+
});
|
|
5416
|
+
return ok;
|
|
5417
|
+
}
|
|
5418
|
+
/*
|
|
5419
|
+
* - Traverse all *visible* of the whole tree, i.e. skip collapsed nodes.
|
|
5420
|
+
* - Store count of rows to `tree.treeRowCount`.
|
|
5421
|
+
* - Renumber `node._rowIdx` for all visible nodes.
|
|
5422
|
+
* - Calculate the index range that must be rendered to fill the viewport
|
|
5423
|
+
* (including upper and lower prefetch)
|
|
5424
|
+
* -
|
|
5425
|
+
*/
|
|
5426
|
+
_updateRows(opts) {
|
|
5427
|
+
const label = this.logTime("_updateRows");
|
|
5428
|
+
// this.log("_updateRows", opts)
|
|
5429
|
+
opts = Object.assign({ newNodesOnly: false }, opts);
|
|
5430
|
+
const newNodesOnly = !!opts.newNodesOnly;
|
|
5431
|
+
const row_height = ROW_HEIGHT;
|
|
5432
|
+
const vp_height = this.scrollContainer.clientHeight;
|
|
5433
|
+
const prefetch = RENDER_MAX_PREFETCH;
|
|
5434
|
+
const ofs = this.scrollContainer.scrollTop;
|
|
5435
|
+
let startIdx = Math.max(0, ofs / row_height - prefetch);
|
|
5436
|
+
startIdx = Math.floor(startIdx);
|
|
5437
|
+
// Make sure start is always even, so the alternating row colors don't
|
|
5438
|
+
// change when scrolling:
|
|
5439
|
+
if (startIdx % 2) {
|
|
5440
|
+
startIdx--;
|
|
5441
|
+
}
|
|
5442
|
+
let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
|
|
5443
|
+
endIdx = Math.ceil(endIdx);
|
|
5444
|
+
// const obsoleteViewNodes = this.viewNodes;
|
|
5445
|
+
// this.viewNodes = new Set();
|
|
5446
|
+
// const viewNodes = this.viewNodes;
|
|
5447
|
+
// this.debug("render", opts);
|
|
5448
|
+
const obsoleteNodes = new Set();
|
|
5449
|
+
this.nodeListElement.childNodes.forEach((elem) => {
|
|
5450
|
+
const tr = elem;
|
|
5451
|
+
obsoleteNodes.add(tr._wb_node);
|
|
5452
|
+
});
|
|
5453
|
+
let idx = 0;
|
|
5454
|
+
let top = 0;
|
|
5455
|
+
let modified = false;
|
|
5456
|
+
let prevElem = "first";
|
|
5457
|
+
this.visitRows(function (node) {
|
|
5458
|
+
// node.log("visit")
|
|
5459
|
+
const rowDiv = node._rowElem;
|
|
5460
|
+
// Renumber all expanded nodes
|
|
5461
|
+
if (node._rowIdx !== idx) {
|
|
5462
|
+
node._rowIdx = idx;
|
|
5463
|
+
modified = true;
|
|
5464
|
+
}
|
|
5465
|
+
if (idx < startIdx || idx > endIdx) {
|
|
5466
|
+
// row is outside viewport bounds
|
|
5467
|
+
if (rowDiv) {
|
|
5468
|
+
prevElem = rowDiv;
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
else if (rowDiv && newNodesOnly) {
|
|
5472
|
+
obsoleteNodes.delete(node);
|
|
5473
|
+
// no need to update existing node markup
|
|
5474
|
+
rowDiv.style.top = idx * ROW_HEIGHT + "px";
|
|
5475
|
+
prevElem = rowDiv;
|
|
5476
|
+
}
|
|
5477
|
+
else {
|
|
5478
|
+
obsoleteNodes.delete(node);
|
|
5479
|
+
// Create new markup
|
|
5480
|
+
if (rowDiv) {
|
|
5481
|
+
rowDiv.style.top = idx * ROW_HEIGHT + "px";
|
|
5482
|
+
}
|
|
5483
|
+
node.render({ top: top, after: prevElem });
|
|
5484
|
+
// node.log("render", top, prevElem, "=>", node._rowElem);
|
|
5485
|
+
prevElem = node._rowElem;
|
|
5486
|
+
}
|
|
5487
|
+
idx++;
|
|
5488
|
+
top += row_height;
|
|
5489
|
+
});
|
|
5490
|
+
this.treeRowCount = idx;
|
|
5491
|
+
for (const n of obsoleteNodes) {
|
|
5492
|
+
n._callEvent("discard");
|
|
5493
|
+
n.removeMarkup();
|
|
5494
|
+
}
|
|
5495
|
+
// Resize tree container
|
|
5496
|
+
this.nodeListElement.style.height = `${top}px`;
|
|
5497
|
+
// this.log(
|
|
5498
|
+
// `render(scrollOfs:${ofs}, ${startIdx}..${endIdx})`,
|
|
5499
|
+
// this.nodeListElement.style.height
|
|
5500
|
+
// );
|
|
5501
|
+
this.logTimeEnd(label);
|
|
5502
|
+
this._validateRows();
|
|
5503
|
+
return modified;
|
|
5504
|
+
}
|
|
5505
|
+
/**
|
|
5506
|
+
* Call callback(node) for all nodes in hierarchical order (depth-first).
|
|
5059
5507
|
*
|
|
5060
5508
|
* @param {function} callback the callback function.
|
|
5061
|
-
* Return false to stop iteration, return "skip" to skip this node and
|
|
5509
|
+
* Return false to stop iteration, return "skip" to skip this node and
|
|
5510
|
+
* children only.
|
|
5062
5511
|
* @returns {boolean} false, if the iterator was stopped.
|
|
5063
5512
|
*/
|
|
5064
5513
|
visit(callback) {
|
|
5065
5514
|
return this.root.visit(callback, false);
|
|
5066
5515
|
}
|
|
5067
|
-
/**
|
|
5516
|
+
/**
|
|
5517
|
+
* Call fn(node) for all nodes in vertical order, top down (or bottom up).
|
|
5518
|
+
*
|
|
5519
|
+
* Note that this considers expansion state, i.e. children of collapsed nodes
|
|
5520
|
+
* are skipped.
|
|
5521
|
+
*
|
|
5068
5522
|
* Stop iteration, if fn() returns false.<br>
|
|
5069
5523
|
* Return false if iteration was stopped.
|
|
5070
5524
|
*
|
|
@@ -5144,7 +5598,8 @@ class Wunderbaum {
|
|
|
5144
5598
|
}
|
|
5145
5599
|
return true;
|
|
5146
5600
|
}
|
|
5147
|
-
/**
|
|
5601
|
+
/**
|
|
5602
|
+
* Call fn(node) for all nodes in vertical order, bottom up.
|
|
5148
5603
|
* @internal
|
|
5149
5604
|
*/
|
|
5150
5605
|
_visitRowsUp(callback, opts) {
|
|
@@ -5188,19 +5643,36 @@ class Wunderbaum {
|
|
|
5188
5643
|
}
|
|
5189
5644
|
return true;
|
|
5190
5645
|
}
|
|
5191
|
-
/**
|
|
5646
|
+
/**
|
|
5647
|
+
* Reload the tree with a new source.
|
|
5648
|
+
*
|
|
5649
|
+
* Previous data is cleared.
|
|
5650
|
+
* Pass `options.columns` to define a header (may also be part of `source.columns`).
|
|
5651
|
+
*/
|
|
5192
5652
|
load(source, options = {}) {
|
|
5193
5653
|
this.clear();
|
|
5194
5654
|
const columns = options.columns || source.columns;
|
|
5195
5655
|
if (columns) {
|
|
5196
5656
|
this.columns = options.columns;
|
|
5197
|
-
this.
|
|
5198
|
-
|
|
5657
|
+
// this._renderHeaderMarkup();
|
|
5658
|
+
this.updateColumns({ calculateCols: false });
|
|
5199
5659
|
}
|
|
5200
5660
|
return this.root.load(source);
|
|
5201
5661
|
}
|
|
5202
5662
|
/**
|
|
5663
|
+
* Disable render requests during operations that would trigger many updates.
|
|
5203
5664
|
*
|
|
5665
|
+
* ```js
|
|
5666
|
+
* try {
|
|
5667
|
+
* tree.enableUpdate(false);
|
|
5668
|
+
* // ... (long running operation that would trigger many updates)
|
|
5669
|
+
* foo();
|
|
5670
|
+
* // ... NOTE: make sure that async operations have finished, e.g.
|
|
5671
|
+
* await foo();
|
|
5672
|
+
* } finally {
|
|
5673
|
+
* tree.enableUpdate(true);
|
|
5674
|
+
* }
|
|
5675
|
+
* ```
|
|
5204
5676
|
*/
|
|
5205
5677
|
enableUpdate(flag) {
|
|
5206
5678
|
/*
|
|
@@ -5208,20 +5680,22 @@ class Wunderbaum {
|
|
|
5208
5680
|
1 >-------------------------------------<
|
|
5209
5681
|
2 >--------------------<
|
|
5210
5682
|
3 >--------------------------<
|
|
5211
|
-
|
|
5212
|
-
5
|
|
5213
|
-
|
|
5214
5683
|
*/
|
|
5215
|
-
// this.logDebug( `enableUpdate(${flag}): count=${this._disableUpdateCount}...` );
|
|
5216
5684
|
if (flag) {
|
|
5217
|
-
assert(this._disableUpdateCount > 0);
|
|
5685
|
+
assert(this._disableUpdateCount > 0, "enableUpdate(true) was called too often");
|
|
5218
5686
|
this._disableUpdateCount--;
|
|
5687
|
+
// this.logDebug(
|
|
5688
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5689
|
+
// );
|
|
5219
5690
|
if (this._disableUpdateCount === 0) {
|
|
5220
5691
|
this.updateViewport();
|
|
5221
5692
|
}
|
|
5222
5693
|
}
|
|
5223
5694
|
else {
|
|
5224
5695
|
this._disableUpdateCount++;
|
|
5696
|
+
// this.logDebug(
|
|
5697
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5698
|
+
// );
|
|
5225
5699
|
// this._disableUpdate = Date.now();
|
|
5226
5700
|
}
|
|
5227
5701
|
// return !flag; // return previous value
|
|
@@ -5254,8 +5728,10 @@ class Wunderbaum {
|
|
|
5254
5728
|
return this.extensions.filter.updateFilter();
|
|
5255
5729
|
}
|
|
5256
5730
|
}
|
|
5257
|
-
Wunderbaum.version = "v0.0.1-0"; // Set to semver by 'grunt release'
|
|
5258
5731
|
Wunderbaum.sequence = 0;
|
|
5732
|
+
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
5733
|
+
Wunderbaum.version = "v0.0.4"; // Set to semver by 'grunt release'
|
|
5734
|
+
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
5259
5735
|
Wunderbaum.util = util;
|
|
5260
5736
|
|
|
5261
5737
|
export { Wunderbaum };
|