wunderbaum 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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 ---
@@ -2741,186 +2416,526 @@ class DndExtension extends WunderbaumExtension {
2741
2416
  targetNode._callEvent("dnd.dragLeave", { event: e, sourceNode: srcNode });
2742
2417
  // --- drop ---
2743
2418
  }
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);
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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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
+ }
2840
+ }
2841
+ if (parentNode) {
2842
+ (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2843
+ parentNode[childrenAttrName].push(kwargs);
2844
+ }
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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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.12.0, Sun, 12 Jan 2025 10:51:41 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
  }
@@ -3376,54 +3411,65 @@ class WunderbaumNode {
3376
3411
  startEditTitle() {
3377
3412
  this.tree._callMethod("edit.startEditTitle", this);
3378
3413
  }
3379
- /** Call `setExpanded()` on all descendant nodes. */
3414
+ /**
3415
+ * Call `setExpanded()` on all descendant nodes.
3416
+ *
3417
+ * @param flag true to expand, false to collapse.
3418
+ * @param options Additional options.
3419
+ * @see {@link Wunderbaum.expandAll}
3420
+ * @see {@link WunderbaumNode.setExpanded}
3421
+ */
3380
3422
  async expandAll(flag = true, options) {
3381
3423
  const tree = this.tree;
3382
- const minExpandLevel = this.tree.options.minExpandLevel;
3383
- const { depth = 99, loadLazy, force, keepActiveNodeVisible = true, } = options !== null && options !== void 0 ? options : {};
3424
+ const { collapseOthers, deep, depth, force, keepActiveNodeVisible = true, loadLazy, resetLazy, } = options !== null && options !== void 0 ? options : {};
3425
+ // limit expansion level to `depth` (or tree.minExpandLevel). Default: unlimited
3426
+ const treeLevel = this.tree.options.minExpandLevel || null; // 0 -> null
3427
+ const minLevel = depth !== null && depth !== void 0 ? depth : (force ? null : treeLevel);
3384
3428
  const expandOpts = {
3385
- scrollIntoView: false, // don't scroll very node on iteration
3429
+ deep: deep,
3386
3430
  force: force,
3387
3431
  loadLazy: loadLazy,
3432
+ resetLazy: resetLazy,
3433
+ scrollIntoView: false, // don't scroll every node while iterating
3388
3434
  };
3389
- // this.logInfo(`expandAll(${flag})`);
3435
+ this.logInfo(`expandAll(${flag}, depth=${depth}, minLevel=${minLevel})`);
3436
+ assert(!(flag && deep != null && !collapseOthers), "Expanding with `deep` option is not supported (implied by the `depth` option).");
3390
3437
  // Expand all direct children in parallel:
3391
3438
  async function _iter(n, level) {
3392
3439
  var _a;
3393
- // n.logInfo(` _iter(${level})`);
3394
- if (level === 0) {
3395
- return;
3396
- }
3397
- // if (!flag && minExpandLevel && !force && n.getLevel() <= minExpandLevel) {
3398
- // return; // Do not collapse until minExpandLevel
3399
- // }
3400
- const level_1 = level == null ? null : level - 1;
3440
+ // n.logInfo(` _iter(level=${level})`);
3401
3441
  const promises = [];
3402
3442
  (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach((cn) => {
3403
3443
  if (flag) {
3404
- if (!cn.expanded && (cn.children || (loadLazy && cn.lazy))) {
3444
+ if (!cn.expanded &&
3445
+ (minLevel == null || level < minLevel) &&
3446
+ (cn.children || (loadLazy && cn.lazy))) {
3405
3447
  // Node is collapsed and may be expanded (i.e. has children or is lazy)
3406
3448
  // Expanding may be async, so we store the promise.
3407
3449
  // Also the recursion is delayed until expansion finished.
3408
3450
  const p = cn.setExpanded(true, expandOpts);
3409
3451
  promises.push(p);
3410
- p.then(async () => {
3411
- await _iter(cn, level_1);
3412
- });
3452
+ if (depth == null) {
3453
+ p.then(async () => {
3454
+ await _iter(cn, level + 1);
3455
+ });
3456
+ }
3413
3457
  }
3414
3458
  else {
3415
3459
  // We don't expand the node, but still visit descendants.
3416
3460
  // There we may find lazy nodes, so we
3417
- promises.push(_iter(cn, level_1));
3461
+ promises.push(_iter(cn, level + 1));
3418
3462
  }
3419
3463
  }
3420
3464
  else {
3421
3465
  // Collapsing is always synchronous, so no promises required
3422
- if (!minExpandLevel || force || cn.getLevel() > minExpandLevel) {
3423
- // Do not collapse until minExpandLevel
3466
+ // Do not collapse until minExpandLevel
3467
+ if (minLevel == null || level >= minLevel) {
3424
3468
  cn.setExpanded(false, expandOpts);
3425
3469
  }
3426
- _iter(cn, level_1); // recursion, even if cn was already collapsed
3470
+ if ((minLevel != null && level < minLevel) || deep) {
3471
+ _iter(cn, level + 1); // recursion, even if cn was already collapsed
3472
+ }
3427
3473
  }
3428
3474
  });
3429
3475
  return new Promise((resolve) => {
@@ -3432,10 +3478,15 @@ class WunderbaumNode {
3432
3478
  });
3433
3479
  });
3434
3480
  }
3435
- const tag = tree.logTime(`${this}.expandAll(${flag})`);
3481
+ const tag = tree.logTime(`${this}.expandAll(${flag}, depth=${depth})`);
3436
3482
  try {
3437
3483
  tree.enableUpdate(false);
3438
- await _iter(this, depth);
3484
+ await _iter(this, 0);
3485
+ if (collapseOthers) {
3486
+ assert(flag, "Option `collapseOthers` requires flag=true");
3487
+ assert(minLevel != null, "Option `collapseOthers` requires `depth` or `minExpandLevel`");
3488
+ this.expandAll(false, { depth: minLevel });
3489
+ }
3439
3490
  }
3440
3491
  finally {
3441
3492
  tree.enableUpdate(true);
@@ -3975,8 +4026,8 @@ class WunderbaumNode {
3975
4026
  let elap = 0, elapLoad = 0, elapProcess = 0;
3976
4027
  // Check for overlapping requests
3977
4028
  if (this._requestId) {
3978
- this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending.`);
3979
- // node.debug("Send load request #" + requestId);
4029
+ this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending. ` +
4030
+ "The previous request will be ignored.");
3980
4031
  }
3981
4032
  this._requestId = requestId;
3982
4033
  // const timerLabel = tree.logTime(this + ".load()");
@@ -4456,6 +4507,7 @@ class WunderbaumNode {
4456
4507
  _render_markup(opts) {
4457
4508
  const tree = this.tree;
4458
4509
  const treeOptions = tree.options;
4510
+ const rowHeight = treeOptions.rowHeightPx;
4459
4511
  const checkbox = this.getOption("checkbox");
4460
4512
  const columns = tree.columns;
4461
4513
  const level = this.getLevel();
@@ -4470,7 +4522,7 @@ class WunderbaumNode {
4470
4522
  assert(!this.isRootNode(), "Root node not allowed");
4471
4523
  rowDiv = document.createElement("div");
4472
4524
  rowDiv.classList.add("wb-row");
4473
- rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
4525
+ rowDiv.style.top = this._rowIdx * rowHeight + "px";
4474
4526
  this._rowElem = rowDiv;
4475
4527
  // Attach a node reference to the DOM Element:
4476
4528
  rowDiv._wb_node = this;
@@ -4923,7 +4975,7 @@ class WunderbaumNode {
4923
4975
  const orgEvent = options === null || options === void 0 ? void 0 : options.event; // Default: null
4924
4976
  const colIdx = options === null || options === void 0 ? void 0 : options.colIdx; // Default: null
4925
4977
  const edit = options === null || options === void 0 ? void 0 : options.edit; // Default: false
4926
- assert(!colIdx || tree.isCellNav(), "colIdx requires cellNav");
4978
+ // util.assert(!colIdx || tree.isCellNav(), "colIdx requires cellNav");
4927
4979
  assert(!edit || colIdx != null, "edit requires colIdx");
4928
4980
  if (!noEvents) {
4929
4981
  if (flag) {
@@ -4977,7 +5029,7 @@ class WunderbaumNode {
4977
5029
  * Expand or collapse this node.
4978
5030
  */
4979
5031
  async setExpanded(flag = true, options) {
4980
- const { force, scrollIntoView, immediate } = options !== null && options !== void 0 ? options : {};
5032
+ const { force, scrollIntoView, immediate, resetLazy } = options !== null && options !== void 0 ? options : {};
4981
5033
  const sendEvents = !(options === null || options === void 0 ? void 0 : options.noEvents); // Default: send events
4982
5034
  if (!flag &&
4983
5035
  this.isExpanded() &&
@@ -5000,6 +5052,9 @@ class WunderbaumNode {
5000
5052
  if (flag && this.lazy && this.children == null) {
5001
5053
  await this.loadLazy();
5002
5054
  }
5055
+ else if (!flag && resetLazy && this.lazy && this.children) {
5056
+ this.resetLazy();
5057
+ }
5003
5058
  this.expanded = flag;
5004
5059
  const updateOpts = { immediate: immediate };
5005
5060
  // const updateOpts = { immediate: !!util.getOption(options, "immediate") };
@@ -5432,6 +5487,21 @@ class WunderbaumNode {
5432
5487
  av = a.data[propName];
5433
5488
  bv = b.data[propName];
5434
5489
  }
5490
+ if (av == null && bv == null) {
5491
+ return 0;
5492
+ }
5493
+ if (av == null) {
5494
+ av = typeof bv === "string" ? "" : 0;
5495
+ }
5496
+ else if (typeof av === "boolean") {
5497
+ av = av ? 1 : 0;
5498
+ }
5499
+ if (bv == null) {
5500
+ bv = typeof av === "string" ? "" : 0;
5501
+ }
5502
+ else if (typeof bv === "boolean") {
5503
+ bv = bv ? 1 : 0;
5504
+ }
5435
5505
  if (caseInsensitive) {
5436
5506
  if (typeof av === "string") {
5437
5507
  av = av.toLowerCase();
@@ -5555,7 +5625,7 @@ WunderbaumNode.sequence = 0;
5555
5625
  /*!
5556
5626
  * Wunderbaum - ext-edit
5557
5627
  * 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)
5628
+ * v0.12.0, Sun, 12 Jan 2025 10:51:41 GMT (https://github.com/mar10/wunderbaum)
5559
5629
  */
5560
5630
  // const START_MARKER = "\uFFF7";
5561
5631
  class EditExtension extends WunderbaumExtension {
@@ -5719,6 +5789,10 @@ class EditExtension extends WunderbaumExtension {
5719
5789
  if (!node) {
5720
5790
  return;
5721
5791
  }
5792
+ if (node.isStatusNode()) {
5793
+ node.logWarn("Cannot edit status node.");
5794
+ return;
5795
+ }
5722
5796
  this.tree.logDebug(`startEditTitle(node=${node})`);
5723
5797
  let inputHtml = node._callEvent("edit.beforeEdit");
5724
5798
  if (inputHtml === false) {
@@ -5886,8 +5960,8 @@ class EditExtension extends WunderbaumExtension {
5886
5960
  * https://github.com/mar10/wunderbaum
5887
5961
  *
5888
5962
  * Released under the MIT license.
5889
- * @version v0.11.0
5890
- * @date Sun, 04 Aug 2024 15:35:53 GMT
5963
+ * @version v0.12.0
5964
+ * @date Sun, 12 Jan 2025 10:51:41 GMT
5891
5965
  */
5892
5966
  // import "./wunderbaum.scss";
5893
5967
  class WbSystemRoot extends WunderbaumNode {
@@ -5969,7 +6043,7 @@ class Wunderbaum {
5969
6043
  debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
5970
6044
  header: null, // Show/hide header (pass bool or string)
5971
6045
  // headerHeightPx: ROW_HEIGHT,
5972
- rowHeightPx: ROW_HEIGHT,
6046
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
5973
6047
  iconMap: "bootstrap",
5974
6048
  columns: null,
5975
6049
  types: null,
@@ -6054,6 +6128,10 @@ class Wunderbaum {
6054
6128
  if (!this.element.getAttribute("tabindex")) {
6055
6129
  this.element.tabIndex = 0;
6056
6130
  }
6131
+ if (opts.rowHeightPx !== DEFAULT_ROW_HEIGHT) {
6132
+ this.element.style.setProperty("--wb-row-outer-height", opts.rowHeightPx + "px");
6133
+ this.element.style.setProperty("--wb-row-inner-height", opts.rowHeightPx - 2 + "px");
6134
+ }
6057
6135
  // Attach tree instance to <div>
6058
6136
  this.element._wb_tree = this;
6059
6137
  // Create header markup, or take it from the existing html
@@ -6216,7 +6294,10 @@ class Wunderbaum {
6216
6294
  false) {
6217
6295
  return false;
6218
6296
  }
6219
- if (node && info.colIdx === 0 && node.isExpandable()) {
6297
+ if (node &&
6298
+ info.colIdx === 0 &&
6299
+ node.isExpandable() &&
6300
+ info.region !== NodeRegion.expander) {
6220
6301
  this._callMethod("edit._stopEditTitle");
6221
6302
  node.setExpanded(!node.isExpanded());
6222
6303
  }
@@ -6463,31 +6544,33 @@ class Wunderbaum {
6463
6544
  }
6464
6545
  /** Return the topmost visible node in the viewport. */
6465
6546
  getTopmostVpNode(complete = true) {
6547
+ const rowHeight = this.options.rowHeightPx;
6466
6548
  const gracePx = 1; // ignore subpixel scrolling
6467
6549
  const scrollParent = this.element;
6468
6550
  // const headerHeight = this.headerElement.clientHeight; // May be 0
6469
6551
  const scrollTop = scrollParent.scrollTop; // + headerHeight;
6470
6552
  let topIdx;
6471
6553
  if (complete) {
6472
- topIdx = Math.ceil((scrollTop - gracePx) / ROW_HEIGHT);
6554
+ topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
6473
6555
  }
6474
6556
  else {
6475
- topIdx = Math.floor(scrollTop / ROW_HEIGHT);
6557
+ topIdx = Math.floor(scrollTop / rowHeight);
6476
6558
  }
6477
6559
  return this._getNodeByRowIdx(topIdx);
6478
6560
  }
6479
6561
  /** Return the lowest visible node in the viewport. */
6480
6562
  getLowestVpNode(complete = true) {
6563
+ const rowHeight = this.options.rowHeightPx;
6481
6564
  const scrollParent = this.element;
6482
6565
  const headerHeight = this.headerElement.clientHeight; // May be 0
6483
6566
  const scrollTop = scrollParent.scrollTop;
6484
6567
  const clientHeight = scrollParent.clientHeight - headerHeight;
6485
6568
  let bottomIdx;
6486
6569
  if (complete) {
6487
- bottomIdx = Math.floor((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6570
+ bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
6488
6571
  }
6489
6572
  else {
6490
- bottomIdx = Math.ceil((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6573
+ bottomIdx = Math.ceil((scrollTop + clientHeight) / rowHeight) - 1;
6491
6574
  }
6492
6575
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
6493
6576
  return this._getNodeByRowIdx(bottomIdx);
@@ -6919,8 +7002,9 @@ class Wunderbaum {
6919
7002
  * @param includeHidden Not yet implemented
6920
7003
  */
6921
7004
  findRelatedNode(node, where, includeHidden = false) {
7005
+ const rowHeight = this.options.rowHeightPx;
6922
7006
  let res = null;
6923
- const pageSize = Math.floor(this.listContainerElement.clientHeight / ROW_HEIGHT);
7007
+ const pageSize = Math.floor(this.listContainerElement.clientHeight / rowHeight);
6924
7008
  switch (where) {
6925
7009
  case "parent":
6926
7010
  if (node.parent && node.parent.parent) {
@@ -7243,6 +7327,7 @@ class Wunderbaum {
7243
7327
  scrollTo(nodeOrOpts) {
7244
7328
  const PADDING = 2; // leave some pixels between viewport bounds
7245
7329
  let node;
7330
+ // WunderbaumNode;
7246
7331
  let options;
7247
7332
  if (nodeOrOpts instanceof WunderbaumNode) {
7248
7333
  node = nodeOrOpts;
@@ -7252,14 +7337,15 @@ class Wunderbaum {
7252
7337
  node = options.node;
7253
7338
  }
7254
7339
  assert(node && node._rowIdx != null, `Invalid node: ${node}`);
7340
+ const rowHeight = this.options.rowHeightPx;
7255
7341
  const scrollParent = this.element;
7256
7342
  const headerHeight = this.headerElement.clientHeight; // May be 0
7257
7343
  const scrollTop = scrollParent.scrollTop;
7258
7344
  const vpHeight = scrollParent.clientHeight;
7259
- const rowTop = node._rowIdx * ROW_HEIGHT + headerHeight;
7345
+ const rowTop = node._rowIdx * rowHeight + headerHeight;
7260
7346
  const vpTop = headerHeight;
7261
7347
  const vpRowTop = rowTop - scrollTop;
7262
- const vpRowBottom = vpRowTop + ROW_HEIGHT;
7348
+ const vpRowBottom = vpRowTop + rowHeight;
7263
7349
  const topNode = options === null || options === void 0 ? void 0 : options.topNode;
7264
7350
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts , options);
7265
7351
  let newScrollTop = null;
@@ -7268,7 +7354,7 @@ class Wunderbaum {
7268
7354
  else {
7269
7355
  // Node is below viewport
7270
7356
  // this.log("Below viewport");
7271
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
7357
+ newScrollTop = rowTop + rowHeight - vpHeight + PADDING; // leave some pixels between viewport bounds
7272
7358
  }
7273
7359
  }
7274
7360
  else {
@@ -7881,19 +7967,19 @@ class Wunderbaum {
7881
7967
  // this.log("_updateRows", opts)
7882
7968
  options = Object.assign({ newNodesOnly: false }, options);
7883
7969
  const newNodesOnly = !!options.newNodesOnly;
7884
- const row_height = ROW_HEIGHT;
7885
- const vp_height = this.element.clientHeight;
7970
+ const rowHeight = this.options.rowHeightPx;
7971
+ const vpHeight = this.element.clientHeight;
7886
7972
  const prefetch = RENDER_MAX_PREFETCH;
7887
7973
  // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
7888
7974
  const ofs = this.element.scrollTop;
7889
- let startIdx = Math.max(0, ofs / row_height - prefetch);
7975
+ let startIdx = Math.max(0, ofs / rowHeight - prefetch);
7890
7976
  startIdx = Math.floor(startIdx);
7891
7977
  // Make sure start is always even, so the alternating row colors don't
7892
7978
  // change when scrolling:
7893
7979
  if (startIdx % 2) {
7894
7980
  startIdx--;
7895
7981
  }
7896
- let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7982
+ let endIdx = Math.max(0, (ofs + vpHeight) / rowHeight + prefetch);
7897
7983
  endIdx = Math.ceil(endIdx);
7898
7984
  // this.debug("render", opts);
7899
7985
  const obsoleteNodes = new Set();
@@ -7922,21 +8008,21 @@ class Wunderbaum {
7922
8008
  else if (rowDiv && newNodesOnly) {
7923
8009
  obsoleteNodes.delete(node);
7924
8010
  // no need to update existing node markup
7925
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
8011
+ rowDiv.style.top = idx * rowHeight + "px";
7926
8012
  prevElem = rowDiv;
7927
8013
  }
7928
8014
  else {
7929
8015
  obsoleteNodes.delete(node);
7930
8016
  // Create new markup
7931
8017
  if (rowDiv) {
7932
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
8018
+ rowDiv.style.top = idx * rowHeight + "px";
7933
8019
  }
7934
8020
  node._render({ top: top, after: prevElem });
7935
8021
  // node.log("render", top, prevElem, "=>", node._rowElem);
7936
8022
  prevElem = node._rowElem;
7937
8023
  }
7938
8024
  idx++;
7939
- top += row_height;
8025
+ top += rowHeight;
7940
8026
  });
7941
8027
  this.treeRowCount = idx;
7942
8028
  for (const n of obsoleteNodes) {
@@ -8200,7 +8286,7 @@ class Wunderbaum {
8200
8286
  }
8201
8287
  Wunderbaum.sequence = 0;
8202
8288
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
8203
- Wunderbaum.version = "v0.11.0"; // Set to semver by 'grunt release'
8289
+ Wunderbaum.version = "v0.12.0"; // Set to semver by 'grunt release'
8204
8290
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
8205
8291
  Wunderbaum.util = util;
8206
8292