wunderbaum 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -288,7 +288,7 @@ function throttle(func, wait = 0, options = {}) {
288
288
  /*!
289
289
  * Wunderbaum - util
290
290
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
291
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
291
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
292
292
  */
293
293
  /** @module util */
294
294
  /** Readable names for `MouseEvent.button` */
@@ -920,6 +920,11 @@ function getOption(opts, name, defaultValue = undefined) {
920
920
  // Use value from value options dict, fallback do default
921
921
  return value !== null && value !== void 0 ? value : defaultValue;
922
922
  }
923
+ /** Return the next value from a list of values (rotating). @since 0.11 */
924
+ function rotate(value, values) {
925
+ const idx = values.indexOf(value);
926
+ return values[(idx + 1) % values.length];
927
+ }
923
928
  /** Convert an Array or space-separated string to a Set. */
924
929
  function toSet(val) {
925
930
  if (val instanceof Set) {
@@ -948,12 +953,7 @@ function toSet(val) {
948
953
  * const width = util.toPixel(x, y, 100); // returns 123
949
954
  * ```
950
955
  */
951
- function toPixel(
952
- // val: string | number | undefined | null,
953
- ...defaults) {
954
- // if (typeof val === "number") {
955
- // return val;
956
- // }
956
+ function toPixel(...defaults) {
957
957
  for (const d of defaults) {
958
958
  if (typeof d === "number") {
959
959
  return d;
@@ -972,12 +972,7 @@ function toPixel(
972
972
  * const value = util.toBool(opts.foo, opts.flag, false); // returns true
973
973
  * ```
974
974
  */
975
- function toBool(
976
- // val: boolean | undefined | null,
977
- ...boolDefaults) {
978
- // if (val != null) {
979
- // return !!val;
980
- // }
975
+ function toBool(...boolDefaults) {
981
976
  for (const d of boolDefaults) {
982
977
  if (d != null) {
983
978
  return !!d;
@@ -985,6 +980,15 @@ function toBool(
985
980
  }
986
981
  throw new Error("No default boolean value provided");
987
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
+ }
988
992
  // /** Check if a string is contained in an Array or Set. */
989
993
  // export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
990
994
  // return Array.prototype.includes.call(items, s)
@@ -1113,6 +1117,7 @@ var util = /*#__PURE__*/Object.freeze({
1113
1117
  extractHtmlText: extractHtmlText,
1114
1118
  getOption: getOption,
1115
1119
  getValueFromElem: getValueFromElem,
1120
+ intToBool: intToBool,
1116
1121
  isArray: isArray,
1117
1122
  isEmptyObject: isEmptyObject,
1118
1123
  isFunction: isFunction,
@@ -1121,6 +1126,7 @@ var util = /*#__PURE__*/Object.freeze({
1121
1126
  noop: noop,
1122
1127
  onEvent: onEvent,
1123
1128
  overrideMethod: overrideMethod,
1129
+ rotate: rotate,
1124
1130
  setElemDisplay: setElemDisplay,
1125
1131
  setTimeoutPromise: setTimeoutPromise,
1126
1132
  setValueToElem: setValueToElem,
@@ -1136,10 +1142,10 @@ var util = /*#__PURE__*/Object.freeze({
1136
1142
  /*!
1137
1143
  * Wunderbaum - types
1138
1144
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1139
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1145
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1140
1146
  */
1141
1147
  /**
1142
- * Possible values for {@link WunderbaumNode.update()} and {@link Wunderbaum.update()}.
1148
+ * Possible values for {@link WunderbaumNode.update} and {@link Wunderbaum.update}.
1143
1149
  */
1144
1150
  var ChangeType;
1145
1151
  (function (ChangeType) {
@@ -1168,7 +1174,7 @@ var RenderFlag;
1168
1174
  RenderFlag["redraw"] = "redraw";
1169
1175
  RenderFlag["scroll"] = "scroll";
1170
1176
  })(RenderFlag || (RenderFlag = {}));
1171
- /** Possible values for {@link WunderbaumNode.setStatus()}. */
1177
+ /** Possible values for {@link WunderbaumNode.setStatus}. */
1172
1178
  var NodeStatusType;
1173
1179
  (function (NodeStatusType) {
1174
1180
  NodeStatusType["ok"] = "ok";
@@ -1200,7 +1206,7 @@ var NavModeEnum;
1200
1206
  /*!
1201
1207
  * Wunderbaum - wb_extension_base
1202
1208
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1203
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1209
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1204
1210
  */
1205
1211
  class WunderbaumExtension {
1206
1212
  constructor(tree, id, defaults) {
@@ -1259,7 +1265,7 @@ class WunderbaumExtension {
1259
1265
  /*!
1260
1266
  * Wunderbaum - ext-filter
1261
1267
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1262
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1268
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1263
1269
  */
1264
1270
  const START_MARKER = "\uFFF7";
1265
1271
  const END_MARKER = "\uFFF8";
@@ -1584,7 +1590,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
1584
1590
  /*!
1585
1591
  * Wunderbaum - ext-keynav
1586
1592
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1587
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1593
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1588
1594
  */
1589
1595
  const QUICKSEARCH_DELAY = 500;
1590
1596
  class KeynavExtension extends WunderbaumExtension {
@@ -1948,7 +1954,7 @@ class KeynavExtension extends WunderbaumExtension {
1948
1954
  /*!
1949
1955
  * Wunderbaum - ext-logger
1950
1956
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1951
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1957
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
1952
1958
  */
1953
1959
  class LoggerExtension extends WunderbaumExtension {
1954
1960
  constructor(tree) {
@@ -1988,409 +1994,84 @@ class LoggerExtension extends WunderbaumExtension {
1988
1994
  }
1989
1995
 
1990
1996
  /*!
1991
- * Wunderbaum - common
1997
+ * Wunderbaum - ext-dnd
1992
1998
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
1993
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
1994
- */
1995
- const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
1996
- /**
1997
- * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
1998
- */
1999
- const ROW_HEIGHT = 22;
2000
- /**
2001
- * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
2002
- */
2003
- const ICON_WIDTH = 20;
2004
- /**
2005
- * Adjust the width of the title span, so overflow ellipsis work.
2006
- * (2 x `$col-padding-x` + 3px rounding errors).
2007
- */
2008
- const TITLE_SPAN_PAD_Y = 7;
2009
- /** Render row markup for N nodes above and below the visible viewport. */
2010
- const RENDER_MAX_PREFETCH = 5;
2011
- /** Minimum column width if not set otherwise. */
2012
- const DEFAULT_MIN_COL_WIDTH = 4;
2013
- /** Regular expression to detect if a string describes an image URL (in contrast
2014
- * to a class name). Strings are considered image urls if they contain '.' or '/'.
2015
- */
2016
- const TEST_IMG = new RegExp(/\.|\//);
2017
- // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2018
- // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2019
- /**
2020
- * Default node icons.
2021
- * Requires bootstrap icons https://icons.getbootstrap.com
2022
- */
2023
- const iconMaps = {
2024
- bootstrap: {
2025
- error: "bi bi-exclamation-triangle",
2026
- // loading: "bi bi-hourglass-split wb-busy",
2027
- loading: "bi bi-chevron-right wb-busy",
2028
- // loading: "bi bi-arrow-repeat wb-spin",
2029
- // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2030
- // noData: "bi bi-search",
2031
- noData: "bi bi-question-circle",
2032
- expanderExpanded: "bi bi-chevron-down",
2033
- // expanderExpanded: "bi bi-dash-square",
2034
- expanderCollapsed: "bi bi-chevron-right",
2035
- // expanderCollapsed: "bi bi-plus-square",
2036
- expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2037
- // expanderLazy: "bi bi-chevron-bar-right",
2038
- checkChecked: "bi bi-check-square",
2039
- checkUnchecked: "bi bi-square",
2040
- checkUnknown: "bi bi-dash-square-dotted",
2041
- radioChecked: "bi bi-circle-fill",
2042
- radioUnchecked: "bi bi-circle",
2043
- radioUnknown: "bi bi-record-circle",
2044
- folder: "bi bi-folder2",
2045
- folderOpen: "bi bi-folder2-open",
2046
- folderLazy: "bi bi-folder-symlink",
2047
- doc: "bi bi-file-earmark",
2048
- },
2049
- fontawesome6: {
2050
- error: "fa-solid fa-triangle-exclamation",
2051
- loading: "fa-solid fa-chevron-right fa-beat",
2052
- noData: "fa-solid fa-circle-question",
2053
- expanderExpanded: "fa-solid fa-chevron-down",
2054
- expanderCollapsed: "fa-solid fa-chevron-right",
2055
- expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2056
- checkChecked: "fa-regular fa-square-check",
2057
- checkUnchecked: "fa-regular fa-square",
2058
- checkUnknown: "fa-regular fa-square-minus",
2059
- radioChecked: "fa-solid fa-circle",
2060
- radioUnchecked: "fa-regular fa-circle",
2061
- radioUnknown: "fa-regular fa-circle-question",
2062
- folder: "fa-solid fa-folder-closed",
2063
- folderOpen: "fa-regular fa-folder-open",
2064
- folderLazy: "fa-solid fa-folder-plus",
2065
- doc: "fa-regular fa-file",
2066
- },
2067
- };
2068
- /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2069
- const RESERVED_TREE_SOURCE_KEYS = new Set([
2070
- "_format", // reserved for future use
2071
- "_keyMap", // Used for compressed data format
2072
- "_positional", // Used for compressed data format
2073
- "_typeList", // Used for compressed data format @deprecated
2074
- "_valueMap", // Used for compressed data format
2075
- "_version", // reserved for future use
2076
- "children",
2077
- "columns",
2078
- "types",
2079
- ]);
2080
- // /** Key codes that trigger grid navigation, even when inside an input element. */
2081
- // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2082
- // // "ArrowDown",
2083
- // // "ArrowUp",
2084
- // "Enter",
2085
- // "Escape",
2086
- // ]);
2087
- /** Map `KeyEvent.key` to navigation action. */
2088
- const KEY_TO_ACTION_DICT = {
2089
- " ": "toggleSelect",
2090
- "+": "expand",
2091
- Add: "expand",
2092
- ArrowDown: "down",
2093
- ArrowLeft: "left",
2094
- ArrowRight: "right",
2095
- ArrowUp: "up",
2096
- Backspace: "parent",
2097
- "/": "collapseAll",
2098
- Divide: "collapseAll",
2099
- End: "lastCol",
2100
- Home: "firstCol",
2101
- "Control+End": "last",
2102
- "Control+Home": "first",
2103
- "Meta+ArrowDown": "last", // macOs
2104
- "Meta+ArrowUp": "first", // macOs
2105
- "*": "expandAll",
2106
- Multiply: "expandAll",
2107
- PageDown: "pageDown",
2108
- PageUp: "pageUp",
2109
- "-": "collapse",
2110
- Subtract: "collapse",
2111
- };
2112
- /** Return a callback that returns true if the node title matches the string
2113
- * or regular expression.
2114
- * @see {@link WunderbaumNode.findAll()}
2115
- */
2116
- function makeNodeTitleMatcher(match) {
2117
- if (match instanceof RegExp) {
2118
- return function (node) {
2119
- return match.test(node.title);
2120
- };
2121
- }
2122
- assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2123
- // s = escapeRegex(s.toLowerCase());
2124
- return function (node) {
2125
- return node.title === match;
2126
- // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2127
- // return node.title.toLowerCase().indexOf(match) >= 0;
2128
- };
2129
- }
2130
- /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2131
- function makeNodeTitleStartMatcher(s) {
2132
- s = escapeRegex(s);
2133
- const reMatch = new RegExp("^" + s, "i");
2134
- return function (node) {
2135
- return reMatch.test(node.title);
2136
- };
2137
- }
2138
- /** Compare two nodes by title (case-insensitive). */
2139
- function nodeTitleSorter(a, b) {
2140
- const x = a.title.toLowerCase();
2141
- const y = b.title.toLowerCase();
2142
- return x === y ? 0 : x > y ? 1 : -1;
2143
- }
2144
- /**
2145
- * Convert 'flat' to 'nested' format.
2146
- *
2147
- * Flat node entry format:
2148
- * [PARENT_ID, [POSITIONAL_ARGS]]
2149
- * or
2150
- * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2151
- *
2152
- * 1. Parent-referencing list is converted to a list of nested dicts with
2153
- * optional `children` properties.
2154
- * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
1999
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2155
2000
  */
2156
- function unflattenSource(source) {
2157
- var _a, _b, _c;
2158
- const { _format, _keyMap = {}, _positional = [], children } = source;
2159
- if (_format !== "flat") {
2160
- throw new Error(`Expected source._format: "flat", but got ${_format}`);
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);
2161
2047
  }
2162
- if (_positional && _positional.includes("children")) {
2163
- throw new Error(`source._positional must not include "children": ${_positional}`);
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));
2061
+ }
2062
+ // Enable drop support if dragEnter() is specified:
2063
+ if (dndOpts.dragEnter) {
2064
+ onEvent(tree.element, "dragenter dragover dragleave drop", this.onDropEvent.bind(this));
2065
+ }
2164
2066
  }
2165
- let longToShort = _keyMap;
2166
- if (_keyMap.t) {
2167
- // Inverse keyMap was used (pre 0.7.0)
2168
- // TODO: raise Error on final 1.x release
2169
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2170
- console.warn(msg); // eslint-disable-line no-console
2171
- longToShort = {};
2172
- for (const [key, value] of Object.entries(_keyMap)) {
2173
- longToShort[value] = key;
2174
- }
2175
- }
2176
- const positionalShort = _positional.map((e) => longToShort[e]);
2177
- const newChildren = [];
2178
- const keyToNodeMap = {};
2179
- const indexToNodeMap = {};
2180
- const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2181
- const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2182
- for (const [index, nodeTuple] of children.entries()) {
2183
- // Node entry format:
2184
- // [PARENT_ID, [POSITIONAL_ARGS]]
2185
- // or
2186
- // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2187
- const [parentId, args, kwargs = {}] = nodeTuple;
2188
- // Free up some memory as we go
2189
- nodeTuple[1] = null;
2190
- if (nodeTuple[2] != null) {
2191
- nodeTuple[2] = null;
2192
- }
2193
- // console.log("flatten", parentId, args, kwargs)
2194
- // We keep `kwargs` as our new node definition. Then we add all positional
2195
- // values to this object:
2196
- args.forEach((val, positionalIdx) => {
2197
- kwargs[positionalShort[positionalIdx]] = val;
2198
- });
2199
- // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2200
- // index of the source.children list. If PARENT_ID is a string, we search
2201
- // a parent with node.key of this value.
2202
- indexToNodeMap[index] = kwargs;
2203
- const key = kwargs[keyAttrName];
2204
- if (key != null) {
2205
- keyToNodeMap[key] = kwargs;
2206
- }
2207
- let parentNode = null;
2208
- if (parentId === null) ;
2209
- else if (typeof parentId === "number") {
2210
- parentNode = indexToNodeMap[parentId];
2211
- if (parentNode === undefined) {
2212
- throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2213
- }
2214
- }
2215
- else {
2216
- parentNode = keyToNodeMap[parentId];
2217
- if (parentNode === undefined) {
2218
- throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2219
- }
2220
- }
2221
- if (parentNode) {
2222
- (_c = parentNode[childrenAttrName]) !== null && _c !== void 0 ? _c : (parentNode[childrenAttrName] = []);
2223
- parentNode[childrenAttrName].push(kwargs);
2224
- }
2225
- else {
2226
- newChildren.push(kwargs);
2227
- }
2228
- }
2229
- source.children = newChildren;
2230
- }
2231
- /**
2232
- * Decompresses the source data by
2233
- * - converting from 'flat' to 'nested' format
2234
- * - expanding short alias names to long names (if defined in _keyMap)
2235
- * - resolving value indexes to value strings (if defined in _valueMap)
2236
- *
2237
- * @param source - The source object to be decompressed.
2238
- * @returns void
2239
- */
2240
- function decompressSourceData(source) {
2241
- let { _format, _version = 1, _keyMap, _valueMap } = source;
2242
- assert(_version === 1, `Expected file version 1 instead of ${_version}`);
2243
- let longToShort = _keyMap;
2244
- let shortToLong = {};
2245
- if (longToShort) {
2246
- for (const [key, value] of Object.entries(longToShort)) {
2247
- shortToLong[value] = key;
2248
- }
2249
- }
2250
- // Fallback for old format (pre 0.7.0, using _keyMap in reverse direction)
2251
- // TODO: raise Error on final 1.x release
2252
- if (longToShort && longToShort.t) {
2253
- const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2254
- console.warn(msg); // eslint-disable-line no-console
2255
- [longToShort, shortToLong] = [shortToLong, longToShort];
2256
- }
2257
- // Fallback for old format (pre 0.7.0, using _typeList instead of _valueMap)
2258
- // TODO: raise Error on final 1.x release
2259
- if (source._typeList != null) {
2260
- const msg = `source._typeList is deprecated since v0.7.0: use source._valueMap: {"type": [...]} instead.`;
2261
- if (_valueMap != null) {
2262
- throw new Error(msg);
2263
- }
2264
- else {
2265
- console.warn(msg); // eslint-disable-line no-console
2266
- _valueMap = { type: source._typeList };
2267
- delete source._typeList;
2268
- }
2269
- }
2270
- if (_format === "flat") {
2271
- unflattenSource(source);
2272
- }
2273
- delete source._format;
2274
- delete source._version;
2275
- delete source._keyMap;
2276
- delete source._valueMap;
2277
- delete source._positional;
2278
- function _iter(childList) {
2279
- for (const node of childList) {
2280
- // Iterate over a list of names, because we modify inside the loop
2281
- // (for ... of ... does not allow this)
2282
- Object.getOwnPropertyNames(node).forEach((propName) => {
2283
- const value = node[propName];
2284
- // Replace short names with long names if defined in _keyMap
2285
- let longName = propName;
2286
- if (_keyMap && shortToLong[propName] != null) {
2287
- longName = shortToLong[propName];
2288
- if (longName !== propName) {
2289
- node[longName] = value;
2290
- delete node[propName];
2291
- }
2292
- }
2293
- // Replace type index with type name if defined in _valueMap
2294
- if (_valueMap &&
2295
- typeof value === "number" &&
2296
- _valueMap[longName] != null) {
2297
- const newValue = _valueMap[longName][value];
2298
- if (newValue == null) {
2299
- throw new Error(`Expected valueMap[${longName}][${value}] entry in [${_valueMap[longName]}]`);
2300
- }
2301
- node[longName] = newValue;
2302
- }
2303
- });
2304
- // Recursion
2305
- if (node.children) {
2306
- _iter(node.children);
2307
- }
2308
- }
2309
- }
2310
- if (_keyMap || _valueMap) {
2311
- _iter(source.children);
2312
- }
2313
- }
2314
-
2315
- /*!
2316
- * Wunderbaum - ext-dnd
2317
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2318
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
2319
- */
2320
- const nodeMimeType = "application/x-wunderbaum-node";
2321
- class DndExtension extends WunderbaumExtension {
2322
- constructor(tree) {
2323
- super(tree, "dnd", {
2324
- autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
2325
- // dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
2326
- // dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
2327
- // #1021 `document.body` is not available yet
2328
- // dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
2329
- multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
2330
- effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
2331
- dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (override in drag, dragOver).
2332
- guessDropEffect: true, // Calculate from `effectAllowed` and modifier keys)
2333
- preventForeignNodes: false, // Prevent dropping nodes from different Wunderbaum trees
2334
- preventLazyParents: true, // Prevent dropping items on unloaded lazy Wunderbaum tree nodes
2335
- preventNonNodes: false, // Prevent dropping items other than Wunderbaum tree nodes
2336
- preventRecursion: true, // Prevent dropping nodes on own descendants
2337
- preventSameParent: false, // Prevent dropping nodes under same direct parent
2338
- preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. (move only)
2339
- serializeClipboardData: true, // Serialize node data to dataTransfer object
2340
- scroll: true, // Enable auto-scrolling while dragging
2341
- scrollSensitivity: 20, // Active top/bottom margin in pixel
2342
- // scrollnterval: 50, // Generate event every 50 ms
2343
- scrollSpeed: 5, // Scroll pixel per 50 ms
2344
- // setTextTypeJson: false, // Allow dragging of nodes to different IE windows
2345
- sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
2346
- // Events (drag support)
2347
- dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
2348
- drag: null, // Callback(sourceNode, data)
2349
- dragEnd: null, // Callback(sourceNode, data)
2350
- // Events (drop support)
2351
- dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
2352
- dragOver: null, // Callback(targetNode, data)
2353
- dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
2354
- drop: null, // Callback(targetNode, data)
2355
- dragLeave: null, // Callback(targetNode, data)
2356
- });
2357
- // public dropMarkerElem?: HTMLElement;
2358
- this.srcNode = null;
2359
- this.lastTargetNode = null;
2360
- this.lastEnterStamp = 0;
2361
- this.lastAllowedDropRegions = null;
2362
- this.lastDropEffect = null;
2363
- this.lastDropRegion = false;
2364
- this.currentScrollDir = 0;
2365
- this.applyScrollDirThrottled = throttle(this._applyScrollDir, 50);
2366
- }
2367
- init() {
2368
- super.init();
2369
- // Store the current scroll parent, which may be the tree
2370
- // container, any enclosing div, or the document.
2371
- // #761: scrollParent() always needs a container child
2372
- // $temp = $("<span>").appendTo(this.$container);
2373
- // this.$scrollParent = $temp.scrollParent();
2374
- // $temp.remove();
2375
- const tree = this.tree;
2376
- const dndOpts = tree.options.dnd;
2377
- // Enable drag support if dragStart() is specified:
2378
- if (dndOpts.dragStart) {
2379
- onEvent(tree.element, "dragstart drag dragend", this.onDragEvent.bind(this));
2380
- }
2381
- // Enable drop support if dragEnter() is specified:
2382
- if (dndOpts.dragEnter) {
2383
- onEvent(tree.element, "dragenter dragover dragleave drop", this.onDropEvent.bind(this));
2384
- }
2385
- }
2386
- /** Cleanup classes after target node is no longer hovered. */
2387
- _leaveNode() {
2388
- // We remove the marker on dragenter from the previous target:
2389
- const ltn = this.lastTargetNode;
2390
- this.lastEnterStamp = 0;
2391
- if (ltn) {
2392
- ltn.setClass("wb-drop-target wb-drop-over wb-drop-after wb-drop-before", false);
2393
- this.lastTargetNode = null;
2067
+ /** Cleanup classes after target node is no longer hovered. */
2068
+ _leaveNode() {
2069
+ // We remove the marker on dragenter from the previous target:
2070
+ const ltn = this.lastTargetNode;
2071
+ this.lastEnterStamp = 0;
2072
+ if (ltn) {
2073
+ ltn.setClass("wb-drop-target wb-drop-over wb-drop-after wb-drop-before", false);
2074
+ this.lastTargetNode = null;
2394
2075
  }
2395
2076
  }
2396
2077
  /** */
@@ -2414,14 +2095,15 @@ class DndExtension extends WunderbaumExtension {
2414
2095
  * Calculates the drop region based on the drag event and the allowed drop regions.
2415
2096
  */
2416
2097
  _calcDropRegion(e, allowed) {
2098
+ const rowHeight = this.tree.options.rowHeightPx;
2417
2099
  const dy = e.offsetY;
2418
2100
  if (!allowed) {
2419
2101
  return false;
2420
2102
  }
2421
2103
  else if (allowed.size === 3) {
2422
- return dy < 0.25 * ROW_HEIGHT
2104
+ return dy < 0.25 * rowHeight
2423
2105
  ? "before"
2424
- : dy > 0.75 * ROW_HEIGHT
2106
+ : dy > 0.75 * rowHeight
2425
2107
  ? "after"
2426
2108
  : "over";
2427
2109
  }
@@ -2430,7 +2112,7 @@ class DndExtension extends WunderbaumExtension {
2430
2112
  }
2431
2113
  else {
2432
2114
  // Only 'before' and 'after':
2433
- return dy > ROW_HEIGHT / 2 ? "after" : "before";
2115
+ return dy > rowHeight / 2 ? "after" : "before";
2434
2116
  }
2435
2117
  // return "over";
2436
2118
  }
@@ -2691,7 +2373,11 @@ class DndExtension extends WunderbaumExtension {
2691
2373
  }
2692
2374
  this.lastAllowedDropRegions = regionSet;
2693
2375
  this.lastDropEffect = dt.dropEffect;
2376
+ const region = this._calcDropRegion(e, this.lastAllowedDropRegions);
2694
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");
2695
2381
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
2696
2382
  return false;
2697
2383
  // --- dragover ---
@@ -2756,160 +2442,500 @@ class DndExtension extends WunderbaumExtension {
2756
2442
  return false;
2757
2443
  }
2758
2444
  }
2759
-
2760
- /*!
2761
- * Wunderbaum - drag_observer
2762
- * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2763
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
2764
- */
2445
+
2446
+ /*!
2447
+ * Wunderbaum - drag_observer
2448
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2449
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2450
+ */
2451
+ /**
2452
+ * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
2453
+ */
2454
+ class DragObserver {
2455
+ constructor(opts) {
2456
+ this.start = {
2457
+ event: null,
2458
+ x: 0,
2459
+ y: 0,
2460
+ altKey: false,
2461
+ ctrlKey: false,
2462
+ metaKey: false,
2463
+ shiftKey: false,
2464
+ };
2465
+ this.dragElem = null;
2466
+ this.dragging = false;
2467
+ this.customData = {};
2468
+ // TODO: touch events
2469
+ this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2470
+ if (!opts.root) {
2471
+ throw new Error("Missing `root` option.");
2472
+ }
2473
+ this.opts = Object.assign({ thresh: 5 }, opts);
2474
+ this.root = opts.root;
2475
+ this._handler = this.handleEvent.bind(this);
2476
+ this.events.forEach((type) => {
2477
+ this.root.addEventListener(type, this._handler);
2478
+ });
2479
+ }
2480
+ /** Unregister all event listeners. */
2481
+ disconnect() {
2482
+ this.events.forEach((type) => {
2483
+ this.root.removeEventListener(type, this._handler);
2484
+ });
2485
+ }
2486
+ getDragElem() {
2487
+ return this.dragElem;
2488
+ }
2489
+ isDragging() {
2490
+ return this.dragging;
2491
+ }
2492
+ stopDrag(cb_event) {
2493
+ if (this.dragging && this.opts.dragstop && cb_event) {
2494
+ cb_event.type = "dragstop";
2495
+ try {
2496
+ this.opts.dragstop(cb_event);
2497
+ }
2498
+ catch (err) {
2499
+ console.error("dragstop error", err); // eslint-disable-line no-console
2500
+ }
2501
+ }
2502
+ this.dragElem = null;
2503
+ this.dragging = false;
2504
+ this.start.event = null;
2505
+ this.customData = {};
2506
+ }
2507
+ handleEvent(e) {
2508
+ const type = e.type;
2509
+ const opts = this.opts;
2510
+ const cb_event = {
2511
+ type: e.type,
2512
+ startEvent: type === "mousedown" ? e : this.start.event,
2513
+ event: e,
2514
+ customData: this.customData,
2515
+ dragElem: this.dragElem,
2516
+ dx: e.pageX - this.start.x,
2517
+ dy: e.pageY - this.start.y,
2518
+ apply: undefined,
2519
+ };
2520
+ // console.log("handleEvent", type, cb_event);
2521
+ switch (type) {
2522
+ case "keydown":
2523
+ this.stopDrag(cb_event);
2524
+ break;
2525
+ case "mousedown":
2526
+ if (this.dragElem) {
2527
+ this.stopDrag(cb_event);
2528
+ break;
2529
+ }
2530
+ if (opts.selector) {
2531
+ let elem = e.target;
2532
+ if (elem.matches(opts.selector)) {
2533
+ this.dragElem = elem;
2534
+ }
2535
+ else {
2536
+ elem = elem.closest(opts.selector);
2537
+ if (elem) {
2538
+ this.dragElem = elem;
2539
+ }
2540
+ else {
2541
+ break; // no event delegation selector matched
2542
+ }
2543
+ }
2544
+ }
2545
+ this.start.event = e;
2546
+ this.start.x = e.pageX;
2547
+ this.start.y = e.pageY;
2548
+ this.start.altKey = e.altKey;
2549
+ this.start.ctrlKey = e.ctrlKey;
2550
+ this.start.metaKey = e.metaKey;
2551
+ this.start.shiftKey = e.shiftKey;
2552
+ break;
2553
+ case "mousemove":
2554
+ // TODO: debounce/throttle?
2555
+ // TODO: horizontal mode: ignore if dx unchanged
2556
+ if (!this.dragElem) {
2557
+ break;
2558
+ }
2559
+ if (!this.dragging) {
2560
+ if (opts.thresh) {
2561
+ const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2562
+ if (dist2 < opts.thresh * opts.thresh) {
2563
+ break;
2564
+ }
2565
+ }
2566
+ cb_event.type = "dragstart";
2567
+ if (opts.dragstart(cb_event) === false) {
2568
+ this.stopDrag(cb_event);
2569
+ break;
2570
+ }
2571
+ this.dragging = true;
2572
+ }
2573
+ if (this.dragging && this.opts.drag) {
2574
+ cb_event.type = "drag";
2575
+ this.opts.drag(cb_event);
2576
+ }
2577
+ break;
2578
+ case "mouseup":
2579
+ if (!this.dragging) {
2580
+ this.stopDrag(cb_event);
2581
+ break;
2582
+ }
2583
+ if (e.button === 0) {
2584
+ cb_event.apply = true;
2585
+ }
2586
+ else {
2587
+ cb_event.apply = false;
2588
+ }
2589
+ this.stopDrag(cb_event);
2590
+ break;
2591
+ }
2592
+ }
2593
+ }
2594
+
2595
+ /*!
2596
+ * Wunderbaum - common
2597
+ * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2598
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2599
+ */
2600
+ const DEFAULT_DEBUGLEVEL = 3; // Replaced by rollup script
2601
+ /**
2602
+ * Fixed height of a row in pixel. Must match the SCSS variable `$row-outer-height`.
2603
+ */
2604
+ const DEFAULT_ROW_HEIGHT = 22;
2605
+ /**
2606
+ * Fixed width of node icons in pixel. Must match the SCSS variable `$icon-outer-width`.
2607
+ */
2608
+ const ICON_WIDTH = 20;
2609
+ /**
2610
+ * Adjust the width of the title span, so overflow ellipsis work.
2611
+ * (2 x `$col-padding-x` + 3px rounding errors).
2612
+ */
2613
+ const TITLE_SPAN_PAD_Y = 7;
2614
+ /** Render row markup for N nodes above and below the visible viewport. */
2615
+ const RENDER_MAX_PREFETCH = 5;
2616
+ /** Minimum column width if not set otherwise. */
2617
+ const DEFAULT_MIN_COL_WIDTH = 4;
2618
+ /** Regular expression to detect if a string describes an image URL (in contrast
2619
+ * to a class name). Strings are considered image urls if they contain '.' or '/'.
2620
+ */
2621
+ const TEST_IMG = new RegExp(/\.|\//);
2622
+ // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
2623
+ // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
2624
+ /**
2625
+ * Default node icons.
2626
+ * Requires bootstrap icons https://icons.getbootstrap.com
2627
+ */
2628
+ const iconMaps = {
2629
+ bootstrap: {
2630
+ error: "bi bi-exclamation-triangle",
2631
+ // loading: "bi bi-hourglass-split wb-busy",
2632
+ loading: "bi bi-chevron-right wb-busy",
2633
+ // loading: "bi bi-arrow-repeat wb-spin",
2634
+ // loading: '<div class="spinner-border spinner-border-sm" role="status"> <span class="visually-hidden">Loading...</span> </div>',
2635
+ // noData: "bi bi-search",
2636
+ noData: "bi bi-question-circle",
2637
+ expanderExpanded: "bi bi-chevron-down",
2638
+ // expanderExpanded: "bi bi-dash-square",
2639
+ expanderCollapsed: "bi bi-chevron-right",
2640
+ // expanderCollapsed: "bi bi-plus-square",
2641
+ expanderLazy: "bi bi-chevron-right wb-helper-lazy-expander",
2642
+ // expanderLazy: "bi bi-chevron-bar-right",
2643
+ checkChecked: "bi bi-check-square",
2644
+ checkUnchecked: "bi bi-square",
2645
+ checkUnknown: "bi bi-dash-square-dotted",
2646
+ radioChecked: "bi bi-circle-fill",
2647
+ radioUnchecked: "bi bi-circle",
2648
+ radioUnknown: "bi bi-record-circle",
2649
+ folder: "bi bi-folder2",
2650
+ folderOpen: "bi bi-folder2-open",
2651
+ folderLazy: "bi bi-folder-symlink",
2652
+ doc: "bi bi-file-earmark",
2653
+ colSortable: "bi bi-chevron-expand",
2654
+ // colSortable: "bi bi-arrow-down-up",
2655
+ // colSortAsc: "bi bi-chevron-down",
2656
+ // colSortDesc: "bi bi-chevron-up",
2657
+ colSortAsc: "bi bi-arrow-down",
2658
+ colSortDesc: "bi bi-arrow-up",
2659
+ colFilter: "bi bi-filter-circle",
2660
+ colFilterActive: "bi bi-filter-circle-fill wb-helper-invalid",
2661
+ colMenu: "bi bi-three-dots-vertical",
2662
+ },
2663
+ fontawesome6: {
2664
+ error: "fa-solid fa-triangle-exclamation",
2665
+ loading: "fa-solid fa-chevron-right fa-beat",
2666
+ noData: "fa-solid fa-circle-question",
2667
+ expanderExpanded: "fa-solid fa-chevron-down",
2668
+ expanderCollapsed: "fa-solid fa-chevron-right",
2669
+ expanderLazy: "fa-solid fa-chevron-right wb-helper-lazy-expander",
2670
+ checkChecked: "fa-regular fa-square-check",
2671
+ checkUnchecked: "fa-regular fa-square",
2672
+ checkUnknown: "fa-regular fa-square-minus",
2673
+ radioChecked: "fa-solid fa-circle",
2674
+ radioUnchecked: "fa-regular fa-circle",
2675
+ radioUnknown: "fa-regular fa-circle-question",
2676
+ folder: "fa-solid fa-folder-closed",
2677
+ folderOpen: "fa-regular fa-folder-open",
2678
+ folderLazy: "fa-solid fa-folder-plus",
2679
+ doc: "fa-regular fa-file",
2680
+ colSortable: "fa-solid fa-fw fa-sort",
2681
+ colSortAsc: "fa-solid fa-fw fa-sort-up",
2682
+ colSortDesc: "fa-solid fa-fw fa-sort-down",
2683
+ colFilter: "fa-solid fa-fw fa-filter",
2684
+ colFilterActive: "fa-solid fa-fw fa-filter wb-helper-invalid",
2685
+ colMenu: "fa-solid fa-fw fa-ellipsis-v",
2686
+ },
2687
+ };
2688
+ /** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
2689
+ const RESERVED_TREE_SOURCE_KEYS = new Set([
2690
+ "_format", // reserved for future use
2691
+ "_keyMap", // Used for compressed data format
2692
+ "_positional", // Used for compressed data format
2693
+ "_typeList", // Used for compressed data format @deprecated
2694
+ "_valueMap", // Used for compressed data format
2695
+ "_version", // reserved for future use
2696
+ "children",
2697
+ "columns",
2698
+ "types",
2699
+ ]);
2700
+ // /** Key codes that trigger grid navigation, even when inside an input element. */
2701
+ // export const INPUT_BREAKOUT_KEYS: Set<string> = new Set([
2702
+ // // "ArrowDown",
2703
+ // // "ArrowUp",
2704
+ // "Enter",
2705
+ // "Escape",
2706
+ // ]);
2707
+ /** Map `KeyEvent.key` to navigation action. */
2708
+ const KEY_TO_ACTION_DICT = {
2709
+ " ": "toggleSelect",
2710
+ "+": "expand",
2711
+ Add: "expand",
2712
+ ArrowDown: "down",
2713
+ ArrowLeft: "left",
2714
+ ArrowRight: "right",
2715
+ ArrowUp: "up",
2716
+ Backspace: "parent",
2717
+ "/": "collapseAll",
2718
+ Divide: "collapseAll",
2719
+ End: "lastCol",
2720
+ Home: "firstCol",
2721
+ "Control+End": "last",
2722
+ "Control+Home": "first",
2723
+ "Meta+ArrowDown": "last", // macOs
2724
+ "Meta+ArrowUp": "first", // macOs
2725
+ "*": "expandAll",
2726
+ Multiply: "expandAll",
2727
+ PageDown: "pageDown",
2728
+ PageUp: "pageUp",
2729
+ "-": "collapse",
2730
+ Subtract: "collapse",
2731
+ };
2732
+ /** Return a callback that returns true if the node title matches the string
2733
+ * or regular expression.
2734
+ * @see {@link WunderbaumNode.findAll}
2735
+ */
2736
+ function makeNodeTitleMatcher(match) {
2737
+ if (match instanceof RegExp) {
2738
+ return function (node) {
2739
+ return match.test(node.title);
2740
+ };
2741
+ }
2742
+ assert(typeof match === "string", `Expected a string or RegExp: ${match}`);
2743
+ // s = escapeRegex(s.toLowerCase());
2744
+ return function (node) {
2745
+ return node.title === match;
2746
+ // console.log("match " + node, node.title.toLowerCase().indexOf(match))
2747
+ // return node.title.toLowerCase().indexOf(match) >= 0;
2748
+ };
2749
+ }
2750
+ /** Return a callback that returns true if the node title starts with a string (case-insensitive). */
2751
+ function makeNodeTitleStartMatcher(s) {
2752
+ s = escapeRegex(s);
2753
+ const reMatch = new RegExp("^" + s, "i");
2754
+ return function (node) {
2755
+ return reMatch.test(node.title);
2756
+ };
2757
+ }
2758
+ /** Compare two nodes by title (case-insensitive). */
2759
+ function nodeTitleSorter(a, b) {
2760
+ const x = a.title.toLowerCase();
2761
+ const y = b.title.toLowerCase();
2762
+ return x === y ? 0 : x > y ? 1 : -1;
2763
+ }
2764
+ /**
2765
+ * Convert 'flat' to 'nested' format.
2766
+ *
2767
+ * Flat node entry format:
2768
+ * [PARENT_ID, [POSITIONAL_ARGS]]
2769
+ * or
2770
+ * [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2771
+ *
2772
+ * 1. Parent-referencing list is converted to a list of nested dicts with
2773
+ * optional `children` properties.
2774
+ * 2. `[POSITIONAL_ARGS]` are added as dict attributes.
2775
+ */
2776
+ function unflattenSource(source) {
2777
+ var _a, _b, _c;
2778
+ const { _format, _keyMap = {}, _positional = [], children } = source;
2779
+ if (_format !== "flat") {
2780
+ throw new Error(`Expected source._format: "flat", but got ${_format}`);
2781
+ }
2782
+ if (_positional && _positional.includes("children")) {
2783
+ throw new Error(`source._positional must not include "children": ${_positional}`);
2784
+ }
2785
+ let longToShort = _keyMap;
2786
+ if (_keyMap.t) {
2787
+ // Inverse keyMap was used (pre 0.7.0)
2788
+ // TODO: raise Error on final 1.x release
2789
+ const msg = `source._keyMap maps from long to short since v0.7.0. Flip key/value!`;
2790
+ console.warn(msg); // eslint-disable-line no-console
2791
+ longToShort = {};
2792
+ for (const [key, value] of Object.entries(_keyMap)) {
2793
+ longToShort[value] = key;
2794
+ }
2795
+ }
2796
+ const positionalShort = _positional.map((e) => longToShort[e]);
2797
+ const newChildren = [];
2798
+ const keyToNodeMap = {};
2799
+ const indexToNodeMap = {};
2800
+ const keyAttrName = (_a = longToShort["key"]) !== null && _a !== void 0 ? _a : "key";
2801
+ const childrenAttrName = (_b = longToShort["children"]) !== null && _b !== void 0 ? _b : "children";
2802
+ for (const [index, nodeTuple] of children.entries()) {
2803
+ // Node entry format:
2804
+ // [PARENT_ID, [POSITIONAL_ARGS]]
2805
+ // or
2806
+ // [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
2807
+ const [parentId, args, kwargs = {}] = nodeTuple;
2808
+ // Free up some memory as we go
2809
+ nodeTuple[1] = null;
2810
+ if (nodeTuple[2] != null) {
2811
+ nodeTuple[2] = null;
2812
+ }
2813
+ // console.log("flatten", parentId, args, kwargs)
2814
+ // We keep `kwargs` as our new node definition. Then we add all positional
2815
+ // values to this object:
2816
+ args.forEach((val, positionalIdx) => {
2817
+ kwargs[positionalShort[positionalIdx]] = val;
2818
+ });
2819
+ // Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
2820
+ // index of the source.children list. If PARENT_ID is a string, we search
2821
+ // a parent with node.key of this value.
2822
+ indexToNodeMap[index] = kwargs;
2823
+ const key = kwargs[keyAttrName];
2824
+ if (key != null) {
2825
+ keyToNodeMap[key] = kwargs;
2826
+ }
2827
+ let parentNode = null;
2828
+ if (parentId === null) ;
2829
+ else if (typeof parentId === "number") {
2830
+ parentNode = indexToNodeMap[parentId];
2831
+ if (parentNode === undefined) {
2832
+ throw new Error(`unflattenSource: Could not find parent node by index: ${parentId}.`);
2833
+ }
2834
+ }
2835
+ else {
2836
+ parentNode = keyToNodeMap[parentId];
2837
+ if (parentNode === undefined) {
2838
+ throw new Error(`unflattenSource: Could not find parent node by key: ${parentId}`);
2839
+ }
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);
2847
+ }
2848
+ }
2849
+ source.children = newChildren;
2850
+ }
2765
2851
  /**
2766
- * 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
2767
2859
  */
2768
- class DragObserver {
2769
- constructor(opts) {
2770
- this.start = {
2771
- event: null,
2772
- x: 0,
2773
- y: 0,
2774
- altKey: false,
2775
- ctrlKey: false,
2776
- metaKey: false,
2777
- shiftKey: false,
2778
- };
2779
- this.dragElem = null;
2780
- this.dragging = false;
2781
- this.customData = {};
2782
- // TODO: touch events
2783
- this.events = ["mousedown", "mouseup", "mousemove", "keydown"];
2784
- if (!opts.root) {
2785
- 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;
2786
2868
  }
2787
- this.opts = Object.assign({ thresh: 5 }, opts);
2788
- this.root = opts.root;
2789
- this._handler = this.handleEvent.bind(this);
2790
- this.events.forEach((type) => {
2791
- this.root.addEventListener(type, this._handler);
2792
- });
2793
- }
2794
- /** Unregister all event listeners. */
2795
- disconnect() {
2796
- this.events.forEach((type) => {
2797
- this.root.removeEventListener(type, this._handler);
2798
- });
2799
- }
2800
- getDragElem() {
2801
- return this.dragElem;
2802
2869
  }
2803
- isDragging() {
2804
- 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];
2805
2876
  }
2806
- stopDrag(cb_event) {
2807
- if (this.dragging && this.opts.dragstop && cb_event) {
2808
- cb_event.type = "dragstop";
2809
- try {
2810
- this.opts.dragstop(cb_event);
2811
- }
2812
- catch (err) {
2813
- console.error("dragstop error", err); // eslint-disable-line no-console
2814
- }
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;
2815
2888
  }
2816
- this.dragElem = null;
2817
- this.dragging = false;
2818
- this.start.event = null;
2819
- this.customData = {};
2820
2889
  }
2821
- handleEvent(e) {
2822
- const type = e.type;
2823
- const opts = this.opts;
2824
- const cb_event = {
2825
- type: e.type,
2826
- startEvent: type === "mousedown" ? e : this.start.event,
2827
- event: e,
2828
- customData: this.customData,
2829
- dragElem: this.dragElem,
2830
- dx: e.pageX - this.start.x,
2831
- dy: e.pageY - this.start.y,
2832
- apply: undefined,
2833
- };
2834
- // console.log("handleEvent", type, cb_event);
2835
- switch (type) {
2836
- case "keydown":
2837
- this.stopDrag(cb_event);
2838
- break;
2839
- case "mousedown":
2840
- if (this.dragElem) {
2841
- this.stopDrag(cb_event);
2842
- break;
2843
- }
2844
- if (opts.selector) {
2845
- let elem = e.target;
2846
- if (elem.matches(opts.selector)) {
2847
- this.dragElem = elem;
2848
- }
2849
- else {
2850
- elem = elem.closest(opts.selector);
2851
- if (elem) {
2852
- this.dragElem = elem;
2853
- }
2854
- else {
2855
- break; // no event delegation selector matched
2856
- }
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];
2857
2911
  }
2858
2912
  }
2859
- this.start.event = e;
2860
- this.start.x = e.pageX;
2861
- this.start.y = e.pageY;
2862
- this.start.altKey = e.altKey;
2863
- this.start.ctrlKey = e.ctrlKey;
2864
- this.start.metaKey = e.metaKey;
2865
- this.start.shiftKey = e.shiftKey;
2866
- break;
2867
- case "mousemove":
2868
- // TODO: debounce/throttle?
2869
- // TODO: horizontal mode: ignore if dx unchanged
2870
- if (!this.dragElem) {
2871
- break;
2872
- }
2873
- if (!this.dragging) {
2874
- if (opts.thresh) {
2875
- const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
2876
- if (dist2 < opts.thresh * opts.thresh) {
2877
- break;
2878
- }
2879
- }
2880
- cb_event.type = "dragstart";
2881
- if (opts.dragstart(cb_event) === false) {
2882
- this.stopDrag(cb_event);
2883
- 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]}]`);
2884
2920
  }
2885
- this.dragging = true;
2886
- }
2887
- if (this.dragging && this.opts.drag) {
2888
- cb_event.type = "drag";
2889
- this.opts.drag(cb_event);
2890
- }
2891
- break;
2892
- case "mouseup":
2893
- if (!this.dragging) {
2894
- this.stopDrag(cb_event);
2895
- break;
2896
- }
2897
- if (e.button === 0) {
2898
- cb_event.apply = true;
2899
- }
2900
- else {
2901
- cb_event.apply = false;
2921
+ node[longName] = newValue;
2902
2922
  }
2903
- this.stopDrag(cb_event);
2904
- break;
2923
+ });
2924
+ // Recursion
2925
+ if (node.children) {
2926
+ _iter(node.children);
2927
+ }
2905
2928
  }
2906
2929
  }
2930
+ if (_keyMap || _valueMap) {
2931
+ _iter(source.children);
2932
+ }
2907
2933
  }
2908
2934
 
2909
2935
  /*!
2910
2936
  * Wunderbaum - ext-grid
2911
2937
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
2912
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
2938
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
2913
2939
  */
2914
2940
  class GridExtension extends WunderbaumExtension {
2915
2941
  constructor(tree) {
@@ -2926,7 +2952,7 @@ class GridExtension extends WunderbaumExtension {
2926
2952
  const colDef = info.colDef;
2927
2953
  const allow = colDef &&
2928
2954
  this.tree.element.contains(e.dragElem) &&
2929
- toBool(colDef.resizable, tree.options.resizableColumns, false);
2955
+ toBool(colDef.resizable, tree.options.columnsResizable, false);
2930
2956
  // this.tree.log("dragstart", colDef, e, info);
2931
2957
  this.tree.element.classList.toggle("wb-col-resizing", !!allow);
2932
2958
  info.colElem.classList.toggle("wb-col-resizing", !!allow);
@@ -3000,7 +3026,7 @@ class GridExtension extends WunderbaumExtension {
3000
3026
  /*!
3001
3027
  * Wunderbaum - deferred
3002
3028
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3003
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
3029
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3004
3030
  */
3005
3031
  /**
3006
3032
  * Implement a ES6 Promise, that exposes a resolve() and reject() method.
@@ -3053,7 +3079,7 @@ class Deferred {
3053
3079
  /*!
3054
3080
  * Wunderbaum - wunderbaum_node
3055
3081
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
3056
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
3082
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
3057
3083
  */
3058
3084
  /** WunderbaumNode properties that can be passed with source data.
3059
3085
  * (Any other source properties will be stored as `node.data.PROP`.)
@@ -3081,6 +3107,20 @@ const NODE_PROPS = new Set([
3081
3107
  const NODE_DICT_PROPS = new Set(NODE_PROPS);
3082
3108
  NODE_DICT_PROPS.delete("_partsel");
3083
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
+ // ]);
3084
3124
  /**
3085
3125
  * A single tree node.
3086
3126
  *
@@ -3096,6 +3136,12 @@ class WunderbaumNode {
3096
3136
  * @see Use {@link setKey} to modify.
3097
3137
  */
3098
3138
  this.refKey = undefined;
3139
+ /**
3140
+ * Array of child nodes (null for leaf nodes).
3141
+ * For lazy nodes, this is `null` or ùndefined` until the children are loaded
3142
+ * and leaf nodes may be `[]` (empty array).
3143
+ * @see {@link hasChildren}, {@link addChildren}, {@link lazy}.
3144
+ */
3099
3145
  this.children = null;
3100
3146
  /** Additional classes added to `div.wb-row`.
3101
3147
  * @see {@link hasClass}, {@link setClass}. */
@@ -3116,20 +3162,26 @@ class WunderbaumNode {
3116
3162
  this.parent = parent;
3117
3163
  this.key = "" + ((_a = data.key) !== null && _a !== void 0 ? _a : ++WunderbaumNode.sequence);
3118
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
3119
3169
  data.refKey != null ? (this.refKey = "" + data.refKey) : 0;
3120
3170
  data.type != null ? (this.type = "" + data.type) : 0;
3121
- this.expanded = data.expanded === true;
3122
- data.icon != null ? (this.icon = data.icon) : 0;
3123
- 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;
3124
3176
  data.statusNodeType != null
3125
3177
  ? (this.statusNodeType = ("" + data.statusNodeType))
3126
3178
  : 0;
3127
3179
  data.colspan != null ? (this.colspan = !!data.colspan) : 0;
3128
3180
  // Selection
3129
- data.checkbox != null ? (this.checkbox = !!data.checkbox) : 0;
3181
+ data.checkbox != null ? intToBool(data.checkbox) : 0;
3130
3182
  data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
3131
- this.selected = data.selected === true;
3132
- 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;
3133
3185
  if (data.classes) {
3134
3186
  this.setClass(data.classes);
3135
3187
  }
@@ -3683,7 +3735,7 @@ class WunderbaumNode {
3683
3735
  hasClass(className) {
3684
3736
  return this.classes ? this.classes.has(className) : false;
3685
3737
  }
3686
- /** Return true if node ist the currently focused node. */
3738
+ /** Return true if node ist the currently focused node. @since 0.9.0 */
3687
3739
  hasFocus() {
3688
3740
  return this.tree.focusNode === this;
3689
3741
  }
@@ -3905,6 +3957,8 @@ class WunderbaumNode {
3905
3957
  if (tree.options.selectMode === "hier") {
3906
3958
  this.fixSelection3FromEndNodes();
3907
3959
  }
3960
+ // Allow to un-sort nodes after sorting
3961
+ this.resetNativeChildOrder();
3908
3962
  this._callEvent("load");
3909
3963
  }
3910
3964
  async _fetchWithOptions(source) {
@@ -3956,8 +4010,8 @@ class WunderbaumNode {
3956
4010
  let elap = 0, elapLoad = 0, elapProcess = 0;
3957
4011
  // Check for overlapping requests
3958
4012
  if (this._requestId) {
3959
- this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending.`);
3960
- // node.debug("Send load request #" + requestId);
4013
+ this.logWarn(`Recursive load request #${requestId} while #${this._requestId} is pending. ` +
4014
+ "The previous request will be ignored.");
3961
4015
  }
3962
4016
  this._requestId = requestId;
3963
4017
  // const timerLabel = tree.logTime(this + ".load()");
@@ -4437,6 +4491,7 @@ class WunderbaumNode {
4437
4491
  _render_markup(opts) {
4438
4492
  const tree = this.tree;
4439
4493
  const treeOptions = tree.options;
4494
+ const rowHeight = treeOptions.rowHeightPx;
4440
4495
  const checkbox = this.getOption("checkbox");
4441
4496
  const columns = tree.columns;
4442
4497
  const level = this.getLevel();
@@ -4451,7 +4506,7 @@ class WunderbaumNode {
4451
4506
  assert(!this.isRootNode(), "Root node not allowed");
4452
4507
  rowDiv = document.createElement("div");
4453
4508
  rowDiv.classList.add("wb-row");
4454
- rowDiv.style.top = this._rowIdx * ROW_HEIGHT + "px";
4509
+ rowDiv.style.top = this._rowIdx * rowHeight + "px";
4455
4510
  this._rowElem = rowDiv;
4456
4511
  // Attach a node reference to the DOM Element:
4457
4512
  rowDiv._wb_node = this;
@@ -4848,7 +4903,7 @@ class WunderbaumNode {
4848
4903
  *
4849
4904
  * @param name name of the option property (on node and tree)
4850
4905
  * @param defaultValue return this if nothing else matched
4851
- * {@link Wunderbaum.getOption|Wunderbaum.getOption()}
4906
+ * {@link Wunderbaum.getOption|Wunderbaum.getOption}
4852
4907
  */
4853
4908
  getOption(name, defaultValue) {
4854
4909
  const tree = this.tree;
@@ -4884,7 +4939,7 @@ class WunderbaumNode {
4884
4939
  return value !== null && value !== void 0 ? value : defaultValue;
4885
4940
  }
4886
4941
  /** Make sure that this node is visible in the viewport.
4887
- * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
4942
+ * @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo}
4888
4943
  */
4889
4944
  async scrollIntoView(options) {
4890
4945
  const opts = Object.assign({ node: this }, options);
@@ -5023,9 +5078,9 @@ class WunderbaumNode {
5023
5078
  * and column content. It can be reduced to 'ChangeType.status' if only
5024
5079
  * active/focus/selected state has changed.
5025
5080
  *
5026
- * This method will eventually call {@link WunderbaumNode._render()} with
5081
+ * This method will eventually call {@link WunderbaumNode._render} with
5027
5082
  * default options, but may be more consistent with the tree's
5028
- * {@link Wunderbaum.update()} API.
5083
+ * {@link Wunderbaum.update} API.
5029
5084
  */
5030
5085
  update(change = ChangeType.data) {
5031
5086
  assert(change === ChangeType.status || change === ChangeType.data, `Invalid change type ${change}`);
@@ -5354,6 +5409,95 @@ class WunderbaumNode {
5354
5409
  this.tree.update(ChangeType.structure);
5355
5410
  // this.triggerModify("sort"); // TODO
5356
5411
  }
5412
+ /**
5413
+ * Renumber nodes `_nativeIndex`. This is useful to allow to restore the
5414
+ * order after sorting a column.
5415
+ * This method is automatically called after loading new child nodes.
5416
+ * @since 0.11.0
5417
+ */
5418
+ resetNativeChildOrder(options) {
5419
+ const { recursive = true, propName = "_nativeIndex" } = options !== null && options !== void 0 ? options : {};
5420
+ if (this.children) {
5421
+ this.children.forEach((child, i) => {
5422
+ child.data[propName] = i;
5423
+ if (recursive && child.children) {
5424
+ child.resetNativeChildOrder(options);
5425
+ }
5426
+ });
5427
+ }
5428
+ }
5429
+ /**
5430
+ * Convenience method to implement column sorting.
5431
+ * @since 0.11.0
5432
+ */
5433
+ sortByProperty(options) {
5434
+ var _a, _b, _c;
5435
+ const { caseInsensitive = true, deep = true, nativeOrderPropName = "_nativeIndex", updateColInfo = false, } = options;
5436
+ let order;
5437
+ let colDef;
5438
+ if (updateColInfo) {
5439
+ colDef = this.tree["_columnsById"][options.colId];
5440
+ assert(colDef, `Invalid colId specified: ${options.colId}`);
5441
+ order =
5442
+ (_a = options.order) !== null && _a !== void 0 ? _a : rotate(colDef.sortOrder, ["asc", "desc", undefined]);
5443
+ for (const col of this.tree.columns) {
5444
+ col.sortOrder = col === colDef ? order : undefined;
5445
+ }
5446
+ this.tree.update(ChangeType.colStructure);
5447
+ }
5448
+ else {
5449
+ order = (_b = options.order) !== null && _b !== void 0 ? _b : "asc";
5450
+ }
5451
+ let propName = (_c = options.propName) !== null && _c !== void 0 ? _c : (options.colId || "");
5452
+ if (propName === "*") {
5453
+ propName = "title";
5454
+ }
5455
+ if (order == null) {
5456
+ propName = nativeOrderPropName;
5457
+ order = "asc";
5458
+ }
5459
+ this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
5460
+ assert(propName, "No property name specified");
5461
+ const cmp = (a, b) => {
5462
+ let av, bv;
5463
+ if (NODE_DICT_PROPS.has(propName)) {
5464
+ av = a[propName];
5465
+ bv = b[propName];
5466
+ }
5467
+ else {
5468
+ av = a.data[propName];
5469
+ bv = b.data[propName];
5470
+ }
5471
+ if (av == null && bv == null) {
5472
+ return 0;
5473
+ }
5474
+ if (av == null) {
5475
+ av = typeof bv === "string" ? "" : 0;
5476
+ }
5477
+ else if (typeof av === "boolean") {
5478
+ av = av ? 1 : 0;
5479
+ }
5480
+ if (bv == null) {
5481
+ bv = typeof av === "string" ? "" : 0;
5482
+ }
5483
+ else if (typeof bv === "boolean") {
5484
+ bv = bv ? 1 : 0;
5485
+ }
5486
+ if (caseInsensitive) {
5487
+ if (typeof av === "string") {
5488
+ av = av.toLowerCase();
5489
+ }
5490
+ if (typeof bv === "string") {
5491
+ bv = bv.toLowerCase();
5492
+ }
5493
+ }
5494
+ if (order === "desc") {
5495
+ return av === bv ? 0 : av > bv ? -1 : 1;
5496
+ }
5497
+ return av === bv ? 0 : av > bv ? 1 : -1;
5498
+ };
5499
+ return this.sortChildren(cmp, deep);
5500
+ }
5357
5501
  /**
5358
5502
  * Trigger `modifyChild` event on a parent to signal that a child was modified.
5359
5503
  * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
@@ -5462,7 +5606,7 @@ WunderbaumNode.sequence = 0;
5462
5606
  /*!
5463
5607
  * Wunderbaum - ext-edit
5464
5608
  * Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
5465
- * v0.10.1, Sat, 20 Jul 2024 13:53:46 GMT (https://github.com/mar10/wunderbaum)
5609
+ * v0.11.1, Fri, 27 Dec 2024 22:58:06 GMT (https://github.com/mar10/wunderbaum)
5466
5610
  */
5467
5611
  // const START_MARKER = "\uFFF7";
5468
5612
  class EditExtension extends WunderbaumExtension {
@@ -5626,6 +5770,10 @@ class EditExtension extends WunderbaumExtension {
5626
5770
  if (!node) {
5627
5771
  return;
5628
5772
  }
5773
+ if (node.isStatusNode()) {
5774
+ node.logWarn("Cannot edit status node.");
5775
+ return;
5776
+ }
5629
5777
  this.tree.logDebug(`startEditTitle(node=${node})`);
5630
5778
  let inputHtml = node._callEvent("edit.beforeEdit");
5631
5779
  if (inputHtml === false) {
@@ -5793,8 +5941,8 @@ class EditExtension extends WunderbaumExtension {
5793
5941
  * https://github.com/mar10/wunderbaum
5794
5942
  *
5795
5943
  * Released under the MIT license.
5796
- * @version v0.10.1
5797
- * @date Sat, 20 Jul 2024 13:53:46 GMT
5944
+ * @version v0.11.1
5945
+ * @date Fri, 27 Dec 2024 22:58:06 GMT
5798
5946
  */
5799
5947
  // import "./wunderbaum.scss";
5800
5948
  class WbSystemRoot extends WunderbaumNode {
@@ -5876,7 +6024,7 @@ class Wunderbaum {
5876
6024
  debugLevel: DEFAULT_DEBUGLEVEL, // 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
5877
6025
  header: null, // Show/hide header (pass bool or string)
5878
6026
  // headerHeightPx: ROW_HEIGHT,
5879
- rowHeightPx: ROW_HEIGHT,
6027
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
5880
6028
  iconMap: "bootstrap",
5881
6029
  columns: null,
5882
6030
  types: null,
@@ -5961,10 +6109,15 @@ class Wunderbaum {
5961
6109
  if (!this.element.getAttribute("tabindex")) {
5962
6110
  this.element.tabIndex = 0;
5963
6111
  }
6112
+ if (opts.rowHeightPx !== DEFAULT_ROW_HEIGHT) {
6113
+ this.element.style.setProperty("--wb-row-outer-height", opts.rowHeightPx + "px");
6114
+ this.element.style.setProperty("--wb-row-inner-height", opts.rowHeightPx - 2 + "px");
6115
+ }
5964
6116
  // Attach tree instance to <div>
5965
6117
  this.element._wb_tree = this;
5966
6118
  // Create header markup, or take it from the existing html
5967
- this.headerElement = this.element.querySelector("div.wb-header");
6119
+ this.headerElement =
6120
+ this.element.querySelector("div.wb-header");
5968
6121
  const wantHeader = opts.header == null ? this.columns.length > 1 : !!opts.header;
5969
6122
  if (this.headerElement) {
5970
6123
  // User existing header markup to define `this.columns`
@@ -6001,8 +6154,10 @@ class Wunderbaum {
6001
6154
  <div class="wb-node-list"></div>
6002
6155
  </div>`;
6003
6156
  this.listContainerElement = this.element.querySelector("div.wb-list-container");
6004
- this.nodeListElement = this.listContainerElement.querySelector("div.wb-node-list");
6005
- this.headerElement = this.element.querySelector("div.wb-header");
6157
+ this.nodeListElement =
6158
+ this.listContainerElement.querySelector("div.wb-node-list");
6159
+ this.headerElement =
6160
+ this.element.querySelector("div.wb-header");
6006
6161
  this.element.classList.toggle("wb-grid", this.columns.length > 1);
6007
6162
  this._initExtensions();
6008
6163
  // --- apply initial options
@@ -6059,6 +6214,16 @@ class Wunderbaum {
6059
6214
  this.update(ChangeType.resize);
6060
6215
  });
6061
6216
  this.resizeObserver.observe(this.element);
6217
+ onEvent(this.element, "click", ".wb-button,.wb-col-icon", (e) => {
6218
+ var _a, _b;
6219
+ const info = Wunderbaum.getEventInfo(e);
6220
+ const command = (_b = (_a = e.target) === null || _a === void 0 ? void 0 : _a.dataset) === null || _b === void 0 ? void 0 : _b.command;
6221
+ this._callEvent("buttonClick", {
6222
+ event: e,
6223
+ info: info,
6224
+ command: command,
6225
+ });
6226
+ });
6062
6227
  onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
6063
6228
  const info = Wunderbaum.getEventInfo(e);
6064
6229
  const node = info.node;
@@ -6357,31 +6522,33 @@ class Wunderbaum {
6357
6522
  }
6358
6523
  /** Return the topmost visible node in the viewport. */
6359
6524
  getTopmostVpNode(complete = true) {
6525
+ const rowHeight = this.options.rowHeightPx;
6360
6526
  const gracePx = 1; // ignore subpixel scrolling
6361
6527
  const scrollParent = this.element;
6362
6528
  // const headerHeight = this.headerElement.clientHeight; // May be 0
6363
6529
  const scrollTop = scrollParent.scrollTop; // + headerHeight;
6364
6530
  let topIdx;
6365
6531
  if (complete) {
6366
- topIdx = Math.ceil((scrollTop - gracePx) / ROW_HEIGHT);
6532
+ topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
6367
6533
  }
6368
6534
  else {
6369
- topIdx = Math.floor(scrollTop / ROW_HEIGHT);
6535
+ topIdx = Math.floor(scrollTop / rowHeight);
6370
6536
  }
6371
6537
  return this._getNodeByRowIdx(topIdx);
6372
6538
  }
6373
6539
  /** Return the lowest visible node in the viewport. */
6374
6540
  getLowestVpNode(complete = true) {
6541
+ const rowHeight = this.options.rowHeightPx;
6375
6542
  const scrollParent = this.element;
6376
6543
  const headerHeight = this.headerElement.clientHeight; // May be 0
6377
6544
  const scrollTop = scrollParent.scrollTop;
6378
6545
  const clientHeight = scrollParent.clientHeight - headerHeight;
6379
6546
  let bottomIdx;
6380
6547
  if (complete) {
6381
- bottomIdx = Math.floor((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6548
+ bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
6382
6549
  }
6383
6550
  else {
6384
- bottomIdx = Math.ceil((scrollTop + clientHeight) / ROW_HEIGHT) - 1;
6551
+ bottomIdx = Math.ceil((scrollTop + clientHeight) / rowHeight) - 1;
6385
6552
  }
6386
6553
  bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
6387
6554
  return this._getNodeByRowIdx(bottomIdx);
@@ -6639,7 +6806,7 @@ class Wunderbaum {
6639
6806
  }
6640
6807
  /** Run code, but defer rendering of viewport until done.
6641
6808
  *
6642
- * ```
6809
+ * ```js
6643
6810
  * tree.runWithDeferredUpdate(() => {
6644
6811
  * return someFuncThatWouldUpdateManyNodes();
6645
6812
  * });
@@ -6813,8 +6980,9 @@ class Wunderbaum {
6813
6980
  * @param includeHidden Not yet implemented
6814
6981
  */
6815
6982
  findRelatedNode(node, where, includeHidden = false) {
6983
+ const rowHeight = this.options.rowHeightPx;
6816
6984
  let res = null;
6817
- const pageSize = Math.floor(this.listContainerElement.clientHeight / ROW_HEIGHT);
6985
+ const pageSize = Math.floor(this.listContainerElement.clientHeight / rowHeight);
6818
6986
  switch (where) {
6819
6987
  case "parent":
6820
6988
  if (node.parent && node.parent.parent) {
@@ -7117,13 +7285,17 @@ class Wunderbaum {
7117
7285
  console.warn(this.toString(), ...args); // eslint-disable-line no-console
7118
7286
  }
7119
7287
  }
7120
- /** Reset column widths to default. */
7288
+ /** Reset column widths to default. @since 0.10.0 */
7121
7289
  resetColumns() {
7122
7290
  this.columns.forEach((col) => {
7123
7291
  delete col.customWidthPx;
7124
7292
  });
7125
7293
  this.update(ChangeType.colStructure);
7126
7294
  }
7295
+ // /** Renumber nodes `_nativeIndex`. @see {@link WunderbaumNode.resetNativeChildOrder} */
7296
+ // resetNativeChildOrder(options?: ResetOrderOptions) {
7297
+ // this.root.resetNativeChildOrder(options);
7298
+ // }
7127
7299
  /**
7128
7300
  * Make sure that this node is vertically scrolled into the viewport.
7129
7301
  *
@@ -7133,6 +7305,7 @@ class Wunderbaum {
7133
7305
  scrollTo(nodeOrOpts) {
7134
7306
  const PADDING = 2; // leave some pixels between viewport bounds
7135
7307
  let node;
7308
+ // WunderbaumNode;
7136
7309
  let options;
7137
7310
  if (nodeOrOpts instanceof WunderbaumNode) {
7138
7311
  node = nodeOrOpts;
@@ -7142,14 +7315,15 @@ class Wunderbaum {
7142
7315
  node = options.node;
7143
7316
  }
7144
7317
  assert(node && node._rowIdx != null, `Invalid node: ${node}`);
7318
+ const rowHeight = this.options.rowHeightPx;
7145
7319
  const scrollParent = this.element;
7146
7320
  const headerHeight = this.headerElement.clientHeight; // May be 0
7147
7321
  const scrollTop = scrollParent.scrollTop;
7148
7322
  const vpHeight = scrollParent.clientHeight;
7149
- const rowTop = node._rowIdx * ROW_HEIGHT + headerHeight;
7323
+ const rowTop = node._rowIdx * rowHeight + headerHeight;
7150
7324
  const vpTop = headerHeight;
7151
7325
  const vpRowTop = rowTop - scrollTop;
7152
- const vpRowBottom = vpRowTop + ROW_HEIGHT;
7326
+ const vpRowBottom = vpRowTop + rowHeight;
7153
7327
  const topNode = options === null || options === void 0 ? void 0 : options.topNode;
7154
7328
  // this.log( `scrollTo(${node.title}), vpTop:${vpTop}px, scrollTop:${scrollTop}, vpHeight:${vpHeight}, rowTop:${rowTop}, vpRowTop:${vpRowTop}`, nodeOrOpts , options);
7155
7329
  let newScrollTop = null;
@@ -7158,7 +7332,7 @@ class Wunderbaum {
7158
7332
  else {
7159
7333
  // Node is below viewport
7160
7334
  // this.log("Below viewport");
7161
- newScrollTop = rowTop + ROW_HEIGHT - vpHeight + PADDING; // leave some pixels between viewport bounds
7335
+ newScrollTop = rowTop + rowHeight - vpHeight + PADDING; // leave some pixels between viewport bounds
7162
7336
  }
7163
7337
  }
7164
7338
  else {
@@ -7438,6 +7612,14 @@ class Wunderbaum {
7438
7612
  sortChildren(cmp = nodeTitleSorter, deep = false) {
7439
7613
  this.root.sortChildren(cmp, deep);
7440
7614
  }
7615
+ /**
7616
+ * Convenience method to implement column sorting.
7617
+ * @see {@link WunderbaumNode.sortByProperty}.
7618
+ * @since 0.11.0
7619
+ */
7620
+ sortByProperty(options) {
7621
+ this.root.sortByProperty(options);
7622
+ }
7441
7623
  /** Convert tree to an array of plain objects.
7442
7624
  *
7443
7625
  * @param callback is called for every node, in order to allow
@@ -7551,6 +7733,11 @@ class Wunderbaum {
7551
7733
  // }
7552
7734
  return modified;
7553
7735
  }
7736
+ _insertIcon(icon, elem) {
7737
+ const iconElem = document.createElement("i");
7738
+ iconElem.className = icon;
7739
+ elem.appendChild(iconElem);
7740
+ }
7554
7741
  /** Create/update header markup from `this.columns` definition.
7555
7742
  * @internal
7556
7743
  */
@@ -7561,6 +7748,7 @@ class Wunderbaum {
7561
7748
  if (!wantHeader) {
7562
7749
  return;
7563
7750
  }
7751
+ const iconMap = this.iconMap;
7564
7752
  const colCount = this.columns.length;
7565
7753
  const headerRow = this.headerElement.querySelector(".wb-row");
7566
7754
  assert(headerRow, "Expected a row in header element");
@@ -7579,23 +7767,54 @@ class Wunderbaum {
7579
7767
  else {
7580
7768
  col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
7581
7769
  }
7582
- const title = escapeHtml(col.title || col.id);
7770
+ // Add tooltip to column title
7583
7771
  let tooltip = "";
7584
7772
  if (col.tooltip) {
7585
7773
  tooltip = escapeTooltip(col.tooltip);
7586
7774
  tooltip = ` title="${tooltip}"`;
7587
7775
  }
7588
- let resizer = "";
7776
+ // Add column header icons
7777
+ let addMarkup = "";
7778
+ // NOTE: we use CSS float: right to align icons, so they must be added in
7779
+ // reverse order
7780
+ if (toBool(col.menu, this.options.columnsMenu, false)) {
7781
+ const iconClass = "wb-col-icon-menu " + iconMap.colMenu;
7782
+ const icon = `<i data-command=menu class="wb-col-icon ${iconClass}"></i>`;
7783
+ addMarkup += icon;
7784
+ }
7785
+ if (toBool(col.sortable, this.options.columnsSortable, false)) {
7786
+ let iconClass = "wb-col-icon-sort " + iconMap.colSortable;
7787
+ if (col.sortOrder) {
7788
+ iconClass += `wb-col-sort-${col.sortOrder}`;
7789
+ iconClass +=
7790
+ col.sortOrder === "asc" ? iconMap.colSortAsc : iconMap.colSortDesc;
7791
+ }
7792
+ const icon = `<i data-command=sort class="wb-col-icon ${iconClass}"></i>`;
7793
+ addMarkup += icon;
7794
+ }
7795
+ if (toBool(col.filterable, this.options.columnsFilterable, false)) {
7796
+ colElem.classList.toggle("wb-col-filter", !!col.filterActive);
7797
+ let iconClass = "wb-col-icon-filter " + iconMap.colFilter;
7798
+ if (col.filterActive) {
7799
+ iconClass += iconMap.colFilterActive;
7800
+ }
7801
+ const icon = `<i data-command=filter class="wb-col-icon ${iconClass}"></i>`;
7802
+ addMarkup += icon;
7803
+ }
7804
+ // Add resizer to all but the last column
7589
7805
  if (i < colCount - 1) {
7590
- if (toBool(col.resizable, this.options.resizableColumns, false)) {
7591
- resizer =
7806
+ if (toBool(col.resizable, this.options.columnsResizable, false)) {
7807
+ addMarkup +=
7592
7808
  '<span class="wb-col-resizer wb-col-resizer-active"></span>';
7593
7809
  }
7594
7810
  else {
7595
- resizer = '<span class="wb-col-resizer"></span>';
7811
+ addMarkup += '<span class="wb-col-resizer"></span>';
7596
7812
  }
7597
7813
  }
7598
- colElem.innerHTML = `<span class="wb-col-title"${tooltip}>${title}</span>${resizer}`;
7814
+ // Create column header
7815
+ const title = escapeHtml(col.title || col.id);
7816
+ colElem.innerHTML = `<span class="wb-col-title"${tooltip}>${title}</span>${addMarkup}`;
7817
+ // Highlight active column
7599
7818
  if (this.isCellNav()) {
7600
7819
  colElem.classList.toggle("wb-active", i === this.activeColIdx);
7601
7820
  }
@@ -7726,19 +7945,19 @@ class Wunderbaum {
7726
7945
  // this.log("_updateRows", opts)
7727
7946
  options = Object.assign({ newNodesOnly: false }, options);
7728
7947
  const newNodesOnly = !!options.newNodesOnly;
7729
- const row_height = ROW_HEIGHT;
7730
- const vp_height = this.element.clientHeight;
7948
+ const rowHeight = this.options.rowHeightPx;
7949
+ const vpHeight = this.element.clientHeight;
7731
7950
  const prefetch = RENDER_MAX_PREFETCH;
7732
7951
  // const grace_prefetch = RENDER_MAX_PREFETCH - RENDER_MIN_PREFETCH;
7733
7952
  const ofs = this.element.scrollTop;
7734
- let startIdx = Math.max(0, ofs / row_height - prefetch);
7953
+ let startIdx = Math.max(0, ofs / rowHeight - prefetch);
7735
7954
  startIdx = Math.floor(startIdx);
7736
7955
  // Make sure start is always even, so the alternating row colors don't
7737
7956
  // change when scrolling:
7738
7957
  if (startIdx % 2) {
7739
7958
  startIdx--;
7740
7959
  }
7741
- let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
7960
+ let endIdx = Math.max(0, (ofs + vpHeight) / rowHeight + prefetch);
7742
7961
  endIdx = Math.ceil(endIdx);
7743
7962
  // this.debug("render", opts);
7744
7963
  const obsoleteNodes = new Set();
@@ -7767,21 +7986,21 @@ class Wunderbaum {
7767
7986
  else if (rowDiv && newNodesOnly) {
7768
7987
  obsoleteNodes.delete(node);
7769
7988
  // no need to update existing node markup
7770
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
7989
+ rowDiv.style.top = idx * rowHeight + "px";
7771
7990
  prevElem = rowDiv;
7772
7991
  }
7773
7992
  else {
7774
7993
  obsoleteNodes.delete(node);
7775
7994
  // Create new markup
7776
7995
  if (rowDiv) {
7777
- rowDiv.style.top = idx * ROW_HEIGHT + "px";
7996
+ rowDiv.style.top = idx * rowHeight + "px";
7778
7997
  }
7779
7998
  node._render({ top: top, after: prevElem });
7780
7999
  // node.log("render", top, prevElem, "=>", node._rowElem);
7781
8000
  prevElem = node._rowElem;
7782
8001
  }
7783
8002
  idx++;
7784
- top += row_height;
8003
+ top += rowHeight;
7785
8004
  });
7786
8005
  this.treeRowCount = idx;
7787
8006
  for (const n of obsoleteNodes) {
@@ -8012,6 +8231,7 @@ class Wunderbaum {
8012
8231
  /**
8013
8232
  * Return the number of nodes that match the current filter.
8014
8233
  * @see {@link Wunderbaum.filterNodes}
8234
+ * @since 0.9.0
8015
8235
  */
8016
8236
  countMatches() {
8017
8237
  return this.extensions.filter.countMatches();
@@ -8044,7 +8264,7 @@ class Wunderbaum {
8044
8264
  }
8045
8265
  Wunderbaum.sequence = 0;
8046
8266
  /** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
8047
- Wunderbaum.version = "v0.10.1"; // Set to semver by 'grunt release'
8267
+ Wunderbaum.version = "v0.11.1"; // Set to semver by 'grunt release'
8048
8268
  /** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
8049
8269
  Wunderbaum.util = util;
8050
8270