wunderbaum 0.7.0 → 0.8.1

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,108 +1,418 @@
1
1
  /*!
2
- * Wunderbaum - util
3
- * 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)
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
5
  */
6
- /** @module util */
7
- /** Readable names for `MouseEvent.button` */
8
- const MOUSE_BUTTONS = {
9
- 0: "",
10
- 1: "left",
11
- 2: "middle",
12
- 3: "right",
13
- 4: "back",
14
- 5: "forward",
15
- };
16
- const MAX_INT = 9007199254740991;
17
- const userInfo = _getUserInfo();
18
- /**True if the client is using a macOS platform. */
19
- const isMac = userInfo.isMac;
20
- const REX_HTML = /[&<>"'/]/g; // Escape those characters
21
- const REX_TOOLTIP = /[<>"'/]/g; // Don't escape `&` in tooltips
22
- const ENTITY_MAP = {
23
- "&": "&amp;",
24
- "<": "&lt;",
25
- ">": "&gt;",
26
- '"': "&quot;",
27
- "'": "&#39;",
28
- "/": "&#x2F;",
29
- };
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")();
30
21
  /**
31
- * A ES6 Promise, that exposes the resolve()/reject() methods.
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
32
43
  */
33
- let Deferred$1 = class Deferred {
34
- constructor() {
35
- this.thens = [];
36
- this.catches = [];
37
- this.status = "";
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");
38
119
  }
39
- resolve(value) {
40
- if (this.status) {
41
- throw new Error("already settled");
42
- }
43
- this.status = "resolved";
44
- this.resolvedValue = value;
45
- this.thens.forEach((t) => t(value));
46
- this.thens = []; // Avoid memleaks.
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;
47
126
  }
48
- reject(error) {
49
- if (this.status) {
50
- throw new Error("already settled");
51
- }
52
- this.status = "rejected";
53
- this.rejectedError = error;
54
- this.catches.forEach((c) => c(error));
55
- this.catches = []; // Avoid memleaks.
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;
56
134
  }
57
- then(cb) {
58
- if (status === "resolved") {
59
- cb(this.resolvedValue);
60
- }
61
- else {
62
- this.thens.unshift(cb);
135
+ function startTimer(pendingFunc, wait) {
136
+ if (useRAF) {
137
+ root.cancelAnimationFrame(timerId);
138
+ return root.requestAnimationFrame(pendingFunc);
63
139
  }
140
+ return setTimeout(pendingFunc, wait);
64
141
  }
65
- catch(cb) {
66
- if (this.status === "rejected") {
67
- cb(this.rejectedError);
68
- }
69
- else {
70
- this.catches.unshift(cb);
142
+ function cancelTimer(id) {
143
+ if (useRAF) {
144
+ return root.cancelAnimationFrame(id);
71
145
  }
146
+ clearTimeout(id);
72
147
  }
73
- promise() {
74
- return {
75
- then: this.then,
76
- catch: this.catch,
77
- };
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;
78
155
  }
79
- };
80
- /**Throw an `Error` if `cond` is falsey. */
81
- function assert(cond, msg) {
82
- if (!cond) {
83
- msg = msg || "Assertion failed.";
84
- throw new Error(msg);
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;
85
163
  }
86
- }
87
- function _getUserInfo() {
88
- const nav = navigator;
89
- // const ua = nav.userAgentData;
90
- const res = {
91
- isMac: /Mac/.test(nav.platform),
92
- };
93
- return res;
94
- }
95
- /** Run `callback` when document was loaded. */
96
- function documentReady(callback) {
97
- if (document.readyState === "loading") {
98
- document.addEventListener("DOMContentLoaded", callback);
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));
99
174
  }
100
- else {
101
- callback();
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));
102
182
  }
103
- }
104
- /** Resolve when document was loaded. */
105
- function documentReadyPromise() {
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
+
298
+ /*!
299
+ * Wunderbaum - util
300
+ * Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
301
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
302
+ */
303
+ /** @module util */
304
+ /** Readable names for `MouseEvent.button` */
305
+ const MOUSE_BUTTONS = {
306
+ 0: "",
307
+ 1: "left",
308
+ 2: "middle",
309
+ 3: "right",
310
+ 4: "back",
311
+ 5: "forward",
312
+ };
313
+ const MAX_INT = 9007199254740991;
314
+ const userInfo = _getUserInfo();
315
+ /**True if the client is using a macOS platform. */
316
+ const isMac = userInfo.isMac;
317
+ const REX_HTML = /[&<>"'/]/g; // Escape those characters
318
+ const REX_TOOLTIP = /[<>"'/]/g; // Don't escape `&` in tooltips
319
+ const ENTITY_MAP = {
320
+ "&": "&amp;",
321
+ "<": "&lt;",
322
+ ">": "&gt;",
323
+ '"': "&quot;",
324
+ "'": "&#39;",
325
+ "/": "&#x2F;",
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
+ }
337
+ /**
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.
342
+ */
343
+ let Deferred$1 = class Deferred {
344
+ constructor() {
345
+ this.thens = [];
346
+ this.catches = [];
347
+ this.status = "";
348
+ }
349
+ resolve(value) {
350
+ if (this.status) {
351
+ throw new Error("already settled");
352
+ }
353
+ this.status = "resolved";
354
+ this.resolvedValue = value;
355
+ this.thens.forEach((t) => t(value));
356
+ this.thens = []; // Avoid memleaks.
357
+ }
358
+ reject(error) {
359
+ if (this.status) {
360
+ throw new Error("already settled");
361
+ }
362
+ this.status = "rejected";
363
+ this.rejectedError = error;
364
+ this.catches.forEach((c) => c(error));
365
+ this.catches = []; // Avoid memleaks.
366
+ }
367
+ then(cb) {
368
+ if (status === "resolved") {
369
+ cb(this.resolvedValue);
370
+ }
371
+ else {
372
+ this.thens.unshift(cb);
373
+ }
374
+ }
375
+ catch(cb) {
376
+ if (this.status === "rejected") {
377
+ cb(this.rejectedError);
378
+ }
379
+ else {
380
+ this.catches.unshift(cb);
381
+ }
382
+ }
383
+ promise() {
384
+ return {
385
+ then: this.then,
386
+ catch: this.catch,
387
+ };
388
+ }
389
+ };
390
+ /**Throw an `Error` if `cond` is falsey. */
391
+ function assert(cond, msg) {
392
+ if (!cond) {
393
+ msg = msg || "Assertion failed.";
394
+ throw new Error(msg);
395
+ }
396
+ }
397
+ function _getUserInfo() {
398
+ const nav = navigator;
399
+ // const ua = nav.userAgentData;
400
+ const res = {
401
+ isMac: /Mac/.test(nav.platform),
402
+ };
403
+ return res;
404
+ }
405
+ /** Run `callback` when document was loaded. */
406
+ function documentReady(callback) {
407
+ if (document.readyState === "loading") {
408
+ document.addEventListener("DOMContentLoaded", callback);
409
+ }
410
+ else {
411
+ callback();
412
+ }
413
+ }
414
+ /** Resolve when document was loaded. */
415
+ function documentReadyPromise() {
106
416
  return new Promise((resolve) => {
107
417
  documentReady(resolve);
108
418
  });
@@ -371,16 +681,18 @@ function elemFromSelector(obj) {
371
681
  }
372
682
  return obj;
373
683
  }
374
- /** Return a EventTarget from selector or cast an existing element. */
375
- function eventTargetFromSelector(obj) {
376
- if (!obj) {
377
- return null;
378
- }
379
- if (typeof obj === "string") {
380
- return document.querySelector(obj);
381
- }
382
- return obj;
383
- }
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.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
769
1100
  */
770
1101
  /**
771
1102
  * Possible values for {@link WunderbaumNode.update()} and {@link Wunderbaum.update()}.
@@ -829,7 +1160,7 @@ 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.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
833
1164
  */
834
1165
  class WunderbaumExtension {
835
1166
  constructor(tree, id, defaults) {
@@ -839,353 +1170,56 @@ class WunderbaumExtension {
839
1170
  this.treeOpts = tree.options;
840
1171
  const opts = tree.options;
841
1172
  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);
1076
- }
1077
- lastArgs = lastThis = undefined;
1078
- return result;
1079
- }
1080
- function cancel() {
1081
- if (timerId !== undefined) {
1082
- cancelTimer(timerId);
1083
- }
1084
- lastInvokeTime = 0;
1085
- lastArgs = lastCallTime = lastThis = timerId = undefined;
1086
- }
1087
- function flush() {
1088
- return timerId === undefined ? result : trailingEdge(Date.now());
1089
- }
1090
- function pending() {
1091
- return timerId !== undefined;
1092
- }
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
- }
1173
+ opts[id] = this.extensionOpts = extend({}, defaults);
1109
1174
  }
1110
- if (timerId === undefined) {
1111
- timerId = startTimer(timerExpired, wait);
1175
+ else {
1176
+ // TODO: do we break existing object instance references here?
1177
+ this.extensionOpts = extend({}, defaults, opts[id]);
1178
+ opts[id] = this.extensionOpts;
1112
1179
  }
1113
- return result;
1180
+ this.enabled = this.getPluginOption("enabled", true);
1114
1181
  }
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");
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);
1173
1185
  }
1174
- if (isObject(options)) {
1175
- leading = "leading" in options ? !!options.leading : leading;
1176
- trailing = "trailing" in options ? !!options.trailing : trailing;
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;
1203
+ }
1204
+ setPluginOption(name, value) {
1205
+ this.extensionOpts[name] = value;
1206
+ }
1207
+ setEnabled(flag = true) {
1208
+ return this.setPluginOption("enabled", !!flag);
1209
+ // this.enabled = !!flag;
1210
+ }
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.1, Sat, 20 Jan 2024 15:57:59 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.1, Sat, 20 Jan 2024 15:57:59 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.1, Sat, 20 Jan 2024 15:57:59 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.1, Sat, 20 Jan 2024 15:57:59 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.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
2200
2257
  */
2201
2258
  const nodeMimeType = "application/x-wunderbaum-node";
2202
2259
  class DndExtension extends WunderbaumExtension {
@@ -2435,7 +2492,7 @@ class DndExtension extends WunderbaumExtension {
2435
2492
  const dndOpts = this.treeOpts.dnd;
2436
2493
  const srcNode = Wunderbaum.getNode(e);
2437
2494
  if (!srcNode) {
2438
- this.tree.logWarn(`onDragEvent.${e.type} no node`);
2495
+ this.tree.logWarn(`onDragEvent.${e.type}: no node`);
2439
2496
  return;
2440
2497
  }
2441
2498
  if (["dragstart", "dragend"].includes(e.type)) {
@@ -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;
@@ -2510,6 +2567,13 @@ class DndExtension extends WunderbaumExtension {
2510
2567
  const dndOpts = this.treeOpts.dnd;
2511
2568
  const dt = e.dataTransfer;
2512
2569
  const dropRegion = this._calcDropRegion(e, this.lastAllowedDropRegions);
2570
+ /** Helper to log a message if predicate is false. */
2571
+ const _t = (pred, msg) => {
2572
+ if (pred) {
2573
+ this.tree.log(`Prevented drop operation (${msg}).`);
2574
+ }
2575
+ return pred;
2576
+ };
2513
2577
  if (!targetNode) {
2514
2578
  this._leaveNode();
2515
2579
  return;
@@ -2520,6 +2584,7 @@ class DndExtension extends WunderbaumExtension {
2520
2584
  }
2521
2585
  // --- dragenter ---
2522
2586
  if (e.type === "dragenter") {
2587
+ // this.tree.logWarn(` onDropEvent.${e.type} targetNode: ${targetNode}`, e);
2523
2588
  this.lastAllowedDropRegions = null;
2524
2589
  // `dragleave` is not reliable with event delegation, so we generate it
2525
2590
  // from dragenter:
@@ -2530,29 +2595,33 @@ class DndExtension extends WunderbaumExtension {
2530
2595
  this.lastEnterStamp = Date.now();
2531
2596
  if (
2532
2597
  // Don't drop on status node:
2533
- targetNode.isStatusNode() ||
2598
+ _t(targetNode.isStatusNode(), "is status node") ||
2534
2599
  // Prevent dropping nodes from different Wunderbaum trees:
2535
- (dndOpts.preventForeignNodes && targetNode.tree !== srcTree) ||
2600
+ _t(dndOpts.preventForeignNodes && targetNode.tree !== srcTree, "preventForeignNodes") ||
2536
2601
  // Prevent dropping items on unloaded lazy Wunderbaum tree nodes:
2537
- (dndOpts.preventLazyParents && !targetNode.isLoaded()) ||
2602
+ _t(dndOpts.preventLazyParents && !targetNode.isLoaded(), "preventLazyParents") ||
2538
2603
  // Prevent dropping items other than Wunderbaum tree nodes:
2539
- (dndOpts.preventNonNodes && !srcNode) ||
2604
+ _t(dndOpts.preventNonNodes && !srcNode, "preventNonNodes") ||
2540
2605
  // Prevent dropping nodes on own descendants:
2541
- (dndOpts.preventRecursion && (srcNode === null || srcNode === void 0 ? void 0 : srcNode.isAncestorOf(targetNode))) ||
2606
+ _t(dndOpts.preventRecursion && (srcNode === null || srcNode === void 0 ? void 0 : srcNode.isAncestorOf(targetNode)), "preventRecursion") ||
2542
2607
  // Prevent dropping nodes under same direct parent:
2543
2608
  (dndOpts.preventSameParent &&
2544
2609
  srcNode &&
2545
- targetNode.parent === srcNode.parent) ||
2610
+ targetNode.parent === srcNode.parent,
2611
+ "preventSameParent") ||
2546
2612
  // Don't allow void operation ('drop on self'): TODO: should be checked on move only
2547
2613
  (dndOpts.preventVoidMoves && targetNode === srcNode)) {
2548
2614
  dt.dropEffect = "none";
2549
- this.tree.log("Prevented drop operation");
2615
+ // this.tree.log("Prevented drop operation");
2550
2616
  return true; // Prevent drop operation
2551
2617
  }
2552
2618
  // User may return a set of regions (or `false` to prevent drop)
2553
2619
  // Figure out a drop effect (copy/link/move) using opinated conventions.
2554
2620
  dt.dropEffect = this._guessDropEffect(e) || "none";
2555
- let regionSet = targetNode._callEvent("dnd.dragEnter", { event: e });
2621
+ let regionSet = targetNode._callEvent("dnd.dragEnter", {
2622
+ event: e,
2623
+ sourceNode: srcNode,
2624
+ });
2556
2625
  //
2557
2626
  regionSet = this.unifyDragover(regionSet);
2558
2627
  if (!regionSet) {
@@ -2570,7 +2639,7 @@ class DndExtension extends WunderbaumExtension {
2570
2639
  const viewportY = e.clientY - this.tree.element.offsetTop;
2571
2640
  this._autoScroll(viewportY);
2572
2641
  dt.dropEffect = this._guessDropEffect(e) || "none";
2573
- targetNode._callEvent("dnd.dragOver", { event: e });
2642
+ targetNode._callEvent("dnd.dragOver", { event: e, sourceNode: srcNode });
2574
2643
  const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
2575
2644
  this.lastDropRegion = region;
2576
2645
  this.lastDropEffect = dt.dropEffect;
@@ -2578,7 +2647,10 @@ class DndExtension extends WunderbaumExtension {
2578
2647
  targetNode.isExpandable(true) &&
2579
2648
  !targetNode._isLoading &&
2580
2649
  Date.now() - this.lastEnterStamp > dndOpts.autoExpandMS &&
2581
- targetNode._callEvent("dnd.dragExpand", { event: e }) !== false) {
2650
+ targetNode._callEvent("dnd.dragExpand", {
2651
+ event: e,
2652
+ sourceNode: srcNode,
2653
+ }) !== false) {
2582
2654
  targetNode.setExpanded();
2583
2655
  }
2584
2656
  if (!region || this._isVoidDrop(targetNode, srcNode, region)) {
@@ -2594,7 +2666,7 @@ class DndExtension extends WunderbaumExtension {
2594
2666
  else if (e.type === "dragleave") {
2595
2667
  // NOTE: we cannot trust this event, since it is always fired,
2596
2668
  // Instead we remove the marker on dragenter
2597
- targetNode._callEvent("dnd.dragLeave", { event: e });
2669
+ targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2598
2670
  // --- drop ---
2599
2671
  }
2600
2672
  else if (e.type === "drop") {
@@ -2627,7 +2699,7 @@ class DndExtension extends WunderbaumExtension {
2627
2699
  /*!
2628
2700
  * Wunderbaum - drag_observer
2629
2701
  * 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)
2702
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
2631
2703
  */
2632
2704
  /**
2633
2705
  * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
@@ -2763,7 +2835,7 @@ class DragObserver {
2763
2835
  /*!
2764
2836
  * Wunderbaum - ext-grid
2765
2837
  * 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)
2838
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
2767
2839
  */
2768
2840
  class GridExtension extends WunderbaumExtension {
2769
2841
  constructor(tree) {
@@ -2800,7 +2872,7 @@ class GridExtension extends WunderbaumExtension {
2800
2872
  /*!
2801
2873
  * Wunderbaum - deferred
2802
2874
  * 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)
2875
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
2804
2876
  */
2805
2877
  /**
2806
2878
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -2824,27 +2896,27 @@ class Deferred {
2824
2896
  this._reject = reject;
2825
2897
  });
2826
2898
  }
2827
- /** Resolve the [[Promise]]. */
2899
+ /** Resolve the Promise. */
2828
2900
  resolve(value) {
2829
2901
  this._resolve(value);
2830
2902
  }
2831
- /** Reject the [[Promise]]. */
2903
+ /** Reject the Promise. */
2832
2904
  reject(reason) {
2833
2905
  this._reject(reason);
2834
2906
  }
2835
- /** Return the native [[Promise]] instance.*/
2907
+ /** Return the native Promise instance.*/
2836
2908
  promise() {
2837
2909
  return this._promise;
2838
2910
  }
2839
- /** Call [[Promise.then]] on the embedded promise instance.*/
2911
+ /** Call Promise.then on the embedded promise instance.*/
2840
2912
  then(cb) {
2841
2913
  return this._promise.then(cb);
2842
2914
  }
2843
- /** Call [[Promise.catch]] on the embedded promise instance.*/
2915
+ /** Call Promise.catch on the embedded promise instance.*/
2844
2916
  catch(cb) {
2845
2917
  return this._promise.catch(cb);
2846
2918
  }
2847
- /** Call [[Promise.finally]] on the embedded promise instance.*/
2919
+ /** Call Promise.finally on the embedded promise instance.*/
2848
2920
  finally(cb) {
2849
2921
  return this._promise.finally(cb);
2850
2922
  }
@@ -2853,7 +2925,7 @@ class Deferred {
2853
2925
  /*!
2854
2926
  * Wunderbaum - wunderbaum_node
2855
2927
  * 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)
2928
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
2857
2929
  */
2858
2930
  /** WunderbaumNode properties that can be passed with source data.
2859
2931
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3154,6 +3226,10 @@ class WunderbaumNode {
3154
3226
  }
3155
3227
  }
3156
3228
  }
3229
+ /** Start editing this node's title. */
3230
+ startEditTitle() {
3231
+ this.tree._callMethod("edit.startEditTitle", this);
3232
+ }
3157
3233
  /** Call `setExpanded()` on all descendant nodes. */
3158
3234
  async expandAll(flag = true, options) {
3159
3235
  const tree = this.tree;
@@ -3363,6 +3439,22 @@ class WunderbaumNode {
3363
3439
  const colElems = (_a = this._rowElem) === null || _a === void 0 ? void 0 : _a.querySelectorAll("span.wb-col");
3364
3440
  return colElems ? colElems[colIdx] : null;
3365
3441
  }
3442
+ /**
3443
+ * Return all nodes with the same refKey.
3444
+ *
3445
+ * @param includeSelf Include this node itself.
3446
+ * @see {@link Wunderbaum.findByRefKey}
3447
+ */
3448
+ getCloneList(includeSelf = false) {
3449
+ if (!this.refKey) {
3450
+ return [];
3451
+ }
3452
+ const clones = this.tree.findByRefKey(this.refKey);
3453
+ if (includeSelf) {
3454
+ return clones;
3455
+ }
3456
+ return [...clones].filter((n) => n !== this);
3457
+ }
3366
3458
  /** Return the first child node or null.
3367
3459
  * @returns {WunderbaumNode | null}
3368
3460
  */
@@ -3467,17 +3559,22 @@ class WunderbaumNode {
3467
3559
  return this.tree.activeNode === this;
3468
3560
  }
3469
3561
  /** Return true if this node is a direct or indirect parent of `other`.
3470
- * (See also [[isParentOf]].)
3562
+ * @see {@link WunderbaumNode.isParentOf}
3471
3563
  */
3472
3564
  isAncestorOf(other) {
3473
3565
  return other && other.isDescendantOf(this);
3474
3566
  }
3475
3567
  /** Return true if this node is a **direct** subnode of `other`.
3476
- * (See also [[isDescendantOf]].)
3568
+ * @see {@link WunderbaumNode.isDescendantOf}
3477
3569
  */
3478
3570
  isChildOf(other) {
3479
3571
  return other && this.parent === other;
3480
3572
  }
3573
+ /** Return true if this node's refKey is used by at least one other node.
3574
+ */
3575
+ isClone() {
3576
+ return !!this.refKey && this.tree.findByRefKey(this.refKey).length > 1;
3577
+ }
3481
3578
  /** Return true if this node's title spans all columns, i.e. the node has no
3482
3579
  * grid cells.
3483
3580
  */
@@ -3485,7 +3582,7 @@ class WunderbaumNode {
3485
3582
  return !!this.getOption("colspan");
3486
3583
  }
3487
3584
  /** Return true if this node is a direct or indirect subnode of `other`.
3488
- * (See also [[isChildOf]].)
3585
+ * @see {@link WunderbaumNode.isChildOf}
3489
3586
  */
3490
3587
  isDescendantOf(other) {
3491
3588
  if (!other || other.tree !== this.tree) {
@@ -3520,8 +3617,11 @@ class WunderbaumNode {
3520
3617
  }
3521
3618
  return true;
3522
3619
  }
3523
- /** Return true if this node is currently in edit-title mode. */
3524
- isEditing() {
3620
+ /** Return true if _this_ node is currently in edit-title mode.
3621
+ *
3622
+ * See {@link Wunderbaum.startEditTitle} to check if any node is currently edited.
3623
+ */
3624
+ isEditingTitle() {
3525
3625
  return this.tree._callMethod("edit.isEditingTitle", this);
3526
3626
  }
3527
3627
  /** Return true if this node is currently expanded. */
@@ -3555,7 +3655,7 @@ class WunderbaumNode {
3555
3655
  return this.statusNodeType === "paging";
3556
3656
  }
3557
3657
  /** Return true if this node is a **direct** parent of `other`.
3558
- * (See also [[isAncestorOf]].)
3658
+ * @see {@link WunderbaumNode.isAncestorOf}
3559
3659
  */
3560
3660
  isParentOf(other) {
3561
3661
  return other && other.parent === this;
@@ -3577,7 +3677,7 @@ class WunderbaumNode {
3577
3677
  return !!this._rowElem;
3578
3678
  }
3579
3679
  /** Return true if this node is the (invisible) system root node.
3580
- * (See also [[isTopLevel()]].)
3680
+ * @see {@link WunderbaumNode.isTopLevel}
3581
3681
  */
3582
3682
  isRootNode() {
3583
3683
  return this.tree.root === this;
@@ -3785,13 +3885,20 @@ class WunderbaumNode {
3785
3885
  }
3786
3886
  }
3787
3887
  }
3788
- /**Load content of a lazy node. */
3888
+ /**
3889
+ * Load content of a lazy node.
3890
+ * If the node is already loaded, nothing happens.
3891
+ * @param [forceReload=false] If true, reload even if already loaded.
3892
+ */
3789
3893
  async loadLazy(forceReload = false) {
3790
3894
  const wasExpanded = this.expanded;
3791
3895
  assert(this.lazy, "load() requires a lazy node");
3792
- // _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
3793
3896
  if (!forceReload && !this.isUnloaded()) {
3794
- return;
3897
+ return; // Already loaded: nothing to do
3898
+ }
3899
+ if (this.isLoading()) {
3900
+ this.logWarn("loadLazy() called while already loading: ignored.");
3901
+ return; // Already loading: prevent duplicate requests
3795
3902
  }
3796
3903
  if (this.isLoaded()) {
3797
3904
  this.resetLazy(); // Also collapses if currently expanded
@@ -3807,7 +3914,7 @@ class WunderbaumNode {
3807
3914
  }
3808
3915
  assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}`, or false.");
3809
3916
  await this.load(source);
3810
- this.setStatus(NodeStatusType.ok);
3917
+ this.setStatus(NodeStatusType.ok); // Also resets `this._isLoading`
3811
3918
  if (wasExpanded) {
3812
3919
  this.expanded = true;
3813
3920
  this.tree.update(ChangeType.structure);
@@ -3819,33 +3926,41 @@ class WunderbaumNode {
3819
3926
  catch (e) {
3820
3927
  this.logError("Error during loadLazy()", e);
3821
3928
  this._callEvent("error", { error: e });
3929
+ // Also resets `this._isLoading`:
3822
3930
  this.setStatus(NodeStatusType.error, { message: "" + e });
3823
3931
  }
3824
3932
  return;
3825
3933
  }
3826
- /** Alias for `logDebug` */
3934
+ /** Write to `console.log` with node name as prefix if opts.debugLevel >= 4.
3935
+ * @see {@link WunderbaumNode.logDebug}
3936
+ */
3827
3937
  log(...args) {
3828
- this.logDebug(...args);
3938
+ if (this.tree.options.debugLevel >= 4) {
3939
+ console.log(this.toString(), ...args); // eslint-disable-line no-console
3940
+ }
3829
3941
  }
3830
- /* Log to console if opts.debugLevel >= 4 */
3942
+ /** Write to `console.debug` with node name as prefix if opts.debugLevel >= 4
3943
+ * and browser console level includes debug/verbose messages.
3944
+ * @see {@link WunderbaumNode.log}
3945
+ */
3831
3946
  logDebug(...args) {
3832
3947
  if (this.tree.options.debugLevel >= 4) {
3833
- console.log(this.toString(), ...args); // eslint-disable-line no-console
3948
+ console.debug(this.toString(), ...args); // eslint-disable-line no-console
3834
3949
  }
3835
3950
  }
3836
- /* Log error to console. */
3951
+ /** Write to `console.error` with node name as prefix if opts.debugLevel >= 1. */
3837
3952
  logError(...args) {
3838
3953
  if (this.tree.options.debugLevel >= 1) {
3839
3954
  console.error(this.toString(), ...args); // eslint-disable-line no-console
3840
3955
  }
3841
3956
  }
3842
- /* Log to console if opts.debugLevel >= 3 */
3957
+ /** Write to `console.info` with node name as prefix if opts.debugLevel >= 3. */
3843
3958
  logInfo(...args) {
3844
3959
  if (this.tree.options.debugLevel >= 3) {
3845
3960
  console.info(this.toString(), ...args); // eslint-disable-line no-console
3846
3961
  }
3847
3962
  }
3848
- /* Log warning to console if opts.debugLevel >= 2 */
3963
+ /** Write to `console.warn` with node name as prefix if opts.debugLevel >= 2. */
3849
3964
  logWarn(...args) {
3850
3965
  if (this.tree.options.debugLevel >= 2) {
3851
3966
  console.warn(this.toString(), ...args); // eslint-disable-line no-console
@@ -4036,15 +4151,16 @@ class WunderbaumNode {
4036
4151
  }
4037
4152
  /** Remove all descendants of this node. */
4038
4153
  removeChildren() {
4154
+ var _a, _b;
4039
4155
  const tree = this.tree;
4040
4156
  if (!this.children) {
4041
4157
  return;
4042
4158
  }
4043
- if (tree.activeNode && tree.activeNode.isDescendantOf(this)) {
4159
+ if ((_a = tree.activeNode) === null || _a === void 0 ? void 0 : _a.isDescendantOf(this)) {
4044
4160
  tree.activeNode.setActive(false); // TODO: don't fire events
4045
4161
  }
4046
- if (tree.focusNode && tree.focusNode.isDescendantOf(this)) {
4047
- tree.focusNode = null;
4162
+ if ((_b = tree.focusNode) === null || _b === void 0 ? void 0 : _b.isDescendantOf(this)) {
4163
+ tree._setFocusNode(null);
4048
4164
  }
4049
4165
  // TODO: persist must take care to clear select and expand cookies
4050
4166
  // Unlink children to support GC
@@ -4456,6 +4572,7 @@ class WunderbaumNode {
4456
4572
  let i = 0;
4457
4573
  for (const colSpan of rowDiv.children) {
4458
4574
  colSpan.classList.toggle("wb-active", i++ === tree.activeColIdx);
4575
+ colSpan.classList.remove("wb-error", "wb-invalid");
4459
4576
  }
4460
4577
  // Update icon (if not opts.isNew, which would rebuild markup anyway)
4461
4578
  const iconSpan = nodeElem.querySelector("i.wb-icon");
@@ -4637,16 +4754,21 @@ class WunderbaumNode {
4637
4754
  return this.tree.scrollTo(opts);
4638
4755
  }
4639
4756
  /**
4640
- * Activate this node, deactivate previous, send events, activate column and scroll int viewport.
4757
+ * Activate this node, deactivate previous, send events, activate column and
4758
+ * scroll into viewport.
4641
4759
  */
4642
4760
  async setActive(flag = true, options) {
4643
4761
  const tree = this.tree;
4644
- const prev = tree.activeNode;
4762
+ const prev = tree.getActiveNode();
4645
4763
  const retrigger = options === null || options === void 0 ? void 0 : options.retrigger; // Default: false
4646
4764
  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
4765
+ // const focusNode = options?.focusNode !== false; // Default: true
4648
4766
  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
4767
+ const orgEvent = options === null || options === void 0 ? void 0 : options.event; // Default: null
4768
+ const colIdx = options === null || options === void 0 ? void 0 : options.colIdx; // Default: null
4769
+ const edit = options === null || options === void 0 ? void 0 : options.edit; // Default: false
4770
+ assert(!colIdx || tree.isCellNav(), "colIdx requires cellNav");
4771
+ assert(!edit || colIdx != null, "edit requires colIdx");
4650
4772
  if (!noEvents) {
4651
4773
  if (flag) {
4652
4774
  if (prev !== this || retrigger) {
@@ -4660,7 +4782,7 @@ class WunderbaumNode {
4660
4782
  }) === false) {
4661
4783
  return;
4662
4784
  }
4663
- tree.activeNode = null;
4785
+ tree._setActiveNode(null);
4664
4786
  prev === null || prev === void 0 ? void 0 : prev.update(ChangeType.status);
4665
4787
  }
4666
4788
  }
@@ -4670,43 +4792,51 @@ class WunderbaumNode {
4670
4792
  }
4671
4793
  if (prev !== this) {
4672
4794
  if (flag) {
4673
- tree.activeNode = this;
4674
- if (focusNode || focusTree) {
4675
- tree.focusNode = this;
4676
- }
4677
- if (focusTree) {
4678
- tree.setFocus();
4679
- }
4795
+ tree._setActiveNode(this);
4680
4796
  }
4681
4797
  prev === null || prev === void 0 ? void 0 : prev.update(ChangeType.status);
4682
4798
  this.update(ChangeType.status);
4683
4799
  }
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();
4800
+ return this.makeVisible().then(() => {
4801
+ if (flag) {
4802
+ if (focusTree || edit) {
4803
+ tree.setFocus();
4804
+ tree._setFocusNode(this);
4805
+ tree.focusNode.setFocus();
4806
+ }
4807
+ // if (focusNode || edit) {
4808
+ // tree.focusNode = this;
4809
+ // tree.focusNode.setFocus();
4810
+ // }
4811
+ if (colIdx != null && tree.isCellNav()) {
4812
+ tree.setColumn(colIdx, { edit: edit });
4813
+ }
4814
+ if (!noEvents) {
4815
+ this._callEvent("activate", { prevNode: prev, event: orgEvent });
4816
+ }
4817
+ }
4818
+ });
4694
4819
  }
4695
4820
  /**
4696
4821
  * Expand or collapse this node.
4697
4822
  */
4698
4823
  async setExpanded(flag = true, options) {
4699
4824
  const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
4825
+ const sendEvents = !(options === null || options === void 0 ? void 0 : options.noEvents); // Default: send events
4700
4826
  if (!flag &&
4701
4827
  this.isExpanded() &&
4702
4828
  this.getLevel() <= this.tree.getOption("minExpandLevel") &&
4703
4829
  !force) {
4704
- this.logDebug("Ignored collapse request below expandLevel.");
4830
+ this.logDebug("Ignored collapse request below minExpandLevel.");
4705
4831
  return;
4706
4832
  }
4707
4833
  if (!flag === !this.expanded) {
4708
4834
  return; // Nothing to do
4709
4835
  }
4836
+ if (sendEvents &&
4837
+ this._callEvent("beforeExpand", { flag: flag }) === false) {
4838
+ return;
4839
+ }
4710
4840
  // this.log("setExpanded()");
4711
4841
  if (flag && this.getOption("autoCollapse")) {
4712
4842
  this.collapseSiblings(options);
@@ -4725,15 +4855,18 @@ class WunderbaumNode {
4725
4855
  lastChild.scrollIntoView({ topNode: this });
4726
4856
  }
4727
4857
  }
4858
+ if (sendEvents) {
4859
+ this._callEvent("expand", { flag: flag });
4860
+ }
4728
4861
  }
4729
4862
  /**
4730
4863
  * Set keyboard focus here.
4731
4864
  * @see {@link setActive}
4732
4865
  */
4733
4866
  setFocus(flag = true) {
4734
- assert(!!flag, "blur is not yet implemented");
4867
+ assert(!!flag, "Blur is not yet implemented");
4735
4868
  const prev = this.tree.focusNode;
4736
- this.tree.focusNode = this;
4869
+ this.tree._setFocusNode(this);
4737
4870
  prev === null || prev === void 0 ? void 0 : prev.update();
4738
4871
  this.update();
4739
4872
  }
@@ -5186,7 +5319,7 @@ WunderbaumNode.sequence = 0;
5186
5319
  /*!
5187
5320
  * Wunderbaum - ext-edit
5188
5321
  * 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)
5322
+ * v0.8.1, Sat, 20 Jan 2024 15:57:59 GMT (https://github.com/mar10/wunderbaum)
5190
5323
  */
5191
5324
  // const START_MARKER = "\uFFF7";
5192
5325
  class EditExtension extends WunderbaumExtension {
@@ -5211,40 +5344,62 @@ class EditExtension extends WunderbaumExtension {
5211
5344
  this.debouncedOnChange = debounce(this._onChange.bind(this), this.getPluginOption("debounce"));
5212
5345
  }
5213
5346
  /*
5214
- * Call an event handler, while marking the current node cell 'dirty'.
5347
+ * Call an event handler, while marking the current node cell 'busy'.
5348
+ * Deal with returned promises and ValidationError.
5349
+ * Convert a ValidationError into a input.setCustomValidity() call and vice versa.
5215
5350
  */
5216
- _applyChange(eventName, node, colElem, extra) {
5217
- let res;
5351
+ async _applyChange(eventName, node, colElem, inputElem, extra) {
5218
5352
  node.log(`_applyChange(${eventName})`, extra);
5219
5353
  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
5354
+ colElem.classList.remove("wb-error", "wb-invalid");
5355
+ inputElem.setCustomValidity("");
5356
+ // Call event handler either ('change' or 'edit.appy'), which may return a
5357
+ // promise or a scalar value or throw a ValidationError.
5358
+ return new Promise((resolve, reject) => {
5359
+ const res = node._callEvent(eventName, extra);
5360
+ // normalize to promise, even if a scalar value was returned and await it
5361
+ Promise.resolve(res)
5362
+ .then((res) => {
5363
+ resolve(res);
5364
+ })
5365
+ .catch((err) => {
5366
+ reject(err);
5367
+ });
5368
+ })
5369
+ .then((res) => {
5370
+ if (!inputElem.checkValidity()) {
5371
+ // Native validation failed or handler called 'inputElem.setCustomValidity()'
5372
+ node.logWarn("inputElem.checkValidity() failed: throwing....");
5373
+ throw new ValidationError(inputElem.validationMessage);
5374
+ }
5375
+ return res;
5376
+ })
5234
5377
  .catch((err) => {
5235
- node.logError(`Error in ${eventName} event promise`, err);
5236
- colElem.classList.add("wb-error");
5378
+ if (err instanceof ValidationError) {
5379
+ node.logWarn("catched ", err);
5380
+ colElem.classList.add("wb-invalid");
5381
+ if (inputElem.setCustomValidity && !inputElem.validationMessage) {
5382
+ inputElem.setCustomValidity(err.message);
5383
+ }
5384
+ if (inputElem.validationMessage) {
5385
+ inputElem.reportValidity();
5386
+ }
5387
+ // throw err;
5388
+ }
5389
+ else {
5390
+ node.logError(`Error in ${eventName} event handler (throw e.util.ValidationError instead if this was intended)`, err);
5391
+ colElem.classList.add("wb-error");
5392
+ throw err;
5393
+ }
5237
5394
  })
5238
5395
  .finally(() => {
5239
5396
  colElem.classList.remove("wb-busy");
5240
5397
  });
5241
- return res;
5242
5398
  }
5243
5399
  /*
5244
5400
  * Called for when a control that is embedded in a cell fires a `change` event.
5245
5401
  */
5246
5402
  _onChange(e) {
5247
- // let res;
5248
5403
  const info = Wunderbaum.getEventInfo(e);
5249
5404
  const node = info.node;
5250
5405
  const colElem = info.colElem;
@@ -5252,16 +5407,15 @@ class EditExtension extends WunderbaumExtension {
5252
5407
  this.tree.log("Ignored change event for removed element or node title");
5253
5408
  return;
5254
5409
  }
5255
- this._applyChange("change", node, colElem, {
5410
+ // See also WbChangeEventType
5411
+ this._applyChange("change", node, colElem, e.target, {
5256
5412
  info: info,
5257
5413
  event: e,
5258
5414
  inputElem: e.target,
5259
5415
  inputValue: Wunderbaum.util.getValueFromElem(e.target),
5416
+ inputValid: e.target.checkValidity(),
5260
5417
  });
5261
5418
  }
5262
- // handleKey(e:KeyboardEvent):boolean {
5263
- // if(this.tree.cellNavMode )
5264
- // }
5265
5419
  init() {
5266
5420
  super.init();
5267
5421
  onEvent(this.tree.element, "change", //"change input",
@@ -5308,7 +5462,6 @@ class EditExtension extends WunderbaumExtension {
5308
5462
  break;
5309
5463
  case "F2":
5310
5464
  if (trigger.indexOf("F2") >= 0) {
5311
- // tree.setNavigationMode(NavigationMode.cellEdit);
5312
5465
  this.startEditTitle();
5313
5466
  return false;
5314
5467
  }
@@ -5333,14 +5486,21 @@ class EditExtension extends WunderbaumExtension {
5333
5486
  this.tree.logDebug(`startEditTitle(node=${node})`);
5334
5487
  let inputHtml = node._callEvent("edit.beforeEdit");
5335
5488
  if (inputHtml === false) {
5336
- node.logInfo("beforeEdit canceled operation.");
5489
+ node.logDebug("beforeEdit canceled operation.");
5337
5490
  return;
5338
5491
  }
5339
- // `beforeEdit(e)` may return an input HTML string. Otherwise use a default.
5492
+ // `beforeEdit(e)` may return an input HTML string. Otherwise use a default
5340
5493
  // (we also treat a `true` return value as 'use default'):
5341
5494
  if (inputHtml === true || !inputHtml) {
5342
5495
  const title = escapeHtml(node.title);
5343
- inputHtml = `<input type=text class="wb-input-edit" tabindex=-1 value="${title}" required autocorrect=off>`;
5496
+ let opt = this.getPluginOption("maxlength");
5497
+ const maxlength = opt ? ` maxlength="${opt}"` : "";
5498
+ opt = this.getPluginOption("minlength");
5499
+ const minlength = opt ? ` minlength="${opt}"` : "";
5500
+ const required = opt > 0 ? " required" : "";
5501
+ inputHtml =
5502
+ `<input type=text class="wb-input-edit" tabindex=-1 value="${title}" ` +
5503
+ `autocorrect="off"${required}${minlength}${maxlength} >`;
5344
5504
  }
5345
5505
  const titleSpan = node
5346
5506
  .getColElem(0)
@@ -5351,7 +5511,9 @@ class EditExtension extends WunderbaumExtension {
5351
5511
  // Permanently apply input validations (CSS and tooltip)
5352
5512
  inputElem.addEventListener("keydown", (e) => {
5353
5513
  inputElem.setCustomValidity("");
5354
- if (!inputElem.reportValidity()) ;
5514
+ if (!inputElem.reportValidity()) {
5515
+ node.logWarn(`Invalid input: '${inputElem.value}'`);
5516
+ }
5355
5517
  });
5356
5518
  }
5357
5519
  inputElem.focus();
@@ -5398,12 +5560,12 @@ class EditExtension extends WunderbaumExtension {
5398
5560
  throw new Error(`Input validation failed for "${newValue}": ${errMsg}.`);
5399
5561
  }
5400
5562
  const colElem = node.getColElem(0);
5401
- this._applyChange("edit.apply", node, colElem, {
5563
+ this._applyChange("edit.apply", node, colElem, focusElem, {
5402
5564
  oldValue: node.title,
5403
5565
  newValue: newValue,
5404
5566
  inputElem: focusElem,
5405
- })
5406
- .then((value) => {
5567
+ inputValid: focusElem.checkValidity(),
5568
+ }).then((value) => {
5407
5569
  const errMsg = focusElem.validationMessage;
5408
5570
  if (validity && errMsg && value !== false) {
5409
5571
  // Handler called 'inputElem.setCustomValidity()' to signal error
@@ -5422,10 +5584,10 @@ class EditExtension extends WunderbaumExtension {
5422
5584
  this.curEditNode = null;
5423
5585
  this.relatedNode = null;
5424
5586
  this.tree.setFocus(); // restore focus that was in the input element
5425
- })
5426
- .catch((err) => {
5427
- node.logError(err);
5428
5587
  });
5588
+ // .catch((err) => {
5589
+ // node.logError(err);
5590
+ // });
5429
5591
  // Trigger 'change' event for embedded `<input>`
5430
5592
  // focusElem.blur();
5431
5593
  }
@@ -5486,8 +5648,8 @@ class EditExtension extends WunderbaumExtension {
5486
5648
  * https://github.com/mar10/wunderbaum
5487
5649
  *
5488
5650
  * Released under the MIT license.
5489
- * @version v0.7.0
5490
- * @date Sat, 09 Dec 2023 13:47:27 GMT
5651
+ * @version v0.8.1
5652
+ * @date Sat, 20 Jan 2024 15:57:59 GMT
5491
5653
  */
5492
5654
  // import "./wunderbaum.scss";
5493
5655
  class WbSystemRoot extends WunderbaumNode {
@@ -5504,9 +5666,25 @@ class WbSystemRoot extends WunderbaumNode {
5504
5666
  /**
5505
5667
  * A persistent plain object or array.
5506
5668
  *
5507
- * See also [[WunderbaumOptions]].
5669
+ * See also {@link WunderbaumOptions}.
5508
5670
  */
5509
5671
  class Wunderbaum {
5672
+ /** Currently active node if any.
5673
+ * Use @link {WunderbaumNode.setActive|setActive} to modify.
5674
+ */
5675
+ get activeNode() {
5676
+ var _a;
5677
+ // Check for deleted node, i.e. node.tree === null
5678
+ return ((_a = this._activeNode) === null || _a === void 0 ? void 0 : _a.tree) ? this._activeNode : null;
5679
+ }
5680
+ /** Current node hat has keyboard focus if any.
5681
+ * Use @link {WunderbaumNode.setFocus|setFocus()} to modify.
5682
+ */
5683
+ get focusNode() {
5684
+ var _a;
5685
+ // Check for deleted node, i.e. node.tree === null
5686
+ return ((_a = this._focusNode) === null || _a === void 0 ? void 0 : _a.tree) ? this._focusNode : null;
5687
+ }
5510
5688
  constructor(options) {
5511
5689
  this.enabled = true;
5512
5690
  /** Contains additional data that was sent as response to an Ajax source load request. */
@@ -5518,10 +5696,8 @@ class Wunderbaum {
5518
5696
  this.treeRowCount = 0;
5519
5697
  this._disableUpdateCount = 0;
5520
5698
  this._disableUpdateIgnoreCount = 0;
5521
- /** Currently active node if any. */
5522
- this.activeNode = null;
5523
- /** Current node hat has keyboard focus if any. */
5524
- this.focusNode = null;
5699
+ this._activeNode = null;
5700
+ this._focusNode = null;
5525
5701
  /** Shared properties, referenced by `node.type`. */
5526
5702
  this.types = {};
5527
5703
  /** List of column definitions. */
@@ -5537,7 +5713,7 @@ class Wunderbaum {
5537
5713
  // --- FILTER ---
5538
5714
  this.filterMode = null;
5539
5715
  // --- KEYNAV ---
5540
- /** @internal Use `setColumn()`/`getActiveColElem()`*/
5716
+ /** @internal Use `setColumn()`/`getActiveColElem()` to access. */
5541
5717
  this.activeColIdx = 0;
5542
5718
  /** @internal */
5543
5719
  this._cellNavMode = false;
@@ -5547,10 +5723,6 @@ class Wunderbaum {
5547
5723
  this.lastQuicksearchTerm = "";
5548
5724
  // --- EDIT ---
5549
5725
  this.lastClickTime = 0;
5550
- /** Alias for {@link Wunderbaum.logDebug}.
5551
- * @alias Wunderbaum.logDebug
5552
- */
5553
- this.log = this.logDebug;
5554
5726
  const opts = (this.options = extend({
5555
5727
  id: null,
5556
5728
  source: null,
@@ -5713,6 +5885,7 @@ class Wunderbaum {
5713
5885
  else {
5714
5886
  this.setNavigationOption(opts.navigationModeOption);
5715
5887
  }
5888
+ this.update(ChangeType.structure, { immediate: true });
5716
5889
  readyDeferred.resolve();
5717
5890
  })
5718
5891
  .catch((error) => {
@@ -5764,7 +5937,7 @@ class Wunderbaum {
5764
5937
  info.region === "title" &&
5765
5938
  node.isActive() &&
5766
5939
  (!slowClickDelay || Date.now() - this.lastClickTime < slowClickDelay)) {
5767
- this._callMethod("edit.startEditTitle", node);
5940
+ node.startEditTitle();
5768
5941
  }
5769
5942
  if (info.colIdx >= 0) {
5770
5943
  node.setActive(true, { colIdx: info.colIdx, event: e });
@@ -5811,7 +5984,7 @@ class Wunderbaum {
5811
5984
  const flag = e.type === "focusin";
5812
5985
  const targetNode = Wunderbaum.getNode(e);
5813
5986
  this._callEvent("focus", { flag: flag, event: e });
5814
- if (flag && this.isRowNav() && !this.isEditing()) {
5987
+ if (flag && this.isRowNav() && !this.isEditingTitle()) {
5815
5988
  if (opts.navigationModeOption === NavModeEnum.row) {
5816
5989
  targetNode === null || targetNode === void 0 ? void 0 : targetNode.setActive();
5817
5990
  }
@@ -5835,7 +6008,7 @@ class Wunderbaum {
5835
6008
  * getTree(1); // Get second Wunderbaum instance on page
5836
6009
  * getTree(event); // Get tree for this mouse- or keyboard event
5837
6010
  * getTree("foo"); // Get tree for this `tree.options.id`
5838
- * getTree("#tree"); // Get tree for this matching element
6011
+ * getTree("#tree"); // Get tree for first matching element selector
5839
6012
  * ```
5840
6013
  */
5841
6014
  static getTree(el) {
@@ -5939,34 +6112,37 @@ class Wunderbaum {
5939
6112
  /** Add node to tree's bookkeeping data structures. */
5940
6113
  _registerNode(node) {
5941
6114
  const key = node.key;
5942
- assert(key != null && !this.keyMap.has(key), `Missing or duplicate key: '${key}'.`);
6115
+ assert(key != null, `Missing key: '${node}'.`);
6116
+ assert(!this.keyMap.has(key), `Duplicate key: '${key}': ${node}.`);
5943
6117
  this.keyMap.set(key, node);
5944
6118
  const rk = node.refKey;
5945
- if (rk) {
6119
+ if (rk != null) {
5946
6120
  const rks = this.refKeyMap.get(rk); // Set of nodes with this refKey
5947
6121
  if (rks) {
5948
6122
  rks.add(node);
5949
6123
  }
5950
6124
  else {
5951
- this.refKeyMap.set(rk, new Set());
6125
+ this.refKeyMap.set(rk, new Set([node]));
5952
6126
  }
5953
6127
  }
5954
6128
  }
5955
6129
  /** Remove node from tree's bookkeeping data structures. */
5956
6130
  _unregisterNode(node) {
6131
+ // Remove refKey reference from map (if any)
5957
6132
  const rk = node.refKey;
5958
- if (rk) {
6133
+ if (rk != null) {
5959
6134
  const rks = this.refKeyMap.get(rk);
5960
6135
  if (rks && rks.delete(node) && !rks.size) {
5961
6136
  // We just removed the last element
5962
6137
  this.refKeyMap.delete(rk);
5963
6138
  }
5964
6139
  }
5965
- // mark as disposed
6140
+ // Remove key reference from map
6141
+ this.keyMap.delete(node.key);
6142
+ // Mark as disposed
5966
6143
  node.tree = null;
5967
6144
  node.parent = null;
5968
- // node.title = "DISPOSED: " + node.title
5969
- // this.viewNodes.delete(node);
6145
+ // Remove HTML markup
5970
6146
  node.removeMarkup();
5971
6147
  }
5972
6148
  /** Call all hook methods of all registered extensions.*/
@@ -6170,7 +6346,7 @@ class Wunderbaum {
6170
6346
  this._callMethod("edit.createNode", "after");
6171
6347
  break;
6172
6348
  case "rename":
6173
- this._callMethod("edit.startEditTitle");
6349
+ node.startEditTitle();
6174
6350
  break;
6175
6351
  // Simple clipboard simulation:
6176
6352
  // case "cut":
@@ -6217,10 +6393,9 @@ class Wunderbaum {
6217
6393
  this.root.children = null;
6218
6394
  this.keyMap.clear();
6219
6395
  this.refKeyMap.clear();
6220
- // this.viewNodes.clear();
6221
6396
  this.treeRowCount = 0;
6222
- this.activeNode = null;
6223
- this.focusNode = null;
6397
+ this._activeNode = null;
6398
+ this._focusNode = null;
6224
6399
  // this.types = {};
6225
6400
  // this. columns =[];
6226
6401
  // this._columnsById = {};
@@ -6410,14 +6585,31 @@ class Wunderbaum {
6410
6585
  /**
6411
6586
  * Find all nodes that match condition.
6412
6587
  *
6588
+ * @param match title string to search for, or a
6589
+ * callback function that returns `true` if a node is matched.
6413
6590
  * @see {@link WunderbaumNode.findAll}
6414
6591
  */
6415
6592
  findAll(match) {
6416
6593
  return this.root.findAll(match);
6417
6594
  }
6595
+ /**
6596
+ * Find all nodes with a given _refKey_ (aka a list of clones).
6597
+ *
6598
+ * @param refKey a `node.refKey` value to search for.
6599
+ * @returns an array of matching nodes with at least two element or `[]`
6600
+ * if nothing found.
6601
+ *
6602
+ * @see {@link WunderbaumNode.getCloneList}
6603
+ */
6604
+ findByRefKey(refKey) {
6605
+ const clones = this.refKeyMap.get(refKey);
6606
+ return clones ? Array.from(clones) : [];
6607
+ }
6418
6608
  /**
6419
6609
  * Find first node that matches condition.
6420
6610
  *
6611
+ * @param match title string to search for, or a
6612
+ * callback function that returns `true` if a node is matched.
6421
6613
  * @see {@link WunderbaumNode.findFirst}
6422
6614
  */
6423
6615
  findFirst(match) {
@@ -6426,8 +6618,6 @@ class Wunderbaum {
6426
6618
  /**
6427
6619
  * Find first node that matches condition.
6428
6620
  *
6429
- * @param match title string to search for, or a
6430
- * callback function that returns `true` if a node is matched.
6431
6621
  * @see {@link WunderbaumNode.findFirst}
6432
6622
  *
6433
6623
  */
@@ -6437,6 +6627,7 @@ class Wunderbaum {
6437
6627
  /**
6438
6628
  * Find the next visible node that starts with `match`, starting at `startNode`
6439
6629
  * and wrap-around at the end.
6630
+ * Used by quicksearch and keyboard navigation.
6440
6631
  */
6441
6632
  findNextNode(match, startNode) {
6442
6633
  //, visibleOnly) {
@@ -6603,7 +6794,13 @@ class Wunderbaum {
6603
6794
  return null;
6604
6795
  }
6605
6796
  /**
6606
- * Return the currently active node or null.
6797
+ * Return the currently active node or null (alias for `tree.activeNode`).
6798
+ * Alias for {@link Wunderbaum.activeNode}.
6799
+ *
6800
+ * @see {@link WunderbaumNode.setActive}
6801
+ * @see {@link WunderbaumNode.isActive}
6802
+ * @see {@link Wunderbaum.activeNode}
6803
+ * @see {@link Wunderbaum.focusNode}
6607
6804
  */
6608
6805
  getActiveNode() {
6609
6806
  return this.activeNode;
@@ -6615,7 +6812,12 @@ class Wunderbaum {
6615
6812
  return this.root.getFirstChild();
6616
6813
  }
6617
6814
  /**
6618
- * Return the currently active node or null.
6815
+ * Return the node that currently has keyboard focus or null.
6816
+ * Alias for {@link Wunderbaum.focusNode}.
6817
+ * @see {@link WunderbaumNode.setFocus}
6818
+ * @see {@link WunderbaumNode.hasFocus}
6819
+ * @see {@link Wunderbaum.activeNode}
6820
+ * @see {@link Wunderbaum.focusNode}
6619
6821
  */
6620
6822
  getFocusNode() {
6621
6823
  return this.focusNode;
@@ -6692,8 +6894,19 @@ class Wunderbaum {
6692
6894
  toString() {
6693
6895
  return `Wunderbaum<'${this.id}'>`;
6694
6896
  }
6695
- /** Return true if any node is currently in edit-title mode. */
6897
+ /** Return true if any node title or grid cell is currently beeing edited.
6898
+ *
6899
+ * See also {@link Wunderbaum.isEditingTitle}.
6900
+ */
6696
6901
  isEditing() {
6902
+ const focusElem = this.nodeListElement.querySelector("input:focus,select:focus");
6903
+ return !!focusElem;
6904
+ }
6905
+ /** Return true if any node is currently in edit-title mode.
6906
+ *
6907
+ * See also {@link WunderbaumNode.isEditingTitle} and {@link Wunderbaum.isEditing}.
6908
+ */
6909
+ isEditingTitle() {
6697
6910
  return this._callMethod("edit.isEditingTitle");
6698
6911
  }
6699
6912
  /**
@@ -6710,19 +6923,30 @@ class Wunderbaum {
6710
6923
  }, true);
6711
6924
  return res;
6712
6925
  }
6713
- /** Log to console if opts.debugLevel >= 4 */
6714
- logDebug(...args) {
6926
+ /** Write to `console.log` with tree name as prefix if opts.debugLevel >= 4.
6927
+ * @see {@link Wunderbaum.logDebug}
6928
+ */
6929
+ log(...args) {
6715
6930
  if (this.options.debugLevel >= 4) {
6716
6931
  console.log(this.toString(), ...args); // eslint-disable-line no-console
6717
6932
  }
6718
6933
  }
6719
- /** Log error to console. */
6934
+ /** Write to `console.debug` with tree name as prefix if opts.debugLevel >= 4.
6935
+ * and browser console level includes debug/verbose messages.
6936
+ * @see {@link Wunderbaum.log}
6937
+ */
6938
+ logDebug(...args) {
6939
+ if (this.options.debugLevel >= 4) {
6940
+ console.debug(this.toString(), ...args); // eslint-disable-line no-console
6941
+ }
6942
+ }
6943
+ /** Write to `console.error` with tree name as prefix. */
6720
6944
  logError(...args) {
6721
6945
  if (this.options.debugLevel >= 1) {
6722
6946
  console.error(this.toString(), ...args); // eslint-disable-line no-console
6723
6947
  }
6724
6948
  }
6725
- /** Log to console if opts.debugLevel >= 3 */
6949
+ /** Write to `console.info` with tree name as prefix if opts.debugLevel >= 3. */
6726
6950
  logInfo(...args) {
6727
6951
  if (this.options.debugLevel >= 3) {
6728
6952
  console.info(this.toString(), ...args); // eslint-disable-line no-console
@@ -6741,7 +6965,7 @@ class Wunderbaum {
6741
6965
  console.timeEnd(this + ": " + label); // eslint-disable-line no-console
6742
6966
  }
6743
6967
  }
6744
- /** Log to console if opts.debugLevel >= 2 */
6968
+ /** Write to `console.warn` with tree name as prefix with if opts.debugLevel >= 2. */
6745
6969
  logWarn(...args) {
6746
6970
  if (this.options.debugLevel >= 2) {
6747
6971
  console.warn(this.toString(), ...args); // eslint-disable-line no-console
@@ -6829,12 +7053,23 @@ class Wunderbaum {
6829
7053
  /**
6830
7054
  * Set column #colIdx to 'active'.
6831
7055
  *
6832
- * This higlights the column header and -cells by adding the `wb-active` class.
7056
+ * This higlights the column header and -cells by adding the `wb-active`
7057
+ * class to all grid cells of the active column. <br>
6833
7058
  * Available in cell-nav mode only.
7059
+ *
7060
+ * If _options.edit_ is true, the embedded input element is focused, or if
7061
+ * colIdx is 0, the node title is put into edit mode.
6834
7062
  */
6835
- setColumn(colIdx) {
6836
- var _a;
6837
- assert(this.isCellNav(), "Exected cellNav mode");
7063
+ setColumn(colIdx, options) {
7064
+ var _a, _b, _c;
7065
+ const edit = options === null || options === void 0 ? void 0 : options.edit;
7066
+ const scroll = (options === null || options === void 0 ? void 0 : options.scrollIntoView) !== false;
7067
+ assert(this.isCellNav(), "Expected cellNav mode");
7068
+ if (typeof colIdx === "string") {
7069
+ const cid = colIdx;
7070
+ colIdx = this.columns.findIndex((c) => c.id === colIdx);
7071
+ assert(colIdx >= 0, `Invalid colId: ${cid}`);
7072
+ }
6838
7073
  assert(0 <= colIdx && colIdx < this.columns.length, `Invalid colIdx: ${colIdx}`);
6839
7074
  this.activeColIdx = colIdx;
6840
7075
  // Update `wb-active` class for all headers
@@ -6854,17 +7089,30 @@ class Wunderbaum {
6854
7089
  colDiv.classList.toggle("wb-active", i++ === colIdx);
6855
7090
  }
6856
7091
  }
6857
- // Vertical scroll into view
6858
- // if (this.options.fixedCol) {
6859
- this.scrollToHorz();
6860
- // }
7092
+ // Horizontically scroll into view
7093
+ if (scroll || edit) {
7094
+ this.scrollToHorz();
7095
+ }
7096
+ if (edit && this.activeNode) {
7097
+ // this.activeNode.setFocus(); // Blur prev. input if any
7098
+ if (colIdx === 0) {
7099
+ this.activeNode.startEditTitle();
7100
+ }
7101
+ else {
7102
+ (_c = (_b = this.getActiveColElem()) === null || _b === void 0 ? void 0 : _b.querySelector("input,select")) === null || _c === void 0 ? void 0 : _c.focus();
7103
+ }
7104
+ }
6861
7105
  }
6862
- /** Set or remove keybaord focus to the tree container. */
7106
+ /* Set or remove keyboard focus to the tree container. @internal */
7107
+ _setActiveNode(node) {
7108
+ this._activeNode = node;
7109
+ }
7110
+ /** Set or remove keyboard focus to the tree container. */
6863
7111
  setActiveNode(key, flag = true, options) {
6864
7112
  var _a;
6865
7113
  (_a = this.findKey(key)) === null || _a === void 0 ? void 0 : _a.setActive(flag, options);
6866
7114
  }
6867
- /** Set or remove keybaord focus to the tree container. */
7115
+ /** Set or remove keyboard focus to the tree container. */
6868
7116
  setFocus(flag = true) {
6869
7117
  if (flag) {
6870
7118
  this.element.focus();
@@ -6873,7 +7121,19 @@ class Wunderbaum {
6873
7121
  this.element.blur();
6874
7122
  }
6875
7123
  }
7124
+ /* Set or remove keyboard focus to the tree container. @internal */
7125
+ _setFocusNode(node) {
7126
+ this._focusNode = node;
7127
+ }
6876
7128
  update(change, node, options) {
7129
+ // this.log(`update(${change}) node=${node}`);
7130
+ if (!(node instanceof WunderbaumNode)) {
7131
+ options = node;
7132
+ node = undefined;
7133
+ }
7134
+ const immediate = !!getOption(options, "immediate");
7135
+ const RF = RenderFlag;
7136
+ const pending = this.pendingChangeTypes;
6877
7137
  if (this._disableUpdateCount) {
6878
7138
  // Assuming that we redraw all when enableUpdate() is re-enabled.
6879
7139
  // this.log(
@@ -6882,14 +7142,6 @@ class Wunderbaum {
6882
7142
  this._disableUpdateIgnoreCount++;
6883
7143
  return;
6884
7144
  }
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
7145
  switch (change) {
6894
7146
  case ChangeType.any:
6895
7147
  case ChangeType.colStructure:
@@ -7217,10 +7469,14 @@ class Wunderbaum {
7217
7469
  _updateViewportImmediately() {
7218
7470
  var _a;
7219
7471
  if (this._disableUpdateCount) {
7220
- this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount})`);
7472
+ this.log(`_updateViewportImmediately() IGNORED (disable level: ${this._disableUpdateCount}).`);
7221
7473
  this._disableUpdateIgnoreCount++;
7222
7474
  return;
7223
7475
  }
7476
+ if (this._updateViewportThrottled.pending()) {
7477
+ // this.logWarn(`_updateViewportImmediately() cancel pending timer.`);
7478
+ this._updateViewportThrottled.cancel();
7479
+ }
7224
7480
  // Shorten container height to avoid v-scrollbar
7225
7481
  const FIX_ADJUST_HEIGHT = 1;
7226
7482
  const RF = RenderFlag;
@@ -7324,9 +7580,6 @@ class Wunderbaum {
7324
7580
  }
7325
7581
  let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7326
7582
  endIdx = Math.ceil(endIdx);
7327
- // const obsoleteViewNodes = this.viewNodes;
7328
- // this.viewNodes = new Set();
7329
- // const viewNodes = this.viewNodes;
7330
7583
  // this.debug("render", opts);
7331
7584
  const obsoleteNodes = new Set();
7332
7585
  this.nodeListElement.childNodes.forEach((elem) => {
@@ -7582,37 +7835,31 @@ class Wunderbaum {
7582
7835
  * FILTER
7583
7836
  * -------------------------------------------------------------------------*/
7584
7837
  /**
7585
- * [ext-filter] Dim or hide nodes.
7838
+ * Dim or hide nodes.
7586
7839
  */
7587
7840
  filterNodes(filter, options) {
7588
7841
  return this.extensions.filter.filterNodes(filter, options);
7589
7842
  }
7590
7843
  /**
7591
- * [ext-filter] Dim or hide whole branches.
7844
+ * Dim or hide whole branches.
7592
7845
  */
7593
7846
  filterBranches(filter, options) {
7594
7847
  return this.extensions.filter.filterBranches(filter, options);
7595
7848
  }
7596
7849
  /**
7597
- * [ext-filter] Reset the filter.
7598
- *
7599
- * @requires [[FilterExtension]]
7850
+ * Reset the filter.
7600
7851
  */
7601
7852
  clearFilter() {
7602
7853
  return this.extensions.filter.clearFilter();
7603
7854
  }
7604
7855
  /**
7605
- * [ext-filter] Return true if a filter is currently applied.
7606
- *
7607
- * @requires [[FilterExtension]]
7856
+ * Return true if a filter is currently applied.
7608
7857
  */
7609
7858
  isFilterActive() {
7610
7859
  return !!this.filterMode;
7611
7860
  }
7612
7861
  /**
7613
- * [ext-filter] Re-apply current filter.
7614
- *
7615
- * @requires [[FilterExtension]]
7862
+ * Re-apply current filter.
7616
7863
  */
7617
7864
  updateFilter() {
7618
7865
  return this.extensions.filter.updateFilter();
@@ -7620,7 +7867,7 @@ class Wunderbaum {
7620
7867
  }
7621
7868
  Wunderbaum.sequence = 0;
7622
7869
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
7623
- Wunderbaum.version = "v0.7.0"; // Set to semver by 'grunt release'
7870
+ Wunderbaum.version = "v0.8.1"; // Set to semver by 'grunt release'
7624
7871
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
7625
7872
  Wunderbaum.util = util;
7626
7873