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.
@@ -294,7 +294,7 @@
294
294
  /*!
295
295
  * Wunderbaum - util
296
296
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
297
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
297
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
298
298
  */
299
299
  /** @module util */
300
300
  /** Readable names for `MouseEvent.button` */
@@ -986,6 +986,15 @@
986
986
  }
987
987
  throw new Error("No default boolean value provided");
988
988
  }
989
+ /**
990
+ * Return `val` unless `val` is a number in which case we convert to boolean.
991
+ * This is useful when a boolean value is stored as a 0/1 (e.g. in JSON) and
992
+ * we still want to maintain string values. null and undefined are returned as
993
+ * is. E.g. `checkbox` may be boolean or 'radio'.
994
+ */
995
+ function intToBool(val) {
996
+ return typeof val === "number" ? !!val : val;
997
+ }
989
998
  // /** Check if a string is contained in an Array or Set. */
990
999
  // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
991
1000
  // return Array.prototype.includes.call(items, s)
@@ -1114,6 +1123,7 @@
1114
1123
  extractHtmlText: extractHtmlText,
1115
1124
  getOption: getOption,
1116
1125
  getValueFromElem: getValueFromElem,
1126
+ intToBool: intToBool,
1117
1127
  isArray: isArray,
1118
1128
  isEmptyObject: isEmptyObject,
1119
1129
  isFunction: isFunction,
@@ -1138,7 +1148,7 @@
1138
1148
  /*!
1139
1149
  * Wunderbaum - types
1140
1150
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1141
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1151
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1142
1152
  */
1143
1153
  /**
1144
1154
  * Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
@@ -1202,7 +1212,7 @@
1202
1212
  /*!
1203
1213
  * Wunderbaum - wb_extension_base
1204
1214
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1205
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1215
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1206
1216
  */
1207
1217
  class WunderbaumExtension {
1208
1218
  constructor(tree, id, defaults) {
@@ -1261,7 +1271,7 @@
1261
1271
  /*!
1262
1272
  * Wunderbaum - ext-filter
1263
1273
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1264
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1274
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1265
1275
  */
1266
1276
  const START_MARKER = "\uFFF7";
1267
1277
  const END_MARKER = "\uFFF8";
@@ -1586,7 +1596,7 @@
1586
1596
  /*!
1587
1597
  * Wunderbaum - ext-keynav
1588
1598
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1589
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1599
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1590
1600
  */
1591
1601
  const QUICKSEARCH_DELAY = 500;
1592
1602
  class KeynavExtension extends WunderbaumExtension {
@@ -1950,7 +1960,7 @@
1950
1960
  /*!
1951
1961
  * Wunderbaum - ext-logger
1952
1962
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1953
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1963
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1954
1964
  */
1955
1965
  class LoggerExtension extends WunderbaumExtension {
1956
1966
  constructor(tree) {
@@ -1990,410 +2000,70 @@
1990
2000
  }
1991
2001
 
1992
2002
  /*!
1993
- * Wunderbaum - common
2003
+ * Wunderbaum - ext-dnd
1994
2004
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1995
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
1996
- */
1997
- const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
1998
- /**
1999
- * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
2000
- */
2001
- const ROW_HEIGHT = 22;
2002
- /**
2003
- * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
2004
- */
2005
- const ICON_WIDTH = 20;
2006
- /**
2007
- * Adjust the width of the title span, so overflow ellipsis work.
2008
- * (2 x `$col-padding-x` + 3px rounding errors).
2009
- */
2010
- const TITLE_SPAN_PAD_Y = 7;
2011
- /** Render row markup for N nodes above and below the visible viewport. */
2012
- const RENDER_MAX_PREFETCH = 5;
2013
- /** Minimum column width if not set otherwise. */
2014
- const DEFAULT_MIN_COL_WIDTH = 4;
2015
- /** Regular expression to detect if a string describes an image URL (in contrast
2016
- * to a class name). Strings are considered image urls if they contain '.' or '/'.
2017
- */
2018
- const TEST_IMG = new RegExp(/\.|\//);
2019
- // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2020
- // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2021
- /**
2022
- * Default node icons.
2023
- * Requires bootstrap icons https://icons.getbootstrap.com
2024
- */
2025
- const iconMaps = {
2026
- bootstrap: {
2027
- error: "bi bi-exclamation-triangle",
2028
- // loading: "bi bi-hourglass-split wb-busy",
2029
- loading: "bi bi-chevron-right wb-busy",
2030
- // loading: "bi bi-arrow-repeat wb-spin",
2031
- // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2032
- // noData: "bi bi-search",
2033
- noData: "bi bi-question-circle",
2034
- expanderExpanded: "bi bi-chevron-down",
2035
- // expanderExpanded: "bi bi-dash-square",
2036
- expanderCollapsed: "bi bi-chevron-right",
2037
- // expanderCollapsed: "bi bi-plus-square",
2038
- expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2039
- // expanderLazy: "bi bi-chevron-bar-right",
2040
- checkChecked: "bi bi-check-square",
2041
- checkUnchecked: "bi bi-square",
2042
- checkUnknown: "bi bi-dash-square-dotted",
2043
- radioChecked: "bi bi-circle-fill",
2044
- radioUnchecked: "bi bi-circle",
2045
- radioUnknown: "bi bi-record-circle",
2046
- folder: "bi bi-folder2",
2047
- folderOpen: "bi bi-folder2-open",
2048
- folderLazy: "bi bi-folder-symlink",
2049
- doc: "bi bi-file-earmark",
2050
- colSortable: "bi bi-chevron-expand",
2051
- // colSortable: "bi bi-arrow-down-up",
2052
- // colSortAsc: "bi bi-chevron-down",
2053
- // colSortDesc: "bi bi-chevron-up",
2054
- colSortAsc: "bi bi-arrow-down",
2055
- colSortDesc: "bi bi-arrow-up",
2056
- colFilter: "bi bi-filter-circle",
2057
- colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
2058
- colMenu: "bi bi-three-dots-vertical",
2059
- },
2060
- fontawesome6: {
2061
- error: "fa-solid fa-triangle-exclamation",
2062
- loading: "fa-solid fa-chevron-right fa-beat",
2063
- noData: "fa-solid fa-circle-question",
2064
- expanderExpanded: "fa-solid fa-chevron-down",
2065
- expanderCollapsed: "fa-solid fa-chevron-right",
2066
- expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2067
- checkChecked: "fa-regular fa-square-check",
2068
- checkUnchecked: "fa-regular fa-square",
2069
- checkUnknown: "fa-regular fa-square-minus",
2070
- radioChecked: "fa-solid fa-circle",
2071
- radioUnchecked: "fa-regular fa-circle",
2072
- radioUnknown: "fa-regular fa-circle-question",
2073
- folder: "fa-solid fa-folder-closed",
2074
- folderOpen: "fa-regular fa-folder-open",
2075
- folderLazy: "fa-solid fa-folder-plus",
2076
- doc: "fa-regular fa-file",
2077
- colSortable: "fa-solid fa-fw fa-sort",
2078
- colSortAsc: "fa-solid fa-fw fa-sort-up",
2079
- colSortDesc: "fa-solid fa-fw fa-sort-down",
2080
- colFilter: "fa-solid fa-fw fa-filter",
2081
- colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
2082
- colMenu: "fa-solid fa-fw fa-ellipsis-v",
2083
- },
2084
- };
2085
- /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2086
- const RESERVED_TREE_SOURCE_KEYS = new Set([
2087
- "_format", // reserved for future use
2088
- "_keyMap", // Used for compressed data format
2089
- "_positional", // Used for compressed data format
2090
- "_typeList", // Used for compressed data format @deprecated
2091
- "_valueMap", // Used for compressed data format
2092
- "_version", // reserved for future use
2093
- "children",
2094
- "columns",
2095
- "types",
2096
- ]);
2097
- // /** Key codes that trigger grid navigation, even when inside an input element. */
2098
- // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2099
- // // "ArrowDown",
2100
- // // "ArrowUp",
2101
- // "Enter",
2102
- // "Escape",
2103
- // ]);
2104
- /** Map `KeyEvent.key` to navigation action. */
2105
- const KEY_TO_ACTION_DICT = {
2106
- " ": "toggleSelect",
2107
- "+": "expand",
2108
- Add: "expand",
2109
- ArrowDown: "down",
2110
- ArrowLeft: "left",
2111
- ArrowRight: "right",
2112
- ArrowUp: "up",
2113
- Backspace: "parent",
2114
- "/": "collapseAll",
2115
- Divide: "collapseAll",
2116
- End: "lastCol",
2117
- Home: "firstCol",
2118
- "Control+End": "last",
2119
- "Control+Home": "first",
2120
- "Meta+ArrowDown": "last", // macOs
2121
- "Meta+ArrowUp": "first", // macOs
2122
- "*": "expandAll",
2123
- Multiply: "expandAll",
2124
- PageDown: "pageDown",
2125
- PageUp: "pageUp",
2126
- "-": "collapse",
2127
- Subtract: "collapse",
2128
- };
2129
- /** Return a callback that returns true if the node title matches the string
2130
- * or regular expression.
2131
- * @see {@link WunderbaumNode.findAll}
2132
- */
2133
- function makeNodeTitleMatcher(match) {
2134
- if (match instanceof RegExp) {
2135
- return function (node) {
2136
- return match.test(node.title);
2137
- };
2138
- }
2139
- assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2140
- // s = escapeRegex(s.toLowerCase());
2141
- return function (node) {
2142
- return node.title === match;
2143
- // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2144
- // return node.title.toLowerCase().indexOf(match) >= 0;
2145
- };
2146
- }
2147
- /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2148
- function makeNodeTitleStartMatcher(s) {
2149
- s = escapeRegex(s);
2150
- const reMatch = new RegExp("^" + s, "i");
2151
- return function (node) {
2152
- return reMatch.test(node.title);
2153
- };
2154
- }
2155
- /** Compare two nodes by title (case-insensitive). */
2156
- function nodeTitleSorter(a, b) {
2157
- const x = a.title.toLowerCase();
2158
- const y = b.title.toLowerCase();
2159
- return x === y ? 0 : x > y ? 1 : -1;
2160
- }
2161
- /**
2162
- * Convert 'flat' to 'nested' format.
2163
- *
2164
- * Flat node entry format:
2165
- * [PARENT_ID, [POSITIONAL_ARGS]]
2166
- * or
2167
- * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2168
- *
2169
- * 1. Parent-referencing list is converted to a list of nested dicts with
2170
- * optional `children` properties.
2171
- * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
2005
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2172
2006
  */
2173
- function unflattenSource(source) {
2174
- var _a, _b, _c;
2175
- const { _format, _keyMap = {}, _positional = [], children } = source;
2176
- if (_format !== "flat") {
2177
- throw new Error(`Expected source._format: "flat", but got ${_format}`);
2178
- }
2179
- if (_positional && _positional.includes("children")) {
2180
- throw new Error(`source._positional must not include "children": ${_positional}`);
2007
+ const nodeMimeType = "application/x-wunderbaum-node";
2008
+ class DndExtension extends WunderbaumExtension {
2009
+ constructor(tree) {
2010
+ super(tree, "dnd", {
2011
+ autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
2012
+ // dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
2013
+ // dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
2014
+ // #1021 `document.body` is not available yet
2015
+ // dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
2016
+ multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
2017
+ effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
2018
+ dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (override in drag, dragOver).
2019
+ guessDropEffect: true, // Calculate from `effectAllowed` and modifier keys)
2020
+ preventForeignNodes: false, // Prevent dropping nodes from different Wunderbaum trees
2021
+ preventLazyParents: true, // Prevent dropping items on unloaded lazy Wunderbaum tree nodes
2022
+ preventNonNodes: false, // Prevent dropping items other than Wunderbaum tree nodes
2023
+ preventRecursion: true, // Prevent dropping nodes on own descendants
2024
+ preventSameParent: false, // Prevent dropping nodes under same direct parent
2025
+ preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. (move only)
2026
+ serializeClipboardData: true, // Serialize node data to dataTransfer object
2027
+ scroll: true, // Enable auto-scrolling while dragging
2028
+ scrollSensitivity: 20, // Active top/bottom margin in pixel
2029
+ // scrollnterval: 50, // Generate event every 50 ms
2030
+ scrollSpeed: 5, // Scroll pixel per 50 ms
2031
+ // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2032
+ sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
2033
+ // Events (drag support)
2034
+ dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
2035
+ drag: null, // Callback(sourceNode, data)
2036
+ dragEnd: null, // Callback(sourceNode, data)
2037
+ // Events (drop support)
2038
+ dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
2039
+ dragOver: null, // Callback(targetNode, data)
2040
+ dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
2041
+ drop: null, // Callback(targetNode, data)
2042
+ dragLeave: null, // Callback(targetNode, data)
2043
+ });
2044
+ // public dropMarkerElem?: HTMLElement;
2045
+ this.srcNode = null;
2046
+ this.lastTargetNode = null;
2047
+ this.lastEnterStamp = 0;
2048
+ this.lastAllowedDropRegions = null;
2049
+ this.lastDropEffect = null;
2050
+ this.lastDropRegion = false;
2051
+ this.currentScrollDir = 0;
2052
+ this.applyScrollDirThrottled = throttle(this._applyScrollDir, 50);
2181
2053
  }
2182
- let longToShort = _keyMap;
2183
- if (_keyMap.t) {
2184
- // Inverse keyMap was used (pre 0.7.0)
2185
- // TODO: raise Error on final 1.x release
2186
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2187
- console.warn(msg); // eslint-disable-line no-console
2188
- longToShort = {};
2189
- for (const [key, value] of Object.entries(_keyMap)) {
2190
- longToShort[value] = key;
2191
- }
2192
- }
2193
- const positionalShort = _positional.map((e) => longToShort[e]);
2194
- const newChildren = [];
2195
- const keyToNodeMap = {};
2196
- const indexToNodeMap = {};
2197
- const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2198
- const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2199
- for (const [index, nodeTuple] of children.entries()) {
2200
- // Node entry format:
2201
- // [PARENT_ID, [POSITIONAL_ARGS]]
2202
- // or
2203
- // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2204
- const [parentId, args, kwargs = {}] = nodeTuple;
2205
- // Free up some memory as we go
2206
- nodeTuple[1] = null;
2207
- if (nodeTuple[2] != null) {
2208
- nodeTuple[2] = null;
2209
- }
2210
- // console.log("flatten", parentId, args, kwargs)
2211
- // We keep `kwargs` as our new node definition. Then we add all positional
2212
- // values to this object:
2213
- args.forEach((val, positionalIdx) => {
2214
- kwargs[positionalShort[positionalIdx]] = val;
2215
- });
2216
- // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2217
- // index of the source.children list. If PARENT_ID is a string, we search
2218
- // a parent with node.key of this value.
2219
- indexToNodeMap[index] = kwargs;
2220
- const key = kwargs[keyAttrName];
2221
- if (key != null) {
2222
- keyToNodeMap[key] = kwargs;
2223
- }
2224
- let parentNode = null;
2225
- if (parentId === null) ;
2226
- else if (typeof parentId === "number") {
2227
- parentNode = indexToNodeMap[parentId];
2228
- if (parentNode === undefined) {
2229
- throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2230
- }
2231
- }
2232
- else {
2233
- parentNode = keyToNodeMap[parentId];
2234
- if (parentNode === undefined) {
2235
- throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2236
- }
2237
- }
2238
- if (parentNode) {
2239
- (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2240
- parentNode[childrenAttrName].push(kwargs);
2241
- }
2242
- else {
2243
- newChildren.push(kwargs);
2244
- }
2245
- }
2246
- source.children = newChildren;
2247
- }
2248
- /**
2249
- * Decompresses the source data by
2250
- * - converting from 'flat' to 'nested' format
2251
- * - expanding short alias names to long names (if defined in _keyMap)
2252
- * - resolving value indexes to value strings (if defined in _valueMap)
2253
- *
2254
- * @param source - The source object to be decompressed.
2255
- * @returns void
2256
- */
2257
- function decompressSourceData(source) {
2258
- let { _format, _version = 1, _keyMap, _valueMap } = source;
2259
- assert(_version === 1, `Expected file version 1 instead of ${_version}`);
2260
- let longToShort = _keyMap;
2261
- let shortToLong = {};
2262
- if (longToShort) {
2263
- for (const [key, value] of Object.entries(longToShort)) {
2264
- shortToLong[value] = key;
2265
- }
2266
- }
2267
- // Fallback for old format (pre 0.7.0, using _keyMap in reverse direction)
2268
- // TODO: raise Error on final 1.x release
2269
- if (longToShort && longToShort.t) {
2270
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2271
- console.warn(msg); // eslint-disable-line no-console
2272
- [longToShort, shortToLong] = [shortToLong, longToShort];
2273
- }
2274
- // Fallback for old format (pre 0.7.0, using _typeList instead of _valueMap)
2275
- // TODO: raise Error on final 1.x release
2276
- if (source._typeList != null) {
2277
- const msg = `source._typeList is deprecated since v0.7.0: use source._valueMap: {"type": [...]} instead.`;
2278
- if (_valueMap != null) {
2279
- throw new Error(msg);
2280
- }
2281
- else {
2282
- console.warn(msg); // eslint-disable-line no-console
2283
- _valueMap = { type: source._typeList };
2284
- delete source._typeList;
2285
- }
2286
- }
2287
- if (_format === "flat") {
2288
- unflattenSource(source);
2289
- }
2290
- delete source._format;
2291
- delete source._version;
2292
- delete source._keyMap;
2293
- delete source._valueMap;
2294
- delete source._positional;
2295
- function _iter(childList) {
2296
- for (const node of childList) {
2297
- // Iterate over a list of names, because we modify inside the loop
2298
- // (for ... of ... does not allow this)
2299
- Object.getOwnPropertyNames(node).forEach((propName) => {
2300
- const value = node[propName];
2301
- // Replace short names with long names if defined in _keyMap
2302
- let longName = propName;
2303
- if (_keyMap && shortToLong[propName] != null) {
2304
- longName = shortToLong[propName];
2305
- if (longName !== propName) {
2306
- node[longName] = value;
2307
- delete node[propName];
2308
- }
2309
- }
2310
- // Replace type index with type name if defined in _valueMap
2311
- if (_valueMap &&
2312
- typeof value === "number" &&
2313
- _valueMap[longName] != null) {
2314
- const newValue = _valueMap[longName][value];
2315
- if (newValue == null) {
2316
- throw new Error(`Expected valueMap[${longName}][${value}] entry in [${_valueMap[longName]}]`);
2317
- }
2318
- node[longName] = newValue;
2319
- }
2320
- });
2321
- // Recursion
2322
- if (node.children) {
2323
- _iter(node.children);
2324
- }
2325
- }
2326
- }
2327
- if (_keyMap || _valueMap) {
2328
- _iter(source.children);
2329
- }
2330
- }
2331
-
2332
- /*!
2333
- * Wunderbaum - ext-dnd
2334
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2335
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2336
- */
2337
- const nodeMimeType = "application/x-wunderbaum-node";
2338
- class DndExtension extends WunderbaumExtension {
2339
- constructor(tree) {
2340
- super(tree, "dnd", {
2341
- autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
2342
- // dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
2343
- // dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
2344
- // #1021 `document.body` is not available yet
2345
- // dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
2346
- multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
2347
- effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
2348
- dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (override in drag, dragOver).
2349
- guessDropEffect: true, // Calculate from `effectAllowed` and modifier keys)
2350
- preventForeignNodes: false, // Prevent dropping nodes from different Wunderbaum trees
2351
- preventLazyParents: true, // Prevent dropping items on unloaded lazy Wunderbaum tree nodes
2352
- preventNonNodes: false, // Prevent dropping items other than Wunderbaum tree nodes
2353
- preventRecursion: true, // Prevent dropping nodes on own descendants
2354
- preventSameParent: false, // Prevent dropping nodes under same direct parent
2355
- preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. (move only)
2356
- serializeClipboardData: true, // Serialize node data to dataTransfer object
2357
- scroll: true, // Enable auto-scrolling while dragging
2358
- scrollSensitivity: 20, // Active top/bottom margin in pixel
2359
- // scrollnterval: 50, // Generate event every 50 ms
2360
- scrollSpeed: 5, // Scroll pixel per 50 ms
2361
- // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2362
- sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
2363
- // Events (drag support)
2364
- dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
2365
- drag: null, // Callback(sourceNode, data)
2366
- dragEnd: null, // Callback(sourceNode, data)
2367
- // Events (drop support)
2368
- dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
2369
- dragOver: null, // Callback(targetNode, data)
2370
- dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
2371
- drop: null, // Callback(targetNode, data)
2372
- dragLeave: null, // Callback(targetNode, data)
2373
- });
2374
- // public dropMarkerElem?: HTMLElement;
2375
- this.srcNode = null;
2376
- this.lastTargetNode = null;
2377
- this.lastEnterStamp = 0;
2378
- this.lastAllowedDropRegions = null;
2379
- this.lastDropEffect = null;
2380
- this.lastDropRegion = false;
2381
- this.currentScrollDir = 0;
2382
- this.applyScrollDirThrottled = throttle(this._applyScrollDir, 50);
2383
- }
2384
- init() {
2385
- super.init();
2386
- // Store the current scroll parent, which may be the tree
2387
- // container, any enclosing div, or the document.
2388
- // #761: scrollParent() always needs a container child
2389
- // $temp = $("<span>").appendTo(this.$container);
2390
- // this.$scrollParent = $temp.scrollParent();
2391
- // $temp.remove();
2392
- const tree = this.tree;
2393
- const dndOpts = tree.options.dnd;
2394
- // Enable drag support if dragStart() is specified:
2395
- if (dndOpts.dragStart) {
2396
- onEvent(tree.element, "dragstart drag dragend", this.onDragEvent.bind(this));
2054
+ init() {
2055
+ super.init();
2056
+ // Store the current scroll parent, which may be the tree
2057
+ // container, any enclosing div, or the document.
2058
+ // #761: scrollParent() always needs a container child
2059
+ // $temp = $("<span>").appendTo(this.$container);
2060
+ // this.$scrollParent = $temp.scrollParent();
2061
+ // $temp.remove();
2062
+ const tree = this.tree;
2063
+ const dndOpts = tree.options.dnd;
2064
+ // Enable drag support if dragStart() is specified:
2065
+ if (dndOpts.dragStart) {
2066
+ onEvent(tree.element, "dragstart drag dragend", this.onDragEvent.bind(this));
2397
2067
  }
2398
2068
  // Enable drop support if dragEnter() is specified:
2399
2069
  if (dndOpts.dragEnter) {
@@ -2431,14 +2101,15 @@
2431
2101
  * Calculates the drop region based on the drag event and the allowed drop regions.
2432
2102
  */
2433
2103
  _calcDropRegion(e, allowed) {
2104
+ const rowHeight = this.tree.options.rowHeightPx;
2434
2105
  const dy = e.offsetY;
2435
2106
  if (!allowed) {
2436
2107
  return false;
2437
2108
  }
2438
2109
  else if (allowed.size === 3) {
2439
- return dy < 0.25 * ROW_HEIGHT
2110
+ return dy < 0.25 * rowHeight
2440
2111
  ? "before"
2441
- : dy > 0.75 * ROW_HEIGHT
2112
+ : dy > 0.75 * rowHeight
2442
2113
  ? "after"
2443
2114
  : "over";
2444
2115
  }
@@ -2447,7 +2118,7 @@
2447
2118
  }
2448
2119
  else {
2449
2120
  // Only 'before' and 'after':
2450
- return dy > ROW_HEIGHT / 2 ? "after" : "before";
2121
+ return dy > rowHeight / 2 ? "after" : "before";
2451
2122
  }
2452
2123
  // return "over";
2453
2124
  }
@@ -2708,7 +2379,11 @@
2708
2379
  }
2709
2380
  this.lastAllowedDropRegions = regionSet;
2710
2381
  this.lastDropEffect = dt.dropEffect;
2382
+ const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
2711
2383
  targetNode.setClass("wb-drop-target");
2384
+ targetNode.setClass("wb-drop-over", region === "over");
2385
+ targetNode.setClass("wb-drop-before", region === "before");
2386
+ targetNode.setClass("wb-drop-after", region === "after");
2712
2387
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
2713
2388
  return false;
2714
2389
  // --- dragover ---
@@ -2734,199 +2409,539 @@
2734
2409
  if (!region || this._isVoidDrop(targetNode, srcNode, region)) {
2735
2410
  return; // We already rejected in dragenter
2736
2411
  }
2737
- targetNode.setClass("wb-drop-over", region === "over");
2738
- targetNode.setClass("wb-drop-before", region === "before");
2739
- targetNode.setClass("wb-drop-after", region === "after");
2740
- e.preventDefault(); // Allow drop (Drop operation is denied by default)
2741
- return false;
2742
- // --- dragleave ---
2412
+ targetNode.setClass("wb-drop-over", region === "over");
2413
+ targetNode.setClass("wb-drop-before", region === "before");
2414
+ targetNode.setClass("wb-drop-after", region === "after");
2415
+ e.preventDefault(); // Allow drop (Drop operation is denied by default)
2416
+ return false;
2417
+ // --- dragleave ---
2418
+ }
2419
+ else if (e.type === "dragleave") {
2420
+ // NOTE: we cannot trust this event, since it is always fired,
2421
+ // Instead we remove the marker on dragenter
2422
+ targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2423
+ // --- drop ---
2424
+ }
2425
+ else if (e.type === "drop") {
2426
+ e.stopPropagation(); // prevent browser from opening links?
2427
+ e.preventDefault(); // #69 prevent iOS browser from opening links
2428
+ this._leaveNode();
2429
+ const region = this.lastDropRegion;
2430
+ let nodeData = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData(nodeMimeType);
2431
+ nodeData = nodeData ? JSON.parse(nodeData) : null;
2432
+ const srcNode = this.srcNode;
2433
+ const lastDropEffect = this.lastDropEffect;
2434
+ setTimeout(() => {
2435
+ // Decouple this call, because drop actions may prevent the dragend event
2436
+ // from being fired on some browsers
2437
+ targetNode._callEvent("dnd.drop", {
2438
+ event: e,
2439
+ region: region,
2440
+ suggestedDropMode: region === "over" ? "appendChild" : region,
2441
+ suggestedDropEffect: lastDropEffect,
2442
+ // suggestedDropEffect: e.dataTransfer?.dropEffect,
2443
+ sourceNode: srcNode,
2444
+ sourceNodeData: nodeData,
2445
+ });
2446
+ }, 10);
2447
+ }
2448
+ return false;
2449
+ }
2450
+ }
2451
+
2452
+ /*!
2453
+ * Wunderbaum - drag_observer
2454
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2455
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2456
+ */
2457
+ /**
2458
+ * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
2459
+ */
2460
+ class DragObserver {
2461
+ constructor(opts) {
2462
+ this.start = {
2463
+ event: null,
2464
+ x: 0,
2465
+ y: 0,
2466
+ altKey: false,
2467
+ ctrlKey: false,
2468
+ metaKey: false,
2469
+ shiftKey: false,
2470
+ };
2471
+ this.dragElem = null;
2472
+ this.dragging = false;
2473
+ this.customData = {};
2474
+ // TODO: touch events
2475
+ this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2476
+ if (!opts.root) {
2477
+ throw new Error("Missing `root` option.");
2478
+ }
2479
+ this.opts = Object.assign({ thresh: 5 }, opts);
2480
+ this.root = opts.root;
2481
+ this._handler = this.handleEvent.bind(this);
2482
+ this.events.forEach((type) => {
2483
+ this.root.addEventListener(type, this._handler);
2484
+ });
2485
+ }
2486
+ /** Unregister all event listeners. */
2487
+ disconnect() {
2488
+ this.events.forEach((type) => {
2489
+ this.root.removeEventListener(type, this._handler);
2490
+ });
2491
+ }
2492
+ getDragElem() {
2493
+ return this.dragElem;
2494
+ }
2495
+ isDragging() {
2496
+ return this.dragging;
2497
+ }
2498
+ stopDrag(cb_event) {
2499
+ if (this.dragging && this.opts.dragstop && cb_event) {
2500
+ cb_event.type = "dragstop";
2501
+ try {
2502
+ this.opts.dragstop(cb_event);
2503
+ }
2504
+ catch (err) {
2505
+ console.error("dragstop error", err); // eslint-disable-line no-console
2506
+ }
2507
+ }
2508
+ this.dragElem = null;
2509
+ this.dragging = false;
2510
+ this.start.event = null;
2511
+ this.customData = {};
2512
+ }
2513
+ handleEvent(e) {
2514
+ const type = e.type;
2515
+ const opts = this.opts;
2516
+ const cb_event = {
2517
+ type: e.type,
2518
+ startEvent: type === "mousedown" ? e : this.start.event,
2519
+ event: e,
2520
+ customData: this.customData,
2521
+ dragElem: this.dragElem,
2522
+ dx: e.pageX - this.start.x,
2523
+ dy: e.pageY - this.start.y,
2524
+ apply: undefined,
2525
+ };
2526
+ // console.log("handleEvent", type, cb_event);
2527
+ switch (type) {
2528
+ case "keydown":
2529
+ this.stopDrag(cb_event);
2530
+ break;
2531
+ case "mousedown":
2532
+ if (this.dragElem) {
2533
+ this.stopDrag(cb_event);
2534
+ break;
2535
+ }
2536
+ if (opts.selector) {
2537
+ let elem = e.target;
2538
+ if (elem.matches(opts.selector)) {
2539
+ this.dragElem = elem;
2540
+ }
2541
+ else {
2542
+ elem = elem.closest(opts.selector);
2543
+ if (elem) {
2544
+ this.dragElem = elem;
2545
+ }
2546
+ else {
2547
+ break; // no event delegation selector matched
2548
+ }
2549
+ }
2550
+ }
2551
+ this.start.event = e;
2552
+ this.start.x = e.pageX;
2553
+ this.start.y = e.pageY;
2554
+ this.start.altKey = e.altKey;
2555
+ this.start.ctrlKey = e.ctrlKey;
2556
+ this.start.metaKey = e.metaKey;
2557
+ this.start.shiftKey = e.shiftKey;
2558
+ break;
2559
+ case "mousemove":
2560
+ // TODO: debounce/throttle?
2561
+ // TODO: horizontal mode: ignore if dx unchanged
2562
+ if (!this.dragElem) {
2563
+ break;
2564
+ }
2565
+ if (!this.dragging) {
2566
+ if (opts.thresh) {
2567
+ const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2568
+ if (dist2 < opts.thresh * opts.thresh) {
2569
+ break;
2570
+ }
2571
+ }
2572
+ cb_event.type = "dragstart";
2573
+ if (opts.dragstart(cb_event) === false) {
2574
+ this.stopDrag(cb_event);
2575
+ break;
2576
+ }
2577
+ this.dragging = true;
2578
+ }
2579
+ if (this.dragging && this.opts.drag) {
2580
+ cb_event.type = "drag";
2581
+ this.opts.drag(cb_event);
2582
+ }
2583
+ break;
2584
+ case "mouseup":
2585
+ if (!this.dragging) {
2586
+ this.stopDrag(cb_event);
2587
+ break;
2588
+ }
2589
+ if (e.button === 0) {
2590
+ cb_event.apply = true;
2591
+ }
2592
+ else {
2593
+ cb_event.apply = false;
2594
+ }
2595
+ this.stopDrag(cb_event);
2596
+ break;
2597
+ }
2598
+ }
2599
+ }
2600
+
2601
+ /*!
2602
+ * Wunderbaum - common
2603
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2604
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2605
+ */
2606
+ const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
2607
+ /**
2608
+ * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
2609
+ */
2610
+ const DEFAULT_ROW_HEIGHT = 22;
2611
+ /**
2612
+ * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
2613
+ */
2614
+ const ICON_WIDTH = 20;
2615
+ /**
2616
+ * Adjust the width of the title span, so overflow ellipsis work.
2617
+ * (2 x `$col-padding-x` + 3px rounding errors).
2618
+ */
2619
+ const TITLE_SPAN_PAD_Y = 7;
2620
+ /** Render row markup for N nodes above and below the visible viewport. */
2621
+ const RENDER_MAX_PREFETCH = 5;
2622
+ /** Minimum column width if not set otherwise. */
2623
+ const DEFAULT_MIN_COL_WIDTH = 4;
2624
+ /** Regular expression to detect if a string describes an image URL (in contrast
2625
+ * to a class name). Strings are considered image urls if they contain '.' or '/'.
2626
+ */
2627
+ const TEST_IMG = new RegExp(/\.|\//);
2628
+ // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2629
+ // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2630
+ /**
2631
+ * Default node icons.
2632
+ * Requires bootstrap icons https://icons.getbootstrap.com
2633
+ */
2634
+ const iconMaps = {
2635
+ bootstrap: {
2636
+ error: "bi bi-exclamation-triangle",
2637
+ // loading: "bi bi-hourglass-split wb-busy",
2638
+ loading: "bi bi-chevron-right wb-busy",
2639
+ // loading: "bi bi-arrow-repeat wb-spin",
2640
+ // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2641
+ // noData: "bi bi-search",
2642
+ noData: "bi bi-question-circle",
2643
+ expanderExpanded: "bi bi-chevron-down",
2644
+ // expanderExpanded: "bi bi-dash-square",
2645
+ expanderCollapsed: "bi bi-chevron-right",
2646
+ // expanderCollapsed: "bi bi-plus-square",
2647
+ expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2648
+ // expanderLazy: "bi bi-chevron-bar-right",
2649
+ checkChecked: "bi bi-check-square",
2650
+ checkUnchecked: "bi bi-square",
2651
+ checkUnknown: "bi bi-dash-square-dotted",
2652
+ radioChecked: "bi bi-circle-fill",
2653
+ radioUnchecked: "bi bi-circle",
2654
+ radioUnknown: "bi bi-record-circle",
2655
+ folder: "bi bi-folder2",
2656
+ folderOpen: "bi bi-folder2-open",
2657
+ folderLazy: "bi bi-folder-symlink",
2658
+ doc: "bi bi-file-earmark",
2659
+ colSortable: "bi bi-chevron-expand",
2660
+ // colSortable: "bi bi-arrow-down-up",
2661
+ // colSortAsc: "bi bi-chevron-down",
2662
+ // colSortDesc: "bi bi-chevron-up",
2663
+ colSortAsc: "bi bi-arrow-down",
2664
+ colSortDesc: "bi bi-arrow-up",
2665
+ colFilter: "bi bi-filter-circle",
2666
+ colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
2667
+ colMenu: "bi bi-three-dots-vertical",
2668
+ },
2669
+ fontawesome6: {
2670
+ error: "fa-solid fa-triangle-exclamation",
2671
+ loading: "fa-solid fa-chevron-right fa-beat",
2672
+ noData: "fa-solid fa-circle-question",
2673
+ expanderExpanded: "fa-solid fa-chevron-down",
2674
+ expanderCollapsed: "fa-solid fa-chevron-right",
2675
+ expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2676
+ checkChecked: "fa-regular fa-square-check",
2677
+ checkUnchecked: "fa-regular fa-square",
2678
+ checkUnknown: "fa-regular fa-square-minus",
2679
+ radioChecked: "fa-solid fa-circle",
2680
+ radioUnchecked: "fa-regular fa-circle",
2681
+ radioUnknown: "fa-regular fa-circle-question",
2682
+ folder: "fa-solid fa-folder-closed",
2683
+ folderOpen: "fa-regular fa-folder-open",
2684
+ folderLazy: "fa-solid fa-folder-plus",
2685
+ doc: "fa-regular fa-file",
2686
+ colSortable: "fa-solid fa-fw fa-sort",
2687
+ colSortAsc: "fa-solid fa-fw fa-sort-up",
2688
+ colSortDesc: "fa-solid fa-fw fa-sort-down",
2689
+ colFilter: "fa-solid fa-fw fa-filter",
2690
+ colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
2691
+ colMenu: "fa-solid fa-fw fa-ellipsis-v",
2692
+ },
2693
+ };
2694
+ /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2695
+ const RESERVED_TREE_SOURCE_KEYS = new Set([
2696
+ "_format", // reserved for future use
2697
+ "_keyMap", // Used for compressed data format
2698
+ "_positional", // Used for compressed data format
2699
+ "_typeList", // Used for compressed data format @deprecated
2700
+ "_valueMap", // Used for compressed data format
2701
+ "_version", // reserved for future use
2702
+ "children",
2703
+ "columns",
2704
+ "types",
2705
+ ]);
2706
+ // /** Key codes that trigger grid navigation, even when inside an input element. */
2707
+ // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2708
+ // // "ArrowDown",
2709
+ // // "ArrowUp",
2710
+ // "Enter",
2711
+ // "Escape",
2712
+ // ]);
2713
+ /** Map `KeyEvent.key` to navigation action. */
2714
+ const KEY_TO_ACTION_DICT = {
2715
+ " ": "toggleSelect",
2716
+ "+": "expand",
2717
+ Add: "expand",
2718
+ ArrowDown: "down",
2719
+ ArrowLeft: "left",
2720
+ ArrowRight: "right",
2721
+ ArrowUp: "up",
2722
+ Backspace: "parent",
2723
+ "/": "collapseAll",
2724
+ Divide: "collapseAll",
2725
+ End: "lastCol",
2726
+ Home: "firstCol",
2727
+ "Control+End": "last",
2728
+ "Control+Home": "first",
2729
+ "Meta+ArrowDown": "last", // macOs
2730
+ "Meta+ArrowUp": "first", // macOs
2731
+ "*": "expandAll",
2732
+ Multiply: "expandAll",
2733
+ PageDown: "pageDown",
2734
+ PageUp: "pageUp",
2735
+ "-": "collapse",
2736
+ Subtract: "collapse",
2737
+ };
2738
+ /** Return a callback that returns true if the node title matches the string
2739
+ * or regular expression.
2740
+ * @see {@link WunderbaumNode.findAll}
2741
+ */
2742
+ function makeNodeTitleMatcher(match) {
2743
+ if (match instanceof RegExp) {
2744
+ return function (node) {
2745
+ return match.test(node.title);
2746
+ };
2747
+ }
2748
+ assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2749
+ // s = escapeRegex(s.toLowerCase());
2750
+ return function (node) {
2751
+ return node.title === match;
2752
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2753
+ // return node.title.toLowerCase().indexOf(match) >= 0;
2754
+ };
2755
+ }
2756
+ /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2757
+ function makeNodeTitleStartMatcher(s) {
2758
+ s = escapeRegex(s);
2759
+ const reMatch = new RegExp("^" + s, "i");
2760
+ return function (node) {
2761
+ return reMatch.test(node.title);
2762
+ };
2763
+ }
2764
+ /** Compare two nodes by title (case-insensitive). */
2765
+ function nodeTitleSorter(a, b) {
2766
+ const x = a.title.toLowerCase();
2767
+ const y = b.title.toLowerCase();
2768
+ return x === y ? 0 : x > y ? 1 : -1;
2769
+ }
2770
+ /**
2771
+ * Convert 'flat' to 'nested' format.
2772
+ *
2773
+ * Flat node entry format:
2774
+ * [PARENT_ID, [POSITIONAL_ARGS]]
2775
+ * or
2776
+ * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2777
+ *
2778
+ * 1. Parent-referencing list is converted to a list of nested dicts with
2779
+ * optional `children` properties.
2780
+ * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
2781
+ */
2782
+ function unflattenSource(source) {
2783
+ var _a, _b, _c;
2784
+ const { _format, _keyMap = {}, _positional = [], children } = source;
2785
+ if (_format !== "flat") {
2786
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
2787
+ }
2788
+ if (_positional && _positional.includes("children")) {
2789
+ throw new Error(`source._positional must not include "children": ${_positional}`);
2790
+ }
2791
+ let longToShort = _keyMap;
2792
+ if (_keyMap.t) {
2793
+ // Inverse keyMap was used (pre 0.7.0)
2794
+ // TODO: raise Error on final 1.x release
2795
+ const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2796
+ console.warn(msg); // eslint-disable-line no-console
2797
+ longToShort = {};
2798
+ for (const [key, value] of Object.entries(_keyMap)) {
2799
+ longToShort[value] = key;
2800
+ }
2801
+ }
2802
+ const positionalShort = _positional.map((e) => longToShort[e]);
2803
+ const newChildren = [];
2804
+ const keyToNodeMap = {};
2805
+ const indexToNodeMap = {};
2806
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2807
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2808
+ for (const [index, nodeTuple] of children.entries()) {
2809
+ // Node entry format:
2810
+ // [PARENT_ID, [POSITIONAL_ARGS]]
2811
+ // or
2812
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2813
+ const [parentId, args, kwargs = {}] = nodeTuple;
2814
+ // Free up some memory as we go
2815
+ nodeTuple[1] = null;
2816
+ if (nodeTuple[2] != null) {
2817
+ nodeTuple[2] = null;
2818
+ }
2819
+ // console.log("flatten", parentId, args, kwargs)
2820
+ // We keep `kwargs` as our new node definition. Then we add all positional
2821
+ // values to this object:
2822
+ args.forEach((val, positionalIdx) => {
2823
+ kwargs[positionalShort[positionalIdx]] = val;
2824
+ });
2825
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2826
+ // index of the source.children list. If PARENT_ID is a string, we search
2827
+ // a parent with node.key of this value.
2828
+ indexToNodeMap[index] = kwargs;
2829
+ const key = kwargs[keyAttrName];
2830
+ if (key != null) {
2831
+ keyToNodeMap[key] = kwargs;
2832
+ }
2833
+ let parentNode = null;
2834
+ if (parentId === null) ;
2835
+ else if (typeof parentId === "number") {
2836
+ parentNode = indexToNodeMap[parentId];
2837
+ if (parentNode === undefined) {
2838
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2839
+ }
2840
+ }
2841
+ else {
2842
+ parentNode = keyToNodeMap[parentId];
2843
+ if (parentNode === undefined) {
2844
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2845
+ }
2743
2846
  }
2744
- else if (e.type === "dragleave") {
2745
- // NOTE: we cannot trust this event, since it is always fired,
2746
- // Instead we remove the marker on dragenter
2747
- targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2748
- // --- drop ---
2847
+ if (parentNode) {
2848
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2849
+ parentNode[childrenAttrName].push(kwargs);
2749
2850
  }
2750
- else if (e.type === "drop") {
2751
- e.stopPropagation(); // prevent browser from opening links?
2752
- e.preventDefault(); // #69 prevent iOS browser from opening links
2753
- this._leaveNode();
2754
- const region = this.lastDropRegion;
2755
- let nodeData = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData(nodeMimeType);
2756
- nodeData = nodeData ? JSON.parse(nodeData) : null;
2757
- const srcNode = this.srcNode;
2758
- const lastDropEffect = this.lastDropEffect;
2759
- setTimeout(() => {
2760
- // Decouple this call, because drop actions may prevent the dragend event
2761
- // from being fired on some browsers
2762
- targetNode._callEvent("dnd.drop", {
2763
- event: e,
2764
- region: region,
2765
- suggestedDropMode: region === "over" ? "appendChild" : region,
2766
- suggestedDropEffect: lastDropEffect,
2767
- // suggestedDropEffect: e.dataTransfer?.dropEffect,
2768
- sourceNode: srcNode,
2769
- sourceNodeData: nodeData,
2770
- });
2771
- }, 10);
2851
+ else {
2852
+ newChildren.push(kwargs);
2772
2853
  }
2773
- return false;
2774
2854
  }
2855
+ source.children = newChildren;
2775
2856
  }
2776
-
2777
- /*!
2778
- * Wunderbaum - drag_observer
2779
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2780
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2781
- */
2782
2857
  /**
2783
- * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
2858
+ * Decompresses the source data by
2859
+ * - converting from 'flat' to 'nested' format
2860
+ * - expanding short alias names to long names (if defined in _keyMap)
2861
+ * - resolving value indexes to value strings (if defined in _valueMap)
2862
+ *
2863
+ * @param source - The source object to be decompressed.
2864
+ * @returns void
2784
2865
  */
2785
- class DragObserver {
2786
- constructor(opts) {
2787
- this.start = {
2788
- event: null,
2789
- x: 0,
2790
- y: 0,
2791
- altKey: false,
2792
- ctrlKey: false,
2793
- metaKey: false,
2794
- shiftKey: false,
2795
- };
2796
- this.dragElem = null;
2797
- this.dragging = false;
2798
- this.customData = {};
2799
- // TODO: touch events
2800
- this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2801
- if (!opts.root) {
2802
- throw new Error("Missing `root` option.");
2866
+ function decompressSourceData(source) {
2867
+ let { _format, _version = 1, _keyMap, _valueMap } = source;
2868
+ assert(_version === 1, `Expected file version 1 instead of ${_version}`);
2869
+ let longToShort = _keyMap;
2870
+ let shortToLong = {};
2871
+ if (longToShort) {
2872
+ for (const [key, value] of Object.entries(longToShort)) {
2873
+ shortToLong[value] = key;
2803
2874
  }
2804
- this.opts = Object.assign({ thresh: 5 }, opts);
2805
- this.root = opts.root;
2806
- this._handler = this.handleEvent.bind(this);
2807
- this.events.forEach((type) => {
2808
- this.root.addEventListener(type, this._handler);
2809
- });
2810
- }
2811
- /** Unregister all event listeners. */
2812
- disconnect() {
2813
- this.events.forEach((type) => {
2814
- this.root.removeEventListener(type, this._handler);
2815
- });
2816
- }
2817
- getDragElem() {
2818
- return this.dragElem;
2819
2875
  }
2820
- isDragging() {
2821
- return this.dragging;
2876
+ // Fallback for old format (pre 0.7.0, using _keyMap in reverse direction)
2877
+ // TODO: raise Error on final 1.x release
2878
+ if (longToShort && longToShort.t) {
2879
+ const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2880
+ console.warn(msg); // eslint-disable-line no-console
2881
+ [longToShort, shortToLong] = [shortToLong, longToShort];
2822
2882
  }
2823
- stopDrag(cb_event) {
2824
- if (this.dragging && this.opts.dragstop && cb_event) {
2825
- cb_event.type = "dragstop";
2826
- try {
2827
- this.opts.dragstop(cb_event);
2828
- }
2829
- catch (err) {
2830
- console.error("dragstop error", err); // eslint-disable-line no-console
2831
- }
2883
+ // Fallback for old format (pre 0.7.0, using _typeList instead of _valueMap)
2884
+ // TODO: raise Error on final 1.x release
2885
+ if (source._typeList != null) {
2886
+ const msg = `source._typeList is deprecated since v0.7.0: use source._valueMap: {"type": [...]} instead.`;
2887
+ if (_valueMap != null) {
2888
+ throw new Error(msg);
2889
+ }
2890
+ else {
2891
+ console.warn(msg); // eslint-disable-line no-console
2892
+ _valueMap = { type: source._typeList };
2893
+ delete source._typeList;
2832
2894
  }
2833
- this.dragElem = null;
2834
- this.dragging = false;
2835
- this.start.event = null;
2836
- this.customData = {};
2837
2895
  }
2838
- handleEvent(e) {
2839
- const type = e.type;
2840
- const opts = this.opts;
2841
- const cb_event = {
2842
- type: e.type,
2843
- startEvent: type === "mousedown" ? e : this.start.event,
2844
- event: e,
2845
- customData: this.customData,
2846
- dragElem: this.dragElem,
2847
- dx: e.pageX - this.start.x,
2848
- dy: e.pageY - this.start.y,
2849
- apply: undefined,
2850
- };
2851
- // console.log("handleEvent", type, cb_event);
2852
- switch (type) {
2853
- case "keydown":
2854
- this.stopDrag(cb_event);
2855
- break;
2856
- case "mousedown":
2857
- if (this.dragElem) {
2858
- this.stopDrag(cb_event);
2859
- break;
2860
- }
2861
- if (opts.selector) {
2862
- let elem = e.target;
2863
- if (elem.matches(opts.selector)) {
2864
- this.dragElem = elem;
2865
- }
2866
- else {
2867
- elem = elem.closest(opts.selector);
2868
- if (elem) {
2869
- this.dragElem = elem;
2870
- }
2871
- else {
2872
- break; // no event delegation selector matched
2873
- }
2896
+ if (_format === "flat") {
2897
+ unflattenSource(source);
2898
+ }
2899
+ delete source._format;
2900
+ delete source._version;
2901
+ delete source._keyMap;
2902
+ delete source._valueMap;
2903
+ delete source._positional;
2904
+ function _iter(childList) {
2905
+ for (const node of childList) {
2906
+ // Iterate over a list of names, because we modify inside the loop
2907
+ // (for ... of ... does not allow this)
2908
+ Object.getOwnPropertyNames(node).forEach((propName) => {
2909
+ const value = node[propName];
2910
+ // Replace short names with long names if defined in _keyMap
2911
+ let longName = propName;
2912
+ if (_keyMap && shortToLong[propName] != null) {
2913
+ longName = shortToLong[propName];
2914
+ if (longName !== propName) {
2915
+ node[longName] = value;
2916
+ delete node[propName];
2874
2917
  }
2875
2918
  }
2876
- this.start.event = e;
2877
- this.start.x = e.pageX;
2878
- this.start.y = e.pageY;
2879
- this.start.altKey = e.altKey;
2880
- this.start.ctrlKey = e.ctrlKey;
2881
- this.start.metaKey = e.metaKey;
2882
- this.start.shiftKey = e.shiftKey;
2883
- break;
2884
- case "mousemove":
2885
- // TODO: debounce/throttle?
2886
- // TODO: horizontal mode: ignore if dx unchanged
2887
- if (!this.dragElem) {
2888
- break;
2889
- }
2890
- if (!this.dragging) {
2891
- if (opts.thresh) {
2892
- const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2893
- if (dist2 < opts.thresh * opts.thresh) {
2894
- break;
2895
- }
2896
- }
2897
- cb_event.type = "dragstart";
2898
- if (opts.dragstart(cb_event) === false) {
2899
- this.stopDrag(cb_event);
2900
- break;
2919
+ // Replace type index with type name if defined in _valueMap
2920
+ if (_valueMap &&
2921
+ typeof value === "number" &&
2922
+ _valueMap[longName] != null) {
2923
+ const newValue = _valueMap[longName][value];
2924
+ if (newValue == null) {
2925
+ throw new Error(`Expected valueMap[${longName}][${value}] entry in [${_valueMap[longName]}]`);
2901
2926
  }
2902
- this.dragging = true;
2903
- }
2904
- if (this.dragging && this.opts.drag) {
2905
- cb_event.type = "drag";
2906
- this.opts.drag(cb_event);
2907
- }
2908
- break;
2909
- case "mouseup":
2910
- if (!this.dragging) {
2911
- this.stopDrag(cb_event);
2912
- break;
2913
- }
2914
- if (e.button === 0) {
2915
- cb_event.apply = true;
2916
- }
2917
- else {
2918
- cb_event.apply = false;
2927
+ node[longName] = newValue;
2919
2928
  }
2920
- this.stopDrag(cb_event);
2921
- break;
2929
+ });
2930
+ // Recursion
2931
+ if (node.children) {
2932
+ _iter(node.children);
2933
+ }
2922
2934
  }
2923
2935
  }
2936
+ if (_keyMap || _valueMap) {
2937
+ _iter(source.children);
2938
+ }
2924
2939
  }
2925
2940
 
2926
2941
  /*!
2927
2942
  * Wunderbaum - ext-grid
2928
2943
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2929
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
2944
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2930
2945
  */
2931
2946
  class GridExtension extends WunderbaumExtension {
2932
2947
  constructor(tree) {
@@ -3017,7 +3032,7 @@
3017
3032
  /*!
3018
3033
  * Wunderbaum - deferred
3019
3034
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3020
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
3035
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3021
3036
  */
3022
3037
  /**
3023
3038
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -3070,7 +3085,7 @@
3070
3085
  /*!
3071
3086
  * Wunderbaum - wunderbaum_node
3072
3087
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3073
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
3088
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3074
3089
  */
3075
3090
  /** WunderbaumNode properties that can be passed with source data.
3076
3091
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3098,6 +3113,20 @@
3098
3113
  const NODE_DICT_PROPS = new Set(NODE_PROPS);
3099
3114
  NODE_DICT_PROPS.delete("_partsel");
3100
3115
  NODE_DICT_PROPS.delete("unselectable");
3116
+ // /** Node properties that are of type bool (or boolean & string).
3117
+ // * When parsing, we accept 0 for false and 1 for true for better JSON compression.
3118
+ // */
3119
+ // export const NODE_BOOL_PROPS: Set<string> = new Set([
3120
+ // "checkbox",
3121
+ // "colspan",
3122
+ // "expanded",
3123
+ // "icon",
3124
+ // "iconTooltip",
3125
+ // "radiogroup",
3126
+ // "selected",
3127
+ // "tooltip",
3128
+ // "unselectable",
3129
+ // ]);
3101
3130
  /**
3102
3131
  * A single tree node.
3103
3132
  *
@@ -3139,20 +3168,26 @@
3139
3168
  this.parent = parent;
3140
3169
  this.key = "" + ((_a = data.key) !== null && _a !== void 0 ? _a : ++WunderbaumNode.sequence);
3141
3170
  this.title = "" + ((_b = data.title) !== null && _b !== void 0 ? _b : "<" + this.key + ">");
3171
+ this.expanded = !!data.expanded;
3172
+ this.lazy = !!data.lazy;
3173
+ // We set the following node properties only if a matching data value is
3174
+ // passed
3142
3175
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
3143
3176
  data.type != null ? (this.type = "" + data.type) : 0;
3144
- this.expanded = data.expanded === true;
3145
- data.icon != null ? (this.icon = data.icon) : 0;
3146
- this.lazy = data.lazy === true;
3177
+ data.icon != null ? (this.icon = intToBool(data.icon)) : 0;
3178
+ data.tooltip != null ? (this.tooltip = intToBool(data.tooltip)) : 0;
3179
+ data.iconTooltip != null
3180
+ ? (this.iconTooltip = intToBool(data.iconTooltip))
3181
+ : 0;
3147
3182
  data.statusNodeType != null
3148
3183
  ? (this.statusNodeType = ("" + data.statusNodeType))
3149
3184
  : 0;
3150
3185
  data.colspan != null ? (this.colspan = !!data.colspan) : 0;
3151
3186
  // Selection
3152
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
3187
+ data.checkbox != null ? intToBool(data.checkbox) : 0;
3153
3188
  data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
3154
- this.selected = data.selected === true;
3155
- data.unselectable === true ? (this.unselectable = true) : 0;
3189
+ data.selected != null ? (this.selected = !!data.selected) : 0;
3190
+ data.unselectable != null ? (this.unselectable = !!data.unselectable) : 0;
3156
3191
  if (data.classes) {
3157
3192
  this.setClass(data.classes);
3158
3193
  }
@@ -3981,8 +4016,8 @@
3981
4016
  let elap = 0, elapLoad = 0, elapProcess = 0;
3982
4017
  // Check for overlapping requests
3983
4018
  if (this._requestId) {
3984
- this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending.`);
3985
- // node.debug("Send load request #" + requestId);
4019
+ this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending. ` +
4020
+ "The previous request will be ignored.");
3986
4021
  }
3987
4022
  this._requestId = requestId;
3988
4023
  // const timerLabel = tree.logTime(this + ".load()");
@@ -4462,6 +4497,7 @@
4462
4497
  _render_markup(opts) {
4463
4498
  const tree = this.tree;
4464
4499
  const treeOptions = tree.options;
4500
+ const rowHeight = treeOptions.rowHeightPx;
4465
4501
  const checkbox = this.getOption("checkbox");
4466
4502
  const columns = tree.columns;
4467
4503
  const level = this.getLevel();
@@ -4476,7 +4512,7 @@
4476
4512
  assert(!this.isRootNode(), "Root node not allowed");
4477
4513
  rowDiv = document.createElement("div");
4478
4514
  rowDiv.classList.add("wb-row");
4479
- rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
4515
+ rowDiv.style.top = this._rowIdx * rowHeight + "px";
4480
4516
  this._rowElem = rowDiv;
4481
4517
  // Attach a node reference to the DOM Element:
4482
4518
  rowDiv._wb_node = this;
@@ -5438,6 +5474,21 @@
5438
5474
  av = a.data[propName];
5439
5475
  bv = b.data[propName];
5440
5476
  }
5477
+ if (av == null && bv == null) {
5478
+ return 0;
5479
+ }
5480
+ if (av == null) {
5481
+ av = typeof bv === "string" ? "" : 0;
5482
+ }
5483
+ else if (typeof av === "boolean") {
5484
+ av = av ? 1 : 0;
5485
+ }
5486
+ if (bv == null) {
5487
+ bv = typeof av === "string" ? "" : 0;
5488
+ }
5489
+ else if (typeof bv === "boolean") {
5490
+ bv = bv ? 1 : 0;
5491
+ }
5441
5492
  if (caseInsensitive) {
5442
5493
  if (typeof av === "string") {
5443
5494
  av = av.toLowerCase();
@@ -5561,7 +5612,7 @@
5561
5612
  /*!
5562
5613
  * Wunderbaum - ext-edit
5563
5614
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
5564
- * v0.11.0, Sun, 04 Aug 2024 15:35:53 GMT (https://github.com/mar10/wunderbaum)
5615
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
5565
5616
  */
5566
5617
  // const START_MARKER = "\uFFF7";
5567
5618
  class EditExtension extends WunderbaumExtension {
@@ -5725,6 +5776,10 @@
5725
5776
  if (!node) {
5726
5777
  return;
5727
5778
  }
5779
+ if (node.isStatusNode()) {
5780
+ node.logWarn("Cannot edit status node.");
5781
+ return;
5782
+ }
5728
5783
  this.tree.logDebug(`startEditTitle(node=${node})`);
5729
5784
  let inputHtml = node._callEvent("edit.beforeEdit");
5730
5785
  if (inputHtml === false) {
@@ -5892,8 +5947,8 @@
5892
5947
  * https://github.com/mar10/wunderbaum
5893
5948
  *
5894
5949
  * Released under the MIT license.
5895
- * @version v0.11.0
5896
- * @date Sun, 04 Aug 2024 15:35:53 GMT
5950
+ * @version v0.11.1
5951
+ * @date Fri, 27 Dec 2024 22:58:06 GMT
5897
5952
  */
5898
5953
  // import "./wunderbaum.scss";
5899
5954
  class WbSystemRoot extends WunderbaumNode {
@@ -5975,7 +6030,7 @@
5975
6030
  debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
5976
6031
  header: null, // Show/hide header (pass bool or string)
5977
6032
  // headerHeightPx: ROW_HEIGHT,
5978
- rowHeightPx: ROW_HEIGHT,
6033
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
5979
6034
  iconMap: "bootstrap",
5980
6035
  columns: null,
5981
6036
  types: null,
@@ -6060,6 +6115,10 @@
6060
6115
  if (!this.element.getAttribute("tabindex")) {
6061
6116
  this.element.tabIndex = 0;
6062
6117
  }
6118
+ if (opts.rowHeightPx !== DEFAULT_ROW_HEIGHT) {
6119
+ this.element.style.setProperty("--wb-row-outer-height", opts.rowHeightPx + "px");
6120
+ this.element.style.setProperty("--wb-row-inner-height", opts.rowHeightPx - 2 + "px");
6121
+ }
6063
6122
  // Attach tree instance to <div>
6064
6123
  this.element._wb_tree = this;
6065
6124
  // Create header markup, or take it from the existing html
@@ -6469,31 +6528,33 @@
6469
6528
  }
6470
6529
  /** Return the topmost visible node in the viewport. */
6471
6530
  getTopmostVpNode(complete = true) {
6531
+ const rowHeight = this.options.rowHeightPx;
6472
6532
  const gracePx = 1; // ignore subpixel scrolling
6473
6533
  const scrollParent = this.element;
6474
6534
  // const headerHeight = this.headerElement.clientHeight; // May be 0
6475
6535
  const scrollTop = scrollParent.scrollTop; // + headerHeight;
6476
6536
  let topIdx;
6477
6537
  if (complete) {
6478
- topIdx = Math.ceil((scrollTop - gracePx) / ROW_HEIGHT);
6538
+ topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
6479
6539
  }
6480
6540
  else {
6481
- topIdx = Math.floor(scrollTop / ROW_HEIGHT);
6541
+ topIdx = Math.floor(scrollTop / rowHeight);
6482
6542
  }
6483
6543
  return this._getNodeByRowIdx(topIdx);
6484
6544
  }
6485
6545
  /** Return the lowest visible node in the viewport. */
6486
6546
  getLowestVpNode(complete = true) {
6547
+ const rowHeight = this.options.rowHeightPx;
6487
6548
  const scrollParent = this.element;
6488
6549
  const headerHeight = this.headerElement.clientHeight; // May be 0
6489
6550
  const scrollTop = scrollParent.scrollTop;
6490
6551
  const clientHeight = scrollParent.clientHeight - headerHeight;
6491
6552
  let bottomIdx;
6492
6553
  if (complete) {
6493
- bottomIdx = Math.floor((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6554
+ bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
6494
6555
  }
6495
6556
  else {
6496
- bottomIdx = Math.ceil((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6557
+ bottomIdx = Math.ceil((scrollTop + clientHeight) / rowHeight) - 1;
6497
6558
  }
6498
6559
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
6499
6560
  return this._getNodeByRowIdx(bottomIdx);
@@ -6925,8 +6986,9 @@
6925
6986
  * @param includeHidden Not yet implemented
6926
6987
  */
6927
6988
  findRelatedNode(node, where, includeHidden = false) {
6989
+ const rowHeight = this.options.rowHeightPx;
6928
6990
  let res = null;
6929
- const pageSize = Math.floor(this.listContainerElement.clientHeight / ROW_HEIGHT);
6991
+ const pageSize = Math.floor(this.listContainerElement.clientHeight / rowHeight);
6930
6992
  switch (where) {
6931
6993
  case "parent":
6932
6994
  if (node.parent && node.parent.parent) {
@@ -7249,6 +7311,7 @@
7249
7311
  scrollTo(nodeOrOpts) {
7250
7312
  const PADDING = 2; // leave some pixels between viewport bounds
7251
7313
  let node;
7314
+ // WunderbaumNode;
7252
7315
  let options;
7253
7316
  if (nodeOrOpts instanceof WunderbaumNode) {
7254
7317
  node = nodeOrOpts;
@@ -7258,14 +7321,15 @@
7258
7321
  node = options.node;
7259
7322
  }
7260
7323
  assert(node && node._rowIdx != null, `Invalid node: ${node}`);
7324
+ const rowHeight = this.options.rowHeightPx;
7261
7325
  const scrollParent = this.element;
7262
7326
  const headerHeight = this.headerElement.clientHeight; // May be 0
7263
7327
  const scrollTop = scrollParent.scrollTop;
7264
7328
  const vpHeight = scrollParent.clientHeight;
7265
- const rowTop = node._rowIdx * ROW_HEIGHT + headerHeight;
7329
+ const rowTop = node._rowIdx * rowHeight + headerHeight;
7266
7330
  const vpTop = headerHeight;
7267
7331
  const vpRowTop = rowTop - scrollTop;
7268
- const vpRowBottom = vpRowTop + ROW_HEIGHT;
7332
+ const vpRowBottom = vpRowTop + rowHeight;
7269
7333
  const topNode = options === null || options === void 0 ? void 0 : options.topNode;
7270
7334
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts , options);
7271
7335
  let newScrollTop = null;
@@ -7274,7 +7338,7 @@
7274
7338
  else {
7275
7339
  // Node is below viewport
7276
7340
  // this.log("Below viewport");
7277
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
7341
+ newScrollTop = rowTop + rowHeight - vpHeight + PADDING; // leave some pixels between viewport bounds
7278
7342
  }
7279
7343
  }
7280
7344
  else {
@@ -7887,19 +7951,19 @@
7887
7951
  // this.log("_updateRows", opts)
7888
7952
  options = Object.assign({ newNodesOnly: false }, options);
7889
7953
  const newNodesOnly = !!options.newNodesOnly;
7890
- const row_height = ROW_HEIGHT;
7891
- const vp_height = this.element.clientHeight;
7954
+ const rowHeight = this.options.rowHeightPx;
7955
+ const vpHeight = this.element.clientHeight;
7892
7956
  const prefetch = RENDER_MAX_PREFETCH;
7893
7957
  // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
7894
7958
  const ofs = this.element.scrollTop;
7895
- let startIdx = Math.max(0, ofs / row_height - prefetch);
7959
+ let startIdx = Math.max(0, ofs / rowHeight - prefetch);
7896
7960
  startIdx = Math.floor(startIdx);
7897
7961
  // Make sure start is always even, so the alternating row colors don't
7898
7962
  // change when scrolling:
7899
7963
  if (startIdx % 2) {
7900
7964
  startIdx--;
7901
7965
  }
7902
- let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7966
+ let endIdx = Math.max(0, (ofs + vpHeight) / rowHeight + prefetch);
7903
7967
  endIdx = Math.ceil(endIdx);
7904
7968
  // this.debug("render", opts);
7905
7969
  const obsoleteNodes = new Set();
@@ -7928,21 +7992,21 @@
7928
7992
  else if (rowDiv && newNodesOnly) {
7929
7993
  obsoleteNodes.delete(node);
7930
7994
  // no need to update existing node markup
7931
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
7995
+ rowDiv.style.top = idx * rowHeight + "px";
7932
7996
  prevElem = rowDiv;
7933
7997
  }
7934
7998
  else {
7935
7999
  obsoleteNodes.delete(node);
7936
8000
  // Create new markup
7937
8001
  if (rowDiv) {
7938
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
8002
+ rowDiv.style.top = idx * rowHeight + "px";
7939
8003
  }
7940
8004
  node._render({ top: top, after: prevElem });
7941
8005
  // node.log("render", top, prevElem, "=>", node._rowElem);
7942
8006
  prevElem = node._rowElem;
7943
8007
  }
7944
8008
  idx++;
7945
- top += row_height;
8009
+ top += rowHeight;
7946
8010
  });
7947
8011
  this.treeRowCount = idx;
7948
8012
  for (const n of obsoleteNodes) {
@@ -8206,7 +8270,7 @@
8206
8270
  }
8207
8271
  Wunderbaum.sequence = 0;
8208
8272
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
8209
- Wunderbaum.version = "v0.11.0"; // Set to semver by 'grunt release'
8273
+ Wunderbaum.version = "v0.11.1"; // Set to semver by 'grunt release'
8210
8274
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
8211
8275
  Wunderbaum.util = util;
8212
8276