Compare commits

...

18 Commits

Author SHA1 Message Date
Aidan Timson
b17dc53d27 Format 2025-11-04 14:08:33 +00:00
Aidan Timson
ae9f194868 SImplify 2025-11-04 14:04:09 +00:00
Aidan Timson
c762f12ffd Refactor 2025-11-04 12:54:19 +00:00
Aidan Timson
cf9f4afcdc Remove duplicate checks 2025-11-04 12:54:19 +00:00
Aidan Timson
9f394a4ed6 Remove multiple shortcuts per disposer 2025-11-04 12:54:19 +00:00
Aidan Timson
9da1835a57 Fix misleading comment 2025-11-04 12:54:19 +00:00
Aidan Timson
8d3c36236f Explainers 2025-11-04 12:54:19 +00:00
Aidan Timson
b4d6ee105f Cleanup 2025-11-04 12:54:19 +00:00
Aidan Timson
ffcb78bc7b Cleanup 2025-11-04 12:54:19 +00:00
Aidan Timson
6f7be230d8 Make class 2025-11-04 12:54:19 +00:00
Aidan Timson
f5fa606ac2 foreach 2025-11-04 12:54:19 +00:00
Aidan Timson
b18c14117d foreach 2025-11-04 12:54:19 +00:00
Aidan Timson
e64366ea61 enableShortcuts check already in mixin, remove hass param 2025-11-04 12:54:19 +00:00
Aidan Timson
4c3e1430bf Drop examples 2025-11-04 12:54:19 +00:00
Aidan Timson
2019bb8ebc Manage disposers from manager and document methods 2025-11-04 12:54:19 +00:00
Aidan Timson
523c26a11a Switch to set and allow removal of all or a list 2025-11-04 12:54:18 +00:00
Aidan Timson
3a7d106bcc Migrate quick bar mixin to use helper 2025-11-04 12:54:18 +00:00
Aidan Timson
b6a841d109 Create keyboard shortcuts helper 2025-11-04 12:54:18 +00:00
2 changed files with 85 additions and 27 deletions

View File

@@ -0,0 +1,66 @@
import { tinykeys } from "tinykeys";
import { canOverrideAlphanumericInput } from "../dom/can-override-input";
/**
* A function to handle a keyboard shortcut.
*/
export type ShortcutHandler = (event: KeyboardEvent) => void;
/**
* Configuration for a keyboard shortcut.
*/
export interface ShortcutConfig {
handler: ShortcutHandler;
/**
* If true, allows shortcuts even when text is selected.
* Default is false to avoid interrupting copy/paste.
*/
allowWhenTextSelected?: boolean;
}
/**
* Register keyboard shortcuts using tinykeys.
* Automatically blocks shortcuts in input fields and during text selection.
*/
function registerShortcuts(
shortcuts: Record<string, ShortcutConfig>
): () => void {
const wrappedShortcuts: Record<string, ShortcutHandler> = {};
Object.entries(shortcuts).forEach(([key, config]) => {
wrappedShortcuts[key] = (event: KeyboardEvent) => {
if (!canOverrideAlphanumericInput(event.composedPath())) {
return;
}
if (!config.allowWhenTextSelected && window.getSelection()?.toString()) {
return;
}
config.handler(event);
};
});
return tinykeys(window, wrappedShortcuts);
}
/**
* Manages keyboard shortcuts registration and cleanup.
*/
export class ShortcutManager {
private _disposer?: () => void;
/**
* Register keyboard shortcuts.
* Uses tinykeys syntax: https://github.com/jamiebuilds/tinykeys#usage
*/
public add(shortcuts: Record<string, ShortcutConfig>) {
this._disposer = registerShortcuts(shortcuts);
}
/**
* Remove all registered shortcuts.
*/
public remove() {
this._disposer?.();
this._disposer = undefined;
}
}

View File

@@ -1,5 +1,4 @@
import type { PropertyValues } from "lit";
import { tinykeys } from "tinykeys";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { mainWindow } from "../common/dom/get_main_window";
@@ -12,9 +11,9 @@ import type { Constructor, HomeAssistant } from "../types";
import { storeState } from "../util/ha-pref-storage";
import { showToast } from "../util/toast";
import type { HassElement } from "./hass-element";
import { ShortcutManager } from "../common/keyboard/shortcuts";
import { extractSearchParamsObject } from "../common/url/search-params";
import { showVoiceCommandDialog } from "../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
import { showShortcutsDialog } from "../dialogs/shortcuts/show-shortcuts-dialog";
import type { Redirects } from "../panels/my/ha-panel-my";
@@ -62,21 +61,22 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
private _registerShortcut() {
tinykeys(window, {
const shortcutManager = new ShortcutManager();
shortcutManager.add({
// Those are for latin keyboards that have e, c, m keys
e: (ev) => this._showQuickBar(ev),
c: (ev) => this._showQuickBar(ev, QuickBarMode.Command),
m: (ev) => this._createMyLink(ev),
a: (ev) => this._showVoiceCommandDialog(ev),
d: (ev) => this._showQuickBar(ev, QuickBarMode.Device),
e: { handler: (ev) => this._showQuickBar(ev) },
c: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Command) },
m: { handler: (ev) => this._createMyLink(ev) },
a: { handler: (ev) => this._showVoiceCommandDialog(ev) },
d: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Device) },
// Workaround see https://github.com/jamiebuilds/tinykeys/issues/130
"Shift+?": (ev) => this._showShortcutDialog(ev),
"Shift+?": { handler: (ev) => this._showShortcutDialog(ev) },
// Those are fallbacks for non-latin keyboards that don't have e, c, m keys (qwerty-based shortcuts)
KeyE: (ev) => this._showQuickBar(ev),
KeyC: (ev) => this._showQuickBar(ev, QuickBarMode.Command),
KeyM: (ev) => this._createMyLink(ev),
KeyA: (ev) => this._showVoiceCommandDialog(ev),
KeyD: (ev) => this._showQuickBar(ev, QuickBarMode.Device),
KeyE: { handler: (ev) => this._showQuickBar(ev) },
KeyC: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Command) },
KeyM: { handler: (ev) => this._createMyLink(ev) },
KeyA: { handler: (ev) => this._showVoiceCommandDialog(ev) },
KeyD: { handler: (ev) => this._showQuickBar(ev, QuickBarMode.Device) },
});
}
@@ -87,7 +87,6 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
private _showVoiceCommandDialog(e: KeyboardEvent) {
if (
!this.hass?.enableShortcuts ||
!canOverrideAlphanumericInput(e.composedPath()) ||
!this._conversation(this.hass.config.components)
) {
return;
@@ -105,7 +104,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
e: KeyboardEvent,
mode: QuickBarMode = QuickBarMode.Entity
) {
if (!this._canShowQuickBar(e)) {
if (!this._canShowQuickBar()) {
return;
}
@@ -118,7 +117,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
private _showShortcutDialog(e: KeyboardEvent) {
if (!this._canShowQuickBar(e)) {
if (!this._canShowQuickBar()) {
return;
}
@@ -131,10 +130,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
private async _createMyLink(e: KeyboardEvent) {
if (
!this.hass?.enableShortcuts ||
!canOverrideAlphanumericInput(e.composedPath())
) {
if (!this.hass?.enableShortcuts) {
return;
}
@@ -193,11 +189,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
});
}
private _canShowQuickBar(e: KeyboardEvent) {
return (
this.hass?.user?.is_admin &&
this.hass.enableShortcuts &&
canOverrideAlphanumericInput(e.composedPath())
);
private _canShowQuickBar() {
return this.hass?.user?.is_admin && this.hass.enableShortcuts;
}
};