wunderbaum 0.0.3 → 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.
@@ -7,7 +7,7 @@
7
7
  /*!
8
8
  * Wunderbaum - util
9
9
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
10
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
10
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
11
11
  */
12
12
  /** @module util */
13
13
  /** Readable names for `MouseEvent.button` */
@@ -305,14 +305,13 @@
305
305
  break;
306
306
  case "text":
307
307
  default:
308
- input.innerText = value;
308
+ input.value = value || "";
309
309
  }
310
310
  }
311
311
  else if (tag === "SELECT") {
312
312
  const select = elem;
313
313
  select.value = value;
314
314
  }
315
- // return value;
316
315
  }
317
316
  /** Create and return an unconnected `HTMLElement` from a HTML string. */
318
317
  function elemFromHtml(html) {
@@ -485,7 +484,7 @@
485
484
  return new Promise((resolve, reject) => {
486
485
  setTimeout(() => {
487
486
  try {
488
- resolve(callback.apply(self));
487
+ resolve(callback.apply(this));
489
488
  }
490
489
  catch (err) {
491
490
  reject(err);
@@ -579,6 +578,72 @@
579
578
  .replace(/^\[object (.+)\]$/, "$1")
580
579
  .toLowerCase();
581
580
  }
581
+ /**
582
+ * Return a function that can be called instead of `callback`, but guarantees
583
+ * a limited execution rate.
584
+ * The execution rate is calculated based on the runtime duration of the
585
+ * previous call.
586
+ * Example:
587
+ * ```js
588
+ * throttledFoo = util.addaptiveThrottle(foo.bind(this), {});
589
+ * throttledFoo();
590
+ * throttledFoo();
591
+ * ```
592
+ */
593
+ function addaptiveThrottle(callback, options) {
594
+ let waiting = 0; // Initially, we're not waiting
595
+ let pendingArgs = null;
596
+ const opts = Object.assign({
597
+ minDelay: 16,
598
+ defaultDelay: 200,
599
+ maxDelay: 5000,
600
+ delayFactor: 2.0,
601
+ }, options);
602
+ const minDelay = Math.max(16, +opts.minDelay);
603
+ const maxDelay = +opts.maxDelay;
604
+ const throttledFn = (...args) => {
605
+ if (waiting) {
606
+ pendingArgs = args;
607
+ // console.log(`addaptiveThrottle() queing request #${waiting}...`, args);
608
+ waiting += 1;
609
+ }
610
+ else {
611
+ // Prevent invocations while running or blocking
612
+ waiting = 1;
613
+ const useArgs = args; // pendingArgs || args;
614
+ pendingArgs = null;
615
+ // console.log(`addaptiveThrottle() execute...`, useArgs);
616
+ const start = Date.now();
617
+ try {
618
+ callback.apply(this, useArgs);
619
+ }
620
+ catch (error) {
621
+ console.error(error);
622
+ }
623
+ const elap = Date.now() - start;
624
+ const curDelay = Math.min(Math.max(minDelay, elap * opts.delayFactor), maxDelay);
625
+ const useDelay = Math.max(minDelay, curDelay - elap);
626
+ // console.log(
627
+ // `addaptiveThrottle() calling worker took ${elap}ms. delay = ${curDelay}ms, using ${useDelay}ms`,
628
+ // pendingArgs
629
+ // );
630
+ setTimeout(() => {
631
+ // Unblock, and trigger pending requests if any
632
+ // const skipped = waiting - 1;
633
+ waiting = 0; // And allow future invocations
634
+ if (pendingArgs != null) {
635
+ // There was another request while running or waiting
636
+ // console.log(
637
+ // `addaptiveThrottle() re-trigger (missed ${skipped})...`,
638
+ // pendingArgs
639
+ // );
640
+ throttledFn.apply(this, pendingArgs);
641
+ }
642
+ }, useDelay);
643
+ }
644
+ };
645
+ return throttledFn;
646
+ }
582
647
 
583
648
  var util = /*#__PURE__*/Object.freeze({
584
649
  __proto__: null,
@@ -614,13 +679,14 @@
614
679
  toggleCheckbox: toggleCheckbox,
615
680
  getOption: getOption,
616
681
  toSet: toSet,
617
- type: type
682
+ type: type,
683
+ addaptiveThrottle: addaptiveThrottle
618
684
  });
619
685
 
620
686
  /*!
621
687
  * Wunderbaum - common
622
688
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
623
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
689
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
624
690
  */
625
691
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
626
692
  const ROW_HEIGHT = 22;
@@ -630,12 +696,20 @@
630
696
  const TEST_IMG = new RegExp(/\.|\//); // strings are considered image urls if they contain '.' or '/'
631
697
  var ChangeType;
632
698
  (function (ChangeType) {
699
+ /** Re-render the whole viewport, headers, and all rows. */
633
700
  ChangeType["any"] = "any";
701
+ /** Update current row title, icon, columns, and status. */
702
+ ChangeType["data"] = "data";
703
+ /** Redraw the header and update the width of all row columns. */
704
+ ChangeType["header"] = "header";
705
+ /** Re-render the whole current row. */
634
706
  ChangeType["row"] = "row";
707
+ /** Alias for 'any'. */
635
708
  ChangeType["structure"] = "structure";
709
+ /** Update current row's classes, to reflect active, selected, ... */
636
710
  ChangeType["status"] = "status";
711
+ /** Update the 'top' property of all rows. */
637
712
  ChangeType["vscroll"] = "vscroll";
638
- ChangeType["header"] = "header";
639
713
  })(ChangeType || (ChangeType = {}));
640
714
  var NodeStatusType;
641
715
  (function (NodeStatusType) {
@@ -736,7 +810,7 @@
736
810
  /*!
737
811
  * Wunderbaum - wb_extension_base
738
812
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
739
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
813
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
740
814
  */
741
815
  class WunderbaumExtension {
742
816
  constructor(tree, id, defaults) {
@@ -759,14 +833,14 @@
759
833
  init() {
760
834
  this.tree.element.classList.add("wb-ext-" + this.id);
761
835
  }
762
- // protected callEvent(name: string, extra?: any): any {
763
- // let func = this.extensionOpts[name];
836
+ // protected callEvent(type: string, extra?: any): any {
837
+ // let func = this.extensionOpts[type];
764
838
  // if (func) {
765
839
  // return func.call(
766
840
  // this.tree,
767
841
  // util.extend(
768
842
  // {
769
- // event: this.id + "." + name,
843
+ // event: this.id + "." + type,
770
844
  // },
771
845
  // extra
772
846
  // )
@@ -1023,75 +1097,11 @@
1023
1097
  debounced.pending = pending;
1024
1098
  return debounced;
1025
1099
  }
1026
- /**
1027
- * Creates a throttled function that only invokes `func` at most once per
1028
- * every `wait` milliseconds (or once per browser frame). The throttled function
1029
- * comes with a `cancel` method to cancel delayed `func` invocations and a
1030
- * `flush` method to immediately invoke them. Provide `options` to indicate
1031
- * whether `func` should be invoked on the leading and/or trailing edge of the
1032
- * `wait` timeout. The `func` is invoked with the last arguments provided to the
1033
- * throttled function. Subsequent calls to the throttled function return the
1034
- * result of the last `func` invocation.
1035
- *
1036
- * **Note:** If `leading` and `trailing` options are `true`, `func` is
1037
- * invoked on the trailing edge of the timeout only if the throttled function
1038
- * is invoked more than once during the `wait` timeout.
1039
- *
1040
- * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
1041
- * until the next tick, similar to `setTimeout` with a timeout of `0`.
1042
- *
1043
- * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
1044
- * invocation will be deferred until the next frame is drawn (typically about
1045
- * 16ms).
1046
- *
1047
- * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
1048
- * for details over the differences between `throttle` and `debounce`.
1049
- *
1050
- * @since 0.1.0
1051
- * @category Function
1052
- * @param {Function} func The function to throttle.
1053
- * @param {number} [wait=0]
1054
- * The number of milliseconds to throttle invocations to; if omitted,
1055
- * `requestAnimationFrame` is used (if available).
1056
- * @param {Object} [options={}] The options object.
1057
- * @param {boolean} [options.leading=true]
1058
- * Specify invoking on the leading edge of the timeout.
1059
- * @param {boolean} [options.trailing=true]
1060
- * Specify invoking on the trailing edge of the timeout.
1061
- * @returns {Function} Returns the new throttled function.
1062
- * @example
1063
- *
1064
- * // Avoid excessively updating the position while scrolling.
1065
- * jQuery(window).on('scroll', throttle(updatePosition, 100))
1066
- *
1067
- * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
1068
- * const throttled = throttle(renewToken, 300000, { 'trailing': false })
1069
- * jQuery(element).on('click', throttled)
1070
- *
1071
- * // Cancel the trailing throttled invocation.
1072
- * jQuery(window).on('popstate', throttled.cancel)
1073
- */
1074
- function throttle(func, wait = 0, options = {}) {
1075
- let leading = true;
1076
- let trailing = true;
1077
- if (typeof func !== "function") {
1078
- throw new TypeError("Expected a function");
1079
- }
1080
- if (isObject(options)) {
1081
- leading = "leading" in options ? !!options.leading : leading;
1082
- trailing = "trailing" in options ? !!options.trailing : trailing;
1083
- }
1084
- return debounce(func, wait, {
1085
- leading,
1086
- trailing,
1087
- maxWait: wait,
1088
- });
1089
- }
1090
1100
 
1091
1101
  /*!
1092
1102
  * Wunderbaum - ext-filter
1093
1103
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1094
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
1104
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1095
1105
  */
1096
1106
  const START_MARKER = "\uFFF7";
1097
1107
  const END_MARKER = "\uFFF8";
@@ -1385,7 +1395,7 @@
1385
1395
  /*!
1386
1396
  * Wunderbaum - ext-keynav
1387
1397
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1388
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
1398
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1389
1399
  */
1390
1400
  class KeynavExtension extends WunderbaumExtension {
1391
1401
  constructor(tree) {
@@ -1575,7 +1585,7 @@
1575
1585
  /*!
1576
1586
  * Wunderbaum - ext-logger
1577
1587
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1578
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
1588
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1579
1589
  */
1580
1590
  class LoggerExtension extends WunderbaumExtension {
1581
1591
  constructor(tree) {
@@ -1615,7 +1625,7 @@
1615
1625
  /*!
1616
1626
  * Wunderbaum - ext-dnd
1617
1627
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1618
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
1628
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1619
1629
  */
1620
1630
  const nodeMimeType = "application/x-wunderbaum-node";
1621
1631
  class DndExtension extends WunderbaumExtension {
@@ -1883,7 +1893,7 @@
1883
1893
  /*!
1884
1894
  * Wunderbaum - drag_observer
1885
1895
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1886
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
1896
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
1887
1897
  */
1888
1898
  /**
1889
1899
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2017,7 +2027,7 @@
2017
2027
  /*!
2018
2028
  * Wunderbaum - ext-grid
2019
2029
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2020
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
2030
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2021
2031
  */
2022
2032
  class GridExtension extends WunderbaumExtension {
2023
2033
  constructor(tree) {
@@ -2054,12 +2064,22 @@
2054
2064
  /*!
2055
2065
  * Wunderbaum - deferred
2056
2066
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2057
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
2067
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2058
2068
  */
2059
2069
  /**
2060
- * Deferred is a ES6 Promise, that exposes the resolve() and reject()` method.
2070
+ * Implement a ES6 Promise, that exposes a resolve() and reject() method.
2061
2071
  *
2062
- * Loosely mimics [`jQuery.Deferred`](https://api.jquery.com/category/deferred-object/).
2072
+ * Loosely mimics {@link https://api.jquery.com/category/deferred-object/ | jQuery.Deferred}.
2073
+ * Example:
2074
+ * ```js
2075
+ * function foo() {
2076
+ * let dfd = new Deferred(),
2077
+ * ...
2078
+ * dfd.resolve('foo')
2079
+ * ...
2080
+ * return dfd.promise();
2081
+ * }
2082
+ * ```
2063
2083
  */
2064
2084
  class Deferred {
2065
2085
  constructor() {
@@ -2097,7 +2117,7 @@
2097
2117
  /*!
2098
2118
  * Wunderbaum - wunderbaum_node
2099
2119
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2100
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
2120
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
2101
2121
  */
2102
2122
  /** Top-level properties that can be passed with `data`. */
2103
2123
  const NODE_PROPS = new Set([
@@ -2231,8 +2251,8 @@
2231
2251
  * node._callEvent("edit.beforeEdit", {foo: 42})
2232
2252
  * ```
2233
2253
  */
2234
- _callEvent(name, extra) {
2235
- return this.tree._callEvent(name, extend({
2254
+ _callEvent(type, extra) {
2255
+ return this.tree._callEvent(type, extend({
2236
2256
  node: this,
2237
2257
  typeInfo: this.type ? this.tree.types[this.type] : {},
2238
2258
  }, extra));
@@ -3069,13 +3089,15 @@
3069
3089
  // this.log("_createIcon: ", iconSpan);
3070
3090
  return iconSpan;
3071
3091
  }
3072
- /** Create HTML markup for this node, i.e. the whole row. */
3073
- render(opts) {
3092
+ /**
3093
+ * Create a whole new `<div class="wb-row">` element.
3094
+ * @see {@link Wunderbaumode.render}
3095
+ */
3096
+ _render_markup(opts) {
3074
3097
  const tree = this.tree;
3075
3098
  const treeOptions = tree.options;
3076
3099
  const checkbox = this.getOption("checkbox") !== false;
3077
3100
  const columns = tree.columns;
3078
- const typeInfo = this.type ? tree.types[this.type] : null;
3079
3101
  const level = this.getLevel();
3080
3102
  let elem;
3081
3103
  let nodeElem;
@@ -3085,11 +3107,171 @@
3085
3107
  let iconSpan;
3086
3108
  let expanderSpan = null;
3087
3109
  const activeColIdx = tree.navMode === NavigationMode.row ? null : tree.activeColIdx;
3088
- // let colElems: HTMLElement[];
3089
3110
  const isNew = !rowDiv;
3111
+ assert(isNew);
3090
3112
  assert(!isNew || (opts && opts.after), "opts.after expected, unless updating");
3091
3113
  assert(!this.isRootNode());
3092
- //
3114
+ rowDiv = document.createElement("div");
3115
+ rowDiv.classList.add("wb-row");
3116
+ rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
3117
+ this._rowElem = rowDiv;
3118
+ // Attach a node reference to the DOM Element:
3119
+ rowDiv._wb_node = this;
3120
+ nodeElem = document.createElement("span");
3121
+ nodeElem.classList.add("wb-node", "wb-col");
3122
+ rowDiv.appendChild(nodeElem);
3123
+ let ofsTitlePx = 0;
3124
+ if (checkbox) {
3125
+ checkboxSpan = document.createElement("i");
3126
+ checkboxSpan.classList.add("wb-checkbox");
3127
+ nodeElem.appendChild(checkboxSpan);
3128
+ ofsTitlePx += ICON_WIDTH;
3129
+ }
3130
+ for (let i = level - 1; i > 0; i--) {
3131
+ elem = document.createElement("i");
3132
+ elem.classList.add("wb-indent");
3133
+ nodeElem.appendChild(elem);
3134
+ ofsTitlePx += ICON_WIDTH;
3135
+ }
3136
+ if (!treeOptions.minExpandLevel || level > treeOptions.minExpandLevel) {
3137
+ expanderSpan = document.createElement("i");
3138
+ expanderSpan.classList.add("wb-expander");
3139
+ nodeElem.appendChild(expanderSpan);
3140
+ ofsTitlePx += ICON_WIDTH;
3141
+ }
3142
+ iconSpan = this._createIcon(nodeElem);
3143
+ if (iconSpan) {
3144
+ ofsTitlePx += ICON_WIDTH;
3145
+ }
3146
+ titleSpan = document.createElement("span");
3147
+ titleSpan.classList.add("wb-title");
3148
+ nodeElem.appendChild(titleSpan);
3149
+ this._callEvent("enhanceTitle", { titleSpan: titleSpan });
3150
+ // Store the width of leading icons with the node, so we can calculate
3151
+ // the width of the embedded title span later
3152
+ nodeElem._ofsTitlePx = ofsTitlePx;
3153
+ // Support HTML5 drag-n-drop
3154
+ if (tree.options.dnd.dragStart) {
3155
+ nodeElem.draggable = true;
3156
+ }
3157
+ // Render columns
3158
+ if (!this.colspan && columns.length > 1) {
3159
+ let colIdx = 0;
3160
+ for (let col of columns) {
3161
+ colIdx++;
3162
+ let colElem;
3163
+ if (col.id === "*") {
3164
+ colElem = nodeElem;
3165
+ }
3166
+ else {
3167
+ colElem = document.createElement("span");
3168
+ colElem.classList.add("wb-col");
3169
+ rowDiv.appendChild(colElem);
3170
+ }
3171
+ if (colIdx === activeColIdx) {
3172
+ colElem.classList.add("wb-active");
3173
+ }
3174
+ // Add classes from `columns` definition to `<div.wb-col>` cells
3175
+ col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
3176
+ colElem.style.left = col._ofsPx + "px";
3177
+ colElem.style.width = col._widthPx + "px";
3178
+ if (isNew && col.html) {
3179
+ if (typeof col.html === "string") {
3180
+ colElem.innerHTML = col.html;
3181
+ }
3182
+ }
3183
+ }
3184
+ }
3185
+ // Now go on and fill in data and update classes
3186
+ opts.isNew = true;
3187
+ this._render_data(opts);
3188
+ // Attach to DOM as late as possible
3189
+ const after = opts ? opts.after : "last";
3190
+ switch (after) {
3191
+ case "first":
3192
+ tree.nodeListElement.prepend(rowDiv);
3193
+ break;
3194
+ case "last":
3195
+ tree.nodeListElement.appendChild(rowDiv);
3196
+ break;
3197
+ default:
3198
+ opts.after.after(rowDiv);
3199
+ }
3200
+ }
3201
+ /**
3202
+ * Render `node.title`, `.icon` into an existing row.
3203
+ *
3204
+ * @see {@link Wunderbaumode.render}
3205
+ */
3206
+ _render_data(opts) {
3207
+ assert(this._rowElem);
3208
+ const tree = this.tree;
3209
+ const treeOptions = tree.options;
3210
+ const rowDiv = this._rowElem;
3211
+ const isNew = !!opts.isNew; // Called by _render_markup()?
3212
+ const columns = tree.columns;
3213
+ const typeInfo = this.type ? tree.types[this.type] : null;
3214
+ // Row markup already exists
3215
+ const nodeElem = rowDiv.querySelector("span.wb-node");
3216
+ const titleSpan = nodeElem.querySelector("span.wb-title");
3217
+ if (this.titleWithHighlight) {
3218
+ titleSpan.innerHTML = this.titleWithHighlight;
3219
+ }
3220
+ else {
3221
+ titleSpan.textContent = this.title;
3222
+ }
3223
+ // Set the width of the title span, so overflow ellipsis work
3224
+ if (!treeOptions.skeleton) {
3225
+ if (this.colspan) {
3226
+ let vpWidth = tree.element.clientWidth;
3227
+ titleSpan.style.width =
3228
+ vpWidth - nodeElem._ofsTitlePx - ROW_EXTRA_PAD + "px";
3229
+ }
3230
+ else {
3231
+ titleSpan.style.width =
3232
+ columns[0]._widthPx -
3233
+ nodeElem._ofsTitlePx -
3234
+ ROW_EXTRA_PAD +
3235
+ "px";
3236
+ }
3237
+ }
3238
+ // Update row classes
3239
+ opts.isDataChange = true;
3240
+ this._render_status(opts);
3241
+ // Let user modify the result
3242
+ if (this.statusNodeType) {
3243
+ this._callEvent("renderStatusNode", {
3244
+ isNew: isNew,
3245
+ nodeElem: nodeElem,
3246
+ });
3247
+ }
3248
+ else if (this.parent) {
3249
+ // Skip root node
3250
+ this._callEvent("render", {
3251
+ isNew: isNew,
3252
+ isDataChange: true,
3253
+ nodeElem: nodeElem,
3254
+ typeInfo: typeInfo,
3255
+ colInfosById: this._getRenderInfo(),
3256
+ });
3257
+ }
3258
+ }
3259
+ /**
3260
+ * Update row classes to reflect active, focuses, etc.
3261
+ * @see {@link Wunderbaumode.render}
3262
+ */
3263
+ _render_status(opts) {
3264
+ // this.log("_render_status", opts);
3265
+ const tree = this.tree;
3266
+ const treeOptions = tree.options;
3267
+ const typeInfo = this.type ? tree.types[this.type] : null;
3268
+ const rowDiv = this._rowElem;
3269
+ // Row markup already exists
3270
+ const nodeElem = rowDiv.querySelector("span.wb-node");
3271
+ const expanderSpan = nodeElem.querySelector("i.wb-expander");
3272
+ const checkboxSpan = nodeElem.querySelector("i.wb-checkbox");
3273
+ // TODO: update icon (if not opts.isNew)
3274
+ // const iconSpan = nodeElem.querySelector("i.wb-icon") as HTMLElement;
3093
3275
  let rowClasses = ["wb-row"];
3094
3276
  this.expanded ? rowClasses.push("wb-expanded") : 0;
3095
3277
  this.lazy ? rowClasses.push("wb-lazy") : 0;
@@ -3104,102 +3286,14 @@
3104
3286
  this.match ? rowClasses.push("wb-match") : 0;
3105
3287
  this.subMatchCount ? rowClasses.push("wb-submatch") : 0;
3106
3288
  treeOptions.skeleton ? rowClasses.push("wb-skeleton") : 0;
3107
- // TODO: no need to hide!
3108
- // !(this.match || this.subMatchCount) ? rowClasses.push("wb-hide") : 0;
3109
- if (rowDiv) {
3110
- // Row markup already exists
3111
- nodeElem = rowDiv.querySelector("span.wb-node");
3112
- titleSpan = nodeElem.querySelector("span.wb-title");
3113
- expanderSpan = nodeElem.querySelector("i.wb-expander");
3114
- checkboxSpan = nodeElem.querySelector("i.wb-checkbox");
3115
- iconSpan = nodeElem.querySelector("i.wb-icon");
3116
- // TODO: we need this, when icons should be replacable
3117
- // iconSpan = this._createIcon(nodeElem, iconSpan);
3118
- // colElems = (<unknown>(
3119
- // rowDiv.querySelectorAll("span.wb-col")
3120
- // )) as HTMLElement[];
3121
- }
3122
- else {
3123
- rowDiv = document.createElement("div");
3124
- // rowDiv.classList.add("wb-row");
3125
- // Attach a node reference to the DOM Element:
3126
- rowDiv._wb_node = this;
3127
- nodeElem = document.createElement("span");
3128
- nodeElem.classList.add("wb-node", "wb-col");
3129
- rowDiv.appendChild(nodeElem);
3130
- let ofsTitlePx = 0;
3131
- if (checkbox) {
3132
- checkboxSpan = document.createElement("i");
3133
- nodeElem.appendChild(checkboxSpan);
3134
- ofsTitlePx += ICON_WIDTH;
3135
- }
3136
- for (let i = level - 1; i > 0; i--) {
3137
- elem = document.createElement("i");
3138
- elem.classList.add("wb-indent");
3139
- nodeElem.appendChild(elem);
3140
- ofsTitlePx += ICON_WIDTH;
3141
- }
3142
- if (!treeOptions.minExpandLevel || level > treeOptions.minExpandLevel) {
3143
- expanderSpan = document.createElement("i");
3144
- nodeElem.appendChild(expanderSpan);
3145
- ofsTitlePx += ICON_WIDTH;
3146
- }
3147
- iconSpan = this._createIcon(nodeElem);
3148
- if (iconSpan) {
3149
- ofsTitlePx += ICON_WIDTH;
3150
- }
3151
- titleSpan = document.createElement("span");
3152
- titleSpan.classList.add("wb-title");
3153
- nodeElem.appendChild(titleSpan);
3154
- this._callEvent("enhanceTitle", { titleSpan: titleSpan });
3155
- // Store the width of leading icons with the node, so we can calculate
3156
- // the width of the embedded title span later
3157
- nodeElem._ofsTitlePx = ofsTitlePx;
3158
- if (tree.options.dnd.dragStart) {
3159
- nodeElem.draggable = true;
3160
- }
3161
- // Render columns
3162
- // colElems = [];
3163
- if (!this.colspan && columns.length > 1) {
3164
- let colIdx = 0;
3165
- for (let col of columns) {
3166
- colIdx++;
3167
- let colElem;
3168
- if (col.id === "*") {
3169
- colElem = nodeElem;
3170
- }
3171
- else {
3172
- colElem = document.createElement("span");
3173
- colElem.classList.add("wb-col");
3174
- // colElem.textContent = "" + col.id;
3175
- rowDiv.appendChild(colElem);
3176
- }
3177
- if (colIdx === activeColIdx) {
3178
- colElem.classList.add("wb-active");
3179
- }
3180
- // Add classes from `columns` definition to `<div.wb-col>` cells
3181
- col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
3182
- colElem.style.left = col._ofsPx + "px";
3183
- colElem.style.width = col._widthPx + "px";
3184
- // colElems.push(colElem);
3185
- if (isNew && col.html) {
3186
- if (typeof col.html === "string") {
3187
- colElem.innerHTML = col.html;
3188
- }
3189
- }
3190
- }
3191
- }
3192
- }
3193
- // --- From here common code starts (either new or existing markup):
3194
- rowDiv.className = rowClasses.join(" "); // Reset prev. classes
3289
+ // Replace previous classes:
3290
+ rowDiv.className = rowClasses.join(" ");
3195
3291
  // Add classes from `node.extraClasses`
3196
3292
  rowDiv.classList.add(...this.extraClasses);
3197
3293
  // Add classes from `tree.types[node.type]`
3198
3294
  if (typeInfo && typeInfo.classes) {
3199
3295
  rowDiv.classList.add(...typeInfo.classes);
3200
3296
  }
3201
- // rowDiv.style.top = (this._rowIdx! * 1.1) + "em";
3202
- rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
3203
3297
  if (expanderSpan) {
3204
3298
  if (this.isExpandable(false)) {
3205
3299
  if (this.expanded) {
@@ -3227,59 +3321,41 @@
3227
3321
  checkboxSpan.className = "wb-checkbox " + iconMap.checkUnchecked;
3228
3322
  }
3229
3323
  }
3230
- if (this.titleWithHighlight) {
3231
- titleSpan.innerHTML = this.titleWithHighlight;
3232
- }
3233
- else {
3234
- // } else if (tree.options.escapeTitles) {
3235
- titleSpan.textContent = this.title;
3236
- // } else {
3237
- // titleSpan.innerHTML = this.title;
3238
- }
3239
- // Set the width of the title span, so overflow ellipsis work
3240
- if (!treeOptions.skeleton) {
3241
- if (this.colspan) {
3242
- let vpWidth = tree.element.clientWidth;
3243
- titleSpan.style.width =
3244
- vpWidth - nodeElem._ofsTitlePx - ROW_EXTRA_PAD + "px";
3245
- }
3246
- else {
3247
- titleSpan.style.width =
3248
- columns[0]._widthPx -
3249
- nodeElem._ofsTitlePx -
3250
- ROW_EXTRA_PAD +
3251
- "px";
3324
+ // Fix active cell in cell-nav mode
3325
+ if (!opts.isNew) {
3326
+ let i = 0;
3327
+ for (let colSpan of rowDiv.children) {
3328
+ colSpan.classList.toggle("wb-active", i++ === tree.activeColIdx);
3252
3329
  }
3253
3330
  }
3254
- this._rowElem = rowDiv;
3255
- if (this.statusNodeType) {
3256
- this._callEvent("renderStatusNode", {
3257
- isNew: isNew,
3258
- nodeElem: nodeElem,
3259
- });
3260
- }
3261
- else if (this.parent) {
3262
- // Skip root node
3263
- this._callEvent("render", {
3264
- isNew: isNew,
3265
- nodeElem: nodeElem,
3266
- typeInfo: typeInfo,
3267
- colInfosById: this._getRenderInfo(),
3268
- });
3269
- }
3270
- // Attach to DOM as late as possible
3271
- if (isNew) {
3272
- const after = opts ? opts.after : "last";
3273
- switch (after) {
3274
- case "first":
3275
- tree.nodeListElement.prepend(rowDiv);
3276
- break;
3277
- case "last":
3278
- tree.nodeListElement.appendChild(rowDiv);
3279
- break;
3280
- default:
3281
- opts.after.after(rowDiv);
3282
- }
3331
+ }
3332
+ /**
3333
+ * Create or update node's markup.
3334
+ *
3335
+ * `options.change` defaults to ChangeType.data, which updates the title,
3336
+ * icon, and status. It also triggers the `render` event, that lets the user
3337
+ * create or update the content of embeded cell elements.<br>
3338
+ *
3339
+ * If only the status or other class-only modifications have changed,
3340
+ * `options.change` should be set to ChangeType.status instead for best
3341
+ * efficiency.
3342
+ */
3343
+ render(options) {
3344
+ // this.log("render", options);
3345
+ const opts = Object.assign({ change: ChangeType.data }, options);
3346
+ if (!this._rowElem) {
3347
+ opts.change = "row";
3348
+ }
3349
+ switch (opts.change) {
3350
+ case "status":
3351
+ this._render_status(opts);
3352
+ break;
3353
+ case "data":
3354
+ this._render_data(opts);
3355
+ break;
3356
+ default:
3357
+ this._render_markup(opts);
3358
+ break;
3283
3359
  }
3284
3360
  }
3285
3361
  /**
@@ -3437,8 +3513,8 @@
3437
3513
  }
3438
3514
  if (prev !== this) {
3439
3515
  tree.activeNode = this;
3440
- prev === null || prev === void 0 ? void 0 : prev.setModified();
3441
- this.setModified();
3516
+ prev === null || prev === void 0 ? void 0 : prev.setModified(ChangeType.status);
3517
+ this.setModified(ChangeType.status);
3442
3518
  }
3443
3519
  if (options &&
3444
3520
  options.colIdx != null &&
@@ -3488,10 +3564,16 @@
3488
3564
  setKey(key, refKey) {
3489
3565
  throw new Error("Not yet implemented");
3490
3566
  }
3491
- /** Schedule a render, typically called to update after a status or data change. */
3492
- setModified(change = ChangeType.status) {
3493
- assert(change === ChangeType.status);
3494
- this.tree.setModified(ChangeType.row, this);
3567
+ /**
3568
+ * Schedule a render, typically called to update after a status or data change.
3569
+ *
3570
+ * `change` defaults to 'data', which handles modifcations of title, icon,
3571
+ * and column content. It can be reduced to 'ChangeType.status' if only
3572
+ * active/focus/selected state has changed.
3573
+ */
3574
+ setModified(change = ChangeType.data) {
3575
+ assert(change === ChangeType.status || change === ChangeType.data);
3576
+ this.tree.setModified(change, this);
3495
3577
  }
3496
3578
  /** Modify the check/uncheck state. */
3497
3579
  setSelected(flag = true, options) {
@@ -3687,7 +3769,7 @@
3687
3769
  /*!
3688
3770
  * Wunderbaum - ext-edit
3689
3771
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
3690
- * v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
3772
+ * v0.0.4, Tue, 03 May 2022 06:21:31 GMT (https://github.com/mar10/wunderbaum)
3691
3773
  */
3692
3774
  // const START_MARKER = "\uFFF7";
3693
3775
  class EditExtension extends WunderbaumExtension {
@@ -3978,8 +4060,8 @@
3978
4060
  * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
3979
4061
  * Released under the MIT license.
3980
4062
  *
3981
- * @version v0.0.3
3982
- * @date Mon, 18 Apr 2022 11:52:44 GMT
4063
+ * @version v0.0.4
4064
+ * @date Tue, 03 May 2022 06:21:31 GMT
3983
4065
  */
3984
4066
  // const class_prefix = "wb-";
3985
4067
  // const node_props: string[] = ["title", "key", "refKey"];
@@ -4123,9 +4205,7 @@
4123
4205
  opts.navigationMode === NavigationModeOption.startCell) {
4124
4206
  this.navMode = NavigationMode.cellNav;
4125
4207
  }
4126
- this._updateViewportThrottled = throttle(() => {
4127
- this._updateViewport();
4128
- }, opts.updateThrottleWait, { leading: true, trailing: true });
4208
+ this._updateViewportThrottled = addaptiveThrottle(this._updateViewport.bind(this), {});
4129
4209
  // --- Create Markup
4130
4210
  this.element = elemFromSelector(opts.element);
4131
4211
  assert(!!this.element, `Invalid 'element' option: ${opts.element}`);
@@ -4209,12 +4289,13 @@
4209
4289
  });
4210
4290
  this.resizeObserver = new ResizeObserver((entries) => {
4211
4291
  this.setModified(ChangeType.vscroll);
4212
- console.log("ResizeObserver: Size changed", entries);
4292
+ // this.log("ResizeObserver: Size changed", entries);
4213
4293
  });
4214
4294
  this.resizeObserver.observe(this.element);
4215
4295
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
4216
4296
  const info = Wunderbaum.getEventInfo(e);
4217
4297
  const node = info.node;
4298
+ // this.log("click", info, e);
4218
4299
  if (this._callEvent("click", { event: e, node: node, info: info }) === false) {
4219
4300
  this.lastClickTime = Date.now();
4220
4301
  return false;
@@ -4242,8 +4323,6 @@
4242
4323
  node.setSelected(!node.isSelected());
4243
4324
  }
4244
4325
  }
4245
- // if(e.target.classList.)
4246
- // this.log("click", info);
4247
4326
  this.lastClickTime = Date.now();
4248
4327
  });
4249
4328
  onEvent(this.element, "keydown", (e) => {
@@ -4427,14 +4506,14 @@
4427
4506
  * tree._callEvent("edit.beforeEdit", {foo: 42})
4428
4507
  * ```
4429
4508
  */
4430
- _callEvent(name, extra) {
4431
- const [p, n] = name.split(".");
4509
+ _callEvent(type, extra) {
4510
+ const [p, n] = type.split(".");
4432
4511
  const opts = this.options;
4433
4512
  const func = n ? opts[p][n] : opts[p];
4434
4513
  if (func) {
4435
- return func.call(this, extend({ name: name, tree: this, util: this._util }, extra));
4514
+ return func.call(this, extend({ type: type, tree: this, util: this._util }, extra));
4436
4515
  // } else {
4437
- // this.logError(`Triggering undefined event '${name}'.`)
4516
+ // this.logError(`Triggering undefined event '${type}'.`)
4438
4517
  }
4439
4518
  }
4440
4519
  /** Return the node for given row index. */
@@ -4450,7 +4529,7 @@
4450
4529
  return node;
4451
4530
  }
4452
4531
  /** Return the topmost visible node in the viewport. */
4453
- _firstNodeInView(complete = true) {
4532
+ getTopmostVpNode(complete = true) {
4454
4533
  let topIdx;
4455
4534
  const gracePy = 1; // ignore subpixel scrolling
4456
4535
  if (complete) {
@@ -4462,7 +4541,7 @@
4462
4541
  return this._getNodeByRowIdx(topIdx);
4463
4542
  }
4464
4543
  /** Return the lowest visible node in the viewport. */
4465
- _lastNodeInView(complete = true) {
4544
+ getLowestVpNode(complete = true) {
4466
4545
  let bottomIdx;
4467
4546
  if (complete) {
4468
4547
  bottomIdx =
@@ -4877,7 +4956,7 @@
4877
4956
  res = this._getNextNodeInView(node);
4878
4957
  break;
4879
4958
  case "pageDown":
4880
- const bottomNode = this._lastNodeInView();
4959
+ const bottomNode = this.getLowestVpNode();
4881
4960
  // this.logDebug(`${where}(${node}) -> ${bottomNode}`);
4882
4961
  if (node._rowIdx < bottomNode._rowIdx) {
4883
4962
  res = bottomNode;
@@ -4891,7 +4970,7 @@
4891
4970
  res = node;
4892
4971
  }
4893
4972
  else {
4894
- const topNode = this._firstNodeInView();
4973
+ const topNode = this.getTopmostVpNode();
4895
4974
  // this.logDebug(`${where}(${node}) -> ${topNode}`);
4896
4975
  if (node._rowIdx > topNode._rowIdx) {
4897
4976
  res = topNode;
@@ -5100,22 +5179,21 @@
5100
5179
  * Available in cell-nav and cell-edit mode, not in row-mode.
5101
5180
  */
5102
5181
  setColumn(colIdx) {
5182
+ var _a;
5103
5183
  assert(this.navMode !== NavigationMode.row);
5104
5184
  assert(0 <= colIdx && colIdx < this.columns.length);
5105
5185
  this.activeColIdx = colIdx;
5106
- // node.setActive(true, { column: tree.activeColIdx + 1 });
5107
- this.setModified(ChangeType.row, this.activeNode);
5108
5186
  // Update `wb-active` class for all headers
5109
5187
  if (this.headerElement) {
5110
5188
  for (let rowDiv of this.headerElement.children) {
5111
- // for (let rowDiv of document.querySelector("div.wb-header").children) {
5112
5189
  let i = 0;
5113
5190
  for (let colDiv of rowDiv.children) {
5114
5191
  colDiv.classList.toggle("wb-active", i++ === colIdx);
5115
5192
  }
5116
5193
  }
5117
5194
  }
5118
- // Update `wb-active` class for all cell divs
5195
+ (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
5196
+ // Update `wb-active` class for all cell spans
5119
5197
  for (let rowDiv of this.nodeListElement.children) {
5120
5198
  let i = 0;
5121
5199
  for (let colDiv of rowDiv.children) {
@@ -5156,11 +5234,12 @@
5156
5234
  this.updateViewport(immediate);
5157
5235
  break;
5158
5236
  case ChangeType.row:
5237
+ case ChangeType.data:
5159
5238
  case ChangeType.status:
5160
5239
  // Single nodes are immedialtely updated if already inside the viewport
5161
5240
  // (otherwise we can ignore)
5162
5241
  if (node._rowElem) {
5163
- node.render();
5242
+ node.render({ change: change });
5164
5243
  }
5165
5244
  break;
5166
5245
  default:
@@ -5169,6 +5248,7 @@
5169
5248
  }
5170
5249
  /** Set the tree's navigation mode. */
5171
5250
  setNavigationMode(mode) {
5251
+ var _a;
5172
5252
  // util.assert(this.cellNavMode);
5173
5253
  // util.assert(0 <= colIdx && colIdx < this.columns.length);
5174
5254
  if (mode === this.navMode) {
@@ -5182,7 +5262,8 @@
5182
5262
  }
5183
5263
  this.element.classList.toggle("wb-cell-mode", cellMode);
5184
5264
  this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
5185
- this.setModified(ChangeType.row, this.activeNode);
5265
+ // this.setModified(ChangeType.row, this.activeNode);
5266
+ (_a = this.activeNode) === null || _a === void 0 ? void 0 : _a.setModified(ChangeType.status);
5186
5267
  }
5187
5268
  /** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
5188
5269
  setStatus(status, message, details) {
@@ -5274,9 +5355,11 @@
5274
5355
  updateViewport(immediate = false) {
5275
5356
  // Call the `throttle` wrapper for `this._updateViewport()` which will
5276
5357
  // execute immediately on the leading edge of a sequence:
5277
- this._updateViewportThrottled();
5278
5358
  if (immediate) {
5279
- this._updateViewportThrottled.flush();
5359
+ this._updateViewport();
5360
+ }
5361
+ else {
5362
+ this._updateViewportThrottled();
5280
5363
  }
5281
5364
  }
5282
5365
  /**
@@ -5304,8 +5387,10 @@
5304
5387
  this.scrollContainer.style.height = wantHeight + "px";
5305
5388
  height = wantHeight;
5306
5389
  }
5390
+ // console.profile(`_updateViewport()`)
5307
5391
  this.updateColumns({ updateRows: false });
5308
5392
  this._updateRows({ newNodesOnly: newNodesOnly });
5393
+ // console.profileEnd(`_updateViewport()`)
5309
5394
  this._callEvent("update");
5310
5395
  }
5311
5396
  /**
@@ -5326,8 +5411,8 @@
5326
5411
  // `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
5327
5412
  // );
5328
5413
  // }
5329
- if (top <= prev) {
5330
- console.warn(`TR order mismatch at index ${i}: top=${top}px, node=${n}`);
5414
+ if (prev >= 0 && top !== prev + ROW_HEIGHT) {
5415
+ n.logWarn(`TR order mismatch at index ${i}: top=${top}px != ${prev + ROW_HEIGHT}`);
5331
5416
  // throw new Error("fault");
5332
5417
  ok = false;
5333
5418
  }
@@ -5346,6 +5431,7 @@
5346
5431
  */
5347
5432
  _updateRows(opts) {
5348
5433
  const label = this.logTime("_updateRows");
5434
+ // this.log("_updateRows", opts)
5349
5435
  opts = Object.assign({ newNodesOnly: false }, opts);
5350
5436
  const newNodesOnly = !!opts.newNodesOnly;
5351
5437
  const row_height = ROW_HEIGHT;
@@ -5375,7 +5461,7 @@
5375
5461
  let modified = false;
5376
5462
  let prevElem = "first";
5377
5463
  this.visitRows(function (node) {
5378
- // console.log("visit", node)
5464
+ // node.log("visit")
5379
5465
  const rowDiv = node._rowElem;
5380
5466
  // Renumber all expanded nodes
5381
5467
  if (node._rowIdx !== idx) {
@@ -5397,8 +5483,11 @@
5397
5483
  else {
5398
5484
  obsoleteNodes.delete(node);
5399
5485
  // Create new markup
5486
+ if (rowDiv) {
5487
+ rowDiv.style.top = idx * ROW_HEIGHT + "px";
5488
+ }
5400
5489
  node.render({ top: top, after: prevElem });
5401
- // console.log("render", top, prevElem, "=>", node._rowElem);
5490
+ // node.log("render", top, prevElem, "=>", node._rowElem);
5402
5491
  prevElem = node._rowElem;
5403
5492
  }
5404
5493
  idx++;
@@ -5584,7 +5673,7 @@
5584
5673
  * tree.enableUpdate(false);
5585
5674
  * // ... (long running operation that would trigger many updates)
5586
5675
  * foo();
5587
- * // ... NOTE: make sure that async operations have finished
5676
+ * // ... NOTE: make sure that async operations have finished, e.g.
5588
5677
  * await foo();
5589
5678
  * } finally {
5590
5679
  * tree.enableUpdate(true);
@@ -5647,7 +5736,7 @@
5647
5736
  }
5648
5737
  Wunderbaum.sequence = 0;
5649
5738
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
5650
- Wunderbaum.version = "v0.0.3"; // Set to semver by 'grunt release'
5739
+ Wunderbaum.version = "v0.0.4"; // Set to semver by 'grunt release'
5651
5740
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
5652
5741
  Wunderbaum.util = util;
5653
5742