wunderbaum 0.11.0 → 0.11.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.
@@ -288,7 +288,7 @@ function throttle(func, wait = 0, options = {}) {
288
288
  /*!
289
289
  * Wunderbaum - util
290
290
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
291
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
291
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
292
292
  */
293
293
  /** @module util */
294
294
  /** Readable names for `MouseEvent.button` */
@@ -980,6 +980,15 @@ function toBool(...boolDefaults) {
980
980
  }
981
981
  throw new Error("No default boolean value provided");
982
982
  }
983
+ /**
984
+ * Return `val` unless `val` is a number in which case we convert to boolean.
985
+ * This is useful when a boolean value is stored as a 0/1 (e.g. in JSON) and
986
+ * we still want to maintain string values. null and undefined are returned as
987
+ * is. E.g. `checkbox` may be boolean or 'radio'.
988
+ */
989
+ function intToBool(val) {
990
+ return typeof val === "number" ? !!val : val;
991
+ }
983
992
  // /** Check if a string is contained in an Array or Set. */
984
993
  // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
985
994
  // return Array.prototype.includes.call(items, s)
@@ -1108,6 +1117,7 @@ var util = /*#__PURE__*/Object.freeze({
1108
1117
  extractHtmlText: extractHtmlText,
1109
1118
  getOption: getOption,
1110
1119
  getValueFromElem: getValueFromElem,
1120
+ intToBool: intToBool,
1111
1121
  isArray: isArray,
1112
1122
  isEmptyObject: isEmptyObject,
1113
1123
  isFunction: isFunction,
@@ -1132,7 +1142,7 @@ var util = /*#__PURE__*/Object.freeze({
1132
1142
  /*!
1133
1143
  * Wunderbaum - types
1134
1144
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1135
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1145
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1136
1146
  */
1137
1147
  /**
1138
1148
  * Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
@@ -1196,7 +1206,7 @@ var NavModeEnum;
1196
1206
  /*!
1197
1207
  * Wunderbaum - wb_extension_base
1198
1208
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1199
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1209
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1200
1210
  */
1201
1211
  class WunderbaumExtension {
1202
1212
  constructor(tree, id, defaults) {
@@ -1255,7 +1265,7 @@ class WunderbaumExtension {
1255
1265
  /*!
1256
1266
  * Wunderbaum - ext-filter
1257
1267
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1258
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1268
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1259
1269
  */
1260
1270
  const START_MARKER = "\uFFF7";
1261
1271
  const END_MARKER = "\uFFF8";
@@ -1580,7 +1590,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1580
1590
  /*!
1581
1591
  * Wunderbaum - ext-keynav
1582
1592
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1583
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1593
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1584
1594
  */
1585
1595
  const QUICKSEARCH_DELAY = 500;
1586
1596
  class KeynavExtension extends WunderbaumExtension {
@@ -1944,7 +1954,7 @@ class KeynavExtension extends WunderbaumExtension {
1944
1954
  /*!
1945
1955
  * Wunderbaum - ext-logger
1946
1956
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1947
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1957
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1948
1958
  */
1949
1959
  class LoggerExtension extends WunderbaumExtension {
1950
1960
  constructor(tree) {
@@ -1984,410 +1994,70 @@ class LoggerExtension extends WunderbaumExtension {
1984
1994
  }
1985
1995
 
1986
1996
  /*!
1987
- * Wunderbaum - common
1997
+ * Wunderbaum - ext-dnd
1988
1998
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1989
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1990
- */
1991
- const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
1992
- /**
1993
- * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
1994
- */
1995
- const ROW_HEIGHT = 22;
1996
- /**
1997
- * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
1998
- */
1999
- const ICON_WIDTH = 20;
2000
- /**
2001
- * Adjust the width of the title span, so overflow ellipsis work.
2002
- * (2 x `$col-padding-x` + 3px rounding errors).
2003
- */
2004
- const TITLE_SPAN_PAD_Y = 7;
2005
- /** Render row markup for N nodes above and below the visible viewport. */
2006
- const RENDER_MAX_PREFETCH = 5;
2007
- /** Minimum column width if not set otherwise. */
2008
- const DEFAULT_MIN_COL_WIDTH = 4;
2009
- /** Regular expression to detect if a string describes an image URL (in contrast
2010
- * to a class name). Strings are considered image urls if they contain '.' or '/'.
2011
- */
2012
- const TEST_IMG = new RegExp(/\.|\//);
2013
- // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2014
- // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2015
- /**
2016
- * Default node icons.
2017
- * Requires bootstrap icons https://icons.getbootstrap.com
2018
- */
2019
- const iconMaps = {
2020
- bootstrap: {
2021
- error: "bi bi-exclamation-triangle",
2022
- // loading: "bi bi-hourglass-split wb-busy",
2023
- loading: "bi bi-chevron-right wb-busy",
2024
- // loading: "bi bi-arrow-repeat wb-spin",
2025
- // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2026
- // noData: "bi bi-search",
2027
- noData: "bi bi-question-circle",
2028
- expanderExpanded: "bi bi-chevron-down",
2029
- // expanderExpanded: "bi bi-dash-square",
2030
- expanderCollapsed: "bi bi-chevron-right",
2031
- // expanderCollapsed: "bi bi-plus-square",
2032
- expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2033
- // expanderLazy: "bi bi-chevron-bar-right",
2034
- checkChecked: "bi bi-check-square",
2035
- checkUnchecked: "bi bi-square",
2036
- checkUnknown: "bi bi-dash-square-dotted",
2037
- radioChecked: "bi bi-circle-fill",
2038
- radioUnchecked: "bi bi-circle",
2039
- radioUnknown: "bi bi-record-circle",
2040
- folder: "bi bi-folder2",
2041
- folderOpen: "bi bi-folder2-open",
2042
- folderLazy: "bi bi-folder-symlink",
2043
- doc: "bi bi-file-earmark",
2044
- colSortable: "bi bi-chevron-expand",
2045
- // colSortable: "bi bi-arrow-down-up",
2046
- // colSortAsc: "bi bi-chevron-down",
2047
- // colSortDesc: "bi bi-chevron-up",
2048
- colSortAsc: "bi bi-arrow-down",
2049
- colSortDesc: "bi bi-arrow-up",
2050
- colFilter: "bi bi-filter-circle",
2051
- colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
2052
- colMenu: "bi bi-three-dots-vertical",
2053
- },
2054
- fontawesome6: {
2055
- error: "fa-solid fa-triangle-exclamation",
2056
- loading: "fa-solid fa-chevron-right fa-beat",
2057
- noData: "fa-solid fa-circle-question",
2058
- expanderExpanded: "fa-solid fa-chevron-down",
2059
- expanderCollapsed: "fa-solid fa-chevron-right",
2060
- expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2061
- checkChecked: "fa-regular fa-square-check",
2062
- checkUnchecked: "fa-regular fa-square",
2063
- checkUnknown: "fa-regular fa-square-minus",
2064
- radioChecked: "fa-solid fa-circle",
2065
- radioUnchecked: "fa-regular fa-circle",
2066
- radioUnknown: "fa-regular fa-circle-question",
2067
- folder: "fa-solid fa-folder-closed",
2068
- folderOpen: "fa-regular fa-folder-open",
2069
- folderLazy: "fa-solid fa-folder-plus",
2070
- doc: "fa-regular fa-file",
2071
- colSortable: "fa-solid fa-fw fa-sort",
2072
- colSortAsc: "fa-solid fa-fw fa-sort-up",
2073
- colSortDesc: "fa-solid fa-fw fa-sort-down",
2074
- colFilter: "fa-solid fa-fw fa-filter",
2075
- colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
2076
- colMenu: "fa-solid fa-fw fa-ellipsis-v",
2077
- },
2078
- };
2079
- /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2080
- const RESERVED_TREE_SOURCE_KEYS = new Set([
2081
- "_format", // reserved for future use
2082
- "_keyMap", // Used for compressed data format
2083
- "_positional", // Used for compressed data format
2084
- "_typeList", // Used for compressed data format @deprecated
2085
- "_valueMap", // Used for compressed data format
2086
- "_version", // reserved for future use
2087
- "children",
2088
- "columns",
2089
- "types",
2090
- ]);
2091
- // /** Key codes that trigger grid navigation, even when inside an input element. */
2092
- // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2093
- // // "ArrowDown",
2094
- // // "ArrowUp",
2095
- // "Enter",
2096
- // "Escape",
2097
- // ]);
2098
- /** Map `KeyEvent.key` to navigation action. */
2099
- const KEY_TO_ACTION_DICT = {
2100
- " ": "toggleSelect",
2101
- "+": "expand",
2102
- Add: "expand",
2103
- ArrowDown: "down",
2104
- ArrowLeft: "left",
2105
- ArrowRight: "right",
2106
- ArrowUp: "up",
2107
- Backspace: "parent",
2108
- "/": "collapseAll",
2109
- Divide: "collapseAll",
2110
- End: "lastCol",
2111
- Home: "firstCol",
2112
- "Control+End": "last",
2113
- "Control+Home": "first",
2114
- "Meta+ArrowDown": "last", // macOs
2115
- "Meta+ArrowUp": "first", // macOs
2116
- "*": "expandAll",
2117
- Multiply: "expandAll",
2118
- PageDown: "pageDown",
2119
- PageUp: "pageUp",
2120
- "-": "collapse",
2121
- Subtract: "collapse",
2122
- };
2123
- /** Return a callback that returns true if the node title matches the string
2124
- * or regular expression.
2125
- * @see {@link WunderbaumNode.findAll}
2126
- */
2127
- function makeNodeTitleMatcher(match) {
2128
- if (match instanceof RegExp) {
2129
- return function (node) {
2130
- return match.test(node.title);
2131
- };
2132
- }
2133
- assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2134
- // s = escapeRegex(s.toLowerCase());
2135
- return function (node) {
2136
- return node.title === match;
2137
- // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2138
- // return node.title.toLowerCase().indexOf(match) >= 0;
2139
- };
2140
- }
2141
- /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2142
- function makeNodeTitleStartMatcher(s) {
2143
- s = escapeRegex(s);
2144
- const reMatch = new RegExp("^" + s, "i");
2145
- return function (node) {
2146
- return reMatch.test(node.title);
2147
- };
2148
- }
2149
- /** Compare two nodes by title (case-insensitive). */
2150
- function nodeTitleSorter(a, b) {
2151
- const x = a.title.toLowerCase();
2152
- const y = b.title.toLowerCase();
2153
- return x === y ? 0 : x > y ? 1 : -1;
2154
- }
2155
- /**
2156
- * Convert 'flat' to 'nested' format.
2157
- *
2158
- * Flat node entry format:
2159
- * [PARENT_ID, [POSITIONAL_ARGS]]
2160
- * or
2161
- * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2162
- *
2163
- * 1. Parent-referencing list is converted to a list of nested dicts with
2164
- * optional `children` properties.
2165
- * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
1999
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2166
2000
  */
2167
- function unflattenSource(source) {
2168
- var _a, _b, _c;
2169
- const { _format, _keyMap = {}, _positional = [], children } = source;
2170
- if (_format !== "flat") {
2171
- throw new Error(`Expected source._format: "flat", but got ${_format}`);
2172
- }
2173
- if (_positional && _positional.includes("children")) {
2174
- throw new Error(`source._positional must not include "children": ${_positional}`);
2001
+ const nodeMimeType = "application/x-wunderbaum-node";
2002
+ class DndExtension extends WunderbaumExtension {
2003
+ constructor(tree) {
2004
+ super(tree, "dnd", {
2005
+ autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
2006
+ // dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
2007
+ // dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
2008
+ // #1021 `document.body` is not available yet
2009
+ // dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
2010
+ multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
2011
+ effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
2012
+ dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (override in drag, dragOver).
2013
+ guessDropEffect: true, // Calculate from `effectAllowed` and modifier keys)
2014
+ preventForeignNodes: false, // Prevent dropping nodes from different Wunderbaum trees
2015
+ preventLazyParents: true, // Prevent dropping items on unloaded lazy Wunderbaum tree nodes
2016
+ preventNonNodes: false, // Prevent dropping items other than Wunderbaum tree nodes
2017
+ preventRecursion: true, // Prevent dropping nodes on own descendants
2018
+ preventSameParent: false, // Prevent dropping nodes under same direct parent
2019
+ preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. (move only)
2020
+ serializeClipboardData: true, // Serialize node data to dataTransfer object
2021
+ scroll: true, // Enable auto-scrolling while dragging
2022
+ scrollSensitivity: 20, // Active top/bottom margin in pixel
2023
+ // scrollnterval: 50, // Generate event every 50 ms
2024
+ scrollSpeed: 5, // Scroll pixel per 50 ms
2025
+ // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2026
+ sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
2027
+ // Events (drag support)
2028
+ dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
2029
+ drag: null, // Callback(sourceNode, data)
2030
+ dragEnd: null, // Callback(sourceNode, data)
2031
+ // Events (drop support)
2032
+ dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
2033
+ dragOver: null, // Callback(targetNode, data)
2034
+ dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
2035
+ drop: null, // Callback(targetNode, data)
2036
+ dragLeave: null, // Callback(targetNode, data)
2037
+ });
2038
+ // public dropMarkerElem?: HTMLElement;
2039
+ this.srcNode = null;
2040
+ this.lastTargetNode = null;
2041
+ this.lastEnterStamp = 0;
2042
+ this.lastAllowedDropRegions = null;
2043
+ this.lastDropEffect = null;
2044
+ this.lastDropRegion = false;
2045
+ this.currentScrollDir = 0;
2046
+ this.applyScrollDirThrottled = throttle(this._applyScrollDir, 50);
2175
2047
  }
2176
- let longToShort = _keyMap;
2177
- if (_keyMap.t) {
2178
- // Inverse keyMap was used (pre 0.7.0)
2179
- // TODO: raise Error on final 1.x release
2180
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2181
- console.warn(msg); // eslint-disable-line no-console
2182
- longToShort = {};
2183
- for (const [key, value] of Object.entries(_keyMap)) {
2184
- longToShort[value] = key;
2185
- }
2186
- }
2187
- const positionalShort = _positional.map((e) => longToShort[e]);
2188
- const newChildren = [];
2189
- const keyToNodeMap = {};
2190
- const indexToNodeMap = {};
2191
- const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2192
- const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2193
- for (const [index, nodeTuple] of children.entries()) {
2194
- // Node entry format:
2195
- // [PARENT_ID, [POSITIONAL_ARGS]]
2196
- // or
2197
- // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2198
- const [parentId, args, kwargs = {}] = nodeTuple;
2199
- // Free up some memory as we go
2200
- nodeTuple[1] = null;
2201
- if (nodeTuple[2] != null) {
2202
- nodeTuple[2] = null;
2203
- }
2204
- // console.log("flatten", parentId, args, kwargs)
2205
- // We keep `kwargs` as our new node definition. Then we add all positional
2206
- // values to this object:
2207
- args.forEach((val, positionalIdx) => {
2208
- kwargs[positionalShort[positionalIdx]] = val;
2209
- });
2210
- // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2211
- // index of the source.children list. If PARENT_ID is a string, we search
2212
- // a parent with node.key of this value.
2213
- indexToNodeMap[index] = kwargs;
2214
- const key = kwargs[keyAttrName];
2215
- if (key != null) {
2216
- keyToNodeMap[key] = kwargs;
2217
- }
2218
- let parentNode = null;
2219
- if (parentId === null) ;
2220
- else if (typeof parentId === "number") {
2221
- parentNode = indexToNodeMap[parentId];
2222
- if (parentNode === undefined) {
2223
- throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2224
- }
2225
- }
2226
- else {
2227
- parentNode = keyToNodeMap[parentId];
2228
- if (parentNode === undefined) {
2229
- throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2230
- }
2231
- }
2232
- if (parentNode) {
2233
- (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2234
- parentNode[childrenAttrName].push(kwargs);
2235
- }
2236
- else {
2237
- newChildren.push(kwargs);
2238
- }
2239
- }
2240
- source.children = newChildren;
2241
- }
2242
- /**
2243
- * Decompresses the source data by
2244
- * - converting from 'flat' to 'nested' format
2245
- * - expanding short alias names to long names (if defined in _keyMap)
2246
- * - resolving value indexes to value strings (if defined in _valueMap)
2247
- *
2248
- * @param source - The source object to be decompressed.
2249
- * @returns void
2250
- */
2251
- function decompressSourceData(source) {
2252
- let { _format, _version = 1, _keyMap, _valueMap } = source;
2253
- assert(_version === 1, `Expected file version 1 instead of ${_version}`);
2254
- let longToShort = _keyMap;
2255
- let shortToLong = {};
2256
- if (longToShort) {
2257
- for (const [key, value] of Object.entries(longToShort)) {
2258
- shortToLong[value] = key;
2259
- }
2260
- }
2261
- // Fallback for old format (pre 0.7.0, using _keyMap in reverse direction)
2262
- // TODO: raise Error on final 1.x release
2263
- if (longToShort && longToShort.t) {
2264
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2265
- console.warn(msg); // eslint-disable-line no-console
2266
- [longToShort, shortToLong] = [shortToLong, longToShort];
2267
- }
2268
- // Fallback for old format (pre 0.7.0, using _typeList instead of _valueMap)
2269
- // TODO: raise Error on final 1.x release
2270
- if (source._typeList != null) {
2271
- const msg = `source._typeList is deprecated since v0.7.0: use source._valueMap: {"type": [...]} instead.`;
2272
- if (_valueMap != null) {
2273
- throw new Error(msg);
2274
- }
2275
- else {
2276
- console.warn(msg); // eslint-disable-line no-console
2277
- _valueMap = { type: source._typeList };
2278
- delete source._typeList;
2279
- }
2280
- }
2281
- if (_format === "flat") {
2282
- unflattenSource(source);
2283
- }
2284
- delete source._format;
2285
- delete source._version;
2286
- delete source._keyMap;
2287
- delete source._valueMap;
2288
- delete source._positional;
2289
- function _iter(childList) {
2290
- for (const node of childList) {
2291
- // Iterate over a list of names, because we modify inside the loop
2292
- // (for ... of ... does not allow this)
2293
- Object.getOwnPropertyNames(node).forEach((propName) => {
2294
- const value = node[propName];
2295
- // Replace short names with long names if defined in _keyMap
2296
- let longName = propName;
2297
- if (_keyMap && shortToLong[propName] != null) {
2298
- longName = shortToLong[propName];
2299
- if (longName !== propName) {
2300
- node[longName] = value;
2301
- delete node[propName];
2302
- }
2303
- }
2304
- // Replace type index with type name if defined in _valueMap
2305
- if (_valueMap &&
2306
- typeof value === "number" &&
2307
- _valueMap[longName] != null) {
2308
- const newValue = _valueMap[longName][value];
2309
- if (newValue == null) {
2310
- throw new Error(`Expected valueMap[${longName}][${value}] entry in [${_valueMap[longName]}]`);
2311
- }
2312
- node[longName] = newValue;
2313
- }
2314
- });
2315
- // Recursion
2316
- if (node.children) {
2317
- _iter(node.children);
2318
- }
2319
- }
2320
- }
2321
- if (_keyMap || _valueMap) {
2322
- _iter(source.children);
2323
- }
2324
- }
2325
-
2326
- /*!
2327
- * Wunderbaum - ext-dnd
2328
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2329
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2330
- */
2331
- const nodeMimeType = "application/x-wunderbaum-node";
2332
- class DndExtension extends WunderbaumExtension {
2333
- constructor(tree) {
2334
- super(tree, "dnd", {
2335
- autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
2336
- // dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
2337
- // dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
2338
- // #1021 `document.body` is not available yet
2339
- // dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
2340
- multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
2341
- effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
2342
- dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (override in drag, dragOver).
2343
- guessDropEffect: true, // Calculate from `effectAllowed` and modifier keys)
2344
- preventForeignNodes: false, // Prevent dropping nodes from different Wunderbaum trees
2345
- preventLazyParents: true, // Prevent dropping items on unloaded lazy Wunderbaum tree nodes
2346
- preventNonNodes: false, // Prevent dropping items other than Wunderbaum tree nodes
2347
- preventRecursion: true, // Prevent dropping nodes on own descendants
2348
- preventSameParent: false, // Prevent dropping nodes under same direct parent
2349
- preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. (move only)
2350
- serializeClipboardData: true, // Serialize node data to dataTransfer object
2351
- scroll: true, // Enable auto-scrolling while dragging
2352
- scrollSensitivity: 20, // Active top/bottom margin in pixel
2353
- // scrollnterval: 50, // Generate event every 50 ms
2354
- scrollSpeed: 5, // Scroll pixel per 50 ms
2355
- // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2356
- sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
2357
- // Events (drag support)
2358
- dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
2359
- drag: null, // Callback(sourceNode, data)
2360
- dragEnd: null, // Callback(sourceNode, data)
2361
- // Events (drop support)
2362
- dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
2363
- dragOver: null, // Callback(targetNode, data)
2364
- dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
2365
- drop: null, // Callback(targetNode, data)
2366
- dragLeave: null, // Callback(targetNode, data)
2367
- });
2368
- // public dropMarkerElem?: HTMLElement;
2369
- this.srcNode = null;
2370
- this.lastTargetNode = null;
2371
- this.lastEnterStamp = 0;
2372
- this.lastAllowedDropRegions = null;
2373
- this.lastDropEffect = null;
2374
- this.lastDropRegion = false;
2375
- this.currentScrollDir = 0;
2376
- this.applyScrollDirThrottled = throttle(this._applyScrollDir, 50);
2377
- }
2378
- init() {
2379
- super.init();
2380
- // Store the current scroll parent, which may be the tree
2381
- // container, any enclosing div, or the document.
2382
- // #761: scrollParent() always needs a container child
2383
- // $temp = $("<span>").appendTo(this.$container);
2384
- // this.$scrollParent = $temp.scrollParent();
2385
- // $temp.remove();
2386
- const tree = this.tree;
2387
- const dndOpts = tree.options.dnd;
2388
- // Enable drag support if dragStart() is specified:
2389
- if (dndOpts.dragStart) {
2390
- onEvent(tree.element, "dragstart drag dragend", this.onDragEvent.bind(this));
2048
+ init() {
2049
+ super.init();
2050
+ // Store the current scroll parent, which may be the tree
2051
+ // container, any enclosing div, or the document.
2052
+ // #761: scrollParent() always needs a container child
2053
+ // $temp = $("<span>").appendTo(this.$container);
2054
+ // this.$scrollParent = $temp.scrollParent();
2055
+ // $temp.remove();
2056
+ const tree = this.tree;
2057
+ const dndOpts = tree.options.dnd;
2058
+ // Enable drag support if dragStart() is specified:
2059
+ if (dndOpts.dragStart) {
2060
+ onEvent(tree.element, "dragstart drag dragend", this.onDragEvent.bind(this));
2391
2061
  }
2392
2062
  // Enable drop support if dragEnter() is specified:
2393
2063
  if (dndOpts.dragEnter) {
@@ -2425,14 +2095,15 @@ class DndExtension extends WunderbaumExtension {
2425
2095
  * Calculates the drop region based on the drag event and the allowed drop regions.
2426
2096
  */
2427
2097
  _calcDropRegion(e, allowed) {
2098
+ const rowHeight = this.tree.options.rowHeightPx;
2428
2099
  const dy = e.offsetY;
2429
2100
  if (!allowed) {
2430
2101
  return false;
2431
2102
  }
2432
2103
  else if (allowed.size === 3) {
2433
- return dy < 0.25 * ROW_HEIGHT
2104
+ return dy < 0.25 * rowHeight
2434
2105
  ? "before"
2435
- : dy > 0.75 * ROW_HEIGHT
2106
+ : dy > 0.75 * rowHeight
2436
2107
  ? "after"
2437
2108
  : "over";
2438
2109
  }
@@ -2441,7 +2112,7 @@ class DndExtension extends WunderbaumExtension {
2441
2112
  }
2442
2113
  else {
2443
2114
  // Only 'before' and 'after':
2444
- return dy > ROW_HEIGHT / 2 ? "after" : "before";
2115
+ return dy > rowHeight / 2 ? "after" : "before";
2445
2116
  }
2446
2117
  // return "over";
2447
2118
  }
@@ -2702,7 +2373,11 @@ class DndExtension extends WunderbaumExtension {
2702
2373
  }
2703
2374
  this.lastAllowedDropRegions = regionSet;
2704
2375
  this.lastDropEffect = dt.dropEffect;
2376
+ const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
2705
2377
  targetNode.setClass("wb-drop-target");
2378
+ targetNode.setClass("wb-drop-over", region === "over");
2379
+ targetNode.setClass("wb-drop-before", region === "before");
2380
+ targetNode.setClass("wb-drop-after", region === "after");
2706
2381
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
2707
2382
  return false;
2708
2383
  // --- dragover ---
@@ -2728,199 +2403,539 @@ class DndExtension extends WunderbaumExtension {
2728
2403
  if (!region || this._isVoidDrop(targetNode, srcNode, region)) {
2729
2404
  return; // We already rejected in dragenter
2730
2405
  }
2731
- targetNode.setClass("wb-drop-over", region === "over");
2732
- targetNode.setClass("wb-drop-before", region === "before");
2733
- targetNode.setClass("wb-drop-after", region === "after");
2734
- e.preventDefault(); // Allow drop (Drop operation is denied by default)
2735
- return false;
2736
- // --- dragleave ---
2406
+ targetNode.setClass("wb-drop-over", region === "over");
2407
+ targetNode.setClass("wb-drop-before", region === "before");
2408
+ targetNode.setClass("wb-drop-after", region === "after");
2409
+ e.preventDefault(); // Allow drop (Drop operation is denied by default)
2410
+ return false;
2411
+ // --- dragleave ---
2412
+ }
2413
+ else if (e.type === "dragleave") {
2414
+ // NOTE: we cannot trust this event, since it is always fired,
2415
+ // Instead we remove the marker on dragenter
2416
+ targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2417
+ // --- drop ---
2418
+ }
2419
+ else if (e.type === "drop") {
2420
+ e.stopPropagation(); // prevent browser from opening links?
2421
+ e.preventDefault(); // #69 prevent iOS browser from opening links
2422
+ this._leaveNode();
2423
+ const region = this.lastDropRegion;
2424
+ let nodeData = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData(nodeMimeType);
2425
+ nodeData = nodeData ? JSON.parse(nodeData) : null;
2426
+ const srcNode = this.srcNode;
2427
+ const lastDropEffect = this.lastDropEffect;
2428
+ setTimeout(() => {
2429
+ // Decouple this call, because drop actions may prevent the dragend event
2430
+ // from being fired on some browsers
2431
+ targetNode._callEvent("dnd.drop", {
2432
+ event: e,
2433
+ region: region,
2434
+ suggestedDropMode: region === "over" ? "appendChild" : region,
2435
+ suggestedDropEffect: lastDropEffect,
2436
+ // suggestedDropEffect: e.dataTransfer?.dropEffect,
2437
+ sourceNode: srcNode,
2438
+ sourceNodeData: nodeData,
2439
+ });
2440
+ }, 10);
2441
+ }
2442
+ return false;
2443
+ }
2444
+ }
2445
+
2446
+ /*!
2447
+ * Wunderbaum - drag_observer
2448
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2449
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2450
+ */
2451
+ /**
2452
+ * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
2453
+ */
2454
+ class DragObserver {
2455
+ constructor(opts) {
2456
+ this.start = {
2457
+ event: null,
2458
+ x: 0,
2459
+ y: 0,
2460
+ altKey: false,
2461
+ ctrlKey: false,
2462
+ metaKey: false,
2463
+ shiftKey: false,
2464
+ };
2465
+ this.dragElem = null;
2466
+ this.dragging = false;
2467
+ this.customData = {};
2468
+ // TODO: touch events
2469
+ this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2470
+ if (!opts.root) {
2471
+ throw new Error("Missing `root` option.");
2472
+ }
2473
+ this.opts = Object.assign({ thresh: 5 }, opts);
2474
+ this.root = opts.root;
2475
+ this._handler = this.handleEvent.bind(this);
2476
+ this.events.forEach((type) => {
2477
+ this.root.addEventListener(type, this._handler);
2478
+ });
2479
+ }
2480
+ /** Unregister all event listeners. */
2481
+ disconnect() {
2482
+ this.events.forEach((type) => {
2483
+ this.root.removeEventListener(type, this._handler);
2484
+ });
2485
+ }
2486
+ getDragElem() {
2487
+ return this.dragElem;
2488
+ }
2489
+ isDragging() {
2490
+ return this.dragging;
2491
+ }
2492
+ stopDrag(cb_event) {
2493
+ if (this.dragging && this.opts.dragstop && cb_event) {
2494
+ cb_event.type = "dragstop";
2495
+ try {
2496
+ this.opts.dragstop(cb_event);
2497
+ }
2498
+ catch (err) {
2499
+ console.error("dragstop error", err); // eslint-disable-line no-console
2500
+ }
2501
+ }
2502
+ this.dragElem = null;
2503
+ this.dragging = false;
2504
+ this.start.event = null;
2505
+ this.customData = {};
2506
+ }
2507
+ handleEvent(e) {
2508
+ const type = e.type;
2509
+ const opts = this.opts;
2510
+ const cb_event = {
2511
+ type: e.type,
2512
+ startEvent: type === "mousedown" ? e : this.start.event,
2513
+ event: e,
2514
+ customData: this.customData,
2515
+ dragElem: this.dragElem,
2516
+ dx: e.pageX - this.start.x,
2517
+ dy: e.pageY - this.start.y,
2518
+ apply: undefined,
2519
+ };
2520
+ // console.log("handleEvent", type, cb_event);
2521
+ switch (type) {
2522
+ case "keydown":
2523
+ this.stopDrag(cb_event);
2524
+ break;
2525
+ case "mousedown":
2526
+ if (this.dragElem) {
2527
+ this.stopDrag(cb_event);
2528
+ break;
2529
+ }
2530
+ if (opts.selector) {
2531
+ let elem = e.target;
2532
+ if (elem.matches(opts.selector)) {
2533
+ this.dragElem = elem;
2534
+ }
2535
+ else {
2536
+ elem = elem.closest(opts.selector);
2537
+ if (elem) {
2538
+ this.dragElem = elem;
2539
+ }
2540
+ else {
2541
+ break; // no event delegation selector matched
2542
+ }
2543
+ }
2544
+ }
2545
+ this.start.event = e;
2546
+ this.start.x = e.pageX;
2547
+ this.start.y = e.pageY;
2548
+ this.start.altKey = e.altKey;
2549
+ this.start.ctrlKey = e.ctrlKey;
2550
+ this.start.metaKey = e.metaKey;
2551
+ this.start.shiftKey = e.shiftKey;
2552
+ break;
2553
+ case "mousemove":
2554
+ // TODO: debounce/throttle?
2555
+ // TODO: horizontal mode: ignore if dx unchanged
2556
+ if (!this.dragElem) {
2557
+ break;
2558
+ }
2559
+ if (!this.dragging) {
2560
+ if (opts.thresh) {
2561
+ const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2562
+ if (dist2 < opts.thresh * opts.thresh) {
2563
+ break;
2564
+ }
2565
+ }
2566
+ cb_event.type = "dragstart";
2567
+ if (opts.dragstart(cb_event) === false) {
2568
+ this.stopDrag(cb_event);
2569
+ break;
2570
+ }
2571
+ this.dragging = true;
2572
+ }
2573
+ if (this.dragging && this.opts.drag) {
2574
+ cb_event.type = "drag";
2575
+ this.opts.drag(cb_event);
2576
+ }
2577
+ break;
2578
+ case "mouseup":
2579
+ if (!this.dragging) {
2580
+ this.stopDrag(cb_event);
2581
+ break;
2582
+ }
2583
+ if (e.button === 0) {
2584
+ cb_event.apply = true;
2585
+ }
2586
+ else {
2587
+ cb_event.apply = false;
2588
+ }
2589
+ this.stopDrag(cb_event);
2590
+ break;
2591
+ }
2592
+ }
2593
+ }
2594
+
2595
+ /*!
2596
+ * Wunderbaum - common
2597
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2598
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2599
+ */
2600
+ const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
2601
+ /**
2602
+ * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
2603
+ */
2604
+ const DEFAULT_ROW_HEIGHT = 22;
2605
+ /**
2606
+ * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
2607
+ */
2608
+ const ICON_WIDTH = 20;
2609
+ /**
2610
+ * Adjust the width of the title span, so overflow ellipsis work.
2611
+ * (2 x `$col-padding-x` + 3px rounding errors).
2612
+ */
2613
+ const TITLE_SPAN_PAD_Y = 7;
2614
+ /** Render row markup for N nodes above and below the visible viewport. */
2615
+ const RENDER_MAX_PREFETCH = 5;
2616
+ /** Minimum column width if not set otherwise. */
2617
+ const DEFAULT_MIN_COL_WIDTH = 4;
2618
+ /** Regular expression to detect if a string describes an image URL (in contrast
2619
+ * to a class name). Strings are considered image urls if they contain '.' or '/'.
2620
+ */
2621
+ const TEST_IMG = new RegExp(/\.|\//);
2622
+ // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2623
+ // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2624
+ /**
2625
+ * Default node icons.
2626
+ * Requires bootstrap icons https://icons.getbootstrap.com
2627
+ */
2628
+ const iconMaps = {
2629
+ bootstrap: {
2630
+ error: "bi bi-exclamation-triangle",
2631
+ // loading: "bi bi-hourglass-split wb-busy",
2632
+ loading: "bi bi-chevron-right wb-busy",
2633
+ // loading: "bi bi-arrow-repeat wb-spin",
2634
+ // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2635
+ // noData: "bi bi-search",
2636
+ noData: "bi bi-question-circle",
2637
+ expanderExpanded: "bi bi-chevron-down",
2638
+ // expanderExpanded: "bi bi-dash-square",
2639
+ expanderCollapsed: "bi bi-chevron-right",
2640
+ // expanderCollapsed: "bi bi-plus-square",
2641
+ expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2642
+ // expanderLazy: "bi bi-chevron-bar-right",
2643
+ checkChecked: "bi bi-check-square",
2644
+ checkUnchecked: "bi bi-square",
2645
+ checkUnknown: "bi bi-dash-square-dotted",
2646
+ radioChecked: "bi bi-circle-fill",
2647
+ radioUnchecked: "bi bi-circle",
2648
+ radioUnknown: "bi bi-record-circle",
2649
+ folder: "bi bi-folder2",
2650
+ folderOpen: "bi bi-folder2-open",
2651
+ folderLazy: "bi bi-folder-symlink",
2652
+ doc: "bi bi-file-earmark",
2653
+ colSortable: "bi bi-chevron-expand",
2654
+ // colSortable: "bi bi-arrow-down-up",
2655
+ // colSortAsc: "bi bi-chevron-down",
2656
+ // colSortDesc: "bi bi-chevron-up",
2657
+ colSortAsc: "bi bi-arrow-down",
2658
+ colSortDesc: "bi bi-arrow-up",
2659
+ colFilter: "bi bi-filter-circle",
2660
+ colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
2661
+ colMenu: "bi bi-three-dots-vertical",
2662
+ },
2663
+ fontawesome6: {
2664
+ error: "fa-solid fa-triangle-exclamation",
2665
+ loading: "fa-solid fa-chevron-right fa-beat",
2666
+ noData: "fa-solid fa-circle-question",
2667
+ expanderExpanded: "fa-solid fa-chevron-down",
2668
+ expanderCollapsed: "fa-solid fa-chevron-right",
2669
+ expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2670
+ checkChecked: "fa-regular fa-square-check",
2671
+ checkUnchecked: "fa-regular fa-square",
2672
+ checkUnknown: "fa-regular fa-square-minus",
2673
+ radioChecked: "fa-solid fa-circle",
2674
+ radioUnchecked: "fa-regular fa-circle",
2675
+ radioUnknown: "fa-regular fa-circle-question",
2676
+ folder: "fa-solid fa-folder-closed",
2677
+ folderOpen: "fa-regular fa-folder-open",
2678
+ folderLazy: "fa-solid fa-folder-plus",
2679
+ doc: "fa-regular fa-file",
2680
+ colSortable: "fa-solid fa-fw fa-sort",
2681
+ colSortAsc: "fa-solid fa-fw fa-sort-up",
2682
+ colSortDesc: "fa-solid fa-fw fa-sort-down",
2683
+ colFilter: "fa-solid fa-fw fa-filter",
2684
+ colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
2685
+ colMenu: "fa-solid fa-fw fa-ellipsis-v",
2686
+ },
2687
+ };
2688
+ /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2689
+ const RESERVED_TREE_SOURCE_KEYS = new Set([
2690
+ "_format", // reserved for future use
2691
+ "_keyMap", // Used for compressed data format
2692
+ "_positional", // Used for compressed data format
2693
+ "_typeList", // Used for compressed data format @deprecated
2694
+ "_valueMap", // Used for compressed data format
2695
+ "_version", // reserved for future use
2696
+ "children",
2697
+ "columns",
2698
+ "types",
2699
+ ]);
2700
+ // /** Key codes that trigger grid navigation, even when inside an input element. */
2701
+ // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2702
+ // // "ArrowDown",
2703
+ // // "ArrowUp",
2704
+ // "Enter",
2705
+ // "Escape",
2706
+ // ]);
2707
+ /** Map `KeyEvent.key` to navigation action. */
2708
+ const KEY_TO_ACTION_DICT = {
2709
+ " ": "toggleSelect",
2710
+ "+": "expand",
2711
+ Add: "expand",
2712
+ ArrowDown: "down",
2713
+ ArrowLeft: "left",
2714
+ ArrowRight: "right",
2715
+ ArrowUp: "up",
2716
+ Backspace: "parent",
2717
+ "/": "collapseAll",
2718
+ Divide: "collapseAll",
2719
+ End: "lastCol",
2720
+ Home: "firstCol",
2721
+ "Control+End": "last",
2722
+ "Control+Home": "first",
2723
+ "Meta+ArrowDown": "last", // macOs
2724
+ "Meta+ArrowUp": "first", // macOs
2725
+ "*": "expandAll",
2726
+ Multiply: "expandAll",
2727
+ PageDown: "pageDown",
2728
+ PageUp: "pageUp",
2729
+ "-": "collapse",
2730
+ Subtract: "collapse",
2731
+ };
2732
+ /** Return a callback that returns true if the node title matches the string
2733
+ * or regular expression.
2734
+ * @see {@link WunderbaumNode.findAll}
2735
+ */
2736
+ function makeNodeTitleMatcher(match) {
2737
+ if (match instanceof RegExp) {
2738
+ return function (node) {
2739
+ return match.test(node.title);
2740
+ };
2741
+ }
2742
+ assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2743
+ // s = escapeRegex(s.toLowerCase());
2744
+ return function (node) {
2745
+ return node.title === match;
2746
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2747
+ // return node.title.toLowerCase().indexOf(match) >= 0;
2748
+ };
2749
+ }
2750
+ /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2751
+ function makeNodeTitleStartMatcher(s) {
2752
+ s = escapeRegex(s);
2753
+ const reMatch = new RegExp("^" + s, "i");
2754
+ return function (node) {
2755
+ return reMatch.test(node.title);
2756
+ };
2757
+ }
2758
+ /** Compare two nodes by title (case-insensitive). */
2759
+ function nodeTitleSorter(a, b) {
2760
+ const x = a.title.toLowerCase();
2761
+ const y = b.title.toLowerCase();
2762
+ return x === y ? 0 : x > y ? 1 : -1;
2763
+ }
2764
+ /**
2765
+ * Convert 'flat' to 'nested' format.
2766
+ *
2767
+ * Flat node entry format:
2768
+ * [PARENT_ID, [POSITIONAL_ARGS]]
2769
+ * or
2770
+ * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2771
+ *
2772
+ * 1. Parent-referencing list is converted to a list of nested dicts with
2773
+ * optional `children` properties.
2774
+ * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
2775
+ */
2776
+ function unflattenSource(source) {
2777
+ var _a, _b, _c;
2778
+ const { _format, _keyMap = {}, _positional = [], children } = source;
2779
+ if (_format !== "flat") {
2780
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
2781
+ }
2782
+ if (_positional && _positional.includes("children")) {
2783
+ throw new Error(`source._positional must not include "children": ${_positional}`);
2784
+ }
2785
+ let longToShort = _keyMap;
2786
+ if (_keyMap.t) {
2787
+ // Inverse keyMap was used (pre 0.7.0)
2788
+ // TODO: raise Error on final 1.x release
2789
+ const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2790
+ console.warn(msg); // eslint-disable-line no-console
2791
+ longToShort = {};
2792
+ for (const [key, value] of Object.entries(_keyMap)) {
2793
+ longToShort[value] = key;
2794
+ }
2795
+ }
2796
+ const positionalShort = _positional.map((e) => longToShort[e]);
2797
+ const newChildren = [];
2798
+ const keyToNodeMap = {};
2799
+ const indexToNodeMap = {};
2800
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2801
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2802
+ for (const [index, nodeTuple] of children.entries()) {
2803
+ // Node entry format:
2804
+ // [PARENT_ID, [POSITIONAL_ARGS]]
2805
+ // or
2806
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2807
+ const [parentId, args, kwargs = {}] = nodeTuple;
2808
+ // Free up some memory as we go
2809
+ nodeTuple[1] = null;
2810
+ if (nodeTuple[2] != null) {
2811
+ nodeTuple[2] = null;
2812
+ }
2813
+ // console.log("flatten", parentId, args, kwargs)
2814
+ // We keep `kwargs` as our new node definition. Then we add all positional
2815
+ // values to this object:
2816
+ args.forEach((val, positionalIdx) => {
2817
+ kwargs[positionalShort[positionalIdx]] = val;
2818
+ });
2819
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2820
+ // index of the source.children list. If PARENT_ID is a string, we search
2821
+ // a parent with node.key of this value.
2822
+ indexToNodeMap[index] = kwargs;
2823
+ const key = kwargs[keyAttrName];
2824
+ if (key != null) {
2825
+ keyToNodeMap[key] = kwargs;
2826
+ }
2827
+ let parentNode = null;
2828
+ if (parentId === null) ;
2829
+ else if (typeof parentId === "number") {
2830
+ parentNode = indexToNodeMap[parentId];
2831
+ if (parentNode === undefined) {
2832
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2833
+ }
2834
+ }
2835
+ else {
2836
+ parentNode = keyToNodeMap[parentId];
2837
+ if (parentNode === undefined) {
2838
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2839
+ }
2737
2840
  }
2738
- else if (e.type === "dragleave") {
2739
- // NOTE: we cannot trust this event, since it is always fired,
2740
- // Instead we remove the marker on dragenter
2741
- targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2742
- // --- drop ---
2841
+ if (parentNode) {
2842
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2843
+ parentNode[childrenAttrName].push(kwargs);
2743
2844
  }
2744
- else if (e.type === "drop") {
2745
- e.stopPropagation(); // prevent browser from opening links?
2746
- e.preventDefault(); // #69 prevent iOS browser from opening links
2747
- this._leaveNode();
2748
- const region = this.lastDropRegion;
2749
- let nodeData = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData(nodeMimeType);
2750
- nodeData = nodeData ? JSON.parse(nodeData) : null;
2751
- const srcNode = this.srcNode;
2752
- const lastDropEffect = this.lastDropEffect;
2753
- setTimeout(() => {
2754
- // Decouple this call, because drop actions may prevent the dragend event
2755
- // from being fired on some browsers
2756
- targetNode._callEvent("dnd.drop", {
2757
- event: e,
2758
- region: region,
2759
- suggestedDropMode: region === "over" ? "appendChild" : region,
2760
- suggestedDropEffect: lastDropEffect,
2761
- // suggestedDropEffect: e.dataTransfer?.dropEffect,
2762
- sourceNode: srcNode,
2763
- sourceNodeData: nodeData,
2764
- });
2765
- }, 10);
2845
+ else {
2846
+ newChildren.push(kwargs);
2766
2847
  }
2767
- return false;
2768
2848
  }
2849
+ source.children = newChildren;
2769
2850
  }
2770
-
2771
- /*!
2772
- * Wunderbaum - drag_observer
2773
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2774
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2775
- */
2776
2851
  /**
2777
- * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
2852
+ * Decompresses the source data by
2853
+ * - converting from 'flat' to 'nested' format
2854
+ * - expanding short alias names to long names (if defined in _keyMap)
2855
+ * - resolving value indexes to value strings (if defined in _valueMap)
2856
+ *
2857
+ * @param source - The source object to be decompressed.
2858
+ * @returns void
2778
2859
  */
2779
- class DragObserver {
2780
- constructor(opts) {
2781
- this.start = {
2782
- event: null,
2783
- x: 0,
2784
- y: 0,
2785
- altKey: false,
2786
- ctrlKey: false,
2787
- metaKey: false,
2788
- shiftKey: false,
2789
- };
2790
- this.dragElem = null;
2791
- this.dragging = false;
2792
- this.customData = {};
2793
- // TODO: touch events
2794
- this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2795
- if (!opts.root) {
2796
- throw new Error("Missing `root` option.");
2860
+ function decompressSourceData(source) {
2861
+ let { _format, _version = 1, _keyMap, _valueMap } = source;
2862
+ assert(_version === 1, `Expected file version 1 instead of ${_version}`);
2863
+ let longToShort = _keyMap;
2864
+ let shortToLong = {};
2865
+ if (longToShort) {
2866
+ for (const [key, value] of Object.entries(longToShort)) {
2867
+ shortToLong[value] = key;
2797
2868
  }
2798
- this.opts = Object.assign({ thresh: 5 }, opts);
2799
- this.root = opts.root;
2800
- this._handler = this.handleEvent.bind(this);
2801
- this.events.forEach((type) => {
2802
- this.root.addEventListener(type, this._handler);
2803
- });
2804
- }
2805
- /** Unregister all event listeners. */
2806
- disconnect() {
2807
- this.events.forEach((type) => {
2808
- this.root.removeEventListener(type, this._handler);
2809
- });
2810
- }
2811
- getDragElem() {
2812
- return this.dragElem;
2813
2869
  }
2814
- isDragging() {
2815
- return this.dragging;
2870
+ // Fallback for old format (pre 0.7.0, using _keyMap in reverse direction)
2871
+ // TODO: raise Error on final 1.x release
2872
+ if (longToShort && longToShort.t) {
2873
+ const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2874
+ console.warn(msg); // eslint-disable-line no-console
2875
+ [longToShort, shortToLong] = [shortToLong, longToShort];
2816
2876
  }
2817
- stopDrag(cb_event) {
2818
- if (this.dragging && this.opts.dragstop && cb_event) {
2819
- cb_event.type = "dragstop";
2820
- try {
2821
- this.opts.dragstop(cb_event);
2822
- }
2823
- catch (err) {
2824
- console.error("dragstop error", err); // eslint-disable-line no-console
2825
- }
2877
+ // Fallback for old format (pre 0.7.0, using _typeList instead of _valueMap)
2878
+ // TODO: raise Error on final 1.x release
2879
+ if (source._typeList != null) {
2880
+ const msg = `source._typeList is deprecated since v0.7.0: use source._valueMap: {"type": [...]} instead.`;
2881
+ if (_valueMap != null) {
2882
+ throw new Error(msg);
2883
+ }
2884
+ else {
2885
+ console.warn(msg); // eslint-disable-line no-console
2886
+ _valueMap = { type: source._typeList };
2887
+ delete source._typeList;
2826
2888
  }
2827
- this.dragElem = null;
2828
- this.dragging = false;
2829
- this.start.event = null;
2830
- this.customData = {};
2831
2889
  }
2832
- handleEvent(e) {
2833
- const type = e.type;
2834
- const opts = this.opts;
2835
- const cb_event = {
2836
- type: e.type,
2837
- startEvent: type === "mousedown" ? e : this.start.event,
2838
- event: e,
2839
- customData: this.customData,
2840
- dragElem: this.dragElem,
2841
- dx: e.pageX - this.start.x,
2842
- dy: e.pageY - this.start.y,
2843
- apply: undefined,
2844
- };
2845
- // console.log("handleEvent", type, cb_event);
2846
- switch (type) {
2847
- case "keydown":
2848
- this.stopDrag(cb_event);
2849
- break;
2850
- case "mousedown":
2851
- if (this.dragElem) {
2852
- this.stopDrag(cb_event);
2853
- break;
2854
- }
2855
- if (opts.selector) {
2856
- let elem = e.target;
2857
- if (elem.matches(opts.selector)) {
2858
- this.dragElem = elem;
2859
- }
2860
- else {
2861
- elem = elem.closest(opts.selector);
2862
- if (elem) {
2863
- this.dragElem = elem;
2864
- }
2865
- else {
2866
- break; // no event delegation selector matched
2867
- }
2890
+ if (_format === "flat") {
2891
+ unflattenSource(source);
2892
+ }
2893
+ delete source._format;
2894
+ delete source._version;
2895
+ delete source._keyMap;
2896
+ delete source._valueMap;
2897
+ delete source._positional;
2898
+ function _iter(childList) {
2899
+ for (const node of childList) {
2900
+ // Iterate over a list of names, because we modify inside the loop
2901
+ // (for ... of ... does not allow this)
2902
+ Object.getOwnPropertyNames(node).forEach((propName) => {
2903
+ const value = node[propName];
2904
+ // Replace short names with long names if defined in _keyMap
2905
+ let longName = propName;
2906
+ if (_keyMap && shortToLong[propName] != null) {
2907
+ longName = shortToLong[propName];
2908
+ if (longName !== propName) {
2909
+ node[longName] = value;
2910
+ delete node[propName];
2868
2911
  }
2869
2912
  }
2870
- this.start.event = e;
2871
- this.start.x = e.pageX;
2872
- this.start.y = e.pageY;
2873
- this.start.altKey = e.altKey;
2874
- this.start.ctrlKey = e.ctrlKey;
2875
- this.start.metaKey = e.metaKey;
2876
- this.start.shiftKey = e.shiftKey;
2877
- break;
2878
- case "mousemove":
2879
- // TODO: debounce/throttle?
2880
- // TODO: horizontal mode: ignore if dx unchanged
2881
- if (!this.dragElem) {
2882
- break;
2883
- }
2884
- if (!this.dragging) {
2885
- if (opts.thresh) {
2886
- const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2887
- if (dist2 < opts.thresh * opts.thresh) {
2888
- break;
2889
- }
2890
- }
2891
- cb_event.type = "dragstart";
2892
- if (opts.dragstart(cb_event) === false) {
2893
- this.stopDrag(cb_event);
2894
- break;
2913
+ // Replace type index with type name if defined in _valueMap
2914
+ if (_valueMap &&
2915
+ typeof value === "number" &&
2916
+ _valueMap[longName] != null) {
2917
+ const newValue = _valueMap[longName][value];
2918
+ if (newValue == null) {
2919
+ throw new Error(`Expected valueMap[${longName}][${value}] entry in [${_valueMap[longName]}]`);
2895
2920
  }
2896
- this.dragging = true;
2897
- }
2898
- if (this.dragging && this.opts.drag) {
2899
- cb_event.type = "drag";
2900
- this.opts.drag(cb_event);
2901
- }
2902
- break;
2903
- case "mouseup":
2904
- if (!this.dragging) {
2905
- this.stopDrag(cb_event);
2906
- break;
2907
- }
2908
- if (e.button === 0) {
2909
- cb_event.apply = true;
2910
- }
2911
- else {
2912
- cb_event.apply = false;
2921
+ node[longName] = newValue;
2913
2922
  }
2914
- this.stopDrag(cb_event);
2915
- break;
2923
+ });
2924
+ // Recursion
2925
+ if (node.children) {
2926
+ _iter(node.children);
2927
+ }
2916
2928
  }
2917
2929
  }
2930
+ if (_keyMap || _valueMap) {
2931
+ _iter(source.children);
2932
+ }
2918
2933
  }
2919
2934
 
2920
2935
  /*!
2921
2936
  * Wunderbaum - ext-grid
2922
2937
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2923
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2938
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2924
2939
  */
2925
2940
  class GridExtension extends WunderbaumExtension {
2926
2941
  constructor(tree) {
@@ -3011,7 +3026,7 @@ class GridExtension extends WunderbaumExtension {
3011
3026
  /*!
3012
3027
  * Wunderbaum - deferred
3013
3028
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3014
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
3029
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3015
3030
  */
3016
3031
  /**
3017
3032
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -3064,7 +3079,7 @@ class Deferred {
3064
3079
  /*!
3065
3080
  * Wunderbaum - wunderbaum_node
3066
3081
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3067
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
3082
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3068
3083
  */
3069
3084
  /** WunderbaumNode properties that can be passed with source data.
3070
3085
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3092,6 +3107,20 @@ const NODE_PROPS = new Set([
3092
3107
  const NODE_DICT_PROPS = new Set(NODE_PROPS);
3093
3108
  NODE_DICT_PROPS.delete("_partsel");
3094
3109
  NODE_DICT_PROPS.delete("unselectable");
3110
+ // /** Node properties that are of type bool (or boolean & string).
3111
+ // * When parsing, we accept 0 for false and 1 for true for better JSON compression.
3112
+ // */
3113
+ // export const NODE_BOOL_PROPS: Set<string> = new Set([
3114
+ // "checkbox",
3115
+ // "colspan",
3116
+ // "expanded",
3117
+ // "icon",
3118
+ // "iconTooltip",
3119
+ // "radiogroup",
3120
+ // "selected",
3121
+ // "tooltip",
3122
+ // "unselectable",
3123
+ // ]);
3095
3124
  /**
3096
3125
  * A single tree node.
3097
3126
  *
@@ -3133,20 +3162,26 @@ class WunderbaumNode {
3133
3162
  this.parent = parent;
3134
3163
  this.key = "" + ((_a = data.key) !== null && _a !== void 0 ? _a : ++WunderbaumNode.sequence);
3135
3164
  this.title = "" + ((_b = data.title) !== null && _b !== void 0 ? _b : "<" + this.key + ">");
3165
+ this.expanded = !!data.expanded;
3166
+ this.lazy = !!data.lazy;
3167
+ // We set the following node properties only if a matching data value is
3168
+ // passed
3136
3169
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
3137
3170
  data.type != null ? (this.type = "" + data.type) : 0;
3138
- this.expanded = data.expanded === true;
3139
- data.icon != null ? (this.icon = data.icon) : 0;
3140
- this.lazy = data.lazy === true;
3171
+ data.icon != null ? (this.icon = intToBool(data.icon)) : 0;
3172
+ data.tooltip != null ? (this.tooltip = intToBool(data.tooltip)) : 0;
3173
+ data.iconTooltip != null
3174
+ ? (this.iconTooltip = intToBool(data.iconTooltip))
3175
+ : 0;
3141
3176
  data.statusNodeType != null
3142
3177
  ? (this.statusNodeType = ("" + data.statusNodeType))
3143
3178
  : 0;
3144
3179
  data.colspan != null ? (this.colspan = !!data.colspan) : 0;
3145
3180
  // Selection
3146
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
3181
+ data.checkbox != null ? intToBool(data.checkbox) : 0;
3147
3182
  data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
3148
- this.selected = data.selected === true;
3149
- data.unselectable === true ? (this.unselectable = true) : 0;
3183
+ data.selected != null ? (this.selected = !!data.selected) : 0;
3184
+ data.unselectable != null ? (this.unselectable = !!data.unselectable) : 0;
3150
3185
  if (data.classes) {
3151
3186
  this.setClass(data.classes);
3152
3187
  }
@@ -3975,8 +4010,8 @@ class WunderbaumNode {
3975
4010
  let elap = 0, elapLoad = 0, elapProcess = 0;
3976
4011
  // Check for overlapping requests
3977
4012
  if (this._requestId) {
3978
- this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending.`);
3979
- // node.debug("Send load request #" + requestId);
4013
+ this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending. ` +
4014
+ "The previous request will be ignored.");
3980
4015
  }
3981
4016
  this._requestId = requestId;
3982
4017
  // const timerLabel = tree.logTime(this + ".load()");
@@ -4456,6 +4491,7 @@ class WunderbaumNode {
4456
4491
  _render_markup(opts) {
4457
4492
  const tree = this.tree;
4458
4493
  const treeOptions = tree.options;
4494
+ const rowHeight = treeOptions.rowHeightPx;
4459
4495
  const checkbox = this.getOption("checkbox");
4460
4496
  const columns = tree.columns;
4461
4497
  const level = this.getLevel();
@@ -4470,7 +4506,7 @@ class WunderbaumNode {
4470
4506
  assert(!this.isRootNode(), "Root node not allowed");
4471
4507
  rowDiv = document.createElement("div");
4472
4508
  rowDiv.classList.add("wb-row");
4473
- rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
4509
+ rowDiv.style.top = this._rowIdx * rowHeight + "px";
4474
4510
  this._rowElem = rowDiv;
4475
4511
  // Attach a node reference to the DOM Element:
4476
4512
  rowDiv._wb_node = this;
@@ -5432,6 +5468,21 @@ class WunderbaumNode {
5432
5468
  av = a.data[propName];
5433
5469
  bv = b.data[propName];
5434
5470
  }
5471
+ if (av == null && bv == null) {
5472
+ return 0;
5473
+ }
5474
+ if (av == null) {
5475
+ av = typeof bv === "string" ? "" : 0;
5476
+ }
5477
+ else if (typeof av === "boolean") {
5478
+ av = av ? 1 : 0;
5479
+ }
5480
+ if (bv == null) {
5481
+ bv = typeof av === "string" ? "" : 0;
5482
+ }
5483
+ else if (typeof bv === "boolean") {
5484
+ bv = bv ? 1 : 0;
5485
+ }
5435
5486
  if (caseInsensitive) {
5436
5487
  if (typeof av === "string") {
5437
5488
  av = av.toLowerCase();
@@ -5555,7 +5606,7 @@ WunderbaumNode.sequence = 0;
5555
5606
  /*!
5556
5607
  * Wunderbaum - ext-edit
5557
5608
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
5558
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
5609
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
5559
5610
  */
5560
5611
  // const START_MARKER = "\uFFF7";
5561
5612
  class EditExtension extends WunderbaumExtension {
@@ -5719,6 +5770,10 @@ class EditExtension extends WunderbaumExtension {
5719
5770
  if (!node) {
5720
5771
  return;
5721
5772
  }
5773
+ if (node.isStatusNode()) {
5774
+ node.logWarn("Cannot edit status node.");
5775
+ return;
5776
+ }
5722
5777
  this.tree.logDebug(`startEditTitle(node=${node})`);
5723
5778
  let inputHtml = node._callEvent("edit.beforeEdit");
5724
5779
  if (inputHtml === false) {
@@ -5886,8 +5941,8 @@ class EditExtension extends WunderbaumExtension {
5886
5941
  * https://github.com/mar10/wunderbaum
5887
5942
  *
5888
5943
  * Released under the MIT license.
5889
- * @version v0.11.0
5890
- * @date Sun, 04 Aug 2024 15:35:53 GMT
5944
+ * @version v0.11.1
5945
+ * @date Fri, 27 Dec 2024 22:58:06 GMT
5891
5946
  */
5892
5947
  // import "./wunderbaum.scss";
5893
5948
  class WbSystemRoot extends WunderbaumNode {
@@ -5969,7 +6024,7 @@ class Wunderbaum {
5969
6024
  debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
5970
6025
  header: null, // Show/hide header (pass bool or string)
5971
6026
  // headerHeightPx: ROW_HEIGHT,
5972
- rowHeightPx: ROW_HEIGHT,
6027
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
5973
6028
  iconMap: "bootstrap",
5974
6029
  columns: null,
5975
6030
  types: null,
@@ -6054,6 +6109,10 @@ class Wunderbaum {
6054
6109
  if (!this.element.getAttribute("tabindex")) {
6055
6110
  this.element.tabIndex = 0;
6056
6111
  }
6112
+ if (opts.rowHeightPx !== DEFAULT_ROW_HEIGHT) {
6113
+ this.element.style.setProperty("--wb-row-outer-height", opts.rowHeightPx + "px");
6114
+ this.element.style.setProperty("--wb-row-inner-height", opts.rowHeightPx - 2 + "px");
6115
+ }
6057
6116
  // Attach tree instance to <div>
6058
6117
  this.element._wb_tree = this;
6059
6118
  // Create header markup, or take it from the existing html
@@ -6463,31 +6522,33 @@ class Wunderbaum {
6463
6522
  }
6464
6523
  /** Return the topmost visible node in the viewport. */
6465
6524
  getTopmostVpNode(complete = true) {
6525
+ const rowHeight = this.options.rowHeightPx;
6466
6526
  const gracePx = 1; // ignore subpixel scrolling
6467
6527
  const scrollParent = this.element;
6468
6528
  // const headerHeight = this.headerElement.clientHeight; // May be 0
6469
6529
  const scrollTop = scrollParent.scrollTop; // + headerHeight;
6470
6530
  let topIdx;
6471
6531
  if (complete) {
6472
- topIdx = Math.ceil((scrollTop - gracePx) / ROW_HEIGHT);
6532
+ topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
6473
6533
  }
6474
6534
  else {
6475
- topIdx = Math.floor(scrollTop / ROW_HEIGHT);
6535
+ topIdx = Math.floor(scrollTop / rowHeight);
6476
6536
  }
6477
6537
  return this._getNodeByRowIdx(topIdx);
6478
6538
  }
6479
6539
  /** Return the lowest visible node in the viewport. */
6480
6540
  getLowestVpNode(complete = true) {
6541
+ const rowHeight = this.options.rowHeightPx;
6481
6542
  const scrollParent = this.element;
6482
6543
  const headerHeight = this.headerElement.clientHeight; // May be 0
6483
6544
  const scrollTop = scrollParent.scrollTop;
6484
6545
  const clientHeight = scrollParent.clientHeight - headerHeight;
6485
6546
  let bottomIdx;
6486
6547
  if (complete) {
6487
- bottomIdx = Math.floor((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6548
+ bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
6488
6549
  }
6489
6550
  else {
6490
- bottomIdx = Math.ceil((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6551
+ bottomIdx = Math.ceil((scrollTop + clientHeight) / rowHeight) - 1;
6491
6552
  }
6492
6553
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
6493
6554
  return this._getNodeByRowIdx(bottomIdx);
@@ -6919,8 +6980,9 @@ class Wunderbaum {
6919
6980
  * @param includeHidden Not yet implemented
6920
6981
  */
6921
6982
  findRelatedNode(node, where, includeHidden = false) {
6983
+ const rowHeight = this.options.rowHeightPx;
6922
6984
  let res = null;
6923
- const pageSize = Math.floor(this.listContainerElement.clientHeight / ROW_HEIGHT);
6985
+ const pageSize = Math.floor(this.listContainerElement.clientHeight / rowHeight);
6924
6986
  switch (where) {
6925
6987
  case "parent":
6926
6988
  if (node.parent && node.parent.parent) {
@@ -7243,6 +7305,7 @@ class Wunderbaum {
7243
7305
  scrollTo(nodeOrOpts) {
7244
7306
  const PADDING = 2; // leave some pixels between viewport bounds
7245
7307
  let node;
7308
+ // WunderbaumNode;
7246
7309
  let options;
7247
7310
  if (nodeOrOpts instanceof WunderbaumNode) {
7248
7311
  node = nodeOrOpts;
@@ -7252,14 +7315,15 @@ class Wunderbaum {
7252
7315
  node = options.node;
7253
7316
  }
7254
7317
  assert(node && node._rowIdx != null, `Invalid node: ${node}`);
7318
+ const rowHeight = this.options.rowHeightPx;
7255
7319
  const scrollParent = this.element;
7256
7320
  const headerHeight = this.headerElement.clientHeight; // May be 0
7257
7321
  const scrollTop = scrollParent.scrollTop;
7258
7322
  const vpHeight = scrollParent.clientHeight;
7259
- const rowTop = node._rowIdx * ROW_HEIGHT + headerHeight;
7323
+ const rowTop = node._rowIdx * rowHeight + headerHeight;
7260
7324
  const vpTop = headerHeight;
7261
7325
  const vpRowTop = rowTop - scrollTop;
7262
- const vpRowBottom = vpRowTop + ROW_HEIGHT;
7326
+ const vpRowBottom = vpRowTop + rowHeight;
7263
7327
  const topNode = options === null || options === void 0 ? void 0 : options.topNode;
7264
7328
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts , options);
7265
7329
  let newScrollTop = null;
@@ -7268,7 +7332,7 @@ class Wunderbaum {
7268
7332
  else {
7269
7333
  // Node is below viewport
7270
7334
  // this.log("Below viewport");
7271
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
7335
+ newScrollTop = rowTop + rowHeight - vpHeight + PADDING; // leave some pixels between viewport bounds
7272
7336
  }
7273
7337
  }
7274
7338
  else {
@@ -7881,19 +7945,19 @@ class Wunderbaum {
7881
7945
  // this.log("_updateRows", opts)
7882
7946
  options = Object.assign({ newNodesOnly: false }, options);
7883
7947
  const newNodesOnly = !!options.newNodesOnly;
7884
- const row_height = ROW_HEIGHT;
7885
- const vp_height = this.element.clientHeight;
7948
+ const rowHeight = this.options.rowHeightPx;
7949
+ const vpHeight = this.element.clientHeight;
7886
7950
  const prefetch = RENDER_MAX_PREFETCH;
7887
7951
  // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
7888
7952
  const ofs = this.element.scrollTop;
7889
- let startIdx = Math.max(0, ofs / row_height - prefetch);
7953
+ let startIdx = Math.max(0, ofs / rowHeight - prefetch);
7890
7954
  startIdx = Math.floor(startIdx);
7891
7955
  // Make sure start is always even, so the alternating row colors don't
7892
7956
  // change when scrolling:
7893
7957
  if (startIdx % 2) {
7894
7958
  startIdx--;
7895
7959
  }
7896
- let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7960
+ let endIdx = Math.max(0, (ofs + vpHeight) / rowHeight + prefetch);
7897
7961
  endIdx = Math.ceil(endIdx);
7898
7962
  // this.debug("render", opts);
7899
7963
  const obsoleteNodes = new Set();
@@ -7922,21 +7986,21 @@ class Wunderbaum {
7922
7986
  else if (rowDiv && newNodesOnly) {
7923
7987
  obsoleteNodes.delete(node);
7924
7988
  // no need to update existing node markup
7925
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
7989
+ rowDiv.style.top = idx * rowHeight + "px";
7926
7990
  prevElem = rowDiv;
7927
7991
  }
7928
7992
  else {
7929
7993
  obsoleteNodes.delete(node);
7930
7994
  // Create new markup
7931
7995
  if (rowDiv) {
7932
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
7996
+ rowDiv.style.top = idx * rowHeight + "px";
7933
7997
  }
7934
7998
  node._render({ top: top, after: prevElem });
7935
7999
  // node.log("render", top, prevElem, "=>", node._rowElem);
7936
8000
  prevElem = node._rowElem;
7937
8001
  }
7938
8002
  idx++;
7939
- top += row_height;
8003
+ top += rowHeight;
7940
8004
  });
7941
8005
  this.treeRowCount = idx;
7942
8006
  for (const n of obsoleteNodes) {
@@ -8200,7 +8264,7 @@ class Wunderbaum {
8200
8264
  }
8201
8265
  Wunderbaum.sequence = 0;
8202
8266
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
8203
- Wunderbaum.version = "v0.11.0"; // Set to semver by 'grunt release'
8267
+ Wunderbaum.version = "v0.11.1"; // Set to semver by 'grunt release'
8204
8268
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
8205
8269
  Wunderbaum.util = util;
8206
8270