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.
- package/README.md +6 -7
- package/dist/wunderbaum.css +18 -10
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +204 -49
- package/dist/wunderbaum.esm.js +826 -606
- package/dist/wunderbaum.esm.min.js +26 -26
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +826 -606
- package/dist/wunderbaum.umd.min.js +32 -32
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +11 -10
- package/src/common.ts +17 -2
- package/src/types.ts +126 -30
- package/src/util.ts +18 -8
- package/src/wb_ext_dnd.ts +9 -4
- package/src/wb_ext_edit.ts +4 -0
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_node.ts +164 -17
- package/src/wb_options.ts +26 -2
- package/src/wunderbaum.scss +10 -0
- package/src/wunderbaum.ts +124 -47
package/dist/wunderbaum.esm.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 -
|
|
1997
|
+
* Wunderbaum - ext-dnd
|
|
1992
1998
|
* Copyright (c) 2021-2024, Martin Wendt. Released under the MIT license.
|
|
1993
|
-
* v0.
|
|
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
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
-
|
|
2163
|
-
|
|
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
|
-
|
|
2166
|
-
|
|
2167
|
-
//
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
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 *
|
|
2104
|
+
return dy < 0.25 * rowHeight
|
|
2423
2105
|
? "before"
|
|
2424
|
-
: dy > 0.75 *
|
|
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 >
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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
|
-
|
|
2804
|
-
|
|
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
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
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
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
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
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2904
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
3122
|
-
data.
|
|
3123
|
-
|
|
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 ? (
|
|
3181
|
+
data.checkbox != null ? intToBool(data.checkbox) : 0;
|
|
3130
3182
|
data.radiogroup != null ? (this.radiogroup = !!data.radiogroup) : 0;
|
|
3131
|
-
this.selected = data.selected
|
|
3132
|
-
data.unselectable
|
|
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
|
-
|
|
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 *
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
5797
|
-
* @date
|
|
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:
|
|
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 =
|
|
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 =
|
|
6005
|
-
|
|
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) /
|
|
6532
|
+
topIdx = Math.ceil((scrollTop - gracePx) / rowHeight);
|
|
6367
6533
|
}
|
|
6368
6534
|
else {
|
|
6369
|
-
topIdx = Math.floor(scrollTop /
|
|
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) /
|
|
6548
|
+
bottomIdx = Math.floor((scrollTop + clientHeight) / rowHeight) - 1;
|
|
6382
6549
|
}
|
|
6383
6550
|
else {
|
|
6384
|
-
bottomIdx = Math.ceil((scrollTop + clientHeight) /
|
|
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 /
|
|
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 *
|
|
7323
|
+
const rowTop = node._rowIdx * rowHeight + headerHeight;
|
|
7150
7324
|
const vpTop = headerHeight;
|
|
7151
7325
|
const vpRowTop = rowTop - scrollTop;
|
|
7152
|
-
const vpRowBottom = vpRowTop +
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
7591
|
-
|
|
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
|
-
|
|
7811
|
+
addMarkup += '<span class="wb-col-resizer"></span>';
|
|
7596
7812
|
}
|
|
7597
7813
|
}
|
|
7598
|
-
|
|
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
|
|
7730
|
-
const
|
|
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 /
|
|
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 +
|
|
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 *
|
|
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 *
|
|
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 +=
|
|
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.
|
|
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
|
|