-
-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(keyboard): allow to configure keyevent type
cherry picked from 1cfdd58 of master
- Loading branch information
Showing
2 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']); | ||
}); | ||
}); | ||
}); |