Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/core/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export class Action {
error(`contains unknown key filter: ${this.keyFilter}`)
}

return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase()
const mappedKey = this.keyMappings[standardFilter].toLowerCase()
const normalizedEventKey = this.normalizedKeyboardEventKey(event, mappedKey)

return mappedKey !== normalizedEventKey
}

shouldIgnoreMouseEvent(event: MouseEvent): boolean {
Expand Down Expand Up @@ -102,6 +105,17 @@ export class Action {

return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift
}

private normalizedKeyboardEventKey(event: KeyboardEvent, mappedKey: string): string {
const eventKey = event.key.toLowerCase()

if (!shouldUseCodeFallback(event, mappedKey)) {
return eventKey
}

const keyFromCode = keyboardEventKeyFromCode(event.code)
return keyFromCode || eventKey
}
}

const defaultEventNames: { [tagName: string]: (element: Element) => string } = {
Expand Down Expand Up @@ -132,3 +146,25 @@ function typecast(value: any): any {
return value
}
}

function shouldUseCodeFallback(event: KeyboardEvent, mappedKey: string): boolean {
return !event.isComposing && isPrintableASCIICharacter(mappedKey) && !isPrintableASCIICharacter(event.key)
}

function isPrintableASCIICharacter(value: string): boolean {
return value.length === 1 && value >= " " && value <= "~"
}

function keyboardEventKeyFromCode(code: string): string | null {
const keyMatch = code.match(/^Key([A-Z])$/)
if (keyMatch) {
return keyMatch[1].toLowerCase()
}

const digitMatch = code.match(/^Digit([0-9])$/)
if (digitMatch) {
return digitMatch[1]
}

return null
}
25 changes: 25 additions & 0 deletions src/tests/modules/core/action_keyboard_filter_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default class ActionKeyboardFilterTests extends LogControllerTestCase {
<button id="button8" data-action="keydown.a->a#log keydown.b->a#log2"></button>
<button id="button9" data-action="keydown.shift+a->a#log keydown.a->a#log2 keydown.ctrl+shift+a->a#log3">
<button id="button10" data-action="jquery.custom.event->a#log jquery.a->a#log2">
<button id="button11" data-action="keydown.j->a#log"></button>
</div>
`

Expand Down Expand Up @@ -197,4 +198,28 @@ export default class ActionKeyboardFilterTests extends LogControllerTestCase {
await this.triggerEvent(button, "jquery.a")
this.assertActions({ name: "log2", identifier: "a", eventType: "jquery.a", currentTarget: button })
}

// Cyrillic (Bulgarian, phonetic layout)
async "test plain j key filter falls back to code for cyrillic key"() {
const button = this.findElement("#button11")
await this.nextFrame
await this.triggerKeyboardEvent(button, "keydown", { key: "й", code: "KeyJ" })
this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
}

// Greek
async "test plain j key filter falls back to code for greek key"() {
const button = this.findElement("#button11")
await this.nextFrame
await this.triggerKeyboardEvent(button, "keydown", { key: "ξ", code: "KeyJ" })
this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
}

// Hebrew
async "test plain j key filter falls back to code for hebrew key"() {
const button = this.findElement("#button11")
await this.nextFrame
await this.triggerKeyboardEvent(button, "keydown", { key: "ח", code: "KeyJ" })
this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
}
}