diff --git a/resources/lang/en.json b/resources/lang/en.json
index 6c89617f8b..effc4e7202 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -1667,6 +1667,7 @@
"toggle_view_desc": "Alternate view (terrain/countries)",
"toggle_visibility": "Toggle Visibility",
"unbind": "Unbind",
+ "unbind_all": "Unbind All",
"view_options": "View Options",
"zoom_controls": "Zoom Controls",
"zoom_in": "Zoom In",
diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts
index 428afd95dd..0a4a9a2dd4 100644
--- a/src/client/UserSettingModal.ts
+++ b/src/client/UserSettingModal.ts
@@ -73,6 +73,12 @@ export class UserSettingModal extends BaseModal {
this.userKeybinds = validated;
}
+ private unbindAllKeybinds() {
+ this.userSettings.unbindAllKeybinds(Platform.isMac);
+ this.loadKeybindsFromStorage();
+ this.requestUpdate();
+ }
+
private handleKeybindChange(
e: CustomEvent<{
action: string;
@@ -366,6 +372,13 @@ export class UserSettingModal extends BaseModal {
${translateText("user_setting.keybinds_hint")}
+
+
diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts
index 7be154af9e..b1b8e6c0b3 100644
--- a/src/core/game/UserSettings.ts
+++ b/src/core/game/UserSettings.ts
@@ -551,6 +551,18 @@ export class UserSettings {
}
}
+ /**
+ * Unbind every keybind at once: set each default action to "Null" (the same
+ * sentinel a single per-key Unbind uses), so keybinds() drops them all.
+ */
+ unbindAllKeybinds(isMac: boolean): void {
+ const unbound: Record = {};
+ for (const action of Object.keys(getDefaultKeybinds(isMac))) {
+ unbound[action] = "Null";
+ }
+ this.setKeybinds(unbound);
+ }
+
soundEffectsVolume(): number {
return this.getFloat("settings.soundEffectsVolume", 0);
}
diff --git a/tests/UserSettings.test.ts b/tests/UserSettings.test.ts
index b526425858..077fa994e5 100644
--- a/tests/UserSettings.test.ts
+++ b/tests/UserSettings.test.ts
@@ -1,5 +1,6 @@
import {
EFFECTS_KEY,
+ getDefaultKeybinds,
PLAYER_STATS_COLUMNS_KEY,
TEAM_STATS_COLUMNS_KEY,
UserSettings,
@@ -135,3 +136,26 @@ describe("UserSettings stats columns", () => {
expect(localStorage.getItem(TEAM_STATS_COLUMNS_KEY)).toBe('["warships"]');
});
});
+
+describe("UserSettings unbindAllKeybinds (#4309)", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ (
+ UserSettings as unknown as { cache: Map }
+ ).cache.clear();
+ });
+
+ it("sets every default keybind action to Null so nothing stays bound", () => {
+ const s = new UserSettings();
+ s.unbindAllKeybinds(false);
+
+ const parsed = s.parsedUserKeybinds();
+ const defaults = getDefaultKeybinds(false);
+ expect(Object.keys(defaults).length).toBeGreaterThan(0);
+ for (const action of Object.keys(defaults)) {
+ expect(parsed[action]).toBe("Null");
+ }
+ // keybinds() drops "Null" entries, so no action resolves to a key.
+ expect(Object.keys(s.keybinds(false))).toHaveLength(0);
+ });
+});