wunderbaum 0.0.1 → 0.0.2

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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Wunderbaum - util
3
3
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
4
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
  /** Readable names for `MouseEvent.button` */
7
7
  const MOUSE_BUTTONS = {
@@ -14,7 +14,7 @@ const MOUSE_BUTTONS = {
14
14
  };
15
15
  const MAX_INT = 9007199254740991;
16
16
  const userInfo = _getUserInfo();
17
- /**True if the user is using a macOS platform. */
17
+ /**True if the client is using a macOS platform. */
18
18
  const isMac = userInfo.isMac;
19
19
  const REX_HTML = /[&<>"'/]/g; // Escape those characters
20
20
  const REX_TOOLTIP = /[<>"'/]/g; // Don't escape `&` in tooltips
@@ -162,7 +162,7 @@ function escapeTooltip(s) {
162
162
  /** TODO */
163
163
  function extractHtmlText(s) {
164
164
  if (s.indexOf(">") >= 0) {
165
- error("not implemented");
165
+ error("Not implemented");
166
166
  // return $("<div/>").html(s).text();
167
167
  }
168
168
  return s;
@@ -284,7 +284,7 @@ function setValueToElem(elem, value) {
284
284
  input.valueAsNumber = value;
285
285
  break;
286
286
  case "radio":
287
- assert(false, "not implemented");
287
+ error("Not implemented");
288
288
  // const name = input.name;
289
289
  // const checked = input.parentElement!.querySelector(
290
290
  // `input[name="${name}"]:checked`
@@ -307,7 +307,7 @@ function setValueToElem(elem, value) {
307
307
  }
308
308
  // return value;
309
309
  }
310
- /** Return an unconnected `HTMLElement` from a HTML string. */
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(rootElem, eventNames, selectorOrHandler, handlerOrNone) {
414
+ function onEvent(rootTarget, eventNames, selectorOrHandler, handlerOrNone) {
392
415
  let selector, handler;
393
- rootElem = elemFromSelector(rootElem);
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
- rootElem.addEventListener(evn, function (e) {
426
+ rootTarget.addEventListener(evn, function (e) {
404
427
  if (!selector) {
405
428
  return handler(e); // no event delegation
406
429
  }
@@ -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,6 +565,7 @@ 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)
@@ -561,6 +592,7 @@ var util = /*#__PURE__*/Object.freeze({
561
592
  setValueToElem: setValueToElem,
562
593
  elemFromHtml: elemFromHtml,
563
594
  elemFromSelector: elemFromSelector,
595
+ eventTargetFromSelector: eventTargetFromSelector,
564
596
  eventToString: eventToString,
565
597
  extend: extend,
566
598
  isArray: isArray,
@@ -581,7 +613,7 @@ var util = /*#__PURE__*/Object.freeze({
581
613
  /*!
582
614
  * Wunderbaum - common
583
615
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
584
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
616
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
585
617
  */
586
618
  const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
587
619
  const ROW_HEIGHT = 22;
@@ -595,6 +627,8 @@ var ChangeType;
595
627
  ChangeType["row"] = "row";
596
628
  ChangeType["structure"] = "structure";
597
629
  ChangeType["status"] = "status";
630
+ ChangeType["vscroll"] = "vscroll";
631
+ ChangeType["header"] = "header";
598
632
  })(ChangeType || (ChangeType = {}));
599
633
  var NodeStatusType;
600
634
  (function (NodeStatusType) {
@@ -693,7 +727,7 @@ function makeNodeTitleStartMatcher(s) {
693
727
  /*!
694
728
  * Wunderbaum - wb_extension_base
695
729
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
696
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
730
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
697
731
  */
698
732
  class WunderbaumExtension {
699
733
  constructor(tree, id, defaults) {
@@ -1048,7 +1082,7 @@ function throttle(func, wait = 0, options = {}) {
1048
1082
  /*!
1049
1083
  * Wunderbaum - ext-filter
1050
1084
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1051
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
1085
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1052
1086
  */
1053
1087
  const START_MARKER = "\uFFF7";
1054
1088
  const END_MARKER = "\uFFF8";
@@ -1087,7 +1121,9 @@ class FilterExtension extends WunderbaumExtension {
1087
1121
  });
1088
1122
  }
1089
1123
  _applyFilterImpl(filter, branchMode, _opts) {
1090
- let match, temp, start = Date.now(), count = 0, tree = this.tree, treeOpts = tree.options, escapeTitles = treeOpts.escapeTitles, prevAutoCollapse = treeOpts.autoCollapse, opts = extend({}, treeOpts.filter, _opts), hideMode = opts.mode === "hide", leavesOnly = !!opts.leavesOnly && !branchMode;
1124
+ let match, temp, start = Date.now(), count = 0, tree = this.tree, treeOpts = tree.options,
1125
+ // escapeTitles = treeOpts.escapeTitles,
1126
+ prevAutoCollapse = treeOpts.autoCollapse, opts = extend({}, treeOpts.filter, _opts), hideMode = opts.mode === "hide", leavesOnly = !!opts.leavesOnly && !branchMode;
1091
1127
  // Default to 'match title substring (case insensitive)'
1092
1128
  if (typeof filter === "string") {
1093
1129
  if (filter === "") {
@@ -1120,37 +1156,36 @@ class FilterExtension extends WunderbaumExtension {
1120
1156
  if (!node.title) {
1121
1157
  return false;
1122
1158
  }
1123
- let text = escapeTitles ? node.title : extractHtmlText(node.title);
1159
+ // let text = escapeTitles ? node.title : extractHtmlText(node.title);
1160
+ let text = node.title;
1124
1161
  // `.match` instead of `.test` to get the capture groups
1125
1162
  let res = text.match(re);
1126
1163
  if (res && opts.highlight) {
1127
- if (escapeTitles) {
1128
- if (opts.fuzzy) {
1129
- temp = _markFuzzyMatchedChars(text, res, escapeTitles);
1130
- }
1131
- else {
1132
- // #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
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>");
1164
+ // if (escapeTitles) {
1165
+ if (opts.fuzzy) {
1166
+ temp = _markFuzzyMatchedChars(text, res, true);
1143
1167
  }
1144
1168
  else {
1145
- if (opts.fuzzy) {
1146
- node.titleWithHighlight = _markFuzzyMatchedChars(text, res);
1147
- }
1148
- else {
1149
- node.titleWithHighlight = text.replace(reHighlight, function (s) {
1150
- return "<mark>" + s + "</mark>";
1151
- });
1152
- }
1169
+ // #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
1170
+ // Use some exotic characters to mark matches:
1171
+ temp = text.replace(reHighlight, function (s) {
1172
+ return START_MARKER + s + END_MARKER;
1173
+ });
1153
1174
  }
1175
+ // now we can escape the title...
1176
+ node.titleWithHighlight = escapeHtml(temp)
1177
+ // ... and finally insert the desired `<mark>` tags
1178
+ .replace(RE_START_MARKER, "<mark>")
1179
+ .replace(RE_END_MARTKER, "</mark>");
1180
+ // } else {
1181
+ // if (opts.fuzzy) {
1182
+ // node.titleWithHighlight = _markFuzzyMatchedChars(text, res);
1183
+ // } else {
1184
+ // node.titleWithHighlight = text.replace(reHighlight, function (s) {
1185
+ // return "<mark>" + s + "</mark>";
1186
+ // });
1187
+ // }
1188
+ // }
1154
1189
  // node.debug("filter", escapeTitles, text, node.titleWithHighlight);
1155
1190
  }
1156
1191
  return !!res;
@@ -1252,9 +1287,9 @@ class FilterExtension extends WunderbaumExtension {
1252
1287
  * [ext-filter] Reset the filter.
1253
1288
  */
1254
1289
  clearFilter() {
1255
- let tree = this.tree,
1290
+ let tree = this.tree;
1256
1291
  // statusNode = tree.root.findDirectChild(KEY_NODATA),
1257
- escapeTitles = tree.options.escapeTitles;
1292
+ // escapeTitles = tree.options.escapeTitles;
1258
1293
  // enhanceTitle = tree.options.enhanceTitle,
1259
1294
  tree.enableUpdate(false);
1260
1295
  // if (statusNode) {
@@ -1268,12 +1303,11 @@ class FilterExtension extends WunderbaumExtension {
1268
1303
  if (node.match && node._rowElem) {
1269
1304
  // #491, #601
1270
1305
  let titleElem = node._rowElem.querySelector("span.wb-title");
1271
- if (escapeTitles) {
1272
- titleElem.textContent = node.title;
1273
- }
1274
- else {
1275
- titleElem.innerHTML = node.title;
1276
- }
1306
+ // if (escapeTitles) {
1307
+ titleElem.textContent = node.title;
1308
+ // } else {
1309
+ // titleElem.innerHTML = node.title;
1310
+ // }
1277
1311
  node._callEvent("enhanceTitle", { titleElem: titleElem });
1278
1312
  }
1279
1313
  delete node.match;
@@ -1309,7 +1343,7 @@ class FilterExtension extends WunderbaumExtension {
1309
1343
  * @param {string} text
1310
1344
  * @param {RegExpMatchArray} matches
1311
1345
  */
1312
- function _markFuzzyMatchedChars(text, matches, escapeTitles = false) {
1346
+ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1313
1347
  let matchingIndices = [];
1314
1348
  // get the indices of matched characters (Iterate through `RegExpMatchArray`)
1315
1349
  for (let _matchingArrIdx = 1; _matchingArrIdx < matches.length; _matchingArrIdx++) {
@@ -1324,7 +1358,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = false) {
1324
1358
  // Map each `text` char to its position and store in `textPoses`.
1325
1359
  let textPoses = text.split("");
1326
1360
  if (escapeTitles) {
1327
- // If escaping the title, then wrap the matchng char within exotic chars
1361
+ // If escaping the title, then wrap the matching char within exotic chars
1328
1362
  matchingIndices.forEach(function (v) {
1329
1363
  textPoses[v] = START_MARKER + textPoses[v] + END_MARKER;
1330
1364
  });
@@ -1342,7 +1376,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = false) {
1342
1376
  /*!
1343
1377
  * Wunderbaum - ext-keynav
1344
1378
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1345
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
1379
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1346
1380
  */
1347
1381
  class KeynavExtension extends WunderbaumExtension {
1348
1382
  constructor(tree) {
@@ -1528,7 +1562,7 @@ class KeynavExtension extends WunderbaumExtension {
1528
1562
  /*!
1529
1563
  * Wunderbaum - ext-logger
1530
1564
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1531
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
1565
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1532
1566
  */
1533
1567
  class LoggerExtension extends WunderbaumExtension {
1534
1568
  constructor(tree) {
@@ -1568,19 +1602,9 @@ class LoggerExtension extends WunderbaumExtension {
1568
1602
  /*!
1569
1603
  * Wunderbaum - ext-dnd
1570
1604
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1571
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
1605
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1572
1606
  */
1573
1607
  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
1608
  class DndExtension extends WunderbaumExtension {
1585
1609
  constructor(tree) {
1586
1610
  super(tree, "dnd", {
@@ -1843,10 +1867,181 @@ class DndExtension extends WunderbaumExtension {
1843
1867
  }
1844
1868
  }
1845
1869
 
1870
+ /*!
1871
+ * Wunderbaum - drag_observer
1872
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1873
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1874
+ */
1875
+ /**
1876
+ * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
1877
+ */
1878
+ class DragObserver {
1879
+ constructor(opts) {
1880
+ this.start = {
1881
+ x: 0,
1882
+ y: 0,
1883
+ altKey: false,
1884
+ ctrlKey: false,
1885
+ metaKey: false,
1886
+ shiftKey: false,
1887
+ };
1888
+ this.dragElem = null;
1889
+ this.dragging = false;
1890
+ // TODO: touch events
1891
+ this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
1892
+ assert(opts.root);
1893
+ this.opts = extend({ thresh: 5 }, opts);
1894
+ this.root = opts.root;
1895
+ this._handler = this.handleEvent.bind(this);
1896
+ this.events.forEach((type) => {
1897
+ this.root.addEventListener(type, this._handler);
1898
+ });
1899
+ }
1900
+ /** Unregister all event listeners. */
1901
+ disconnect() {
1902
+ this.events.forEach((type) => {
1903
+ this.root.removeEventListener(type, this._handler);
1904
+ });
1905
+ }
1906
+ getDragElem() {
1907
+ return this.dragElem;
1908
+ }
1909
+ isDragging() {
1910
+ return this.dragging;
1911
+ }
1912
+ stopDrag(cb_event) {
1913
+ if (this.dragging && this.opts.dragstop && cb_event) {
1914
+ cb_event.type = "dragstop";
1915
+ this.opts.dragstop(cb_event);
1916
+ }
1917
+ this.dragElem = null;
1918
+ this.dragging = false;
1919
+ }
1920
+ handleEvent(e) {
1921
+ const type = e.type;
1922
+ const opts = this.opts;
1923
+ const cb_event = {
1924
+ type: e.type,
1925
+ event: e,
1926
+ dragElem: this.dragElem,
1927
+ dx: e.pageX - this.start.x,
1928
+ dy: e.pageY - this.start.y,
1929
+ apply: undefined,
1930
+ };
1931
+ switch (type) {
1932
+ case "keydown":
1933
+ this.stopDrag(cb_event);
1934
+ break;
1935
+ case "mousedown":
1936
+ if (this.dragElem) {
1937
+ this.stopDrag(cb_event);
1938
+ break;
1939
+ }
1940
+ if (opts.selector) {
1941
+ let elem = e.target;
1942
+ if (elem.matches(opts.selector)) {
1943
+ this.dragElem = elem;
1944
+ }
1945
+ else {
1946
+ elem = elem.closest(opts.selector);
1947
+ if (elem) {
1948
+ this.dragElem = elem;
1949
+ }
1950
+ else {
1951
+ break; // no event delegation selector matched
1952
+ }
1953
+ }
1954
+ }
1955
+ this.start.x = e.pageX;
1956
+ this.start.y = e.pageY;
1957
+ this.start.altKey = e.altKey;
1958
+ this.start.ctrlKey = e.ctrlKey;
1959
+ this.start.metaKey = e.metaKey;
1960
+ this.start.shiftKey = e.shiftKey;
1961
+ break;
1962
+ case "mousemove":
1963
+ // TODO: debounce/throttle?
1964
+ // TODO: horizontal mode: ignore if dx unchanged
1965
+ if (!this.dragElem) {
1966
+ break;
1967
+ }
1968
+ if (!this.dragging) {
1969
+ if (opts.thresh) {
1970
+ const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
1971
+ if (dist2 < opts.thresh * opts.thresh) {
1972
+ break;
1973
+ }
1974
+ }
1975
+ cb_event.type = "dragstart";
1976
+ if (opts.dragstart(cb_event) === false) {
1977
+ this.stopDrag(cb_event);
1978
+ break;
1979
+ }
1980
+ this.dragging = true;
1981
+ }
1982
+ if (this.dragging && this.opts.drag) {
1983
+ cb_event.type = "drag";
1984
+ this.opts.drag(cb_event);
1985
+ }
1986
+ break;
1987
+ case "mouseup":
1988
+ if (!this.dragging) {
1989
+ this.stopDrag(cb_event);
1990
+ break;
1991
+ }
1992
+ if (e.button === 0) {
1993
+ cb_event.apply = true;
1994
+ }
1995
+ else {
1996
+ cb_event.apply = false;
1997
+ }
1998
+ this.stopDrag(cb_event);
1999
+ break;
2000
+ }
2001
+ }
2002
+ }
2003
+
2004
+ /*!
2005
+ * Wunderbaum - ext-grid
2006
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
2007
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
2008
+ */
2009
+ class GridExtension extends WunderbaumExtension {
2010
+ constructor(tree) {
2011
+ super(tree, "grid", {
2012
+ // throttle: 200,
2013
+ });
2014
+ this.observer = new DragObserver({
2015
+ root: window.document,
2016
+ selector: "span.wb-col-resizer",
2017
+ thresh: 4,
2018
+ // throttle: 400,
2019
+ dragstart: (e) => {
2020
+ return this.tree.element.contains(e.dragElem);
2021
+ },
2022
+ drag: (e) => {
2023
+ // TODO: throttle
2024
+ return this.handleDrag(e);
2025
+ },
2026
+ dragstop: (e) => {
2027
+ return this.handleDrag(e);
2028
+ },
2029
+ });
2030
+ }
2031
+ init() {
2032
+ super.init();
2033
+ }
2034
+ handleDrag(e) {
2035
+ const info = Wunderbaum.getEventInfo(e.event);
2036
+ // this.tree.options.
2037
+ this.tree.log(`${e.type}(${e.dx})`, e, info);
2038
+ }
2039
+ }
2040
+
1846
2041
  /*!
1847
2042
  * Wunderbaum - deferred
1848
2043
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1849
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
2044
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1850
2045
  */
1851
2046
  /**
1852
2047
  * Deferred is a ES6 Promise, that exposes the resolve() and reject()` method.
@@ -1889,7 +2084,7 @@ class Deferred {
1889
2084
  /*!
1890
2085
  * Wunderbaum - wunderbaum_node
1891
2086
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
1892
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
2087
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
1893
2088
  */
1894
2089
  /** Top-level properties that can be passed with `data`. */
1895
2090
  const NODE_PROPS = new Set([
@@ -2061,7 +2256,7 @@ class WunderbaumNode {
2061
2256
  // this.fixSelection3FromEndNodes();
2062
2257
  // }
2063
2258
  // this.triggerModifyChild("add", nodeList.length === 1 ? nodeList[0] : null);
2064
- this.tree.setModified(ChangeType.structure, this);
2259
+ this.tree.setModified(ChangeType.structure);
2065
2260
  return nodeList[0];
2066
2261
  }
2067
2262
  finally {
@@ -2535,7 +2730,7 @@ class WunderbaumNode {
2535
2730
  await this.load(source); // also calls setStatus('ok')
2536
2731
  if (wasExpanded) {
2537
2732
  this.expanded = true;
2538
- this.tree.updateViewport();
2733
+ this.tree.setModified(ChangeType.structure);
2539
2734
  }
2540
2735
  else {
2541
2736
  this.render(); // Fix expander icon to 'loaded'
@@ -2691,7 +2886,7 @@ class WunderbaumNode {
2691
2886
  n.tree = targetNode.tree;
2692
2887
  }, true);
2693
2888
  }
2694
- tree.updateViewport();
2889
+ tree.setModified(ChangeType.structure);
2695
2890
  // TODO: fix selection state
2696
2891
  // TODO: fix active state
2697
2892
  }
@@ -2767,7 +2962,7 @@ class WunderbaumNode {
2767
2962
  if (!this.isRootNode()) {
2768
2963
  this.expanded = false;
2769
2964
  }
2770
- this.tree.updateViewport();
2965
+ this.tree.setModified(ChangeType.structure);
2771
2966
  }
2772
2967
  /** Remove all HTML markup from the DOM. */
2773
2968
  removeMarkup() {
@@ -2911,7 +3106,7 @@ class WunderbaumNode {
2911
3106
  nodeElem.appendChild(elem);
2912
3107
  ofsTitlePx += ICON_WIDTH;
2913
3108
  }
2914
- if (level > treeOptions.minExpandLevel) {
3109
+ if (treeOptions.minExpandLevel && level > treeOptions.minExpandLevel) {
2915
3110
  expanderSpan = document.createElement("i");
2916
3111
  nodeElem.appendChild(expanderSpan);
2917
3112
  ofsTitlePx += ICON_WIDTH;
@@ -3002,11 +3197,11 @@ class WunderbaumNode {
3002
3197
  if (this.titleWithHighlight) {
3003
3198
  titleSpan.innerHTML = this.titleWithHighlight;
3004
3199
  }
3005
- else if (tree.options.escapeTitles) {
3006
- titleSpan.textContent = this.title;
3007
- }
3008
3200
  else {
3009
- titleSpan.innerHTML = this.title;
3201
+ // } else if (tree.options.escapeTitles) {
3202
+ titleSpan.textContent = this.title;
3203
+ // } else {
3204
+ // titleSpan.innerHTML = this.title;
3010
3205
  }
3011
3206
  // Set the width of the title span, so overflow ellipsis work
3012
3207
  if (!treeOptions.skeleton) {
@@ -3053,7 +3248,7 @@ class WunderbaumNode {
3053
3248
  this.expanded = false;
3054
3249
  this.lazy = true;
3055
3250
  this.children = null;
3056
- this.tree.updateViewport();
3251
+ this.tree.setModified(ChangeType.structure);
3057
3252
  }
3058
3253
  /** Convert node (or whole branch) into a plain object.
3059
3254
  *
@@ -3181,7 +3376,7 @@ class WunderbaumNode {
3181
3376
  orgEvent: orgEvent,
3182
3377
  }) === false) {
3183
3378
  tree.activeNode = null;
3184
- prev === null || prev === void 0 ? void 0 : prev.setDirty(ChangeType.status);
3379
+ prev === null || prev === void 0 ? void 0 : prev.setModified();
3185
3380
  return;
3186
3381
  }
3187
3382
  }
@@ -3192,8 +3387,8 @@ class WunderbaumNode {
3192
3387
  }
3193
3388
  if (prev !== this) {
3194
3389
  tree.activeNode = this;
3195
- prev === null || prev === void 0 ? void 0 : prev.setDirty(ChangeType.status);
3196
- this.setDirty(ChangeType.status);
3390
+ prev === null || prev === void 0 ? void 0 : prev.setModified();
3391
+ this.setModified();
3197
3392
  }
3198
3393
  if (options &&
3199
3394
  options.colIdx != null &&
@@ -3206,17 +3401,9 @@ class WunderbaumNode {
3206
3401
  // })
3207
3402
  this.scrollIntoView();
3208
3403
  }
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
- }
3404
+ setModified(change = ChangeType.status) {
3405
+ assert(change === ChangeType.status);
3406
+ this.tree.setModified(ChangeType.row, this);
3220
3407
  }
3221
3408
  async setExpanded(flag = true, options) {
3222
3409
  // alert("" + this.getLevel() + ", "+ this.getOption("minExpandLevel");
@@ -3231,7 +3418,7 @@ class WunderbaumNode {
3231
3418
  await this.loadLazy();
3232
3419
  }
3233
3420
  this.expanded = flag;
3234
- this.setDirty(ChangeType.structure);
3421
+ this.tree.setModified(ChangeType.structure);
3235
3422
  }
3236
3423
  setIcon() {
3237
3424
  throw new Error("Not yet implemented");
@@ -3240,8 +3427,8 @@ class WunderbaumNode {
3240
3427
  setFocus(flag = true, options) {
3241
3428
  const prev = this.tree.focusNode;
3242
3429
  this.tree.focusNode = this;
3243
- prev === null || prev === void 0 ? void 0 : prev.setDirty(ChangeType.status);
3244
- this.setDirty(ChangeType.status);
3430
+ prev === null || prev === void 0 ? void 0 : prev.setModified();
3431
+ this.setModified();
3245
3432
  }
3246
3433
  setSelected(flag = true, options) {
3247
3434
  const prev = this.selected;
@@ -3249,7 +3436,7 @@ class WunderbaumNode {
3249
3436
  this._callEvent("select", { flag: flag });
3250
3437
  }
3251
3438
  this.selected = !!flag;
3252
- this.setDirty(ChangeType.status);
3439
+ this.setModified();
3253
3440
  }
3254
3441
  /** Show node status (ok, loading, error, noData) using styles and a dummy child node.
3255
3442
  */
@@ -3325,12 +3512,12 @@ class WunderbaumNode {
3325
3512
  default:
3326
3513
  error("invalid node status " + status);
3327
3514
  }
3328
- tree.updateViewport();
3515
+ tree.setModified(ChangeType.structure);
3329
3516
  return statusNode;
3330
3517
  }
3331
3518
  setTitle(title) {
3332
3519
  this.title = title;
3333
- this.setDirty(ChangeType.status);
3520
+ this.setModified();
3334
3521
  // this.triggerModify("rename"); // TODO
3335
3522
  }
3336
3523
  /**
@@ -3429,7 +3616,7 @@ WunderbaumNode.sequence = 0;
3429
3616
  /*!
3430
3617
  * Wunderbaum - ext-edit
3431
3618
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
3432
- * v0.0.1-0, Thu, 31 Mar 2022 15:13:20 GMT (https://github.com/mar10/wunderbaum)
3619
+ * v0.0.2, Tue, 12 Apr 2022 18:36:21 GMT (https://github.com/mar10/wunderbaum)
3433
3620
  */
3434
3621
  // const START_MARKER = "\uFFF7";
3435
3622
  class EditExtension extends WunderbaumExtension {
@@ -3714,12 +3901,12 @@ class EditExtension extends WunderbaumExtension {
3714
3901
  * Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
3715
3902
  * Released under the MIT license.
3716
3903
  *
3717
- * @version v0.0.1-0
3718
- * @date Thu, 31 Mar 2022 15:13:20 GMT
3904
+ * @version v0.0.2
3905
+ * @date Tue, 12 Apr 2022 18:36:21 GMT
3719
3906
  */
3720
3907
  // const class_prefix = "wb-";
3721
3908
  // const node_props: string[] = ["title", "key", "refKey"];
3722
- const MAX_CHANGED_NODES = 10;
3909
+ // const MAX_CHANGED_NODES = 10;
3723
3910
  /**
3724
3911
  * A persistent plain object or array.
3725
3912
  *
@@ -3748,6 +3935,7 @@ class Wunderbaum {
3748
3935
  this.changedSince = 0;
3749
3936
  this.changes = new Set();
3750
3937
  this.changedNodes = new Set();
3938
+ this.changeRedrawPending = false;
3751
3939
  // --- FILTER ---
3752
3940
  this.filterMode = null;
3753
3941
  // --- KEYNAV ---
@@ -3771,7 +3959,7 @@ class Wunderbaum {
3771
3959
  rowHeightPx: ROW_HEIGHT,
3772
3960
  columns: null,
3773
3961
  types: null,
3774
- escapeTitles: true,
3962
+ // escapeTitles: true,
3775
3963
  showSpinner: false,
3776
3964
  checkbox: true,
3777
3965
  minExpandLevel: 0,
@@ -3828,6 +4016,7 @@ class Wunderbaum {
3828
4016
  this._registerExtension(new EditExtension(this));
3829
4017
  this._registerExtension(new FilterExtension(this));
3830
4018
  this._registerExtension(new DndExtension(this));
4019
+ this._registerExtension(new GridExtension(this));
3831
4020
  this._registerExtension(new LoggerExtension(this));
3832
4021
  // --- Evaluate options
3833
4022
  this.columns = opts.columns;
@@ -3921,11 +4110,9 @@ class Wunderbaum {
3921
4110
  var _a;
3922
4111
  (_a = this.element.querySelector("progress.spinner")) === null || _a === void 0 ? void 0 : _a.remove();
3923
4112
  this.element.classList.remove("wb-initializing");
3924
- // this.updateViewport();
3925
4113
  });
3926
4114
  }
3927
4115
  else {
3928
- // this.updateViewport();
3929
4116
  readyDeferred.resolve();
3930
4117
  }
3931
4118
  // TODO: This is sometimes required, because this.element.clientWidth
@@ -3935,13 +4122,10 @@ class Wunderbaum {
3935
4122
  }, 50);
3936
4123
  // --- Bind listeners
3937
4124
  this.scrollContainer.addEventListener("scroll", (e) => {
3938
- this.updateViewport();
4125
+ this.setModified(ChangeType.vscroll);
3939
4126
  });
3940
- // window.addEventListener("resize", (e: Event) => {
3941
- // this.updateViewport();
3942
- // });
3943
4127
  this.resizeObserver = new ResizeObserver((entries) => {
3944
- this.updateViewport();
4128
+ this.setModified(ChangeType.vscroll);
3945
4129
  console.log("ResizeObserver: Size changed", entries);
3946
4130
  });
3947
4131
  this.resizeObserver.observe(this.element);
@@ -4391,7 +4575,7 @@ class Wunderbaum {
4391
4575
  // public cellNavMode = false;
4392
4576
  // public lastQuicksearchTime = 0;
4393
4577
  // public lastQuicksearchTerm = "";
4394
- this.updateViewport();
4578
+ this.setModified(ChangeType.structure);
4395
4579
  }
4396
4580
  /**
4397
4581
  * Clear nodes and markup and detach events and observers.
@@ -4678,7 +4862,8 @@ class Wunderbaum {
4678
4862
  * TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
4679
4863
  */
4680
4864
  static getEventInfo(event) {
4681
- let target = event.target, cl = target.classList, parentCol = target.closest(".wb-col"), node = Wunderbaum.getNode(target), res = {
4865
+ 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 = {
4866
+ tree: tree,
4682
4867
  node: node,
4683
4868
  region: TargetType.unknown,
4684
4869
  colDef: undefined,
@@ -4710,13 +4895,15 @@ class Wunderbaum {
4710
4895
  }
4711
4896
  else {
4712
4897
  // Somewhere near the title
4713
- console.warn("getEventInfo(): not found", event, res);
4898
+ if (event.type !== "mousemove") {
4899
+ console.warn("getEventInfo(): not found", event, res);
4900
+ }
4714
4901
  return res;
4715
4902
  }
4716
4903
  if (res.colIdx === -1) {
4717
4904
  res.colIdx = 0;
4718
4905
  }
4719
- res.colDef = node.tree.columns[res.colIdx];
4906
+ res.colDef = tree === null || tree === void 0 ? void 0 : tree.columns[res.colIdx];
4720
4907
  res.colDef != null ? (res.colId = res.colDef.id) : 0;
4721
4908
  // this.log("Event", event, res);
4722
4909
  return res;
@@ -4804,6 +4991,7 @@ class Wunderbaum {
4804
4991
  let start = opts === null || opts === void 0 ? void 0 : opts.startIdx;
4805
4992
  let end = opts === null || opts === void 0 ? void 0 : opts.endIdx;
4806
4993
  const obsoleteViewNodes = this.viewNodes;
4994
+ const newNodesOnly = !!getOption(opts, "newNodesOnly");
4807
4995
  this.viewNodes = new Set();
4808
4996
  let viewNodes = this.viewNodes;
4809
4997
  // this.debug("render", opts);
@@ -4825,9 +5013,10 @@ class Wunderbaum {
4825
5013
  node._callEvent("discard");
4826
5014
  node.removeMarkup();
4827
5015
  }
4828
- else {
4829
- // if (!node._rowElem || prevIdx != idx) {
5016
+ else if (!node._rowElem || !newNodesOnly) {
4830
5017
  node.render({ top: top });
5018
+ // }else{
5019
+ // node.log("ignrored render")
4831
5020
  }
4832
5021
  idx++;
4833
5022
  top += height;
@@ -4851,20 +5040,23 @@ class Wunderbaum {
4851
5040
  assert(headerRow);
4852
5041
  headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
4853
5042
  for (let i = 0; i < this.columns.length; i++) {
4854
- let col = this.columns[i];
4855
- let colElem = headerRow.children[i];
5043
+ const col = this.columns[i];
5044
+ const colElem = headerRow.children[i];
4856
5045
  colElem.style.left = col._ofsPx + "px";
4857
5046
  colElem.style.width = col._widthPx + "px";
4858
- colElem.textContent = col.title || col.id;
5047
+ // colElem.textContent = col.title || col.id;
5048
+ const title = escapeHtml(col.title || col.id);
5049
+ colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
5050
+ // colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
4859
5051
  }
4860
5052
  }
4861
5053
  /**
5054
+ * Make sure that this node is scrolled into the viewport.
4862
5055
  *
4863
5056
  * @param {boolean | PlainObject} [effects=false] animation options.
4864
5057
  * @param {object} [options=null] {topNode: null, effects: ..., parent: ...}
4865
5058
  * this node will remain visible in
4866
5059
  * any case, even if `this` is outside the scroll pane.
4867
- * Make sure that a node is scrolled into the viewport.
4868
5060
  */
4869
5061
  scrollTo(opts) {
4870
5062
  const MARGIN = 1;
@@ -4885,10 +5077,10 @@ class Wunderbaum {
4885
5077
  // Node is above viewport
4886
5078
  newTop = nodeOfs + MARGIN;
4887
5079
  }
4888
- this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
4889
5080
  if (newTop != null) {
5081
+ this.log("scrollTo(" + nodeOfs + "): " + curTop + " => " + newTop, height);
4890
5082
  this.scrollContainer.scrollTop = newTop;
4891
- this.updateViewport();
5083
+ this.setModified(ChangeType.vscroll);
4892
5084
  }
4893
5085
  }
4894
5086
  /** */
@@ -4942,24 +5134,50 @@ class Wunderbaum {
4942
5134
  this.element.blur();
4943
5135
  }
4944
5136
  }
4945
- /** */
5137
+ /* */
4946
5138
  setModified(change, node, options) {
4947
- if (!this.changedSince) {
4948
- this.changedSince = Date.now();
5139
+ if (!(node instanceof WunderbaumNode)) {
5140
+ options = node;
4949
5141
  }
4950
- this.changes.add(change);
4951
- if (change === ChangeType.structure) {
4952
- this.changedNodes.clear();
5142
+ if (this._disableUpdate) {
5143
+ return;
4953
5144
  }
4954
- else if (node && !this.changes.has(ChangeType.structure)) {
4955
- if (this.changedNodes.size < MAX_CHANGED_NODES) {
4956
- this.changedNodes.add(node);
4957
- }
4958
- else {
4959
- this.changes.add(ChangeType.structure);
4960
- this.changedNodes.clear();
4961
- }
5145
+ const immediate = !!getOption(options, "immediate");
5146
+ switch (change) {
5147
+ case ChangeType.any:
5148
+ case ChangeType.structure:
5149
+ case ChangeType.header:
5150
+ this.changeRedrawPending = true;
5151
+ this.updateViewport(immediate);
5152
+ break;
5153
+ case ChangeType.vscroll:
5154
+ this.updateViewport(immediate);
5155
+ break;
5156
+ case ChangeType.row:
5157
+ case ChangeType.status:
5158
+ // Single nodes are immedialtely updated if already inside the viewport
5159
+ // (otherwise we can ignore)
5160
+ if (node._rowElem) {
5161
+ node.render();
5162
+ }
5163
+ break;
5164
+ default:
5165
+ error(`Invalid change type ${change}`);
4962
5166
  }
5167
+ // if (!this.changedSince) {
5168
+ // this.changedSince = Date.now();
5169
+ // }
5170
+ // this.changes.add(change);
5171
+ // if (change === ChangeType.structure) {
5172
+ // this.changedNodes.clear();
5173
+ // } else if (node && !this.changes.has(ChangeType.structure)) {
5174
+ // if (this.changedNodes.size < MAX_CHANGED_NODES) {
5175
+ // this.changedNodes.add(node);
5176
+ // } else {
5177
+ // this.changes.add(ChangeType.structure);
5178
+ // this.changedNodes.clear();
5179
+ // }
5180
+ // }
4963
5181
  // this.log("setModified(" + change + ")", node);
4964
5182
  }
4965
5183
  setStatus(status, message, details) {
@@ -5036,13 +5254,15 @@ class Wunderbaum {
5036
5254
  if (this._disableUpdate) {
5037
5255
  return;
5038
5256
  }
5257
+ const newNodesOnly = !this.changeRedrawPending;
5258
+ this.changeRedrawPending = false;
5039
5259
  let height = this.scrollContainer.clientHeight;
5040
- // We cannot get the height for abolut positioned parent, so look at first col
5260
+ // We cannot get the height for absolut positioned parent, so look at first col
5041
5261
  // let headerHeight = this.headerElement.clientHeight
5042
5262
  // let headerHeight = this.headerElement.children[0].children[0].clientHeight;
5043
5263
  const headerHeight = this.options.headerHeightPx;
5044
- let wantHeight = this.element.clientHeight - headerHeight;
5045
- let ofs = this.scrollContainer.scrollTop;
5264
+ const wantHeight = this.element.clientHeight - headerHeight;
5265
+ const ofs = this.scrollContainer.scrollTop;
5046
5266
  if (Math.abs(height - wantHeight) > 1.0) {
5047
5267
  // this.log("resize", height, wantHeight);
5048
5268
  this.scrollContainer.style.height = wantHeight + "px";
@@ -5052,6 +5272,7 @@ class Wunderbaum {
5052
5272
  this.render({
5053
5273
  startIdx: Math.max(0, ofs / ROW_HEIGHT - RENDER_MAX_PREFETCH),
5054
5274
  endIdx: Math.max(0, (ofs + height) / ROW_HEIGHT + RENDER_MAX_PREFETCH),
5275
+ newNodesOnly: newNodesOnly,
5055
5276
  });
5056
5277
  this._callEvent("update");
5057
5278
  }
@@ -5254,7 +5475,7 @@ class Wunderbaum {
5254
5475
  return this.extensions.filter.updateFilter();
5255
5476
  }
5256
5477
  }
5257
- Wunderbaum.version = "v0.0.1-0"; // Set to semver by 'grunt release'
5478
+ Wunderbaum.version = "v0.0.2"; // Set to semver by 'grunt release'
5258
5479
  Wunderbaum.sequence = 0;
5259
5480
  Wunderbaum.util = util;
5260
5481