services
data_display
hotkey
Odoo 19 services — hotkey (core)
Live preview
Interactive
Source excerpt
web/static/src/core/hotkeys/hotkey_service.js
import { isMacOS } from "../browser/feature_detection";
import { registry } from "../registry";
import { browser } from "../browser/browser";
import { getVisibleElements } from "../utils/ui";
/**
* @typedef {(context: { area: HTMLElement, target: EventTarget }) => void} HotkeyCallback
*
* @typedef {Object} HotkeyOptions
* @property {boolean} [allowRepeat]
* allow registration to perform multiple times when hotkey is held down
* @property {boolean} [bypassEditableProtection]
* if true the hotkey service will call this registration
* even if an editable element is focused
* @property {boolean} [global]
* allow registration to perform no matter the UI active element
* @property {() => HTMLElement} [area]
* adds a restricted operating area for this hotkey
* @property {(target: HTMLElement) => boolean} [isAvailable]
* adds a validation before calling the hotkey registration's callback
* @property {() => HTMLElement} [withOverlay]
* provides the element on which the overlay should be displayed
* Please note that if provided the hotkey will only work with
* the overlay access key, similarly to all [data-hotkey] DOM attributes.
*
* @typedef {HotkeyOptions & {
* hotkey: string,
* callback: HotkeyCallback,
* activeElement: HTMLElement,
* }} HotkeyRegistration
*/
const ALPHANUM_KEYS = "abcdefghijklmnopqrstuvwxyz0123456789".split("");
const NAV_KEYS = [
"arrowleft",
"arrowright",
"arrowup",
"arrowdown",
"pageup",
"pagedown",
"home",
"end",
"backspace",
"enter",
"tab",
"delete",
"space",
];
const MODIFIERS = ["alt", "control", "shift"];
const AUTHORIZED_KEYS = [...ALPHANUM_KEYS, ...NAV_KEYS, "escape", "<", ">"];
/**
* Get the actual hotkey being pressed.
*
* @param {KeyboardEvent} ev
* @returns {string} the active hotkey, in lowercase
*/
export function getActiveHotkey(ev) {
if (!ev.key) {
// Chrome may trigger incomplete keydown events under certain circumstances.
// E.g. when using browser built-in autocomplete on an input.
// See https://stackoverflow.com/questions/59534586/google-chrome-fires-keydown-event-when-form-autocomplete
return "";
}
if (ev.isComposing) {
// This case happens with an IME for example: we let it handle all key events.
return "";
}
const hotkey = [];
// ------- Modifiers -------
// Modifiers are pushed in ascending order to the hotkey.
if (isMacOS() ? ev.ctrlKey : ev.altKey) {
hotkey.push("alt");
}
if (isMacOS() ? ev.metaKey : ev.ctrlKey) {
hotkey.push("control");
}
if (ev.shiftKey) {
hotkey.push("shift");
}
// ------- Key -------
let key = ev.key.toLowerCase();
// The browser space is natively " ", we want "space" for esthetic reasons
if (key === " ") {
key = "space";
}
// Identify if the user has tapped on the number keys above the text keys.
if (ev.code && ev.code.indexOf("Digit") === 0) {
key = ev.code.slice(-1);
}
// Prefer physical keys for non-latin keyboard layout.
if (!AUTHORIZED_KEYS.includes(key) && ev.code && ev.code.indexOf("Key") === 0) {
key = ev.code.slice(-1).toLowerCase();
}
// Make sure we do not duplicate a modifier key
if (!MODIFIERS.includes(key)) {
hotkey.push(key);
}
return hotkey.join("+");
}
export const hotkeyService = {
dependencies: ["ui"],
// Be aware that all odoo hotkeys are designed with this modifier in mind,
// so changing the overlay modifier may conflict with some shortcuts.
overlayModifier: "alt",
start(env, { ui }) {
/** @type {Map<number, HotkeyRegistration>} */
const registrations = new Map();
let nextToken = 0;
let overlaysVisible = false;
addListeners(browser);
function addListeners(target) {
Registry / API
- Registry name
hotkey- Category
services- Module
web- Slug
hotkey- Nav group
data_display