wunderbaum 0.0.7 → 0.0.8
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 -1
- package/dist/wunderbaum.css +1 -1
- package/dist/wunderbaum.d.ts +97 -53
- package/dist/wunderbaum.esm.js +330 -123
- package/dist/wunderbaum.esm.min.js +20 -20
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +330 -123
- package/dist/wunderbaum.umd.min.js +32 -32
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/common.ts +156 -14
- package/src/types.ts +43 -19
- package/src/util.ts +9 -4
- package/src/wb_node.ts +154 -53
- package/src/wunderbaum.ts +50 -55
package/src/common.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
7
|
+
import { MatcherCallback } from "./types";
|
|
8
|
+
import * as util from "./util";
|
|
9
9
|
import { WunderbaumNode } from "./wb_node";
|
|
10
10
|
|
|
11
11
|
export const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
|
|
@@ -66,7 +66,8 @@ export const iconMap = {
|
|
|
66
66
|
export const KEY_NODATA = "__not_found__";
|
|
67
67
|
|
|
68
68
|
/** Define which keys are handled by embedded <input> control, and should
|
|
69
|
-
* *not* be passed to tree navigation handler in cell-edit mode.
|
|
69
|
+
* *not* be passed to tree navigation handler in cell-edit mode.
|
|
70
|
+
*/
|
|
70
71
|
export const INPUT_KEYS: { [key: string]: Array<string> } = {
|
|
71
72
|
text: ["left", "right", "home", "end", "backspace"],
|
|
72
73
|
number: ["up", "down", "left", "right", "home", "end", "backspace"],
|
|
@@ -79,14 +80,14 @@ export const INPUT_KEYS: { [key: string]: Array<string> } = {
|
|
|
79
80
|
|
|
80
81
|
/** Dict keys that are evaluated by source loader (others are added to `tree.data` instead). */
|
|
81
82
|
export const RESERVED_TREE_SOURCE_KEYS: Set<string> = new Set([
|
|
83
|
+
"_format", // reserved for future use
|
|
84
|
+
"_keyMap", // reserved for future use
|
|
85
|
+
"_positional", // reserved for future use
|
|
86
|
+
"_typeList", // reserved for future use
|
|
87
|
+
"_version", // reserved for future use
|
|
82
88
|
"children",
|
|
83
89
|
"columns",
|
|
84
|
-
"format", // reserved for future use
|
|
85
|
-
"keyMap", // reserved for future use
|
|
86
|
-
"positional", // reserved for future use
|
|
87
|
-
"typeList", // reserved for future use
|
|
88
90
|
"types",
|
|
89
|
-
"version", // reserved for future use
|
|
90
91
|
]);
|
|
91
92
|
|
|
92
93
|
// /** Key codes that trigger grid navigation, even when inside an input element. */
|
|
@@ -123,19 +124,160 @@ export const KEY_TO_ACTION_DICT: { [key: string]: string } = {
|
|
|
123
124
|
Subtract: "collapse",
|
|
124
125
|
};
|
|
125
126
|
|
|
126
|
-
/** Return a callback that returns true if the node title
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
/** Return a callback that returns true if the node title matches the string
|
|
128
|
+
* or regular expression.
|
|
129
|
+
* @see {@link WunderbaumNode.findAll}
|
|
130
|
+
*/
|
|
131
|
+
export function makeNodeTitleMatcher(match: string | RegExp): MatcherCallback {
|
|
132
|
+
if (match instanceof RegExp) {
|
|
133
|
+
return function (node: WunderbaumNode) {
|
|
134
|
+
return (<RegExp>match).test(node.title);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
util.assert(typeof match === "string");
|
|
138
|
+
|
|
139
|
+
// s = escapeRegex(s.toLowerCase());
|
|
129
140
|
return function (node: WunderbaumNode) {
|
|
130
|
-
return node.title
|
|
141
|
+
return node.title === match;
|
|
142
|
+
// console.log("match " + node, node.title.toLowerCase().indexOf(match))
|
|
143
|
+
// return node.title.toLowerCase().indexOf(match) >= 0;
|
|
131
144
|
};
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
/** Return a callback that returns true if the node title starts with a string (case-insensitive). */
|
|
135
|
-
export function makeNodeTitleStartMatcher(s: string):
|
|
136
|
-
s = escapeRegex(s);
|
|
148
|
+
export function makeNodeTitleStartMatcher(s: string): MatcherCallback {
|
|
149
|
+
s = util.escapeRegex(s);
|
|
137
150
|
const reMatch = new RegExp("^" + s, "i");
|
|
138
151
|
return function (node: WunderbaumNode) {
|
|
139
152
|
return reMatch.test(node.title);
|
|
140
153
|
};
|
|
141
154
|
}
|
|
155
|
+
|
|
156
|
+
function unflattenSource(source: any): void {
|
|
157
|
+
const { _format, _keyMap, _positional, children } = source;
|
|
158
|
+
|
|
159
|
+
if (_format !== "flat") {
|
|
160
|
+
throw new Error(`Expected source._format: "flat", but got ${_format}`);
|
|
161
|
+
}
|
|
162
|
+
if (_positional && _positional.includes("children")) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`source._positional must not include "children": ${_positional}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
// Inverse keyMap:
|
|
168
|
+
let longToShort: any = {};
|
|
169
|
+
if (_keyMap) {
|
|
170
|
+
for (const [key, value] of Object.entries(_keyMap)) {
|
|
171
|
+
longToShort[<string>value] = key;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const positionalShort = _positional.map((e: string) => longToShort[e]);
|
|
175
|
+
const newChildren: any[] = [];
|
|
176
|
+
const keyToNodeMap: { [key: string]: number } = {};
|
|
177
|
+
const indexToNodeMap: { [key: number]: any } = {};
|
|
178
|
+
const keyAttrName = longToShort["key"] ?? "key";
|
|
179
|
+
const childrenAttrName = longToShort["children"] ?? "children";
|
|
180
|
+
|
|
181
|
+
for (const [index, node] of children.entries()) {
|
|
182
|
+
// Node entry format:
|
|
183
|
+
// [PARENT_ID, [POSITIONAL_ARGS]]
|
|
184
|
+
// or
|
|
185
|
+
// [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}]
|
|
186
|
+
const [parentId, args, kwargs = {}] = node;
|
|
187
|
+
|
|
188
|
+
// Free up some memory as we go
|
|
189
|
+
node[1] = null;
|
|
190
|
+
if (node[2] != null) {
|
|
191
|
+
node[2] = null;
|
|
192
|
+
}
|
|
193
|
+
// console.log("flatten", parentId, args, kwargs)
|
|
194
|
+
|
|
195
|
+
// We keep `kwargs` as our new node definition. Then we add all positional
|
|
196
|
+
// values to this object:
|
|
197
|
+
args.forEach((val: string, positionalIdx: number) => {
|
|
198
|
+
kwargs[positionalShort[positionalIdx]] = val;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Find the parent node. `null` means 'toplevel'. PARENT_ID may be the numeric
|
|
202
|
+
// index of the source.children list. If PARENT_ID is a string, we search
|
|
203
|
+
// a parent with node.key of this value.
|
|
204
|
+
indexToNodeMap[index] = kwargs;
|
|
205
|
+
const key = kwargs[keyAttrName];
|
|
206
|
+
if (key != null) {
|
|
207
|
+
keyToNodeMap[key] = kwargs;
|
|
208
|
+
}
|
|
209
|
+
let parentNode = null;
|
|
210
|
+
if (parentId === null) {
|
|
211
|
+
// top-level node
|
|
212
|
+
} else if (typeof parentId === "number") {
|
|
213
|
+
parentNode = indexToNodeMap[parentId];
|
|
214
|
+
if (parentNode === undefined) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`unflattenSource: Could not find parent node by index: ${parentId}.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
parentNode = keyToNodeMap[parentId];
|
|
221
|
+
if (parentNode === undefined) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`unflattenSource: Could not find parent node by key: ${parentId}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (parentNode) {
|
|
228
|
+
parentNode[childrenAttrName] ??= [];
|
|
229
|
+
parentNode[childrenAttrName].push(kwargs);
|
|
230
|
+
} else {
|
|
231
|
+
newChildren.push(kwargs);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
delete source.children;
|
|
236
|
+
source.children = newChildren;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function inflateSourceData(source: any): void {
|
|
240
|
+
const { _format, _keyMap, _typeList } = source;
|
|
241
|
+
|
|
242
|
+
if (_format === "flat") {
|
|
243
|
+
unflattenSource(source);
|
|
244
|
+
}
|
|
245
|
+
delete source._format;
|
|
246
|
+
delete source._version;
|
|
247
|
+
delete source._keyMap;
|
|
248
|
+
delete source._typeList;
|
|
249
|
+
delete source._positional;
|
|
250
|
+
|
|
251
|
+
function _iter(childList: any[]) {
|
|
252
|
+
for (let node of childList) {
|
|
253
|
+
// Expand short alias names
|
|
254
|
+
if (_keyMap) {
|
|
255
|
+
// Iterate over a list of names, because we modify inside the loop:
|
|
256
|
+
Object.getOwnPropertyNames(node).forEach((propName) => {
|
|
257
|
+
const long = _keyMap[propName] ?? propName;
|
|
258
|
+
if (long !== propName) {
|
|
259
|
+
node[long] = node[propName];
|
|
260
|
+
delete node[propName];
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// `node` now has long attribute names
|
|
265
|
+
|
|
266
|
+
// Resolve node type indexes
|
|
267
|
+
const type = node.type;
|
|
268
|
+
if (_typeList && type != null && typeof type === "number") {
|
|
269
|
+
const newType = _typeList[type];
|
|
270
|
+
if (newType == null) {
|
|
271
|
+
throw new Error(`Expected typeList[${type}] entry in [${_typeList}]`);
|
|
272
|
+
}
|
|
273
|
+
node.type = newType;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Recursion
|
|
277
|
+
if (node.children) {
|
|
278
|
+
_iter(node.children);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
_iter(source.children);
|
|
283
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
import { WunderbaumNode } from "./wb_node";
|
|
8
8
|
import { Wunderbaum } from "./wunderbaum";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
export type
|
|
10
|
+
/** Passed to find... methods. Should return true if node matches. */
|
|
11
|
+
export type MatcherCallback = (node: WunderbaumNode) => boolean;
|
|
12
|
+
/** When set as option, called when the value is needed (e.g. `colspan` type definition). */
|
|
12
13
|
export type BoolOptionResolver = (node: WunderbaumNode) => boolean;
|
|
14
|
+
/** When set as option, called when the value is needed (e.g. `icon` type definition). */
|
|
15
|
+
export type BoolOrStringOptionResolver = (
|
|
16
|
+
node: WunderbaumNode
|
|
17
|
+
) => boolean | string;
|
|
13
18
|
|
|
14
19
|
export type NodeAnyCallback = (node: WunderbaumNode) => any;
|
|
15
20
|
|
|
@@ -72,13 +77,13 @@ export interface NodeTypeDefinition {
|
|
|
72
77
|
// /** Type ID that matches `node.type`. */
|
|
73
78
|
// id: string;
|
|
74
79
|
/** En/disable checkbox for matching nodes.*/
|
|
75
|
-
checkbox?: boolean |
|
|
80
|
+
checkbox?: boolean | BoolOrStringOptionResolver;
|
|
76
81
|
/** En/disable checkbox for matching nodes.*/
|
|
77
82
|
colspan?: boolean | BoolOptionResolver;
|
|
78
83
|
/** Optional class names that are added to all `div.wb-row` elements of matching nodes.*/
|
|
79
84
|
classes?: string;
|
|
80
85
|
/**Default icon for matching nodes.*/
|
|
81
|
-
icon?: boolean | string |
|
|
86
|
+
icon?: boolean | string | BoolOrStringOptionResolver;
|
|
82
87
|
/**
|
|
83
88
|
* See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
|
|
84
89
|
* to evaluate `node.NAME` setting and `tree.types[node.type].NAME`.
|
|
@@ -215,22 +220,33 @@ export enum TargetType {
|
|
|
215
220
|
title = "title",
|
|
216
221
|
}
|
|
217
222
|
|
|
218
|
-
/**
|
|
219
|
-
export
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
/** Possible values for {@link WunderbaumNode.addChildren()}. */
|
|
224
|
+
export interface AddChildrenOptions {
|
|
225
|
+
/** Insert children before this node (or index)
|
|
226
|
+
* @default undefined or null: append as last child
|
|
227
|
+
*/
|
|
228
|
+
before?: WunderbaumNode | number | null;
|
|
229
|
+
/**
|
|
230
|
+
* Set `node.expanded = true` according to tree.options.minExpandLevel.
|
|
231
|
+
* This does *not* load lazy nodes.
|
|
232
|
+
* @default true
|
|
233
|
+
*/
|
|
234
|
+
applyMinExpanLevel?: boolean;
|
|
235
|
+
/** (@internal Internal use, do not set! ) */
|
|
236
|
+
_level?: number;
|
|
224
237
|
}
|
|
225
238
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
239
|
+
/** Possible values for {@link Wunderbaum.expandAll()} and {@link WunderbaumNode.expandAll()}. */
|
|
240
|
+
export interface ExpandAllOptions {
|
|
241
|
+
/** Restrict expand level @default 99 */
|
|
242
|
+
depth?: number;
|
|
243
|
+
/** Expand and load lazy nodes @default false */
|
|
244
|
+
loadLazy?: boolean;
|
|
245
|
+
/** Ignore `minExpandLevel` option @default false */
|
|
246
|
+
force?: boolean;
|
|
247
|
+
}
|
|
232
248
|
|
|
233
|
-
/** Possible values for
|
|
249
|
+
/** Possible values for {@link WunderbaumNode.makeVisible()}. */
|
|
234
250
|
export interface MakeVisibleOptions {
|
|
235
251
|
/** Do not animate expand (currently not implemented). @default false */
|
|
236
252
|
noAnimation?: boolean;
|
|
@@ -240,7 +256,15 @@ export interface MakeVisibleOptions {
|
|
|
240
256
|
noEvents?: boolean;
|
|
241
257
|
}
|
|
242
258
|
|
|
243
|
-
/**
|
|
259
|
+
/** Initial navigation mode and possible transition. */
|
|
260
|
+
export enum NavigationOptions {
|
|
261
|
+
startRow = "startRow", // Start with row mode, but allow cell-nav mode
|
|
262
|
+
cell = "cell", // Cell-nav mode only
|
|
263
|
+
startCell = "startCell", // Start in cell-nav mode, but allow row mode
|
|
264
|
+
row = "row", // Row mode only
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Possible values for {@link scrollIntoView()}. */
|
|
244
268
|
export interface ScrollIntoViewOptions {
|
|
245
269
|
/** Do not animate (currently not implemented). @default false */
|
|
246
270
|
noAnimation?: boolean;
|
|
@@ -252,7 +276,7 @@ export interface ScrollIntoViewOptions {
|
|
|
252
276
|
ofsY?: number;
|
|
253
277
|
}
|
|
254
278
|
|
|
255
|
-
/** Possible values for
|
|
279
|
+
/** Possible values for {@link Wunderbaum.scrollTo()}. */
|
|
256
280
|
export interface ScrollToOptions extends ScrollIntoViewOptions {
|
|
257
281
|
/** Which node to scroll into the viewport.*/
|
|
258
282
|
node: WunderbaumNode;
|
package/src/util.ts
CHANGED
|
@@ -312,11 +312,16 @@ export function setValueToElem(elem: HTMLElement, value: any): void {
|
|
|
312
312
|
case "week":
|
|
313
313
|
case "datetime":
|
|
314
314
|
case "datetime-local":
|
|
315
|
-
input.valueAsDate = value;
|
|
315
|
+
input.valueAsDate = new Date(value);
|
|
316
|
+
// input.valueAsDate = value; // breaks in Edge?
|
|
316
317
|
break;
|
|
317
318
|
case "number":
|
|
318
319
|
case "range":
|
|
319
|
-
|
|
320
|
+
if (value == null) {
|
|
321
|
+
input.value = value;
|
|
322
|
+
} else {
|
|
323
|
+
input.valueAsNumber = value;
|
|
324
|
+
}
|
|
320
325
|
break;
|
|
321
326
|
case "radio":
|
|
322
327
|
error("Not implemented");
|
|
@@ -333,7 +338,7 @@ export function setValueToElem(elem: HTMLElement, value: any): void {
|
|
|
333
338
|
break;
|
|
334
339
|
case "text":
|
|
335
340
|
default:
|
|
336
|
-
input.value = value
|
|
341
|
+
input.value = value ?? "";
|
|
337
342
|
}
|
|
338
343
|
} else if (tag === "SELECT") {
|
|
339
344
|
const select = <HTMLSelectElement>elem;
|
|
@@ -669,7 +674,7 @@ export function toggleCheckbox(
|
|
|
669
674
|
export function getOption(
|
|
670
675
|
opts: any,
|
|
671
676
|
name: string,
|
|
672
|
-
defaultValue = undefined
|
|
677
|
+
defaultValue: any = undefined
|
|
673
678
|
): any {
|
|
674
679
|
let ext;
|
|
675
680
|
|