fix(input): gate hotkeys while text inputs are focused

A use-site `changed has-focus` on a std-widgets LineEdit/TextEdit never
fires — has-focus is an alias into the inner TextInput (slint#7747) — so
typing a letter that doubles as a shortcut ran the action instead of
inserting the character. Sweep every text-input surface (search, import,
create/edit playlist & folder, tag editor, colour picker, settings
fields, QConnect dev modal, kiosk shell) and drive
UiFocusState.text-input-focused from a `guard-focused` probe property
that the change tracker does observe.

cc
This commit is contained in:
vicrodh committed 2026-07-16 20:37:57 -06:00
1 parent 134eddc1ae
commit c4ddca0223
18 files changed
+165 -36

No files matched your search

@@ -125,7 +125,11 @@ export component CreateMyQbzModal inherits Rectangle {
}
name-input := LineEdit {
text <=> MyQbzCreateState.name;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
accepted => {
MyQbzCreateActions.submit();
}
+10 -2
View File
@@ -74,7 +74,11 @@ export component MyQbzEditModal inherits Rectangle {
// --- Body: rename (name input) ---------------------------
if root.is-rename: rename-input := LineEdit {
text <=> MyQbzEditState.draft-name;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
accepted => {
if (root.can-submit) {
MyQbzEditActions.submit-rename();
@@ -87,7 +91,11 @@ export component MyQbzEditModal inherits Rectangle {
height: 96px;
wrap: word-wrap;
text <=> MyQbzEditState.draft-description;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
// --- Body: delete-confirm (prompt) -----------------------
@@ -823,7 +823,11 @@ component PmListRow inherits Rectangle {
height: 28px;
width: parent.width;
placeholder-text: @tr("Search folders");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> root.folder-query;
edited => {
PlaylistManagerActions.search-folders(self.text);
@@ -224,8 +224,13 @@ export component ColorPicker inherits Rectangle {
hex-edit := LineEdit {
text: root.current-hex;
placeholder-text: "#rrggbb";
changed has-focus => {
UiFocusState.text-input-focused = self.has-focus;
// Hotkey-guard probe: a use-site `changed has-focus` on a
// std-widgets input never fires (has-focus is an alias into
// the inner TextInput), so observe it through a local
// property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => {
UiFocusState.text-input-focused = self.guard-focused;
}
accepted(s) => {
root.hex-committed(s);
@@ -65,7 +65,11 @@ export component CreateFolderModal inherits Rectangle {
name-input := LineEdit {
placeholder-text: @tr("Folder name");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> CreateFolderState.name;
accepted => {
CreateFolderActions.submit();
@@ -79,7 +79,11 @@ export component CreatePlaylistModal inherits Rectangle {
}
name-input := LineEdit {
placeholder-text: @tr("My Playlist");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> CreatePlaylistState.name;
accepted => {
CreatePlaylistActions.submit();
@@ -100,7 +104,11 @@ export component CreatePlaylistModal inherits Rectangle {
height: 80px;
wrap: word-wrap;
text <=> CreatePlaylistState.description;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
}
@@ -65,7 +65,11 @@ export component EditPlaylistModal inherits Rectangle {
name-input := LineEdit {
placeholder-text: @tr("Playlist name");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> EditPlaylistState.name;
accepted => {
EditPlaylistActions.save();
@@ -81,7 +85,11 @@ export component EditPlaylistModal inherits Rectangle {
height: 90px;
wrap: word-wrap;
text <=> EditPlaylistState.description;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
// Offline-only toggle (D8) — LOCAL playlists only. Unmark it
@@ -76,7 +76,11 @@ export component FolderEditModal inherits Rectangle {
}
name-input := LineEdit {
placeholder-text: @tr("Enter folder name");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> FolderEditState.name;
accepted => { FolderEditActions.save(); }
}
@@ -96,7 +96,11 @@ export component TagEditorModal inherits Rectangle {
horizontal-stretch: 1;
LineEdit {
placeholder-text: @tr("Album title");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> TagEditorState.album_title;
}
}
@@ -105,7 +109,11 @@ export component TagEditorModal inherits Rectangle {
horizontal-stretch: 1;
LineEdit {
placeholder-text: @tr("Album artist");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> TagEditorState.album_artist;
}
}
@@ -119,7 +127,11 @@ export component TagEditorModal inherits Rectangle {
width: 120px;
LineEdit {
placeholder-text: "—";
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> TagEditorState.year_input;
}
}
@@ -128,7 +140,11 @@ export component TagEditorModal inherits Rectangle {
horizontal-stretch: 1;
LineEdit {
placeholder-text: @tr("Genre");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> TagEditorState.genre;
}
}
@@ -137,7 +153,11 @@ export component TagEditorModal inherits Rectangle {
horizontal-stretch: 1;
LineEdit {
placeholder-text: "—";
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> TagEditorState.catalog_number;
}
}
@@ -367,7 +387,11 @@ export component TagEditorModal inherits Rectangle {
LineEdit {
width: 52px;
placeholder-text: "#";
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text: row.track_number;
edited(t) => {
TagEditorState.tracks[i].track_number = t;
@@ -377,7 +401,11 @@ export component TagEditorModal inherits Rectangle {
LineEdit {
horizontal-stretch: 1;
placeholder-text: @tr("Title");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text: row.title;
edited(t) => {
TagEditorState.tracks[i].title = t;
@@ -387,7 +415,11 @@ export component TagEditorModal inherits Rectangle {
LineEdit {
width: 52px;
placeholder-text: @tr("Disc");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text: row.disc_number;
edited(t) => {
TagEditorState.tracks[i].disc_number = t;
@@ -541,7 +541,11 @@ export component AppearanceSettings inherits VerticalLayout {
name-edit := LineEdit {
text: MyQbzBrandingState.label;
placeholder-text: @tr("My QBZ");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
edited(s) => {
MyQbzBrandingState.set-label(s);
}
@@ -665,7 +669,11 @@ export component AppearanceSettings inherits VerticalLayout {
LineEdit {
text: AppearanceState.window-title-template;
placeholder-text: "{artist} - {track}";
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
edited(s) => {
AppearanceState.window-title-template = s;
AppearanceState.appearance-text("window-title-template", s);
@@ -259,7 +259,11 @@ export component IntegrationsSettings inherits VerticalLayout {
LineEdit {
text: ScrobbleState.listenbrainz-token-input;
placeholder-text: @tr("ListenBrainz token");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
input-type: password;
enabled: !ScrobbleState.listenbrainz-busy;
edited(s) => {
@@ -192,7 +192,11 @@ export component LibFolderEditModal inherits Rectangle {
label: @tr("Display name");
LineEdit {
placeholder-text: @tr("Optional friendly name");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> LibFolderEditState.alias;
}
Text {
@@ -538,7 +538,11 @@ export component LocalLibrarySettings inherits VerticalLayout {
LineEdit {
text: PlexSettingsState.server-url;
placeholder-text: @tr("http://127.0.0.1:32400");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
edited(s) => {
PlexSettingsState.server-url = s;
}
@@ -641,7 +645,11 @@ export component LocalLibrarySettings inherits VerticalLayout {
LineEdit {
text: PlexSettingsState.token-input;
placeholder-text: @tr("X-Plex-Token");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
input-type: password;
edited(s) => {
PlexSettingsState.token-input = s;
@@ -129,9 +129,13 @@ export component PlaybackSettings inherits VerticalLayout {
placeholder-text: SettingsState.qconnect-device-name-default;
// Commit on focus loss too (mirrors the Tauri onchange
// semantics: blur or Enter), not only on Enter.
changed has-focus => {
UiFocusState.text-input-focused = self.has-focus;
if (!self.has-focus) {
// Probe property because a use-site `changed has-focus` on a
// std-widgets input never fires (has-focus is an alias into
// the inner TextInput) — #619.
property <bool> guard-focused: self.has-focus;
changed guard-focused => {
UiFocusState.text-input-focused = self.guard-focused;
if (!self.guard-focused) {
root.settings-string("qconnect-device-name", self.text);
}
}
+6 -2
View File
@@ -166,8 +166,12 @@ export component KioskShell inherits Rectangle {
// Suppress the global single-key hotkeys while typing (the
// desktop search box drives the same guard); without this,
// letters fire hotkeys instead of reaching the field.
changed has-focus => {
UiFocusState.text-input-focused = self.has-focus;
// Probe property because a use-site `changed has-focus` on a
// std-widgets input never fires (has-focus is an alias into
// the inner TextInput) — #619.
property <bool> guard-focused: self.has-focus;
changed guard-focused => {
UiFocusState.text-input-focused = self.guard-focused;
}
accepted(text) => {
SearchActions.submit(text);
+10 -2
View File
@@ -87,7 +87,11 @@ export component QconnectDevModal inherits Rectangle {
: QconnectDevState.status;
// Read-only but still focusable (select/copy) — keep the
// single-key hotkeys quiet while it holds focus.
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
}
@@ -135,7 +139,11 @@ export component QconnectDevModal inherits Rectangle {
wrap: word-wrap;
font-size: 11px;
text: QconnectDevState.log-text;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
}
}
+10 -2
View File
@@ -485,7 +485,11 @@ component SidebarRow inherits Rectangle {
LineEdit {
height: 26px;
placeholder-text: @tr("Search folders");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> root.folder-query;
edited => {
SidebarActions.search-folders(self.text);
@@ -829,7 +833,11 @@ export component Sidebar inherits Rectangle {
width: 100%;
height: 22px;
placeholder-text: @tr("Search playlists");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> SidebarState.search-query;
edited => {
SidebarActions.search-changed(self.text);
@@ -130,7 +130,11 @@ export component SidebarPlaylistsPopup inherits Rectangle {
width: parent.width - 16px;
height: 26px;
placeholder-text: @tr("Search playlists");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
text <=> SidebarState.search-query;
edited => {
SidebarActions.search-changed(self.text);