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