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/src/common.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
7
- import { MatcherType } from "./types";
8
- import { escapeRegex } from "./util";
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 contains a substring (case-insensitive). */
127
- export function makeNodeTitleMatcher(s: string): MatcherType {
128
- s = escapeRegex(s.toLowerCase());
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.toLowerCase().indexOf(s) >= 0;
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): MatcherType {
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
- // export type WunderbaumOptions = any;
11
- export type MatcherType = (node: WunderbaumNode) => boolean;
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 | BoolOptionResolver;
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 | BoolOptionResolver;
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
- /** Initial navigation mode and possible transition. */
219
- export enum NavigationOptions {
220
- startRow = "startRow", // Start with row mode, but allow cell-nav mode
221
- cell = "cell", // Cell-nav mode only
222
- startCell = "startCell", // Start in cell-nav mode, but allow row mode
223
- row = "row", // Row mode only
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
- // /** Tree's current navigation mode (see `tree.setNavigationMode()`). */
227
- // export enum NavigationMode {
228
- // row = "row",
229
- // cellNav = "cellNav",
230
- // // cellEdit = "cellEdit",
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 `node.makeVisible()`. */
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
- /** Possible values for `node.scrollIntoView()`. */
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 `tree.scrollTo()`. */
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
- input.valueAsNumber = value;
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