use keymap_parser::{self as parser, Key, Modifier, Node};
use web_sys::{KeyboardEvent, KeyboardEventInit};
use crate::{
keymap::{FromKeyMap, IntoKeyMap, KeyMap, ToKeyMap},
Error,
};
pub fn parse(s: &str) -> Result<KeyboardEvent, Error> {
parser::parse(s)
.map_err(Error::Parse)
.and_then(KeyboardEvent::from_keymap)
}
impl IntoKeyMap for KeyboardEvent {
fn into_keymap(self) -> Result<KeyMap, Error> {
self.to_keymap()
}
}
impl ToKeyMap for KeyboardEvent {
fn to_keymap(&self) -> Result<KeyMap, Error> {
ToKeyMap::to_keymap(&self)
}
}
impl ToKeyMap for &KeyboardEvent {
fn to_keymap(&self) -> Result<KeyMap, Error> {
let modifiers = modifiers_from_backend(self);
let key = match self.key().as_str() {
"Tab" if modifiers & Modifier::Shift as u8 != 0 => Key::BackTab,
"Backspace" => Key::Backspace,
"Delete" => Key::Delete,
"ArrowDown" => Key::Down,
"End" => Key::End,
"Enter" => Key::Enter,
"Escape" => Key::Esc,
"Home" => Key::Home,
"Insert" => Key::Insert,
"ArrowLeft" => Key::Left,
"PageDown" => Key::PageDown,
"PageUp" => Key::PageUp,
"ArrowRight" => Key::Right,
" " => Key::Space,
"Tab" => Key::Tab,
"ArrowUp" => Key::Up,
s if s.starts_with('F') && s.len() > 1 => {
if let Ok(n) = s[1..].parse::<u8>() {
Key::F(n)
} else {
return Err(Error::UnsupportedKey(format!(
"Unsupported KeyboardEvent key: {s:?}"
)));
}
}
s => Key::Char(s.chars().next().unwrap_or_default()),
};
Ok(Node::new(modifiers_from_backend(self), key))
}
}
impl FromKeyMap for KeyboardEvent {
fn from_keymap(keymap: KeyMap) -> Result<Self, Error> {
let key_str = match keymap.key {
Key::BackTab => "Tab".to_string(), Key::Backspace => "Backspace".to_string(),
Key::Char(c) => c.to_string(),
Key::Delete => "Delete".to_string(),
Key::Down => "ArrowDown".to_string(),
Key::End => "End".to_string(),
Key::Enter => "Enter".to_string(),
Key::Esc => "Escape".to_string(),
Key::F(n) => format!("F{n}"),
Key::Home => "Home".to_string(),
Key::Insert => "Insert".to_string(),
Key::Left => "ArrowLeft".to_string(),
Key::PageDown => "PageDown".to_string(),
Key::PageUp => "PageUp".to_string(),
Key::Right => "ArrowRight".to_string(),
Key::Tab => "Tab".to_string(),
Key::Space => " ".to_string(),
Key::Up => "ArrowUp".to_string(),
Key::Group(group) => {
return Err(Error::UnsupportedKey(format!(
"Group {group:?} not supported. There's no way to map char group back to KeyboardEvent"
)))
}
};
let event_init = KeyboardEventInit::new();
event_init.set_key(&key_str);
event_init.set_alt_key(keymap.modifiers & Modifier::Alt as u8 != 0);
event_init.set_ctrl_key(keymap.modifiers & Modifier::Ctrl as u8 != 0);
event_init.set_meta_key(keymap.modifiers & Modifier::Cmd as u8 != 0);
event_init.set_shift_key(keymap.modifiers & Modifier::Shift as u8 != 0);
KeyboardEvent::new_with_keyboard_event_init_dict("keydown", &event_init)
.map_err(|e| Error::UnsupportedKey(format!("Failed to create KeyboardEvent: {:?}", e)))
}
}
fn modifiers_from_backend(value: &KeyboardEvent) -> parser::Modifiers {
let mut modifiers = 0;
if value.alt_key() {
modifiers |= Modifier::Alt as u8;
}
if value.ctrl_key() {
modifiers |= Modifier::Ctrl as u8;
}
if value.meta_key() {
modifiers |= Modifier::Cmd as u8;
}
if value.shift_key() {
modifiers |= Modifier::Shift as u8;
}
modifiers
}
#[cfg(all(target_arch = "wasm32", test))]
mod tests {
use super::*;
use keymap_parser as parser;
use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
fn keyboard_event(key: &str, alt: bool, ctrl: bool, shift: bool, meta: bool) -> KeyboardEvent {
let event_init = KeyboardEventInit::new();
event_init.set_key(key);
event_init.set_alt_key(alt);
event_init.set_ctrl_key(ctrl);
event_init.set_shift_key(shift);
event_init.set_meta_key(meta);
KeyboardEvent::new_with_keyboard_event_init_dict("keydown", &event_init).unwrap()
}
#[wasm_bindgen_test]
fn test_to_keymap() {
[
("a", true, true, true, false, "alt-ctrl-shift-a"),
("F1", false, false, false, false, "f1"),
("Delete", false, false, false, false, "del"),
]
.into_iter()
.for_each(|(key, alt, ctrl, shift, meta, expected)| {
let event = keyboard_event(key, alt, ctrl, shift, meta);
let expected_keymap = parser::parse(expected).unwrap();
assert_eq!(event.to_keymap().unwrap(), expected_keymap);
});
}
#[wasm_bindgen_test]
fn test_from_keymap() {
let keymap = parser::parse("alt-ctrl-shift-a").unwrap();
let event = KeyboardEvent::from_keymap(keymap).unwrap();
assert_eq!(event.key(), "a");
assert!(event.alt_key());
assert!(event.ctrl_key());
assert!(event.shift_key());
assert!(!event.meta_key());
}
}