wunderbaum 0.0.4 → 0.0.7

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/wb_ext_dnd.ts CHANGED
@@ -8,162 +8,10 @@ import { EventCallbackType, onEvent } from "./util";
8
8
  import { Wunderbaum } from "./wunderbaum";
9
9
  import { WunderbaumExtension } from "./wb_extension_base";
10
10
  import { WunderbaumNode } from "./wb_node";
11
- import { ROW_HEIGHT, WbNodeEventType } from "./common";
11
+ import { DropRegionType, DropRegionTypeSet } from "./types";
12
+ import { ROW_HEIGHT } from "./common";
12
13
 
13
14
  const nodeMimeType = "application/x-wunderbaum-node";
14
- export type DropRegionType = "over" | "before" | "after";
15
- type DropRegionTypeSet = Set<DropRegionType>;
16
- // type AllowedDropRegionType =
17
- // | "after"
18
- // | "afterBefore"
19
- // // | "afterBeforeOver" // == all == true
20
- // | "afterOver"
21
- // | "all" // == true
22
- // | "before"
23
- // | "beforeOver"
24
- // | "none" // == false == "" == null
25
- // | "over"; // == "child"
26
-
27
- export type DndOptionsType = {
28
- /**
29
- * Expand nodes after n milliseconds of hovering
30
- * Default: 1500
31
- */
32
- autoExpandMS: 1500;
33
- // /**
34
- // * Additional offset for drop-marker with hitMode = "before"/"after"
35
- // * Default:
36
- // */
37
- // dropMarkerInsertOffsetX: -16;
38
- // /**
39
- // * Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
40
- // * Default:
41
- // */
42
- // dropMarkerOffsetX: -24;
43
- // /**
44
- // * Root Container used for drop marker (could be a shadow root)
45
- // * (#1021 `document.body` is not available yet)
46
- // * Default:
47
- // */
48
- // dropMarkerParent: "body";
49
- /**
50
- * true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
51
- * Default: false
52
- */
53
- multiSource: false;
54
- /**
55
- * Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
56
- * Default: "all"
57
- */
58
- effectAllowed: "all";
59
- // /**
60
- // * 'copy'|'link'|'move'|'auto'(calculate from `effectAllowed`+modifier keys) or callback(node, data) that returns such string.
61
- // * Default:
62
- // */
63
- // dropEffect: "auto";
64
- /**
65
- * Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (overide in dragDrag, dragOver).
66
- * Default: "move"
67
- */
68
- dropEffectDefault: string;
69
- /**
70
- * Prevent dropping nodes from different Wunderbaum trees
71
- * Default: false
72
- */
73
- preventForeignNodes: boolean;
74
- /**
75
- * Prevent dropping items on unloaded lazy Wunderbaum tree nodes
76
- * Default: true
77
- */
78
- preventLazyParents: boolean;
79
- /**
80
- * Prevent dropping items other than Wunderbaum tree nodes
81
- * Default: false
82
- */
83
- preventNonNodes: boolean;
84
- /**
85
- * Prevent dropping nodes on own descendants
86
- * Default: true
87
- */
88
- preventRecursion: boolean;
89
- /**
90
- * Prevent dropping nodes under same direct parent
91
- * Default: false
92
- */
93
- preventSameParent: false;
94
- /**
95
- * Prevent dropping nodes 'before self', etc. (move only)
96
- * Default: true
97
- */
98
- preventVoidMoves: boolean;
99
- /**
100
- * Enable auto-scrolling while dragging
101
- * Default: true
102
- */
103
- scroll: boolean;
104
- /**
105
- * Active top/bottom margin in pixel
106
- * Default: 20
107
- */
108
- scrollSensitivity: 20;
109
- /**
110
- * Pixel per event
111
- * Default: 5
112
- */
113
- scrollSpeed: 5;
114
- // /**
115
- // * Allow dragging of nodes to different IE windows
116
- // * Default: false
117
- // */
118
- // setTextTypeJson: boolean;
119
- /**
120
- * Optional callback passed to `toDict` on dragStart @since 2.38
121
- * Default: null
122
- */
123
- sourceCopyHook: null;
124
- // Events (drag support)
125
- /**
126
- * Callback(sourceNode, data), return true, to enable dnd drag
127
- * Default: null
128
- */
129
- dragStart?: WbNodeEventType;
130
- /**
131
- * Callback(sourceNode, data)
132
- * Default: null
133
- */
134
- dragDrag: null;
135
- /**
136
- * Callback(sourceNode, data)
137
- * Default: null
138
- */
139
- dragEnd: null;
140
- // Events (drop support)
141
- /**
142
- * Callback(targetNode, data), return true, to enable dnd drop
143
- * Default: null
144
- */
145
- dragEnter: null;
146
- /**
147
- * Callback(targetNode, data)
148
- * Default: null
149
- */
150
- dragOver: null;
151
- /**
152
- * Callback(targetNode, data), return false to prevent autoExpand
153
- * Default: null
154
- */
155
- dragExpand: null;
156
- /**
157
- * Callback(targetNode, data)
158
- * Default: null
159
- */
160
- dragDrop: null;
161
- /**
162
- * Callback(targetNode, data)
163
- * Default: null
164
- */
165
- dragLeave: null;
166
- };
167
15
 
168
16
  export class DndExtension extends WunderbaumExtension {
169
17
  // public dropMarkerElem?: HTMLElement;
@@ -245,8 +93,9 @@ export class DndExtension extends WunderbaumExtension {
245
93
  const ltn = this.lastTargetNode;
246
94
  this.lastEnterStamp = 0;
247
95
  if (ltn) {
248
- ltn.removeClass(
249
- "wb-drop-target wb-drop-over wb-drop-after wb-drop-before"
96
+ ltn.setClass(
97
+ "wb-drop-target wb-drop-over wb-drop-after wb-drop-before",
98
+ false
250
99
  );
251
100
  this.lastTargetNode = null;
252
101
  }
@@ -295,7 +144,7 @@ export class DndExtension extends WunderbaumExtension {
295
144
  protected autoScroll(event: DragEvent): number {
296
145
  let tree = this.tree,
297
146
  dndOpts = tree.options.dnd!,
298
- sp = tree.scrollContainer,
147
+ sp = tree.scrollContainerElement,
299
148
  sensitivity = dndOpts.scrollSensitivity,
300
149
  speed = dndOpts.scrollSpeed,
301
150
  scrolled = 0;
@@ -352,7 +201,7 @@ export class DndExtension extends WunderbaumExtension {
352
201
  setTimeout(() => {
353
202
  // Decouple this call, so the CSS is applied to the node, but not to
354
203
  // the system generated drag image
355
- srcNode.addClass("wb-drag-source");
204
+ srcNode.setClass("wb-drag-source");
356
205
  }, 0);
357
206
 
358
207
  // --- drag ---
@@ -360,7 +209,7 @@ export class DndExtension extends WunderbaumExtension {
360
209
  // This event occurs very often...
361
210
  // --- dragend ---
362
211
  } else if (e.type === "dragend") {
363
- srcNode.removeClass("wb-drag-source");
212
+ srcNode.setClass("wb-drag-source", false);
364
213
  this.srcNode = null;
365
214
  if (this.lastTargetNode) {
366
215
  this._leaveNode();
@@ -424,7 +273,7 @@ export class DndExtension extends WunderbaumExtension {
424
273
  }
425
274
  this.lastAllowedDropRegions = regionSet;
426
275
  this.lastDropEffect = dt.dropEffect;
427
- targetNode.addClass("wb-drop-target");
276
+ targetNode.setClass("wb-drop-target");
428
277
 
429
278
  e.preventDefault(); // Allow drop (Drop operation is denied by default)
430
279
  return false;
@@ -450,9 +299,9 @@ export class DndExtension extends WunderbaumExtension {
450
299
  if (!region) {
451
300
  return; // We already rejected in dragenter
452
301
  }
453
- targetNode.toggleClass("wb-drop-over", region === "over");
454
- targetNode.toggleClass("wb-drop-before", region === "before");
455
- targetNode.toggleClass("wb-drop-after", region === "after");
302
+ targetNode.setClass("wb-drop-over", region === "over");
303
+ targetNode.setClass("wb-drop-before", region === "before");
304
+ targetNode.setClass("wb-drop-after", region === "after");
456
305
  // console.log("dragover", e);
457
306
 
458
307
  // dt.dropEffect = this.lastDropEffect!;
@@ -17,7 +17,7 @@ import {
17
17
  } from "./util";
18
18
  import { debounce } from "./debounce";
19
19
  import { WunderbaumNode } from "./wb_node";
20
- import { AddNodeType, NavigationMode } from "./common";
20
+ import { AddNodeType } from "./types";
21
21
  import { WbNodeData } from "./wb_options";
22
22
 
23
23
  // const START_MARKER = "\uFFF7";
@@ -63,14 +63,14 @@ export class EditExtension extends WunderbaumExtension {
63
63
 
64
64
  node.log(`_applyChange(${eventName})`, extra);
65
65
 
66
- colElem.classList.add("wb-dirty");
66
+ colElem.classList.add("wb-busy");
67
67
  colElem.classList.remove("wb-error");
68
68
  try {
69
69
  res = node._callEvent(eventName, extra);
70
70
  } catch (err) {
71
71
  node.logError(`Error in ${eventName} event handler`, err);
72
72
  colElem.classList.add("wb-error");
73
- colElem.classList.remove("wb-dirty");
73
+ colElem.classList.remove("wb-busy");
74
74
  }
75
75
  // Convert scalar return value to a resolved promise
76
76
  if (!(res instanceof Promise)) {
@@ -82,7 +82,7 @@ export class EditExtension extends WunderbaumExtension {
82
82
  colElem.classList.add("wb-error");
83
83
  })
84
84
  .finally(() => {
85
- colElem.classList.remove("wb-dirty");
85
+ colElem.classList.remove("wb-busy");
86
86
  });
87
87
  return res;
88
88
  }
@@ -130,15 +130,14 @@ export class EditExtension extends WunderbaumExtension {
130
130
  const eventName = eventToString(event);
131
131
  const tree = this.tree;
132
132
  const trigger = this.getPluginOption("trigger");
133
- const inputElem =
134
- event.target && event.target.closest("input,[contenteditable]");
135
- // let handled = true;
133
+ // const inputElem =
134
+ // event.target && event.target.closest("input,[contenteditable]");
136
135
 
137
- tree.logDebug(`_preprocessKeyEvent: ${eventName}`);
136
+ // tree.logDebug(`_preprocessKeyEvent: ${eventName}`);
138
137
 
139
138
  // --- Title editing: apply/discard ---
140
- if (inputElem) {
141
- //this.isEditingTitle()) {
139
+ // if (inputElem) {
140
+ if (this.isEditingTitle()) {
142
141
  switch (eventName) {
143
142
  case "Enter":
144
143
  this._stopEditTitle(true, { event: event });
@@ -152,7 +151,7 @@ export class EditExtension extends WunderbaumExtension {
152
151
  return false;
153
152
  }
154
153
  // --- Trigger title editing
155
- if (tree.navMode === NavigationMode.row || tree.activeColIdx === 0) {
154
+ if (tree.isRowNav() || tree.activeColIdx === 0) {
156
155
  switch (eventName) {
157
156
  case "Enter":
158
157
  if (trigger.indexOf("macEnter") >= 0 && isMac) {
@@ -193,8 +192,9 @@ export class EditExtension extends WunderbaumExtension {
193
192
  node.logInfo("beforeEdit canceled operation.");
194
193
  return;
195
194
  }
196
- // `beforeEdit(e)` may return an input HTML string. Otherwise use a default:
197
- if (!inputHtml) {
195
+ // `beforeEdit(e)` may return an input HTML string. Otherwise use a default.
196
+ // (we also treat a `true` return value as 'use default'):
197
+ if (inputHtml === true || !inputHtml) {
198
198
  const title = escapeHtml(node.title);
199
199
  inputHtml = `<input type=text class="wb-input-edit" value="${title}" required autocorrect=off>`;
200
200
  }
@@ -205,7 +205,7 @@ export class EditExtension extends WunderbaumExtension {
205
205
  titleSpan.innerHTML = inputHtml;
206
206
  const inputElem = titleSpan.firstElementChild as HTMLInputElement;
207
207
  if (validity) {
208
- // Permanently apply input validations (CSS and tooltip)
208
+ // Permanently apply input validations (CSS and tooltip)
209
209
  inputElem.addEventListener("keydown", (e) => {
210
210
  inputElem.setCustomValidity("");
211
211
  if (!inputElem.reportValidity()) {
@@ -336,7 +336,7 @@ export class EditExtension extends WunderbaumExtension {
336
336
  return;
337
337
  }
338
338
  const newNode = node.addNode(init, mode);
339
- newNode.addClass("wb-edit-new");
339
+ newNode.setClass("wb-edit-new");
340
340
  this.relatedNode = node;
341
341
 
342
342
  // Don't filter new nodes:
@@ -11,7 +11,7 @@ import {
11
11
  extend,
12
12
  onEvent,
13
13
  } from "./util";
14
- import { NodeFilterCallback, NodeStatusType } from "./common";
14
+ import { NodeFilterCallback, NodeStatusType } from "./types";
15
15
  import { Wunderbaum } from "./wunderbaum";
16
16
  import { WunderbaumNode } from "./wb_node";
17
17
  import { WunderbaumExtension } from "./wb_extension_base";
@@ -28,6 +28,7 @@ export class FilterExtension extends WunderbaumExtension {
28
28
 
29
29
  constructor(tree: Wunderbaum) {
30
30
  super(tree, "filter", {
31
+ connectInput: null, // Element or selector of an input control for filter query strings
31
32
  autoApply: true, // Re-apply last filter if lazy data is loaded
32
33
  autoExpand: false, // Expand all branches that contain matches while filtered
33
34
  counter: true, // Show a badge with number of matching child nodes near parent icons
@@ -36,16 +37,16 @@ export class FilterExtension extends WunderbaumExtension {
36
37
  hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
37
38
  highlight: true, // Highlight matches by wrapping inside <mark> tags
38
39
  leavesOnly: false, // Match end nodes only
39
- mode: "hide", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
40
+ mode: "dim", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
40
41
  noData: true, // Display a 'no data' status node if result is empty
41
42
  });
42
43
  }
43
44
 
44
45
  init() {
45
46
  super.init();
46
- let attachInput = this.getPluginOption("attachInput");
47
- if (attachInput) {
48
- this.queryInput = elemFromSelector(attachInput) as HTMLInputElement;
47
+ const connectInput = this.getPluginOption("connectInput");
48
+ if (connectInput) {
49
+ this.queryInput = elemFromSelector(connectInput) as HTMLInputElement;
49
50
  onEvent(
50
51
  this.queryInput,
51
52
  "input",
@@ -57,6 +58,17 @@ export class FilterExtension extends WunderbaumExtension {
57
58
  }
58
59
  }
59
60
 
61
+ setPluginOption(name: string, value: any): void {
62
+ // alert("filter opt=" + name + ", " + value)
63
+ super.setPluginOption(name, value);
64
+ switch (name) {
65
+ case "mode":
66
+ this.tree.filterMode = value === "hide" ? "hide" : "dim";
67
+ this.tree.updateFilter();
68
+ break;
69
+ }
70
+ }
71
+
60
72
  _applyFilterNoUpdate(
61
73
  filter: string | NodeFilterCallback,
62
74
  branchMode: boolean,
@@ -4,29 +4,62 @@
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
7
- import { NavigationMode, NavigationModeOption } from "./common";
7
+ import { NavigationOptions } from "./types";
8
8
  import { eventToString } from "./util";
9
9
  import { Wunderbaum } from "./wunderbaum";
10
10
  import { WunderbaumNode } from "./wb_node";
11
11
  import { WunderbaumExtension } from "./wb_extension_base";
12
12
 
13
+ const QUICKSEARCH_DELAY = 500;
14
+
13
15
  export class KeynavExtension extends WunderbaumExtension {
14
16
  constructor(tree: Wunderbaum) {
15
17
  super(tree, "keynav", {});
16
18
  }
17
19
 
20
+ protected _getEmbeddedInputElem(elem: any): HTMLInputElement | null {
21
+ let input = null;
22
+
23
+ if (elem && elem.type != null) {
24
+ input = elem;
25
+ } else {
26
+ // ,[contenteditable]
27
+ const ace = this.tree.getActiveColElem()?.querySelector("input,select");
28
+ if (ace) {
29
+ input = ace as HTMLInputElement;
30
+ }
31
+ }
32
+ return input;
33
+ }
34
+
35
+ /* Return true if the current cell's embedded input has keyboard focus. */
36
+ protected _isCurInputFocused(): boolean {
37
+ const ace = this.tree
38
+ .getActiveColElem()
39
+ ?.querySelector("input:focus,select:focus");
40
+ console.log(`_isCurInputFocused`, ace);
41
+ return !!ace;
42
+ }
43
+
18
44
  onKeyEvent(data: any): boolean | undefined {
19
- let event = data.event,
20
- eventName = eventToString(event),
21
- focusNode,
22
- node = data.node as WunderbaumNode,
45
+ const event = data.event,
23
46
  tree = this.tree,
24
47
  opts = data.options,
25
- handled = true,
26
- activate = !event.ctrlKey || opts.autoActivate;
27
- const navModeOption = opts.navigationMode;
48
+ activate = !event.ctrlKey || opts.autoActivate,
49
+ curInput = this._getEmbeddedInputElem(event.target),
50
+ navModeOption = opts.navigationModeOption as NavigationOptions;
51
+ // isCellEditMode = tree.navMode === NavigationMode.cellEdit;
52
+
53
+ let focusNode,
54
+ eventName = eventToString(event),
55
+ node = data.node as WunderbaumNode,
56
+ handled = true;
28
57
 
29
- tree.logDebug(`onKeyEvent: ${eventName}`);
58
+ // tree.log(`onKeyEvent: ${eventName}, curInput`, curInput);
59
+ if (!tree.isEnabled()) {
60
+ // tree.logDebug(`onKeyEvent ignored for disabled tree: ${eventName}`);
61
+ return false;
62
+ }
30
63
 
31
64
  // Let callback prevent default processing
32
65
  if (tree._callEvent("keydown", data) === false) {
@@ -40,34 +73,38 @@ export class KeynavExtension extends WunderbaumExtension {
40
73
 
41
74
  // Set focus to active (or first node) if no other node has the focus yet
42
75
  if (!node) {
43
- const activeNode = tree.getActiveNode();
76
+ const currentNode = tree.getFocusNode() || tree.getActiveNode();
44
77
  const firstNode = tree.getFirstChild();
45
78
 
46
- if (!activeNode && firstNode && eventName === "ArrowDown") {
79
+ if (!currentNode && firstNode && eventName === "ArrowDown") {
47
80
  firstNode.logInfo("Keydown: activate first node.");
48
81
  firstNode.setActive();
49
82
  return;
50
83
  }
51
84
 
52
- focusNode = activeNode || firstNode;
85
+ focusNode = currentNode || firstNode;
53
86
  if (focusNode) {
54
87
  focusNode.setFocus();
55
88
  node = tree.getFocusNode()!;
56
89
  node.logInfo("Keydown: force focus on active node.");
57
90
  }
58
91
  }
92
+ const isColspan = node.isColspan();
59
93
 
60
- if (tree.navMode === NavigationMode.row) {
94
+ if (tree.isRowNav()) {
95
+ // -----------------------------------------------------------------------
96
+ // --- Row Mode ---
97
+ // -----------------------------------------------------------------------
61
98
  // --- Quick-Search
62
99
  if (
63
100
  opts.quicksearch &&
64
101
  eventName.length === 1 &&
65
- /^\w$/.test(eventName)
66
- // && !$target.is(":input:enabled")
102
+ /^\w$/.test(eventName) &&
103
+ !curInput
67
104
  ) {
68
105
  // Allow to search for longer streaks if typed in quickly
69
106
  const stamp = Date.now();
70
- if (stamp - tree.lastQuicksearchTime > 500) {
107
+ if (stamp - tree.lastQuicksearchTime > QUICKSEARCH_DELAY) {
71
108
  tree.lastQuicksearchTerm = "";
72
109
  }
73
110
  tree.lastQuicksearchTime = stamp;
@@ -93,8 +130,8 @@ export class KeynavExtension extends WunderbaumExtension {
93
130
  case "ArrowRight":
94
131
  if (!node.expanded && (node.children || node.lazy)) {
95
132
  eventName = "Add"; // expand
96
- } else if (navModeOption === NavigationModeOption.startRow) {
97
- tree.setNavigationMode(NavigationMode.cellNav);
133
+ } else if (navModeOption === NavigationOptions.startRow) {
134
+ tree.setCellNav();
98
135
  return;
99
136
  }
100
137
  break;
@@ -143,50 +180,111 @@ export class KeynavExtension extends WunderbaumExtension {
143
180
  handled = false;
144
181
  }
145
182
  } else {
146
- // Standard navigation (cell mode)
183
+ // -----------------------------------------------------------------------
184
+ // --- Cell Mode ---
185
+ // -----------------------------------------------------------------------
186
+ // // Standard navigation (cell mode)
187
+ // if (isCellEditMode && INPUT_BREAKOUT_KEYS.has(eventName)) {
188
+ // }
189
+ const ENTER_EDITS = false;
190
+ const curInput = this._getEmbeddedInputElem(null);
191
+ const curInputType = curInput ? curInput.type || curInput.tagName : "";
192
+ const inputHasFocus = curInput && this._isCurInputFocused();
193
+ const inputCanFocus = curInput && curInputType !== "checkbox";
194
+
195
+ if (inputHasFocus) {
196
+ if (eventName === "Escape") {
197
+ // Discard changes
198
+ node.render();
199
+ // } else if (!INPUT_BREAKOUT_KEYS.has(eventName)) {
200
+ } else if (eventName !== "Enter") {
201
+ // Let current `<input>` handle it
202
+ node.logDebug(`Ignored ${eventName} inside focused input`);
203
+ return;
204
+ }
205
+ // const curInputType = curInput.type || curInput.tagName;
206
+ // const breakoutKeys = INPUT_KEYS[curInputType];
207
+ // if (!breakoutKeys.includes(eventName)) {
208
+ // node.logDebug(`Ignored ${eventName} inside ${curInputType} input`);
209
+ // return;
210
+ // }
211
+ } else if (curInput) {
212
+ // On a cell that has an embedded, unfocused <input>
213
+ if (eventName.length === 1 && inputCanFocus) {
214
+ curInput.focus();
215
+ curInput.value = "";
216
+ node.logDebug(`Focus imput: ${eventName}`);
217
+ return false;
218
+ }
219
+ }
220
+ if (eventName === "Tab") {
221
+ eventName = "ArrowRight";
222
+ handled = true;
223
+ } else if (eventName === "Shift+Tab") {
224
+ eventName = tree.activeColIdx > 0 ? "ArrowLeft" : "";
225
+ handled = true;
226
+ } else if (ENTER_EDITS && eventName === "Enter") {
227
+ eventName = "F2";
228
+ }
229
+
147
230
  switch (eventName) {
148
231
  case " ":
149
232
  if (tree.activeColIdx === 0 && node.getOption("checkbox")) {
150
233
  node.setSelected(!node.isSelected());
151
234
  handled = true;
152
- } else {
153
- // [Space] key should trigger embedded checkbox
154
- const elem = tree.getActiveColElem();
155
- const cb = elem?.querySelector(
156
- "input[type=checkbox]"
157
- ) as HTMLInputElement;
158
- cb?.click();
235
+ } else if (curInput && curInputType === "checkbox") {
236
+ curInput.click();
237
+ // toggleCheckbox(curInput)
238
+ // new Event("change")
239
+ // curInput.change
240
+ handled = true;
241
+ }
242
+ break;
243
+ case "F2":
244
+ if (curInput && !inputHasFocus && inputCanFocus) {
245
+ curInput.focus();
246
+ handled = true;
159
247
  }
160
248
  break;
161
249
  case "Enter":
250
+ tree.setFocus(); // Blur prev. input if any
162
251
  if (tree.activeColIdx === 0 && node.isExpandable()) {
163
252
  node.setExpanded(!node.isExpanded());
164
253
  handled = true;
254
+ } else if (curInput && !inputHasFocus && inputCanFocus) {
255
+ curInput.focus();
256
+ handled = true;
165
257
  }
166
258
  break;
167
259
  case "Escape":
168
- if (tree.navMode === NavigationMode.cellEdit) {
169
- tree.setNavigationMode(NavigationMode.cellNav);
170
- handled = true;
171
- } else if (tree.navMode === NavigationMode.cellNav) {
172
- tree.setNavigationMode(NavigationMode.row);
260
+ tree.setFocus(); // Blur prev. input if any
261
+ if (tree.isCellNav() && navModeOption !== NavigationOptions.cell) {
262
+ tree.setCellNav(false); // row-nav mode
173
263
  handled = true;
174
264
  }
175
265
  break;
176
266
  case "ArrowLeft":
177
- if (tree.activeColIdx > 0) {
267
+ tree.setFocus(); // Blur prev. input if any
268
+ if (isColspan && node.isExpanded()) {
269
+ node.setExpanded(false);
270
+ } else if (!isColspan && tree.activeColIdx > 0) {
178
271
  tree.setColumn(tree.activeColIdx - 1);
179
- handled = true;
180
- } else if (navModeOption !== NavigationModeOption.cell) {
181
- tree.setNavigationMode(NavigationMode.row);
182
- handled = true;
272
+ } else if (navModeOption !== NavigationOptions.cell) {
273
+ tree.setCellNav(false); // row-nav mode
183
274
  }
275
+ handled = true;
184
276
  break;
185
277
  case "ArrowRight":
186
- if (tree.activeColIdx < tree.columns.length - 1) {
278
+ tree.setFocus(); // Blur prev. input if any
279
+ if (isColspan && !node.isExpanded()) {
280
+ node.setExpanded();
281
+ } else if (
282
+ !isColspan &&
283
+ tree.activeColIdx < tree.columns.length - 1
284
+ ) {
187
285
  tree.setColumn(tree.activeColIdx + 1);
188
- handled = true;
189
286
  }
287
+ handled = true;
190
288
  break;
191
289
  case "ArrowDown":
192
290
  case "ArrowUp":
@@ -200,6 +298,10 @@ export class KeynavExtension extends WunderbaumExtension {
200
298
  case "PageDown":
201
299
  case "PageUp":
202
300
  node.navigate(eventName, { activate: activate, event: event });
301
+ // if (isCellEditMode) {
302
+ // this._getEmbeddedInputElem(null, true); // set focus to input
303
+ // }
304
+ handled = true;
203
305
  break;
204
306
  default:
205
307
  handled = false;
@@ -35,7 +35,7 @@ export class LoggerExtension extends WunderbaumExtension {
35
35
  if (ignoreEvents.has(name)) {
36
36
  return (<any>tree)._superApply(arguments);
37
37
  }
38
- let start = Date.now();
38
+ const start = Date.now();
39
39
  const res = (<any>tree)._superApply(arguments);
40
40
  console.debug(
41
41
  `${prefix}: callEvent('${name}') took ${Date.now() - start} ms.`,