Skip to content

Commit

Permalink
feat(keyboard): allow to configure keyevent type
Browse files Browse the repository at this point in the history
cherry picked from 1cfdd58 of master
  • Loading branch information
zewa666 authored and bigopon committed May 11, 2019
1 parent 7e3b072 commit 2f57949
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/dialog-keyboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DOM } from 'aurelia-pal';
import { DialogController } from './dialog-controller';
import { ActionKey, KeyEventType } from './dialog-settings';
import { DialogService } from './dialog-service';

function getActionKey(eo: KeyboardEvent): ActionKey | undefined {
if ((eo.code || eo.key) === 'Escape' || eo.keyCode === 27) {
return 'Escape';
}
if ((eo.code || eo.key) === 'Enter' || eo.keyCode === 13) {
return 'Enter';
}
return undefined;
}

/**
* @internal
*/
export class DialogKeyboardService {
private controllers: Set<DialogController> = new Set();
private keyboardHandler: (eo: KeyboardEvent) => void;

// tslint:disable-next-line:member-ordering
public static inject = [DialogService];
public constructor(dialogService: DialogService) {
this.keyboardHandler = eo => {
const key = getActionKey(eo);
if (!key) { return; }
const top = dialogService.controllers[dialogService.controllers.length - 1];
if (!top || !this.controllers.has(top)) { return; }
const keyboard = top.settings.keyboard;
if (key === 'Escape'
&& (keyboard === true || keyboard === key || (Array.isArray(keyboard) && keyboard.indexOf(key) > -1))) {
top.cancel();
} else if (key === 'Enter'
&& (keyboard === key || (Array.isArray(keyboard) && keyboard.indexOf(key) > -1))) {
top.ok();
}
};
}

private addListener(keyEvent: KeyEventType = 'keyup'): void {
DOM.addEventListener(keyEvent, this.keyboardHandler, false);
}

private removeListener(keyEvent: KeyEventType = 'keyup'): void {
DOM.removeEventListener(keyEvent, this.keyboardHandler, false);
}

public enlist(controller: DialogController): void {
if (!controller.settings.keyboard) { return; }
if (!this.controllers.size) {
this.addListener(controller.settings.keyEvent);
}
this.controllers.add(controller);
}

public remove(controller: DialogController): void {
if (!this.controllers.has(controller)) { return; }
this.controllers.delete(controller);
if (!this.controllers.size) {
this.removeListener();
}
}
}
124 changes: 124 additions & 0 deletions test/unit/dialog-keyboard-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { DialogKeyboardService } from '../../src/dialog-keyboard-service';
import { DialogService } from '../../src/dialog-service';
import { DOM } from 'aurelia-pal';
import { DialogController, ActionKey, KeyEventType } from '../../src/aurelia-dialog';

describe(`${DialogKeyboardService.name}`, () => {
function createDialogService(controllers: DialogController[] = []): DialogService {
return {
controllers
} as DialogService;
}

function createKeyboardService(...controllers: DialogController[]): DialogKeyboardService {
const service = new DialogKeyboardService(createDialogService(controllers));
controllers.forEach(c => service.enlist(c));
return service;
}

function createController(keyboard?: boolean | ActionKey | ActionKey[], keyType?: KeyEventType): DialogController;
function createController(keyboard?: boolean | ActionKey | ActionKey[], keyType?: KeyEventType): DialogController {
const controller = jasmine.createSpyObj(`${DialogController.name}Spy`, ['ok', 'cancel']) as DialogController;
controller.settings = { keyboard };

if (keyType) {
controller.settings.keyEvent = keyType;
}

return controller;
}

it('setups keyboard listener on first added controller with meaningful "keyboard" setting', () => {
const service = createKeyboardService();
spyOn(DOM, 'addEventListener');
service.enlist(createController(true));
expect(DOM.addEventListener).toHaveBeenCalled();
});

it('skips adding controller if the "keyboard" setting is not meaningful', () => {
const service = createKeyboardService();
spyOn(DOM, 'addEventListener');
service.enlist(createController(false));
expect(DOM.addEventListener).not.toHaveBeenCalled();
});

it('clears keyboard listener on last removed controller', () => {
const controllersCount = 5;
const controllers = [] as DialogController[];
for (let i = 0; i < controllersCount; i++) { controllers.push(createController(true)); }
const service = createKeyboardService(...controllers);
spyOn(DOM, 'removeEventListener');
const controllersToRemove = controllersCount - 1;
for (let i = 0; i < controllersToRemove; i++) { service.remove(controllers.pop()!); }
expect(DOM.removeEventListener).not.toHaveBeenCalled();
service.remove(controllers.pop()!);
expect(DOM.removeEventListener).toHaveBeenCalled();
});

it('closes the top dialog if enlisted with it', () => {
const first = createController(true);
const top = createController(true);
const service = createKeyboardService(first, top);
// tslint:disable-next-line:no-unused-expression
service;
DOM.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true }));
expect(first.cancel).not.toHaveBeenCalled();
expect(top.cancel).toHaveBeenCalled();
});

it('does not close the top dialog if not enlisted with it', () => {
const first = createController(true);
const top = createController(true);
const service = createKeyboardService(first, top);
service.remove(top);
DOM.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true }));
expect(first.cancel).not.toHaveBeenCalled();
expect(top.cancel).not.toHaveBeenCalled();
});

describe('cancels the top dialog when the "keyboard" setting is set to', () => {
function cancelOnEscapeKeySpec(keyboard?: boolean | ActionKey | ActionKey[]) {
const controller = createController(keyboard);
const service = createKeyboardService(controller);
// tslint:disable-next-line:no-unused-expression
service;
DOM.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true }));
expect(controller.cancel).toHaveBeenCalled();
}

it('"true"', () => {
cancelOnEscapeKeySpec(true);
});

it('"Escape"', () => {
cancelOnEscapeKeySpec('Escape');
});

it('Array containing "Escape"', () => {
cancelOnEscapeKeySpec(['Escape']);
});
});

describe('OKs the top dialog when the "keyboard" setting is set to', () => {
function okOnEnterKeySpec(keyboard: ActionKey | ActionKey[], keyType: KeyEventType = 'keyup'): void {
const controller = createController(keyboard, keyType);
const service = createKeyboardService(controller);
// tslint:disable-next-line:no-unused-expression
service;
DOM.dispatchEvent(new KeyboardEvent(keyType, { key: 'Enter', bubbles: true }));
expect(controller.ok).toHaveBeenCalled();
}

it('"Enter"', () => {
okOnEnterKeySpec('Enter');
});

it('"Enter" using keydown event', () => {
okOnEnterKeySpec('Enter', 'keydown');
});

it('Array containing "Enter"', () => {
okOnEnterKeySpec(['Enter']);
});
});
});

0 comments on commit 2f57949

Please sign in to comment.