Skip to content

Commit 7bf0212

Browse files
committed
Et2Nextmatch: Fix action keyboard shortcuts
- keyboard events were swallowed by the datagrid
1 parent 840c38a commit 7bf0212

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

api/js/etemplate/Et2Nextmatch/Et2Nextmatch.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ export class Et2Nextmatch extends Et2Widget(LitElement) implements et2_IInput
661661
this.addEventListener("et2-column-selection-apply", this._handleColumnSelectionApply as EventListener);
662662
this.addEventListener("contextmenu", this._handleContextMenu as EventListener, true);
663663
this.addEventListener("dblclick", this._handleDoubleClick as EventListener, true);
664+
this.addEventListener("keydown", this._handleActionShortcut as EventListener, true);
664665
this.addEventListener("keydown", this._handleKeydown as EventListener);
665666
this.addEventListener("pointerdown", this._handlePointerDown as EventListener);
666667
this.addEventListener("pointermove", this._handlePointerMove as EventListener);
@@ -699,6 +700,7 @@ export class Et2Nextmatch extends Et2Widget(LitElement) implements et2_IInput
699700
this.removeEventListener("et2-column-selection-apply", this._handleColumnSelectionApply as EventListener);
700701
this.removeEventListener("contextmenu", this._handleContextMenu as EventListener, true);
701702
this.removeEventListener("dblclick", this._handleDoubleClick as EventListener, true);
703+
this.removeEventListener("keydown", this._handleActionShortcut as EventListener, true);
702704
this.removeEventListener("keydown", this._handleKeydown as EventListener);
703705
this.removeEventListener("pointerdown", this._handlePointerDown as EventListener);
704706
this.removeEventListener("pointermove", this._handlePointerMove as EventListener);
@@ -2927,6 +2929,19 @@ export class Et2Nextmatch extends Et2Widget(LitElement) implements et2_IInput
29272929
}
29282930
};
29292931

2932+
/**
2933+
* Let nextmatch action shortcuts run before the datagrid handles navigation
2934+
* keys and stops their propagation.
2935+
*/
2936+
private _handleActionShortcut = (event : KeyboardEvent) =>
2937+
{
2938+
if(this._actionController.handleShortcut(event))
2939+
{
2940+
event.preventDefault();
2941+
event.stopPropagation();
2942+
}
2943+
};
2944+
29302945
/**
29312946
* Forward pointerdown to the action controller for long-press/drag setup.
29322947
*/

api/js/etemplate/Et2Nextmatch/Et2NextmatchActionController.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,31 @@ export class Et2NextmatchActionController
288288
return {ids: [...this.selectedRowIds], all: this.allSelected};
289289
}
290290

291+
/**
292+
* Execute an action shortcut originating in this nextmatch.
293+
*
294+
* This is called while the event is being captured by Et2Nextmatch. The
295+
* datagrid consumes some keys (notably Ctrl+A) for its own navigation, so
296+
* waiting for the document-level key manager would make those action
297+
* shortcuts unreachable.
298+
*/
299+
handleShortcut(event : KeyboardEvent) : boolean
300+
{
301+
const keyCode = event.keyCode;
302+
if(!keyCode)
303+
{
304+
return false;
305+
}
306+
return !!this.objectManager?.executeActionImplementation?.({
307+
keyEvent: {
308+
keyCode,
309+
shift: event.shiftKey,
310+
ctrl: event.ctrlKey || event.metaKey,
311+
alt: event.altKey
312+
}
313+
}, "popup", EGW_AO_EXEC_SELECTED);
314+
}
315+
291316
/**
292317
* Return the pending submit payload produced by the last submit action.
293318
*

api/js/etemplate/Et2Nextmatch/test/Et2Nextmatch.actions.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,38 @@ describe("Et2Nextmatch action setup", () =>
370370
}
371371
});
372372

373+
/**
374+
* Contract under test:
375+
* - A shortcut declared by a nextmatch action is executed before datagrid key handling.
376+
*
377+
* Setup strategy:
378+
* - Provide a Delete action and an action-object manager spy to the controller.
379+
*
380+
* Pass criteria:
381+
* - Delete is forwarded with its matching shortcut data; an unmatched Ctrl+A is ignored.
382+
*/
383+
it("forwards matching action shortcuts to the action object manager", () =>
384+
{
385+
const controller : any = new Et2NextmatchActionController({} as any);
386+
const execute = sinon.stub().callsFake((context) => context.keyEvent.keyCode === 46);
387+
controller.actionManager = {
388+
children: [{
389+
id: "delete",
390+
shortcut: {keyCode: 46, shift: false, ctrl: false, alt: false}
391+
}]
392+
};
393+
controller.objectManager = {executeActionImplementation: execute};
394+
395+
const deleteEvent = new KeyboardEvent("keydown", {key: "Delete"});
396+
Object.defineProperty(deleteEvent, "keyCode", {value: 46});
397+
assert.isTrue(controller.handleShortcut(deleteEvent), "Delete shortcut should be handled");
398+
assert.isTrue(execute.calledOnce, "matching shortcut should execute through the action manager");
399+
assert.deepInclude(execute.firstCall.args[0].keyEvent, {keyCode: 46, ctrl: false}, "shortcut data should preserve key and modifiers");
400+
const selectAllEvent = new KeyboardEvent("keydown", {key: "a", ctrlKey: true});
401+
Object.defineProperty(selectAllEvent, "keyCode", {value: 65});
402+
assert.isFalse(controller.handleShortcut(selectAllEvent), "unmatched shortcut should remain available to other handlers");
403+
});
404+
373405
/**
374406
* Contract under test:
375407
* - Selecting a popup action executes the configured handler with the selected row object.

0 commit comments

Comments
 (0)