wunderbaum 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,304 @@
1
+ /*!
2
+ * debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
3
+ * MIT License: https://raw.githubusercontent.com/lodash/lodash/4.17.21-npm/LICENSE
4
+ * Modified for TypeScript type annotations.
5
+ */
6
+ /* --- */
7
+ /** Detect free variable `global` from Node.js. */
8
+ const freeGlobal = typeof global === "object" &&
9
+ global !== null &&
10
+ global.Object === Object &&
11
+ global;
12
+ /** Detect free variable `globalThis` */
13
+ const freeGlobalThis = typeof globalThis === "object" &&
14
+ globalThis !== null &&
15
+ globalThis.Object == Object &&
16
+ globalThis;
17
+ /** Detect free variable `self`. */
18
+ const freeSelf = typeof self === "object" && self !== null && self.Object === Object && self;
19
+ /** Used as a reference to the global object. */
20
+ const root = freeGlobalThis || freeGlobal || freeSelf || Function("return this")();
21
+ /**
22
+ * Checks if `value` is the
23
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
24
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
25
+ *
26
+ * @since 0.1.0
27
+ * @category Lang
28
+ * @param {*} value The value to check.
29
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
30
+ * @example
31
+ *
32
+ * isObject({})
33
+ * // => true
34
+ *
35
+ * isObject([1, 2, 3])
36
+ * // => true
37
+ *
38
+ * isObject(Function)
39
+ * // => true
40
+ *
41
+ * isObject(null)
42
+ * // => false
43
+ */
44
+ function isObject(value) {
45
+ const type = typeof value;
46
+ return value != null && (type === "object" || type === "function");
47
+ }
48
+ /**
49
+ * Creates a debounced function that delays invoking `func` until after `wait`
50
+ * milliseconds have elapsed since the last time the debounced function was
51
+ * invoked, or until the next browser frame is drawn. The debounced function
52
+ * comes with a `cancel` method to cancel delayed `func` invocations and a
53
+ * `flush` method to immediately invoke them. Provide `options` to indicate
54
+ * whether `func` should be invoked on the leading and/or trailing edge of the
55
+ * `wait` timeout. The `func` is invoked with the last arguments provided to the
56
+ * debounced function. Subsequent calls to the debounced function return the
57
+ * result of the last `func` invocation.
58
+ *
59
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
60
+ * invoked on the trailing edge of the timeout only if the debounced function
61
+ * is invoked more than once during the `wait` timeout.
62
+ *
63
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
64
+ * until the next tick, similar to `setTimeout` with a timeout of `0`.
65
+ *
66
+ * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
67
+ * invocation will be deferred until the next frame is drawn (typically about
68
+ * 16ms).
69
+ *
70
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
71
+ * for details over the differences between `debounce` and `throttle`.
72
+ *
73
+ * @since 0.1.0
74
+ * @category Function
75
+ * @param {Function} func The function to debounce.
76
+ * @param {number} [wait=0]
77
+ * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is
78
+ * used (if available).
79
+ * @param {Object} [options={}] The options object.
80
+ * @param {boolean} [options.leading=false]
81
+ * Specify invoking on the leading edge of the timeout.
82
+ * @param {number} [options.maxWait]
83
+ * The maximum time `func` is allowed to be delayed before it's invoked.
84
+ * @param {boolean} [options.trailing=true]
85
+ * Specify invoking on the trailing edge of the timeout.
86
+ * @returns {Function} Returns the new debounced function.
87
+ * @example
88
+ *
89
+ * // Avoid costly calculations while the window size is in flux.
90
+ * jQuery(window).on('resize', debounce(calculateLayout, 150))
91
+ *
92
+ * // Invoke `sendMail` when clicked, debouncing subsequent calls.
93
+ * jQuery(element).on('click', debounce(sendMail, 300, {
94
+ * 'leading': true,
95
+ * 'trailing': false
96
+ * }))
97
+ *
98
+ * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
99
+ * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
100
+ * const source = new EventSource('/stream')
101
+ * jQuery(source).on('message', debounced)
102
+ *
103
+ * // Cancel the trailing debounced invocation.
104
+ * jQuery(window).on('popstate', debounced.cancel)
105
+ *
106
+ * // Check for pending invocations.
107
+ * const status = debounced.pending() ? "Pending..." : "Ready"
108
+ */
109
+ function debounce(func, wait = 0, options = {}) {
110
+ let lastArgs, lastThis, maxWait, result, timerId, lastCallTime;
111
+ let lastInvokeTime = 0;
112
+ let leading = false;
113
+ let maxing = false;
114
+ let trailing = true;
115
+ // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
116
+ const useRAF = !wait && wait !== 0 && typeof root.requestAnimationFrame === "function";
117
+ if (typeof func !== "function") {
118
+ throw new TypeError("Expected a function");
119
+ }
120
+ wait = +wait || 0;
121
+ if (isObject(options)) {
122
+ leading = !!options.leading;
123
+ maxing = "maxWait" in options;
124
+ maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
125
+ trailing = "trailing" in options ? !!options.trailing : trailing;
126
+ }
127
+ function invokeFunc(time) {
128
+ const args = lastArgs;
129
+ const thisArg = lastThis;
130
+ lastArgs = lastThis = undefined;
131
+ lastInvokeTime = time;
132
+ result = func.apply(thisArg, args);
133
+ return result;
134
+ }
135
+ function startTimer(pendingFunc, wait) {
136
+ if (useRAF) {
137
+ root.cancelAnimationFrame(timerId);
138
+ return root.requestAnimationFrame(pendingFunc);
139
+ }
140
+ return setTimeout(pendingFunc, wait);
141
+ }
142
+ function cancelTimer(id) {
143
+ if (useRAF) {
144
+ return root.cancelAnimationFrame(id);
145
+ }
146
+ clearTimeout(id);
147
+ }
148
+ function leadingEdge(time) {
149
+ // Reset any `maxWait` timer.
150
+ lastInvokeTime = time;
151
+ // Start the timer for the trailing edge.
152
+ timerId = startTimer(timerExpired, wait);
153
+ // Invoke the leading edge.
154
+ return leading ? invokeFunc(time) : result;
155
+ }
156
+ function remainingWait(time) {
157
+ const timeSinceLastCall = time - lastCallTime;
158
+ const timeSinceLastInvoke = time - lastInvokeTime;
159
+ const timeWaiting = wait - timeSinceLastCall;
160
+ return maxing
161
+ ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
162
+ : timeWaiting;
163
+ }
164
+ function shouldInvoke(time) {
165
+ const timeSinceLastCall = time - lastCallTime;
166
+ const timeSinceLastInvoke = time - lastInvokeTime;
167
+ // Either this is the first call, activity has stopped and we're at the
168
+ // trailing edge, the system time has gone backwards and we're treating
169
+ // it as the trailing edge, or we've hit the `maxWait` limit.
170
+ return (lastCallTime === undefined ||
171
+ timeSinceLastCall >= wait ||
172
+ timeSinceLastCall < 0 ||
173
+ (maxing && timeSinceLastInvoke >= maxWait));
174
+ }
175
+ function timerExpired() {
176
+ const time = Date.now();
177
+ if (shouldInvoke(time)) {
178
+ return trailingEdge(time);
179
+ }
180
+ // Restart the timer.
181
+ timerId = startTimer(timerExpired, remainingWait(time));
182
+ }
183
+ function trailingEdge(time) {
184
+ timerId = undefined;
185
+ // Only invoke if we have `lastArgs` which means `func` has been
186
+ // debounced at least once.
187
+ if (trailing && lastArgs) {
188
+ return invokeFunc(time);
189
+ }
190
+ lastArgs = lastThis = undefined;
191
+ return result;
192
+ }
193
+ function cancel() {
194
+ if (timerId !== undefined) {
195
+ cancelTimer(timerId);
196
+ }
197
+ lastInvokeTime = 0;
198
+ lastArgs = lastCallTime = lastThis = timerId = undefined;
199
+ }
200
+ function flush() {
201
+ return timerId === undefined ? result : trailingEdge(Date.now());
202
+ }
203
+ function pending() {
204
+ return timerId !== undefined;
205
+ }
206
+ function debounced(...args) {
207
+ const time = Date.now();
208
+ const isInvoking = shouldInvoke(time);
209
+ lastArgs = args;
210
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
211
+ lastThis = this;
212
+ lastCallTime = time;
213
+ if (isInvoking) {
214
+ if (timerId === undefined) {
215
+ return leadingEdge(lastCallTime);
216
+ }
217
+ if (maxing) {
218
+ // Handle invocations in a tight loop.
219
+ timerId = startTimer(timerExpired, wait);
220
+ return invokeFunc(lastCallTime);
221
+ }
222
+ }
223
+ if (timerId === undefined) {
224
+ timerId = startTimer(timerExpired, wait);
225
+ }
226
+ return result;
227
+ }
228
+ debounced.cancel = cancel;
229
+ debounced.flush = flush;
230
+ debounced.pending = pending;
231
+ return debounced;
232
+ }
233
+ /**
234
+ * Creates a throttled function that only invokes `func` at most once per
235
+ * every `wait` milliseconds (or once per browser frame). The throttled function
236
+ * comes with a `cancel` method to cancel delayed `func` invocations and a
237
+ * `flush` method to immediately invoke them. Provide `options` to indicate
238
+ * whether `func` should be invoked on the leading and/or trailing edge of the
239
+ * `wait` timeout. The `func` is invoked with the last arguments provided to the
240
+ * throttled function. Subsequent calls to the throttled function return the
241
+ * result of the last `func` invocation.
242
+ *
243
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
244
+ * invoked on the trailing edge of the timeout only if the throttled function
245
+ * is invoked more than once during the `wait` timeout.
246
+ *
247
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
248
+ * until the next tick, similar to `setTimeout` with a timeout of `0`.
249
+ *
250
+ * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
251
+ * invocation will be deferred until the next frame is drawn (typically about
252
+ * 16ms).
253
+ *
254
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
255
+ * for details over the differences between `throttle` and `debounce`.
256
+ *
257
+ * @since 0.1.0
258
+ * @category Function
259
+ * @param {Function} func The function to throttle.
260
+ * @param {number} [wait=0]
261
+ * The number of milliseconds to throttle invocations to; if omitted,
262
+ * `requestAnimationFrame` is used (if available).
263
+ * @param {Object} [options={}] The options object.
264
+ * @param {boolean} [options.leading=true]
265
+ * Specify invoking on the leading edge of the timeout.
266
+ * @param {boolean} [options.trailing=true]
267
+ * Specify invoking on the trailing edge of the timeout.
268
+ * @returns {Function} Returns the new throttled function.
269
+ * @example
270
+ *
271
+ * // Avoid excessively updating the position while scrolling.
272
+ * jQuery(window).on('scroll', throttle(updatePosition, 100))
273
+ *
274
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
275
+ * const throttled = throttle(renewToken, 300000, { 'trailing': false })
276
+ * jQuery(element).on('click', throttled)
277
+ *
278
+ * // Cancel the trailing throttled invocation.
279
+ * jQuery(window).on('popstate', throttled.cancel)
280
+ */
281
+ function throttle(func, wait = 0, options = {}) {
282
+ let leading = true;
283
+ let trailing = true;
284
+ if (typeof func !== "function") {
285
+ throw new TypeError("Expected a function");
286
+ }
287
+ if (isObject(options)) {
288
+ leading = "leading" in options ? !!options.leading : leading;
289
+ trailing = "trailing" in options ? !!options.trailing : trailing;
290
+ }
291
+ return debounce(func, wait, {
292
+ leading,
293
+ trailing,
294
+ maxWait: wait,
295
+ });
296
+ }
297
+
1
298
  /*!
2
299
  * Wunderbaum - util
3
300
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
4
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
301
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
5
302
  */
6
303
  /** @module util */
7
304
  /** Readable names for `MouseEvent.button` */
@@ -27,8 +324,21 @@ const ENTITY_MAP = {
27
324
  "'": "&#39;",
28
325
  "/": "&#x2F;",
29
326
  };
327
+ /** A generic error that can be thrown to indicate a validation error when
328
+ * handling the `apply` event for a node title or the `change` event for a
329
+ * grid cell.
330
+ */
331
+ class ValidationError extends Error {
332
+ constructor(message) {
333
+ super(message);
334
+ this.name = "ValidationError";
335
+ }
336
+ }
30
337
  /**
31
338
  * A ES6 Promise, that exposes the resolve()/reject() methods.
339
+ *
340
+ * TODO: See [Promise.withResolvers()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers#description)
341
+ * , a proposed standard, but not yet implemented in any browser.
32
342
  */
33
343
  let Deferred$1 = class Deferred {
34
344
  constructor() {
@@ -360,27 +670,29 @@ function elemFromHtml(html) {
360
670
  t.innerHTML = html.trim();
361
671
  return t.content.firstElementChild;
362
672
  }
363
- const _IGNORE_KEYS = new Set(["Alt", "Control", "Meta", "Shift"]);
364
- /** Return a HtmlElement from selector or cast an existing element. */
365
- function elemFromSelector(obj) {
366
- if (!obj) {
367
- return null; //(null as unknown) as HTMLElement;
368
- }
369
- if (typeof obj === "string") {
370
- return document.querySelector(obj);
371
- }
372
- return obj;
373
- }
374
- /** Return a EventTarget from selector or cast an existing element. */
375
- function eventTargetFromSelector(obj) {
673
+ const _IGNORE_KEYS = new Set(["Alt", "Control", "Meta", "Shift"]);
674
+ /** Return a HtmlElement from selector or cast an existing element. */
675
+ function elemFromSelector(obj) {
376
676
  if (!obj) {
377
- return null;
677
+ return null; //(null as unknown) as HTMLElement;
378
678
  }
379
679
  if (typeof obj === "string") {
380
680
  return document.querySelector(obj);
381
681
  }
382
682
  return obj;
383
683
  }
684
+ // /** Return a EventTarget from selector or cast an existing element. */
685
+ // export function eventTargetFromSelector(
686
+ // obj: string | EventTarget
687
+ // ): EventTarget | null {
688
+ // if (!obj) {
689
+ // return null;
690
+ // }
691
+ // if (typeof obj === "string") {
692
+ // return document.querySelector(obj) as EventTarget;
693
+ // }
694
+ // return obj as EventTarget;
695
+ // }
384
696
  /**
385
697
  * Return a canonical descriptive string for a keyboard or mouse event.
386
698
  *
@@ -478,7 +790,8 @@ function isPlainObject(obj) {
478
790
  function noop(...args) { }
479
791
  function onEvent(rootTarget, eventNames, selectorOrHandler, handlerOrNone) {
480
792
  let selector, handler;
481
- rootTarget = eventTargetFromSelector(rootTarget);
793
+ rootTarget = elemFromSelector(rootTarget);
794
+ // rootTarget = eventTargetFromSelector<EventTarget>(rootTarget)!;
482
795
  if (handlerOrNone) {
483
796
  selector = selectorOrHandler;
484
797
  handler = handlerOrNone;
@@ -669,8 +982,6 @@ function type(obj) {
669
982
  * ```
670
983
  */
671
984
  function adaptiveThrottle(callback, options) {
672
- let waiting = 0; // Initially, we're not waiting
673
- let pendingArgs = null;
674
985
  const opts = Object.assign({
675
986
  minDelay: 16,
676
987
  defaultDelay: 200,
@@ -679,6 +990,9 @@ function adaptiveThrottle(callback, options) {
679
990
  }, options);
680
991
  const minDelay = Math.max(16, +opts.minDelay);
681
992
  const maxDelay = +opts.maxDelay;
993
+ let waiting = 0; // Initially, we're not waiting
994
+ let pendingArgs = null;
995
+ let pendingTimer = null;
682
996
  const throttledFn = (...args) => {
683
997
  if (waiting) {
684
998
  pendingArgs = args;
@@ -705,9 +1019,10 @@ function adaptiveThrottle(callback, options) {
705
1019
  // `adaptiveThrottle() calling worker took ${elap}ms. delay = ${curDelay}ms, using ${useDelay}ms`,
706
1020
  // pendingArgs
707
1021
  // );
708
- setTimeout(() => {
1022
+ pendingTimer = setTimeout(() => {
709
1023
  // Unblock, and trigger pending requests if any
710
1024
  // const skipped = waiting - 1;
1025
+ pendingTimer = null;
711
1026
  waiting = 0; // And allow future invocations
712
1027
  if (pendingArgs != null) {
713
1028
  // There was another request while running or waiting
@@ -720,52 +1035,68 @@ function adaptiveThrottle(callback, options) {
720
1035
  }, useDelay);
721
1036
  }
722
1037
  };
1038
+ throttledFn.cancel = () => {
1039
+ if (pendingTimer) {
1040
+ clearTimeout(pendingTimer);
1041
+ pendingTimer = null;
1042
+ }
1043
+ pendingArgs = null;
1044
+ waiting = 0;
1045
+ };
1046
+ throttledFn.pending = () => {
1047
+ return !!pendingTimer;
1048
+ };
1049
+ throttledFn.flush = () => {
1050
+ throw new Error("Not implemented");
1051
+ };
723
1052
  return throttledFn;
724
1053
  }
725
1054
 
726
1055
  var util = /*#__PURE__*/Object.freeze({
727
- __proto__: null,
728
- Deferred: Deferred$1,
729
- MAX_INT: MAX_INT,
730
- MOUSE_BUTTONS: MOUSE_BUTTONS,
731
- adaptiveThrottle: adaptiveThrottle,
732
- assert: assert,
733
- documentReady: documentReady,
734
- documentReadyPromise: documentReadyPromise,
735
- each: each,
736
- elemFromHtml: elemFromHtml,
737
- elemFromSelector: elemFromSelector,
738
- error: error,
739
- escapeHtml: escapeHtml,
740
- escapeRegex: escapeRegex,
741
- escapeTooltip: escapeTooltip,
742
- eventTargetFromSelector: eventTargetFromSelector,
743
- eventToString: eventToString,
744
- extend: extend,
745
- extractHtmlText: extractHtmlText,
746
- getOption: getOption,
747
- getValueFromElem: getValueFromElem,
748
- isArray: isArray,
749
- isEmptyObject: isEmptyObject,
750
- isFunction: isFunction,
751
- isMac: isMac,
752
- isPlainObject: isPlainObject,
753
- noop: noop,
754
- onEvent: onEvent,
755
- overrideMethod: overrideMethod,
756
- setElemDisplay: setElemDisplay,
757
- setTimeoutPromise: setTimeoutPromise,
758
- setValueToElem: setValueToElem,
759
- sleep: sleep,
760
- toSet: toSet,
761
- toggleCheckbox: toggleCheckbox,
762
- type: type
1056
+ __proto__: null,
1057
+ Deferred: Deferred$1,
1058
+ MAX_INT: MAX_INT,
1059
+ MOUSE_BUTTONS: MOUSE_BUTTONS,
1060
+ ValidationError: ValidationError,
1061
+ adaptiveThrottle: adaptiveThrottle,
1062
+ assert: assert,
1063
+ debounce: debounce,
1064
+ documentReady: documentReady,
1065
+ documentReadyPromise: documentReadyPromise,
1066
+ each: each,
1067
+ elemFromHtml: elemFromHtml,
1068
+ elemFromSelector: elemFromSelector,
1069
+ error: error,
1070
+ escapeHtml: escapeHtml,
1071
+ escapeRegex: escapeRegex,
1072
+ escapeTooltip: escapeTooltip,
1073
+ eventToString: eventToString,
1074
+ extend: extend,
1075
+ extractHtmlText: extractHtmlText,
1076
+ getOption: getOption,
1077
+ getValueFromElem: getValueFromElem,
1078
+ isArray: isArray,
1079
+ isEmptyObject: isEmptyObject,
1080
+ isFunction: isFunction,
1081
+ isMac: isMac,
1082
+ isPlainObject: isPlainObject,
1083
+ noop: noop,
1084
+ onEvent: onEvent,
1085
+ overrideMethod: overrideMethod,
1086
+ setElemDisplay: setElemDisplay,
1087
+ setTimeoutPromise: setTimeoutPromise,
1088
+ setValueToElem: setValueToElem,
1089
+ sleep: sleep,
1090
+ throttle: throttle,
1091
+ toSet: toSet,
1092
+ toggleCheckbox: toggleCheckbox,
1093
+ type: type
763
1094
  });
764
1095
 
765
1096
  /*!
766
1097
  * Wunderbaum - types
767
1098
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
768
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1099
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
769
1100
  */
770
1101
  /**
771
1102
  * Possible values for {@link WunderbaumNode.update()} and {@link Wunderbaum.update()}.
@@ -829,363 +1160,66 @@ var NavModeEnum;
829
1160
  /*!
830
1161
  * Wunderbaum - wb_extension_base
831
1162
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
832
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1163
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
833
1164
  */
834
1165
  class WunderbaumExtension {
835
1166
  constructor(tree, id, defaults) {
836
1167
  this.enabled = true;
837
- this.tree = tree;
838
- this.id = id;
839
- this.treeOpts = tree.options;
840
- const opts = tree.options;
841
- if (this.treeOpts[id] === undefined) {
842
- opts[id] = this.extensionOpts = extend({}, defaults);
843
- }
844
- else {
845
- // TODO: do we break existing object instance references here?
846
- this.extensionOpts = extend({}, defaults, opts[id]);
847
- opts[id] = this.extensionOpts;
848
- }
849
- this.enabled = this.getPluginOption("enabled", true);
850
- }
851
- /** Called on tree (re)init after all extensions are added, but before loading.*/
852
- init() {
853
- this.tree.element.classList.add("wb-ext-" + this.id);
854
- }
855
- // protected callEvent(type: string, extra?: any): any {
856
- // let func = this.extensionOpts[type];
857
- // if (func) {
858
- // return func.call(
859
- // this.tree,
860
- // util.extend(
861
- // {
862
- // event: this.id + "." + type,
863
- // },
864
- // extra
865
- // )
866
- // );
867
- // }
868
- // }
869
- getPluginOption(name, defaultValue) {
870
- var _a;
871
- return (_a = this.extensionOpts[name]) !== null && _a !== void 0 ? _a : defaultValue;
872
- }
873
- setPluginOption(name, value) {
874
- this.extensionOpts[name] = value;
875
- }
876
- setEnabled(flag = true) {
877
- return this.setPluginOption("enabled", !!flag);
878
- // this.enabled = !!flag;
879
- }
880
- onKeyEvent(data) {
881
- return;
882
- }
883
- onRender(data) {
884
- return;
885
- }
886
- }
887
-
888
- /*!
889
- * debounce & throttle, taken from https://github.com/lodash/lodash v4.17.21
890
- * MIT License: https://raw.githubusercontent.com/lodash/lodash/4.17.21-npm/LICENSE
891
- * Modified for TypeScript type annotations.
892
- */
893
- /* --- */
894
- /** Detect free variable `global` from Node.js. */
895
- const freeGlobal = typeof global === "object" &&
896
- global !== null &&
897
- global.Object === Object &&
898
- global;
899
- /** Detect free variable `globalThis` */
900
- const freeGlobalThis = typeof globalThis === "object" &&
901
- globalThis !== null &&
902
- globalThis.Object == Object &&
903
- globalThis;
904
- /** Detect free variable `self`. */
905
- const freeSelf = typeof self === "object" && self !== null && self.Object === Object && self;
906
- /** Used as a reference to the global object. */
907
- const root = freeGlobalThis || freeGlobal || freeSelf || Function("return this")();
908
- /**
909
- * Checks if `value` is the
910
- * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
911
- * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
912
- *
913
- * @since 0.1.0
914
- * @category Lang
915
- * @param {*} value The value to check.
916
- * @returns {boolean} Returns `true` if `value` is an object, else `false`.
917
- * @example
918
- *
919
- * isObject({})
920
- * // => true
921
- *
922
- * isObject([1, 2, 3])
923
- * // => true
924
- *
925
- * isObject(Function)
926
- * // => true
927
- *
928
- * isObject(null)
929
- * // => false
930
- */
931
- function isObject(value) {
932
- const type = typeof value;
933
- return value != null && (type === "object" || type === "function");
934
- }
935
- /**
936
- * Creates a debounced function that delays invoking `func` until after `wait`
937
- * milliseconds have elapsed since the last time the debounced function was
938
- * invoked, or until the next browser frame is drawn. The debounced function
939
- * comes with a `cancel` method to cancel delayed `func` invocations and a
940
- * `flush` method to immediately invoke them. Provide `options` to indicate
941
- * whether `func` should be invoked on the leading and/or trailing edge of the
942
- * `wait` timeout. The `func` is invoked with the last arguments provided to the
943
- * debounced function. Subsequent calls to the debounced function return the
944
- * result of the last `func` invocation.
945
- *
946
- * **Note:** If `leading` and `trailing` options are `true`, `func` is
947
- * invoked on the trailing edge of the timeout only if the debounced function
948
- * is invoked more than once during the `wait` timeout.
949
- *
950
- * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
951
- * until the next tick, similar to `setTimeout` with a timeout of `0`.
952
- *
953
- * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
954
- * invocation will be deferred until the next frame is drawn (typically about
955
- * 16ms).
956
- *
957
- * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
958
- * for details over the differences between `debounce` and `throttle`.
959
- *
960
- * @since 0.1.0
961
- * @category Function
962
- * @param {Function} func The function to debounce.
963
- * @param {number} [wait=0]
964
- * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is
965
- * used (if available).
966
- * @param {Object} [options={}] The options object.
967
- * @param {boolean} [options.leading=false]
968
- * Specify invoking on the leading edge of the timeout.
969
- * @param {number} [options.maxWait]
970
- * The maximum time `func` is allowed to be delayed before it's invoked.
971
- * @param {boolean} [options.trailing=true]
972
- * Specify invoking on the trailing edge of the timeout.
973
- * @returns {Function} Returns the new debounced function.
974
- * @example
975
- *
976
- * // Avoid costly calculations while the window size is in flux.
977
- * jQuery(window).on('resize', debounce(calculateLayout, 150))
978
- *
979
- * // Invoke `sendMail` when clicked, debouncing subsequent calls.
980
- * jQuery(element).on('click', debounce(sendMail, 300, {
981
- * 'leading': true,
982
- * 'trailing': false
983
- * }))
984
- *
985
- * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
986
- * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
987
- * const source = new EventSource('/stream')
988
- * jQuery(source).on('message', debounced)
989
- *
990
- * // Cancel the trailing debounced invocation.
991
- * jQuery(window).on('popstate', debounced.cancel)
992
- *
993
- * // Check for pending invocations.
994
- * const status = debounced.pending() ? "Pending..." : "Ready"
995
- */
996
- function debounce(func, wait = 0, options = {}) {
997
- let lastArgs, lastThis, maxWait, result, timerId, lastCallTime;
998
- let lastInvokeTime = 0;
999
- let leading = false;
1000
- let maxing = false;
1001
- let trailing = true;
1002
- // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
1003
- const useRAF = !wait && wait !== 0 && typeof root.requestAnimationFrame === "function";
1004
- if (typeof func !== "function") {
1005
- throw new TypeError("Expected a function");
1006
- }
1007
- wait = +wait || 0;
1008
- if (isObject(options)) {
1009
- leading = !!options.leading;
1010
- maxing = "maxWait" in options;
1011
- maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
1012
- trailing = "trailing" in options ? !!options.trailing : trailing;
1013
- }
1014
- function invokeFunc(time) {
1015
- const args = lastArgs;
1016
- const thisArg = lastThis;
1017
- lastArgs = lastThis = undefined;
1018
- lastInvokeTime = time;
1019
- result = func.apply(thisArg, args);
1020
- return result;
1021
- }
1022
- function startTimer(pendingFunc, wait) {
1023
- if (useRAF) {
1024
- root.cancelAnimationFrame(timerId);
1025
- return root.requestAnimationFrame(pendingFunc);
1026
- }
1027
- return setTimeout(pendingFunc, wait);
1028
- }
1029
- function cancelTimer(id) {
1030
- if (useRAF) {
1031
- return root.cancelAnimationFrame(id);
1032
- }
1033
- clearTimeout(id);
1034
- }
1035
- function leadingEdge(time) {
1036
- // Reset any `maxWait` timer.
1037
- lastInvokeTime = time;
1038
- // Start the timer for the trailing edge.
1039
- timerId = startTimer(timerExpired, wait);
1040
- // Invoke the leading edge.
1041
- return leading ? invokeFunc(time) : result;
1042
- }
1043
- function remainingWait(time) {
1044
- const timeSinceLastCall = time - lastCallTime;
1045
- const timeSinceLastInvoke = time - lastInvokeTime;
1046
- const timeWaiting = wait - timeSinceLastCall;
1047
- return maxing
1048
- ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
1049
- : timeWaiting;
1050
- }
1051
- function shouldInvoke(time) {
1052
- const timeSinceLastCall = time - lastCallTime;
1053
- const timeSinceLastInvoke = time - lastInvokeTime;
1054
- // Either this is the first call, activity has stopped and we're at the
1055
- // trailing edge, the system time has gone backwards and we're treating
1056
- // it as the trailing edge, or we've hit the `maxWait` limit.
1057
- return (lastCallTime === undefined ||
1058
- timeSinceLastCall >= wait ||
1059
- timeSinceLastCall < 0 ||
1060
- (maxing && timeSinceLastInvoke >= maxWait));
1061
- }
1062
- function timerExpired() {
1063
- const time = Date.now();
1064
- if (shouldInvoke(time)) {
1065
- return trailingEdge(time);
1066
- }
1067
- // Restart the timer.
1068
- timerId = startTimer(timerExpired, remainingWait(time));
1069
- }
1070
- function trailingEdge(time) {
1071
- timerId = undefined;
1072
- // Only invoke if we have `lastArgs` which means `func` has been
1073
- // debounced at least once.
1074
- if (trailing && lastArgs) {
1075
- return invokeFunc(time);
1168
+ this.tree = tree;
1169
+ this.id = id;
1170
+ this.treeOpts = tree.options;
1171
+ const opts = tree.options;
1172
+ if (this.treeOpts[id] === undefined) {
1173
+ opts[id] = this.extensionOpts = extend({}, defaults);
1076
1174
  }
1077
- lastArgs = lastThis = undefined;
1078
- return result;
1079
- }
1080
- function cancel() {
1081
- if (timerId !== undefined) {
1082
- cancelTimer(timerId);
1175
+ else {
1176
+ // TODO: do we break existing object instance references here?
1177
+ this.extensionOpts = extend({}, defaults, opts[id]);
1178
+ opts[id] = this.extensionOpts;
1083
1179
  }
1084
- lastInvokeTime = 0;
1085
- lastArgs = lastCallTime = lastThis = timerId = undefined;
1180
+ this.enabled = this.getPluginOption("enabled", true);
1086
1181
  }
1087
- function flush() {
1088
- return timerId === undefined ? result : trailingEdge(Date.now());
1182
+ /** Called on tree (re)init after all extensions are added, but before loading.*/
1183
+ init() {
1184
+ this.tree.element.classList.add("wb-ext-" + this.id);
1089
1185
  }
1090
- function pending() {
1091
- return timerId !== undefined;
1186
+ // protected callEvent(type: string, extra?: any): any {
1187
+ // let func = this.extensionOpts[type];
1188
+ // if (func) {
1189
+ // return func.call(
1190
+ // this.tree,
1191
+ // util.extend(
1192
+ // {
1193
+ // event: this.id + "." + type,
1194
+ // },
1195
+ // extra
1196
+ // )
1197
+ // );
1198
+ // }
1199
+ // }
1200
+ getPluginOption(name, defaultValue) {
1201
+ var _a;
1202
+ return (_a = this.extensionOpts[name]) !== null && _a !== void 0 ? _a : defaultValue;
1092
1203
  }
1093
- function debounced(...args) {
1094
- const time = Date.now();
1095
- const isInvoking = shouldInvoke(time);
1096
- lastArgs = args;
1097
- // eslint-disable-next-line @typescript-eslint/no-this-alias
1098
- lastThis = this;
1099
- lastCallTime = time;
1100
- if (isInvoking) {
1101
- if (timerId === undefined) {
1102
- return leadingEdge(lastCallTime);
1103
- }
1104
- if (maxing) {
1105
- // Handle invocations in a tight loop.
1106
- timerId = startTimer(timerExpired, wait);
1107
- return invokeFunc(lastCallTime);
1108
- }
1109
- }
1110
- if (timerId === undefined) {
1111
- timerId = startTimer(timerExpired, wait);
1112
- }
1113
- return result;
1204
+ setPluginOption(name, value) {
1205
+ this.extensionOpts[name] = value;
1114
1206
  }
1115
- debounced.cancel = cancel;
1116
- debounced.flush = flush;
1117
- debounced.pending = pending;
1118
- return debounced;
1119
- }
1120
- /**
1121
- * Creates a throttled function that only invokes `func` at most once per
1122
- * every `wait` milliseconds (or once per browser frame). The throttled function
1123
- * comes with a `cancel` method to cancel delayed `func` invocations and a
1124
- * `flush` method to immediately invoke them. Provide `options` to indicate
1125
- * whether `func` should be invoked on the leading and/or trailing edge of the
1126
- * `wait` timeout. The `func` is invoked with the last arguments provided to the
1127
- * throttled function. Subsequent calls to the throttled function return the
1128
- * result of the last `func` invocation.
1129
- *
1130
- * **Note:** If `leading` and `trailing` options are `true`, `func` is
1131
- * invoked on the trailing edge of the timeout only if the throttled function
1132
- * is invoked more than once during the `wait` timeout.
1133
- *
1134
- * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
1135
- * until the next tick, similar to `setTimeout` with a timeout of `0`.
1136
- *
1137
- * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
1138
- * invocation will be deferred until the next frame is drawn (typically about
1139
- * 16ms).
1140
- *
1141
- * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
1142
- * for details over the differences between `throttle` and `debounce`.
1143
- *
1144
- * @since 0.1.0
1145
- * @category Function
1146
- * @param {Function} func The function to throttle.
1147
- * @param {number} [wait=0]
1148
- * The number of milliseconds to throttle invocations to; if omitted,
1149
- * `requestAnimationFrame` is used (if available).
1150
- * @param {Object} [options={}] The options object.
1151
- * @param {boolean} [options.leading=true]
1152
- * Specify invoking on the leading edge of the timeout.
1153
- * @param {boolean} [options.trailing=true]
1154
- * Specify invoking on the trailing edge of the timeout.
1155
- * @returns {Function} Returns the new throttled function.
1156
- * @example
1157
- *
1158
- * // Avoid excessively updating the position while scrolling.
1159
- * jQuery(window).on('scroll', throttle(updatePosition, 100))
1160
- *
1161
- * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
1162
- * const throttled = throttle(renewToken, 300000, { 'trailing': false })
1163
- * jQuery(element).on('click', throttled)
1164
- *
1165
- * // Cancel the trailing throttled invocation.
1166
- * jQuery(window).on('popstate', throttled.cancel)
1167
- */
1168
- function throttle(func, wait = 0, options = {}) {
1169
- let leading = true;
1170
- let trailing = true;
1171
- if (typeof func !== "function") {
1172
- throw new TypeError("Expected a function");
1207
+ setEnabled(flag = true) {
1208
+ return this.setPluginOption("enabled", !!flag);
1209
+ // this.enabled = !!flag;
1173
1210
  }
1174
- if (isObject(options)) {
1175
- leading = "leading" in options ? !!options.leading : leading;
1176
- trailing = "trailing" in options ? !!options.trailing : trailing;
1211
+ onKeyEvent(data) {
1212
+ return;
1213
+ }
1214
+ onRender(data) {
1215
+ return;
1177
1216
  }
1178
- return debounce(func, wait, {
1179
- leading,
1180
- trailing,
1181
- maxWait: wait,
1182
- });
1183
1217
  }
1184
1218
 
1185
1219
  /*!
1186
1220
  * Wunderbaum - ext-filter
1187
1221
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1188
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1222
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
1189
1223
  */
1190
1224
  const START_MARKER = "\uFFF7";
1191
1225
  const END_MARKER = "\uFFF8";
@@ -1367,7 +1401,12 @@ class FilterExtension extends WunderbaumExtension {
1367
1401
  });
1368
1402
  treeOpts.autoCollapse = prevAutoCollapse;
1369
1403
  if (count === 0 && opts.noData && hideMode) {
1370
- tree.root.setStatus(NodeStatusType.noData);
1404
+ if (typeof opts.noData === "string") {
1405
+ tree.root.setStatus(NodeStatusType.noData, { message: opts.noData });
1406
+ }
1407
+ else {
1408
+ tree.root.setStatus(NodeStatusType.noData);
1409
+ }
1371
1410
  }
1372
1411
  // Redraw whole tree
1373
1412
  tree.logInfo(`Filter '${match}' found ${count} nodes in ${Date.now() - start} ms.`);
@@ -1485,7 +1524,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1485
1524
  /*!
1486
1525
  * Wunderbaum - ext-keynav
1487
1526
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1488
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1527
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
1489
1528
  */
1490
1529
  const QUICKSEARCH_DELAY = 500;
1491
1530
  class KeynavExtension extends WunderbaumExtension {
@@ -1507,6 +1546,13 @@ class KeynavExtension extends WunderbaumExtension {
1507
1546
  }
1508
1547
  return input;
1509
1548
  }
1549
+ // /* Return the current cell's embedded input that has keyboard focus. */
1550
+ // protected _getFocusedInputElem(): HTMLInputElement | null {
1551
+ // const ace = this.tree
1552
+ // .getActiveColElem()
1553
+ // ?.querySelector<HTMLInputElement>("input:focus,select:focus");
1554
+ // return ace || null;
1555
+ // }
1510
1556
  /* Return true if the current cell's embedded input has keyboard focus. */
1511
1557
  _isCurInputFocused() {
1512
1558
  var _a;
@@ -1522,7 +1568,6 @@ class KeynavExtension extends WunderbaumExtension {
1522
1568
  const curInput = this._getEmbeddedInputElem(event.target);
1523
1569
  const inputHasFocus = curInput && this._isCurInputFocused();
1524
1570
  const navModeOption = opts.navigationModeOption;
1525
- // isCellEditMode = tree.navMode === NavigationMode.cellEdit;
1526
1571
  let focusNode, eventName = eventToString(event), node = data.node, handled = true;
1527
1572
  // tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
1528
1573
  if (!tree.isEnabled()) {
@@ -1667,22 +1712,34 @@ class KeynavExtension extends WunderbaumExtension {
1667
1712
  }
1668
1713
  }
1669
1714
  else {
1670
- const curInput = this._getEmbeddedInputElem(null);
1715
+ // -----------------------------------------------------------------------
1716
+ // --- Cell Mode ---
1717
+ // -----------------------------------------------------------------------
1718
+ // // Standard navigation (cell mode)
1719
+ // if (isCellEditMode && INPUT_BREAKOUT_KEYS.has(eventName)) {
1720
+ // }
1721
+ // const curInput = this._getEmbeddedInputElem(null);
1671
1722
  const curInputType = curInput ? curInput.type || curInput.tagName : "";
1672
- const inputHasFocus = curInput && this._isCurInputFocused();
1723
+ // const inputHasFocus = curInput && this._isCurInputFocused();
1673
1724
  const inputCanFocus = curInput && curInputType !== "checkbox";
1674
1725
  if (inputHasFocus) {
1675
1726
  if (eventName === "Escape") {
1676
- // Discard changes
1727
+ node.logDebug(`Reset focused input on Escape`);
1728
+ // Discard changes and reset input validation state
1729
+ curInput.setCustomValidity("");
1677
1730
  node._render();
1678
1731
  // Keep cell-nav mode
1679
- node.logDebug(`Reset focused input`);
1680
1732
  tree.setFocus();
1681
1733
  tree.setColumn(tree.activeColIdx);
1682
1734
  return;
1683
1735
  // } else if (!INPUT_BREAKOUT_KEYS.has(eventName)) {
1684
1736
  }
1685
1737
  else if (eventName !== "Enter") {
1738
+ if (curInput && curInput.checkValidity && !curInput.checkValidity()) {
1739
+ // Invalid input: ignore all keys except Enter and Escape
1740
+ node.logDebug(`Ignored ${eventName} inside invalid input`);
1741
+ return false;
1742
+ }
1686
1743
  // Let current `<input>` handle it
1687
1744
  node.logDebug(`Ignored ${eventName} inside focused input`);
1688
1745
  return;
@@ -1697,9 +1754,10 @@ class KeynavExtension extends WunderbaumExtension {
1697
1754
  else if (curInput) {
1698
1755
  // On a cell that has an embedded, unfocused <input>
1699
1756
  if (eventName.length === 1 && inputCanFocus) {
1757
+ // Typing a single char
1700
1758
  curInput.focus();
1701
1759
  curInput.value = "";
1702
- node.logDebug(`Focus imput: ${eventName}`);
1760
+ node.logDebug(`Focus input: ${eventName}`);
1703
1761
  return false;
1704
1762
  }
1705
1763
  }
@@ -1711,7 +1769,6 @@ class KeynavExtension extends WunderbaumExtension {
1711
1769
  eventName = tree.activeColIdx > 0 ? "ArrowLeft" : "";
1712
1770
  handled = true;
1713
1771
  }
1714
- else ;
1715
1772
  switch (eventName) {
1716
1773
  case "+":
1717
1774
  case "Add":
@@ -1831,7 +1888,7 @@ class KeynavExtension extends WunderbaumExtension {
1831
1888
  /*!
1832
1889
  * Wunderbaum - ext-logger
1833
1890
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1834
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1891
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
1835
1892
  */
1836
1893
  class LoggerExtension extends WunderbaumExtension {
1837
1894
  constructor(tree) {
@@ -1873,7 +1930,7 @@ class LoggerExtension extends WunderbaumExtension {
1873
1930
  /*!
1874
1931
  * Wunderbaum - common
1875
1932
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
1876
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
1933
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
1877
1934
  */
1878
1935
  const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
1879
1936
  /**
@@ -1992,7 +2049,7 @@ const KEY_TO_ACTION_DICT = {
1992
2049
  };
1993
2050
  /** Return a callback that returns true if the node title matches the string
1994
2051
  * or regular expression.
1995
- * @see {@link WunderbaumNode.findAll}
2052
+ * @see {@link WunderbaumNode.findAll()}
1996
2053
  */
1997
2054
  function makeNodeTitleMatcher(match) {
1998
2055
  if (match instanceof RegExp) {
@@ -2196,7 +2253,7 @@ function decompressSourceData(source) {
2196
2253
  /*!
2197
2254
  * Wunderbaum - ext-dnd
2198
2255
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2199
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
2256
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
2200
2257
  */
2201
2258
  const nodeMimeType = "application/x-wunderbaum-node";
2202
2259
  class DndExtension extends WunderbaumExtension {
@@ -2445,7 +2502,7 @@ class DndExtension extends WunderbaumExtension {
2445
2502
  if (e.type === "dragstart") {
2446
2503
  // Set a default definition of allowed effects
2447
2504
  e.dataTransfer.effectAllowed = dndOpts.effectAllowed; //"copyMove"; // "all";
2448
- if (srcNode.isEditing()) {
2505
+ if (srcNode.isEditingTitle()) {
2449
2506
  srcNode.logDebug("Prevented dragging node in edit mode.");
2450
2507
  e.preventDefault();
2451
2508
  return false;
@@ -2627,7 +2684,7 @@ class DndExtension extends WunderbaumExtension {
2627
2684
  /*!
2628
2685
  * Wunderbaum - drag_observer
2629
2686
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2630
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
2687
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
2631
2688
  */
2632
2689
  /**
2633
2690
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2763,7 +2820,7 @@ class DragObserver {
2763
2820
  /*!
2764
2821
  * Wunderbaum - ext-grid
2765
2822
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2766
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
2823
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
2767
2824
  */
2768
2825
  class GridExtension extends WunderbaumExtension {
2769
2826
  constructor(tree) {
@@ -2800,7 +2857,7 @@ class GridExtension extends WunderbaumExtension {
2800
2857
  /*!
2801
2858
  * Wunderbaum - deferred
2802
2859
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2803
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
2860
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
2804
2861
  */
2805
2862
  /**
2806
2863
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2824,27 +2881,27 @@ class Deferred {
2824
2881
  this._reject = reject;
2825
2882
  });
2826
2883
  }
2827
- /** Resolve the [[Promise]]. */
2884
+ /** Resolve the Promise. */
2828
2885
  resolve(value) {
2829
2886
  this._resolve(value);
2830
2887
  }
2831
- /** Reject the [[Promise]]. */
2888
+ /** Reject the Promise. */
2832
2889
  reject(reason) {
2833
2890
  this._reject(reason);
2834
2891
  }
2835
- /** Return the native [[Promise]] instance.*/
2892
+ /** Return the native Promise instance.*/
2836
2893
  promise() {
2837
2894
  return this._promise;
2838
2895
  }
2839
- /** Call [[Promise.then]] on the embedded promise instance.*/
2896
+ /** Call Promise.then on the embedded promise instance.*/
2840
2897
  then(cb) {
2841
2898
  return this._promise.then(cb);
2842
2899
  }
2843
- /** Call [[Promise.catch]] on the embedded promise instance.*/
2900
+ /** Call Promise.catch on the embedded promise instance.*/
2844
2901
  catch(cb) {
2845
2902
  return this._promise.catch(cb);
2846
2903
  }
2847
- /** Call [[Promise.finally]] on the embedded promise instance.*/
2904
+ /** Call Promise.finally on the embedded promise instance.*/
2848
2905
  finally(cb) {
2849
2906
  return this._promise.finally(cb);
2850
2907
  }
@@ -2853,7 +2910,7 @@ class Deferred {
2853
2910
  /*!
2854
2911
  * Wunderbaum - wunderbaum_node
2855
2912
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
2856
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
2913
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
2857
2914
  */
2858
2915
  /** WunderbaumNode properties that can be passed with source data.
2859
2916
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3154,6 +3211,10 @@ class WunderbaumNode {
3154
3211
  }
3155
3212
  }
3156
3213
  }
3214
+ /** Start editing this node's title. */
3215
+ startEditTitle() {
3216
+ this.tree._callMethod("edit.startEditTitle", this);
3217
+ }
3157
3218
  /** Call `setExpanded()` on all descendant nodes. */
3158
3219
  async expandAll(flag = true, options) {
3159
3220
  const tree = this.tree;
@@ -3363,6 +3424,22 @@ class WunderbaumNode {
3363
3424
  const colElems = (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.querySelectorAll("span.wb-col");
3364
3425
  return colElems ? colElems[colIdx] : null;
3365
3426
  }
3427
+ /**
3428
+ * Return all nodes with the same refKey.
3429
+ *
3430
+ * @param includeSelf Include this node itself.
3431
+ * @see {@link Wunderbaum.findByRefKey}
3432
+ */
3433
+ getCloneList(includeSelf = false) {
3434
+ if (!this.refKey) {
3435
+ return [];
3436
+ }
3437
+ const clones = this.tree.findByRefKey(this.refKey);
3438
+ if (includeSelf) {
3439
+ return clones;
3440
+ }
3441
+ return [...clones].filter((n) => n !== this);
3442
+ }
3366
3443
  /** Return the first child node or null.
3367
3444
  * @returns {WunderbaumNode | null}
3368
3445
  */
@@ -3467,17 +3544,22 @@ class WunderbaumNode {
3467
3544
  return this.tree.activeNode === this;
3468
3545
  }
3469
3546
  /** Return true if this node is a direct or indirect parent of `other`.
3470
- * (See also [[isParentOf]].)
3547
+ * @see {@link WunderbaumNode.isParentOf}
3471
3548
  */
3472
3549
  isAncestorOf(other) {
3473
3550
  return other && other.isDescendantOf(this);
3474
3551
  }
3475
3552
  /** Return true if this node is a **direct** subnode of `other`.
3476
- * (See also [[isDescendantOf]].)
3553
+ * @see {@link WunderbaumNode.isDescendantOf}
3477
3554
  */
3478
3555
  isChildOf(other) {
3479
3556
  return other && this.parent === other;
3480
3557
  }
3558
+ /** Return true if this node's refKey is used by at least one other node.
3559
+ */
3560
+ isClone() {
3561
+ return !!this.refKey && this.tree.findByRefKey(this.refKey).length > 1;
3562
+ }
3481
3563
  /** Return true if this node's title spans all columns, i.e. the node has no
3482
3564
  * grid cells.
3483
3565
  */
@@ -3485,7 +3567,7 @@ class WunderbaumNode {
3485
3567
  return !!this.getOption("colspan");
3486
3568
  }
3487
3569
  /** Return true if this node is a direct or indirect subnode of `other`.
3488
- * (See also [[isChildOf]].)
3570
+ * @see {@link WunderbaumNode.isChildOf}
3489
3571
  */
3490
3572
  isDescendantOf(other) {
3491
3573
  if (!other || other.tree !== this.tree) {
@@ -3520,8 +3602,11 @@ class WunderbaumNode {
3520
3602
  }
3521
3603
  return true;
3522
3604
  }
3523
- /** Return true if this node is currently in edit-title mode. */
3524
- isEditing() {
3605
+ /** Return true if _this_ node is currently in edit-title mode.
3606
+ *
3607
+ * See {@link Wunderbaum.startEditTitle} to check if any node is currently edited.
3608
+ */
3609
+ isEditingTitle() {
3525
3610
  return this.tree._callMethod("edit.isEditingTitle", this);
3526
3611
  }
3527
3612
  /** Return true if this node is currently expanded. */
@@ -3555,7 +3640,7 @@ class WunderbaumNode {
3555
3640
  return this.statusNodeType === "paging";
3556
3641
  }
3557
3642
  /** Return true if this node is a **direct** parent of `other`.
3558
- * (See also [[isAncestorOf]].)
3643
+ * @see {@link WunderbaumNode.isAncestorOf}
3559
3644
  */
3560
3645
  isParentOf(other) {
3561
3646
  return other && other.parent === this;
@@ -3577,7 +3662,7 @@ class WunderbaumNode {
3577
3662
  return !!this._rowElem;
3578
3663
  }
3579
3664
  /** Return true if this node is the (invisible) system root node.
3580
- * (See also [[isTopLevel()]].)
3665
+ * @see {@link WunderbaumNode.isTopLevel}
3581
3666
  */
3582
3667
  isRootNode() {
3583
3668
  return this.tree.root === this;
@@ -4456,6 +4541,7 @@ class WunderbaumNode {
4456
4541
  let i = 0;
4457
4542
  for (const colSpan of rowDiv.children) {
4458
4543
  colSpan.classList.toggle("wb-active", i++ === tree.activeColIdx);
4544
+ colSpan.classList.remove("wb-error", "wb-invalid");
4459
4545
  }
4460
4546
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
4461
4547
  const iconSpan = nodeElem.querySelector("i.wb-icon");
@@ -4637,16 +4723,21 @@ class WunderbaumNode {
4637
4723
  return this.tree.scrollTo(opts);
4638
4724
  }
4639
4725
  /**
4640
- * Activate this node, deactivate previous, send events, activate column and scroll int viewport.
4726
+ * Activate this node, deactivate previous, send events, activate column and
4727
+ * scroll into viewport.
4641
4728
  */
4642
4729
  async setActive(flag = true, options) {
4643
4730
  const tree = this.tree;
4644
4731
  const prev = tree.activeNode;
4645
4732
  const retrigger = options === null || options === void 0 ? void 0 : options.retrigger; // Default: false
4646
4733
  const focusTree = options === null || options === void 0 ? void 0 : options.focusTree; // Default: false
4647
- const focusNode = (options === null || options === void 0 ? void 0 : options.focusNode) !== false; // Default: true
4734
+ // const focusNode = options?.focusNode !== false; // Default: true
4648
4735
  const noEvents = options === null || options === void 0 ? void 0 : options.noEvents; // Default: false
4649
- const orgEvent = options === null || options === void 0 ? void 0 : options.event; // Default: false
4736
+ const orgEvent = options === null || options === void 0 ? void 0 : options.event; // Default: null
4737
+ const colIdx = options === null || options === void 0 ? void 0 : options.colIdx; // Default: null
4738
+ const edit = options === null || options === void 0 ? void 0 : options.edit; // Default: false
4739
+ assert(!colIdx || tree.isCellNav(), "colIdx requires cellNav");
4740
+ assert(!edit || colIdx != null, "edit requires colIdx");
4650
4741
  if (!noEvents) {
4651
4742
  if (flag) {
4652
4743
  if (prev !== this || retrigger) {
@@ -4671,32 +4762,36 @@ class WunderbaumNode {
4671
4762
  if (prev !== this) {
4672
4763
  if (flag) {
4673
4764
  tree.activeNode = this;
4674
- if (focusNode || focusTree) {
4675
- tree.focusNode = this;
4676
- }
4677
- if (focusTree) {
4678
- tree.setFocus();
4679
- }
4680
4765
  }
4681
4766
  prev === null || prev === void 0 ? void 0 : prev.update(ChangeType.status);
4682
4767
  this.update(ChangeType.status);
4683
4768
  }
4684
- if (options &&
4685
- options.colIdx != null &&
4686
- options.colIdx !== tree.activeColIdx &&
4687
- tree.isCellNav()) {
4688
- tree.setColumn(options.colIdx);
4689
- }
4690
- if (flag && !noEvents) {
4691
- this._callEvent("activate", { prevNode: prev, event: orgEvent });
4692
- }
4693
- return this.makeVisible();
4769
+ return this.makeVisible().then(() => {
4770
+ if (flag) {
4771
+ if (focusTree || edit) {
4772
+ tree.setFocus();
4773
+ tree.focusNode = this;
4774
+ tree.focusNode.setFocus();
4775
+ }
4776
+ // if (focusNode || edit) {
4777
+ // tree.focusNode = this;
4778
+ // tree.focusNode.setFocus();
4779
+ // }
4780
+ if (colIdx != null && tree.isCellNav()) {
4781
+ tree.setColumn(colIdx, { edit: edit });
4782
+ }
4783
+ if (!noEvents) {
4784
+ this._callEvent("activate", { prevNode: prev, event: orgEvent });
4785
+ }
4786
+ }
4787
+ });
4694
4788
  }
4695
4789
  /**
4696
4790
  * Expand or collapse this node.
4697
4791
  */
4698
4792
  async setExpanded(flag = true, options) {
4699
4793
  const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
4794
+ const sendEvents = !(options === null || options === void 0 ? void 0 : options.noEvents); // Default: send events
4700
4795
  if (!flag &&
4701
4796
  this.isExpanded() &&
4702
4797
  this.getLevel() <= this.tree.getOption("minExpandLevel") &&
@@ -4707,6 +4802,10 @@ class WunderbaumNode {
4707
4802
  if (!flag === !this.expanded) {
4708
4803
  return; // Nothing to do
4709
4804
  }
4805
+ if (sendEvents &&
4806
+ this._callEvent("beforeExpand", { flag: flag }) === false) {
4807
+ return;
4808
+ }
4710
4809
  // this.log("setExpanded()");
4711
4810
  if (flag && this.getOption("autoCollapse")) {
4712
4811
  this.collapseSiblings(options);
@@ -4725,13 +4824,16 @@ class WunderbaumNode {
4725
4824
  lastChild.scrollIntoView({ topNode: this });
4726
4825
  }
4727
4826
  }
4827
+ if (sendEvents) {
4828
+ this._callEvent("expand", { flag: flag });
4829
+ }
4728
4830
  }
4729
4831
  /**
4730
4832
  * Set keyboard focus here.
4731
4833
  * @see {@link setActive}
4732
4834
  */
4733
4835
  setFocus(flag = true) {
4734
- assert(!!flag, "blur is not yet implemented");
4836
+ assert(!!flag, "Blur is not yet implemented");
4735
4837
  const prev = this.tree.focusNode;
4736
4838
  this.tree.focusNode = this;
4737
4839
  prev === null || prev === void 0 ? void 0 : prev.update();
@@ -5186,7 +5288,7 @@ WunderbaumNode.sequence = 0;
5186
5288
  /*!
5187
5289
  * Wunderbaum - ext-edit
5188
5290
  * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
5189
- * v0.7.0, Sat, 09 Dec 2023 13:47:27 GMT (https://github.com/mar10/wunderbaum)
5291
+ * v0.8.0, Thu, 11 Jan 2024 19:37:23 GMT (https://github.com/mar10/wunderbaum)
5190
5292
  */
5191
5293
  // const START_MARKER = "\uFFF7";
5192
5294
  class EditExtension extends WunderbaumExtension {
@@ -5211,40 +5313,62 @@ class EditExtension extends WunderbaumExtension {
5211
5313
  this.debouncedOnChange = debounce(this._onChange.bind(this), this.getPluginOption("debounce"));
5212
5314
  }
5213
5315
  /*
5214
- * Call an event handler, while marking the current node cell 'dirty'.
5316
+ * Call an event handler, while marking the current node cell 'busy'.
5317
+ * Deal with returned promises and ValidationError.
5318
+ * Convert a ValidationError into a input.setCustomValidity() call and vice versa.
5215
5319
  */
5216
- _applyChange(eventName, node, colElem, extra) {
5217
- let res;
5320
+ async _applyChange(eventName, node, colElem, inputElem, extra) {
5218
5321
  node.log(`_applyChange(${eventName})`, extra);
5219
5322
  colElem.classList.add("wb-busy");
5220
- colElem.classList.remove("wb-error");
5221
- try {
5222
- res = node._callEvent(eventName, extra);
5223
- }
5224
- catch (err) {
5225
- node.logError(`Error in ${eventName} event handler`, err);
5226
- colElem.classList.add("wb-error");
5227
- colElem.classList.remove("wb-busy");
5228
- }
5229
- // Convert scalar return value to a resolved promise
5230
- if (!(res instanceof Promise)) {
5231
- res = Promise.resolve(res);
5232
- }
5233
- res
5323
+ colElem.classList.remove("wb-error", "wb-invalid");
5324
+ inputElem.setCustomValidity("");
5325
+ // Call event handler either ('change' or 'edit.appy'), which may return a
5326
+ // promise or a scalar value or throw a ValidationError.
5327
+ return new Promise((resolve, reject) => {
5328
+ const res = node._callEvent(eventName, extra);
5329
+ // normalize to promise, even if a scalar value was returned and await it
5330
+ Promise.resolve(res)
5331
+ .then((res) => {
5332
+ resolve(res);
5333
+ })
5334
+ .catch((err) => {
5335
+ reject(err);
5336
+ });
5337
+ })
5338
+ .then((res) => {
5339
+ if (!inputElem.checkValidity()) {
5340
+ // Native validation failed or handler called 'inputElem.setCustomValidity()'
5341
+ node.logWarn("inputElem.checkValidity() failed: throwing....");
5342
+ throw new ValidationError(inputElem.validationMessage);
5343
+ }
5344
+ return res;
5345
+ })
5234
5346
  .catch((err) => {
5235
- node.logError(`Error in ${eventName} event promise`, err);
5236
- colElem.classList.add("wb-error");
5347
+ if (err instanceof ValidationError) {
5348
+ node.logWarn("catched ", err);
5349
+ colElem.classList.add("wb-invalid");
5350
+ if (inputElem.setCustomValidity && !inputElem.validationMessage) {
5351
+ inputElem.setCustomValidity(err.message);
5352
+ }
5353
+ if (inputElem.validationMessage) {
5354
+ inputElem.reportValidity();
5355
+ }
5356
+ // throw err;
5357
+ }
5358
+ else {
5359
+ node.logError(`Error in ${eventName} event handler (throw e.util.ValidationError instead if this was intended)`, err);
5360
+ colElem.classList.add("wb-error");
5361
+ throw err;
5362
+ }
5237
5363
  })
5238
5364
  .finally(() => {
5239
5365
  colElem.classList.remove("wb-busy");
5240
5366
  });
5241
- return res;
5242
5367
  }
5243
5368
  /*
5244
5369
  * Called for when a control that is embedded in a cell fires a `change` event.
5245
5370
  */
5246
5371
  _onChange(e) {
5247
- // let res;
5248
5372
  const info = Wunderbaum.getEventInfo(e);
5249
5373
  const node = info.node;
5250
5374
  const colElem = info.colElem;
@@ -5252,16 +5376,15 @@ class EditExtension extends WunderbaumExtension {
5252
5376
  this.tree.log("Ignored change event for removed element or node title");
5253
5377
  return;
5254
5378
  }
5255
- this._applyChange("change", node, colElem, {
5379
+ // See also WbChangeEventType
5380
+ this._applyChange("change", node, colElem, e.target, {
5256
5381
  info: info,
5257
5382
  event: e,
5258
5383
  inputElem: e.target,
5259
5384
  inputValue: Wunderbaum.util.getValueFromElem(e.target),
5385
+ inputValid: e.target.checkValidity(),
5260
5386
  });
5261
5387
  }
5262
- // handleKey(e:KeyboardEvent):boolean {
5263
- // if(this.tree.cellNavMode )
5264
- // }
5265
5388
  init() {
5266
5389
  super.init();
5267
5390
  onEvent(this.tree.element, "change", //"change input",
@@ -5308,7 +5431,6 @@ class EditExtension extends WunderbaumExtension {
5308
5431
  break;
5309
5432
  case "F2":
5310
5433
  if (trigger.indexOf("F2") >= 0) {
5311
- // tree.setNavigationMode(NavigationMode.cellEdit);
5312
5434
  this.startEditTitle();
5313
5435
  return false;
5314
5436
  }
@@ -5333,14 +5455,21 @@ class EditExtension extends WunderbaumExtension {
5333
5455
  this.tree.logDebug(`startEditTitle(node=${node})`);
5334
5456
  let inputHtml = node._callEvent("edit.beforeEdit");
5335
5457
  if (inputHtml === false) {
5336
- node.logInfo("beforeEdit canceled operation.");
5458
+ node.logDebug("beforeEdit canceled operation.");
5337
5459
  return;
5338
5460
  }
5339
- // `beforeEdit(e)` may return an input HTML string. Otherwise use a default.
5461
+ // `beforeEdit(e)` may return an input HTML string. Otherwise use a default
5340
5462
  // (we also treat a `true` return value as 'use default'):
5341
5463
  if (inputHtml === true || !inputHtml) {
5342
5464
  const title = escapeHtml(node.title);
5343
- inputHtml = `<input type=text class="wb-input-edit" tabindex=-1 value="${title}" required autocorrect=off>`;
5465
+ let opt = this.getPluginOption("maxlength");
5466
+ const maxlength = opt ? ` maxlength="${opt}"` : "";
5467
+ opt = this.getPluginOption("minlength");
5468
+ const minlength = opt ? ` minlength="${opt}"` : "";
5469
+ const required = opt > 0 ? " required" : "";
5470
+ inputHtml =
5471
+ `<input type=text class="wb-input-edit" tabindex=-1 value="${title}" ` +
5472
+ `autocorrect="off"${required}${minlength}${maxlength} >`;
5344
5473
  }
5345
5474
  const titleSpan = node
5346
5475
  .getColElem(0)
@@ -5351,7 +5480,9 @@ class EditExtension extends WunderbaumExtension {
5351
5480
  // Permanently apply input validations (CSS and tooltip)
5352
5481
  inputElem.addEventListener("keydown", (e) => {
5353
5482
  inputElem.setCustomValidity("");
5354
- if (!inputElem.reportValidity()) ;
5483
+ if (!inputElem.reportValidity()) {
5484
+ node.logWarn(`Invalid input: '${inputElem.value}'`);
5485
+ }
5355
5486
  });
5356
5487
  }
5357
5488
  inputElem.focus();
@@ -5398,12 +5529,12 @@ class EditExtension extends WunderbaumExtension {
5398
5529
  throw new Error(`Input validation failed for "${newValue}": ${errMsg}.`);
5399
5530
  }
5400
5531
  const colElem = node.getColElem(0);
5401
- this._applyChange("edit.apply", node, colElem, {
5532
+ this._applyChange("edit.apply", node, colElem, focusElem, {
5402
5533
  oldValue: node.title,
5403
5534
  newValue: newValue,
5404
5535
  inputElem: focusElem,
5405
- })
5406
- .then((value) => {
5536
+ inputValid: focusElem.checkValidity(),
5537
+ }).then((value) => {
5407
5538
  const errMsg = focusElem.validationMessage;
5408
5539
  if (validity && errMsg && value !== false) {
5409
5540
  // Handler called 'inputElem.setCustomValidity()' to signal error
@@ -5422,10 +5553,10 @@ class EditExtension extends WunderbaumExtension {
5422
5553
  this.curEditNode = null;
5423
5554
  this.relatedNode = null;
5424
5555
  this.tree.setFocus(); // restore focus that was in the input element
5425
- })
5426
- .catch((err) => {
5427
- node.logError(err);
5428
5556
  });
5557
+ // .catch((err) => {
5558
+ // node.logError(err);
5559
+ // });
5429
5560
  // Trigger 'change' event for embedded `<input>`
5430
5561
  // focusElem.blur();
5431
5562
  }
@@ -5486,8 +5617,8 @@ class EditExtension extends WunderbaumExtension {
5486
5617
  * https://github.com/mar10/wunderbaum
5487
5618
  *
5488
5619
  * Released under the MIT license.
5489
- * @version v0.7.0
5490
- * @date Sat, 09 Dec 2023 13:47:27 GMT
5620
+ * @version v0.8.0
5621
+ * @date Thu, 11 Jan 2024 19:37:23 GMT
5491
5622
  */
5492
5623
  // import "./wunderbaum.scss";
5493
5624
  class WbSystemRoot extends WunderbaumNode {
@@ -5504,7 +5635,7 @@ class WbSystemRoot extends WunderbaumNode {
5504
5635
  /**
5505
5636
  * A persistent plain object or array.
5506
5637
  *
5507
- * See also [[WunderbaumOptions]].
5638
+ * See also {@link WunderbaumOptions}.
5508
5639
  */
5509
5640
  class Wunderbaum {
5510
5641
  constructor(options) {
@@ -5537,7 +5668,7 @@ class Wunderbaum {
5537
5668
  // --- FILTER ---
5538
5669
  this.filterMode = null;
5539
5670
  // --- KEYNAV ---
5540
- /** @internal Use `setColumn()`/`getActiveColElem()`*/
5671
+ /** @internal Use `setColumn()`/`getActiveColElem()` to access. */
5541
5672
  this.activeColIdx = 0;
5542
5673
  /** @internal */
5543
5674
  this._cellNavMode = false;
@@ -5713,6 +5844,7 @@ class Wunderbaum {
5713
5844
  else {
5714
5845
  this.setNavigationOption(opts.navigationModeOption);
5715
5846
  }
5847
+ this.update(ChangeType.structure, { immediate: true });
5716
5848
  readyDeferred.resolve();
5717
5849
  })
5718
5850
  .catch((error) => {
@@ -5764,7 +5896,7 @@ class Wunderbaum {
5764
5896
  info.region === "title" &&
5765
5897
  node.isActive() &&
5766
5898
  (!slowClickDelay || Date.now() - this.lastClickTime < slowClickDelay)) {
5767
- this._callMethod("edit.startEditTitle", node);
5899
+ node.startEditTitle();
5768
5900
  }
5769
5901
  if (info.colIdx >= 0) {
5770
5902
  node.setActive(true, { colIdx: info.colIdx, event: e });
@@ -5811,7 +5943,7 @@ class Wunderbaum {
5811
5943
  const flag = e.type === "focusin";
5812
5944
  const targetNode = Wunderbaum.getNode(e);
5813
5945
  this._callEvent("focus", { flag: flag, event: e });
5814
- if (flag && this.isRowNav() && !this.isEditing()) {
5946
+ if (flag && this.isRowNav() && !this.isEditingTitle()) {
5815
5947
  if (opts.navigationModeOption === NavModeEnum.row) {
5816
5948
  targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
5817
5949
  }
@@ -5835,7 +5967,7 @@ class Wunderbaum {
5835
5967
  * getTree(1); // Get second Wunderbaum instance on page
5836
5968
  * getTree(event); // Get tree for this mouse- or keyboard event
5837
5969
  * getTree("foo"); // Get tree for this `tree.options.id`
5838
- * getTree("#tree"); // Get tree for this matching element
5970
+ * getTree("#tree"); // Get tree for first matching element selector
5839
5971
  * ```
5840
5972
  */
5841
5973
  static getTree(el) {
@@ -5942,31 +6074,33 @@ class Wunderbaum {
5942
6074
  assert(key != null && !this.keyMap.has(key), `Missing or duplicate key: '${key}'.`);
5943
6075
  this.keyMap.set(key, node);
5944
6076
  const rk = node.refKey;
5945
- if (rk) {
6077
+ if (rk != null) {
5946
6078
  const rks = this.refKeyMap.get(rk); // Set of nodes with this refKey
5947
6079
  if (rks) {
5948
6080
  rks.add(node);
5949
6081
  }
5950
6082
  else {
5951
- this.refKeyMap.set(rk, new Set());
6083
+ this.refKeyMap.set(rk, new Set([node]));
5952
6084
  }
5953
6085
  }
5954
6086
  }
5955
6087
  /** Remove node from tree's bookkeeping data structures. */
5956
6088
  _unregisterNode(node) {
6089
+ // Remove refKey reference from map (if any)
5957
6090
  const rk = node.refKey;
5958
- if (rk) {
6091
+ if (rk != null) {
5959
6092
  const rks = this.refKeyMap.get(rk);
5960
6093
  if (rks && rks.delete(node) && !rks.size) {
5961
6094
  // We just removed the last element
5962
6095
  this.refKeyMap.delete(rk);
5963
6096
  }
5964
6097
  }
5965
- // mark as disposed
6098
+ // Remove key reference from map
6099
+ this.keyMap.delete(node.key);
6100
+ // Mark as disposed
5966
6101
  node.tree = null;
5967
6102
  node.parent = null;
5968
- // node.title = "DISPOSED: " + node.title
5969
- // this.viewNodes.delete(node);
6103
+ // Remove HTML markup
5970
6104
  node.removeMarkup();
5971
6105
  }
5972
6106
  /** Call all hook methods of all registered extensions.*/
@@ -6170,7 +6304,7 @@ class Wunderbaum {
6170
6304
  this._callMethod("edit.createNode", "after");
6171
6305
  break;
6172
6306
  case "rename":
6173
- this._callMethod("edit.startEditTitle");
6307
+ node.startEditTitle();
6174
6308
  break;
6175
6309
  // Simple clipboard simulation:
6176
6310
  // case "cut":
@@ -6217,7 +6351,6 @@ class Wunderbaum {
6217
6351
  this.root.children = null;
6218
6352
  this.keyMap.clear();
6219
6353
  this.refKeyMap.clear();
6220
- // this.viewNodes.clear();
6221
6354
  this.treeRowCount = 0;
6222
6355
  this.activeNode = null;
6223
6356
  this.focusNode = null;
@@ -6410,14 +6543,31 @@ class Wunderbaum {
6410
6543
  /**
6411
6544
  * Find all nodes that match condition.
6412
6545
  *
6546
+ * @param match title string to search for, or a
6547
+ * callback function that returns `true` if a node is matched.
6413
6548
  * @see {@link WunderbaumNode.findAll}
6414
6549
  */
6415
6550
  findAll(match) {
6416
6551
  return this.root.findAll(match);
6417
6552
  }
6553
+ /**
6554
+ * Find all nodes with a given _refKey_ (aka a list of clones).
6555
+ *
6556
+ * @param refKey a `node.refKey` value to search for.
6557
+ * @returns an array of matching nodes with at least two element or `[]`
6558
+ * if nothing found.
6559
+ *
6560
+ * @see {@link WunderbaumNode.getCloneList}
6561
+ */
6562
+ findByRefKey(refKey) {
6563
+ const clones = this.refKeyMap.get(refKey);
6564
+ return clones ? Array.from(clones) : [];
6565
+ }
6418
6566
  /**
6419
6567
  * Find first node that matches condition.
6420
6568
  *
6569
+ * @param match title string to search for, or a
6570
+ * callback function that returns `true` if a node is matched.
6421
6571
  * @see {@link WunderbaumNode.findFirst}
6422
6572
  */
6423
6573
  findFirst(match) {
@@ -6426,8 +6576,6 @@ class Wunderbaum {
6426
6576
  /**
6427
6577
  * Find first node that matches condition.
6428
6578
  *
6429
- * @param match title string to search for, or a
6430
- * callback function that returns `true` if a node is matched.
6431
6579
  * @see {@link WunderbaumNode.findFirst}
6432
6580
  *
6433
6581
  */
@@ -6437,6 +6585,7 @@ class Wunderbaum {
6437
6585
  /**
6438
6586
  * Find the next visible node that starts with `match`, starting at `startNode`
6439
6587
  * and wrap-around at the end.
6588
+ * Used by quicksearch and keyboard navigation.
6440
6589
  */
6441
6590
  findNextNode(match, startNode) {
6442
6591
  //, visibleOnly) {
@@ -6604,6 +6753,9 @@ class Wunderbaum {
6604
6753
  }
6605
6754
  /**
6606
6755
  * Return the currently active node or null.
6756
+ * @see {@link WunderbaumNode.setActive}
6757
+ * @see {@link WunderbaumNode.isActive}
6758
+ * @see {@link WunderbaumNode.getFocusNode}
6607
6759
  */
6608
6760
  getActiveNode() {
6609
6761
  return this.activeNode;
@@ -6615,7 +6767,8 @@ class Wunderbaum {
6615
6767
  return this.root.getFirstChild();
6616
6768
  }
6617
6769
  /**
6618
- * Return the currently active node or null.
6770
+ * Return the node that currently has keyboard focus or null.
6771
+ * @see {@link WunderbaumNode.getActiveNode}
6619
6772
  */
6620
6773
  getFocusNode() {
6621
6774
  return this.focusNode;
@@ -6692,8 +6845,19 @@ class Wunderbaum {
6692
6845
  toString() {
6693
6846
  return `Wunderbaum<'${this.id}'>`;
6694
6847
  }
6695
- /** Return true if any node is currently in edit-title mode. */
6848
+ /** Return true if any node title or grid cell is currently beeing edited.
6849
+ *
6850
+ * See also {@link Wunderbaum.isEditingTitle}.
6851
+ */
6696
6852
  isEditing() {
6853
+ const focusElem = this.nodeListElement.querySelector("input:focus,select:focus");
6854
+ return !!focusElem;
6855
+ }
6856
+ /** Return true if any node is currently in edit-title mode.
6857
+ *
6858
+ * See also {@link WunderbaumNode.isEditingTitle} and {@link Wunderbaum.isEditing}.
6859
+ */
6860
+ isEditingTitle() {
6697
6861
  return this._callMethod("edit.isEditingTitle");
6698
6862
  }
6699
6863
  /**
@@ -6829,12 +6993,23 @@ class Wunderbaum {
6829
6993
  /**
6830
6994
  * Set column #colIdx to 'active'.
6831
6995
  *
6832
- * This higlights the column header and -cells by adding the `wb-active` class.
6996
+ * This higlights the column header and -cells by adding the `wb-active`
6997
+ * class to all grid cells of the active column. <br>
6833
6998
  * Available in cell-nav mode only.
6999
+ *
7000
+ * If _options.edit_ is true, the embedded input element is focused, or if
7001
+ * colIdx is 0, the node title is put into edit mode.
6834
7002
  */
6835
- setColumn(colIdx) {
6836
- var _a;
6837
- assert(this.isCellNav(), "Exected cellNav mode");
7003
+ setColumn(colIdx, options) {
7004
+ var _a, _b, _c;
7005
+ const edit = options === null || options === void 0 ? void 0 : options.edit;
7006
+ const scroll = (options === null || options === void 0 ? void 0 : options.scrollIntoView) !== false;
7007
+ assert(this.isCellNav(), "Expected cellNav mode");
7008
+ if (typeof colIdx === "string") {
7009
+ const cid = colIdx;
7010
+ colIdx = this.columns.findIndex((c) => c.id === colIdx);
7011
+ assert(colIdx >= 0, `Invalid colId: ${cid}`);
7012
+ }
6838
7013
  assert(0 <= colIdx && colIdx < this.columns.length, `Invalid colIdx: ${colIdx}`);
6839
7014
  this.activeColIdx = colIdx;
6840
7015
  // Update `wb-active` class for all headers
@@ -6854,17 +7029,26 @@ class Wunderbaum {
6854
7029
  colDiv.classList.toggle("wb-active", i++ === colIdx);
6855
7030
  }
6856
7031
  }
6857
- // Vertical scroll into view
6858
- // if (this.options.fixedCol) {
6859
- this.scrollToHorz();
6860
- // }
7032
+ // Horizontically scroll into view
7033
+ if (scroll || edit) {
7034
+ this.scrollToHorz();
7035
+ }
7036
+ if (edit && this.activeNode) {
7037
+ // this.activeNode.setFocus(); // Blur prev. input if any
7038
+ if (colIdx === 0) {
7039
+ this.activeNode.startEditTitle();
7040
+ }
7041
+ else {
7042
+ (_c = (_b = this.getActiveColElem()) === null || _b === void 0 ? void 0 : _b.querySelector("input,select")) === null || _c === void 0 ? void 0 : _c.focus();
7043
+ }
7044
+ }
6861
7045
  }
6862
- /** Set or remove keybaord focus to the tree container. */
7046
+ /** Set or remove keyboard focus to the tree container. */
6863
7047
  setActiveNode(key, flag = true, options) {
6864
7048
  var _a;
6865
7049
  (_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setActive(flag, options);
6866
7050
  }
6867
- /** Set or remove keybaord focus to the tree container. */
7051
+ /** Set or remove keyboard focus to the tree container. */
6868
7052
  setFocus(flag = true) {
6869
7053
  if (flag) {
6870
7054
  this.element.focus();
@@ -6874,6 +7058,14 @@ class Wunderbaum {
6874
7058
  }
6875
7059
  }
6876
7060
  update(change, node, options) {
7061
+ // this.log(`update(${change}) node=${node}`);
7062
+ if (!(node instanceof WunderbaumNode)) {
7063
+ options = node;
7064
+ node = undefined;
7065
+ }
7066
+ const immediate = !!getOption(options, "immediate");
7067
+ const RF = RenderFlag;
7068
+ const pending = this.pendingChangeTypes;
6877
7069
  if (this._disableUpdateCount) {
6878
7070
  // Assuming that we redraw all when enableUpdate() is re-enabled.
6879
7071
  // this.log(
@@ -6882,14 +7074,6 @@ class Wunderbaum {
6882
7074
  this._disableUpdateIgnoreCount++;
6883
7075
  return;
6884
7076
  }
6885
- // this.log(`update(${change}) node=${node}`);
6886
- if (!(node instanceof WunderbaumNode)) {
6887
- options = node;
6888
- node = null;
6889
- }
6890
- const immediate = !!getOption(options, "immediate");
6891
- const RF = RenderFlag;
6892
- const pending = this.pendingChangeTypes;
6893
7077
  switch (change) {
6894
7078
  case ChangeType.any:
6895
7079
  case ChangeType.colStructure:
@@ -7217,10 +7401,14 @@ class Wunderbaum {
7217
7401
  _updateViewportImmediately() {
7218
7402
  var _a;
7219
7403
  if (this._disableUpdateCount) {
7220
- this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`);
7404
+ this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount}).`);
7221
7405
  this._disableUpdateIgnoreCount++;
7222
7406
  return;
7223
7407
  }
7408
+ if (this._updateViewportThrottled.pending()) {
7409
+ // this.logWarn(`_updateViewportImmediately() cancel pending timer.`);
7410
+ this._updateViewportThrottled.cancel();
7411
+ }
7224
7412
  // Shorten container height to avoid v-scrollbar
7225
7413
  const FIX_ADJUST_HEIGHT = 1;
7226
7414
  const RF = RenderFlag;
@@ -7324,9 +7512,6 @@ class Wunderbaum {
7324
7512
  }
7325
7513
  let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7326
7514
  endIdx = Math.ceil(endIdx);
7327
- // const obsoleteViewNodes = this.viewNodes;
7328
- // this.viewNodes = new Set();
7329
- // const viewNodes = this.viewNodes;
7330
7515
  // this.debug("render", opts);
7331
7516
  const obsoleteNodes = new Set();
7332
7517
  this.nodeListElement.childNodes.forEach((elem) => {
@@ -7582,37 +7767,31 @@ class Wunderbaum {
7582
7767
  * FILTER
7583
7768
  * -------------------------------------------------------------------------*/
7584
7769
  /**
7585
- * [ext-filter] Dim or hide nodes.
7770
+ * Dim or hide nodes.
7586
7771
  */
7587
7772
  filterNodes(filter, options) {
7588
7773
  return this.extensions.filter.filterNodes(filter, options);
7589
7774
  }
7590
7775
  /**
7591
- * [ext-filter] Dim or hide whole branches.
7776
+ * Dim or hide whole branches.
7592
7777
  */
7593
7778
  filterBranches(filter, options) {
7594
7779
  return this.extensions.filter.filterBranches(filter, options);
7595
7780
  }
7596
7781
  /**
7597
- * [ext-filter] Reset the filter.
7598
- *
7599
- * @requires [[FilterExtension]]
7782
+ * Reset the filter.
7600
7783
  */
7601
7784
  clearFilter() {
7602
7785
  return this.extensions.filter.clearFilter();
7603
7786
  }
7604
7787
  /**
7605
- * [ext-filter] Return true if a filter is currently applied.
7606
- *
7607
- * @requires [[FilterExtension]]
7788
+ * Return true if a filter is currently applied.
7608
7789
  */
7609
7790
  isFilterActive() {
7610
7791
  return !!this.filterMode;
7611
7792
  }
7612
7793
  /**
7613
- * [ext-filter] Re-apply current filter.
7614
- *
7615
- * @requires [[FilterExtension]]
7794
+ * Re-apply current filter.
7616
7795
  */
7617
7796
  updateFilter() {
7618
7797
  return this.extensions.filter.updateFilter();
@@ -7620,7 +7799,7 @@ class Wunderbaum {
7620
7799
  }
7621
7800
  Wunderbaum.sequence = 0;
7622
7801
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
7623
- Wunderbaum.version = "v0.7.0"; // Set to semver by 'grunt release'
7802
+ Wunderbaum.version = "v0.8.0"; // Set to semver by 'grunt release'
7624
7803
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
7625
7804
  Wunderbaum.util = util;
7626
7805