From ae57b5620bc6709c86096f6d7d8c39a4dd425a78 Mon Sep 17 00:00:00 2001 From: Yevhen Kozlov Date: Sat, 7 Mar 2026 08:27:11 +0100 Subject: [PATCH 1/2] feat: callback version of conflictBehavior --- .changeset/vast-jokes-serve.md | 6 ++++++ docs/reference/type-aliases/ConflictBehavior.md | 6 +++++- .../src/components/DetailsPanel.tsx | 17 ++++++++++++++--- packages/hotkeys/src/manager.utils.ts | 12 +++++++++++- packages/hotkeys/tests/manager.utils.test.ts | 15 +++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 .changeset/vast-jokes-serve.md diff --git a/.changeset/vast-jokes-serve.md b/.changeset/vast-jokes-serve.md new file mode 100644 index 0000000..ffc5952 --- /dev/null +++ b/.changeset/vast-jokes-serve.md @@ -0,0 +1,6 @@ +--- +'@tanstack/hotkeys-devtools': patch +'@tanstack/hotkeys': patch +--- + +feat: callback variant of conflictBehavior diff --git a/docs/reference/type-aliases/ConflictBehavior.md b/docs/reference/type-aliases/ConflictBehavior.md index 50f06ca..cf77172 100644 --- a/docs/reference/type-aliases/ConflictBehavior.md +++ b/docs/reference/type-aliases/ConflictBehavior.md @@ -6,7 +6,10 @@ title: ConflictBehavior # Type Alias: ConflictBehavior ```ts -type ConflictBehavior = "warn" | "error" | "replace" | "allow"; +type ConflictBehavior = "warn" | "error" | "replace" | "allow" | ( + keyDisplay: string, + unregisterConflicting: () => void +) => void; ``` Defined in: [manager.utils.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/manager.utils.ts#L11) @@ -17,3 +20,4 @@ Behavior when registering a hotkey/sequence that conflicts with an existing regi - `'error'` - Throw an error and prevent the new registration - `'replace'` - Unregister the existing registration and register the new one - `'allow'` - Allow multiple registrations without warning +- custom callback - You can log the issue with configuration or unregister conflicting registration conditionally e.g. with showing confirmation popup diff --git a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx index 38411e3..d5fa1cd 100644 --- a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx +++ b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx @@ -119,7 +119,18 @@ function getConflictLabel( if (behavior === 'allow') return 'allowed' if (behavior === 'error') return 'error' if (behavior === 'replace') return 'replaced' - return 'warning' + if (behavior === 'warn') return 'warning' + return 'callback handled conflict with' +} + +function serializeConflictBehavior( + behavior: ConflictBehavior +): string { + if (typeof behavior === 'string') { + return behavior + } + + return '[Function function]' } function HotkeyDetails(props: { @@ -285,7 +296,7 @@ function HotkeyDetails(props: {
conflictBehavior - {conflictBehavior()} + {serializeConflictBehavior(conflictBehavior())}
hasFired @@ -498,7 +509,7 @@ function SequenceDetails(props: {
conflictBehavior - {conflictBehavior()} + {serializeConflictBehavior(conflictBehavior())}
diff --git a/packages/hotkeys/src/manager.utils.ts b/packages/hotkeys/src/manager.utils.ts index dc084fc..55587b8 100644 --- a/packages/hotkeys/src/manager.utils.ts +++ b/packages/hotkeys/src/manager.utils.ts @@ -1,5 +1,10 @@ import type { ParsedHotkey } from './hotkey' +type CustomConflictHandler = ( + keyDisplay: string, + unregisterAnotherConflictingId: () => void +) => void + /** * Behavior when registering a hotkey/sequence that conflicts with an existing registration. * @@ -8,7 +13,7 @@ import type { ParsedHotkey } from './hotkey' * - `'replace'` - Unregister the existing registration and register the new one * - `'allow'` - Allow multiple registrations without warning */ -export type ConflictBehavior = 'warn' | 'error' | 'replace' | 'allow' +export type ConflictBehavior = 'warn' | 'error' | 'replace' | 'allow' | CustomConflictHandler /** * Default options for hotkey/sequence registration. @@ -164,6 +169,11 @@ export function handleConflict( ) } + if (typeof conflictBehavior === 'function') { + conflictBehavior(keyDisplay, () => unregister(conflictingId)) + return + } + // At this point, conflictBehavior must be 'replace' unregister(conflictingId) } diff --git a/packages/hotkeys/tests/manager.utils.test.ts b/packages/hotkeys/tests/manager.utils.test.ts index b929367..e17df03 100644 --- a/packages/hotkeys/tests/manager.utils.test.ts +++ b/packages/hotkeys/tests/manager.utils.test.ts @@ -286,5 +286,20 @@ describe('manager.utils', () => { expect(unregister).toHaveBeenCalledWith('id-1') }) + + it('should call custom callback if passed as conflictBehaviour', () => { + const unregister = vi.fn() + const handleConflictCallback = vi.fn() + + handleConflict('id-1', 'Mod+S', handleConflictCallback, unregister) + + expect(unregister).not.toHaveBeenCalledWith() + expect(handleConflictCallback).toHaveBeenCalledWith( + 'Mod+S', + expect.any(Function), + ) + handleConflictCallback.mock.calls[0]?.[1]() + expect(unregister).toHaveBeenCalledWith('id-1') + }) }) }) From 337ce813e35ba675857db5d992fab1cb327290ed Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:52:19 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- docs/reference/type-aliases/ConflictBehavior.md | 8 ++------ .../src/components/DetailsPanel.tsx | 12 +++++++----- packages/hotkeys/src/manager.utils.ts | 13 +++++++++---- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/reference/type-aliases/ConflictBehavior.md b/docs/reference/type-aliases/ConflictBehavior.md index cf77172..c473ba7 100644 --- a/docs/reference/type-aliases/ConflictBehavior.md +++ b/docs/reference/type-aliases/ConflictBehavior.md @@ -6,13 +6,10 @@ title: ConflictBehavior # Type Alias: ConflictBehavior ```ts -type ConflictBehavior = "warn" | "error" | "replace" | "allow" | ( - keyDisplay: string, - unregisterConflicting: () => void -) => void; +type ConflictBehavior = "warn" | "error" | "replace" | "allow" | CustomConflictHandler; ``` -Defined in: [manager.utils.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/manager.utils.ts#L11) +Defined in: [manager.utils.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/manager.utils.ts#L16) Behavior when registering a hotkey/sequence that conflicts with an existing registration. @@ -20,4 +17,3 @@ Behavior when registering a hotkey/sequence that conflicts with an existing regi - `'error'` - Throw an error and prevent the new registration - `'replace'` - Unregister the existing registration and register the new one - `'allow'` - Allow multiple registrations without warning -- custom callback - You can log the issue with configuration or unregister conflicting registration conditionally e.g. with showing confirmation popup diff --git a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx index d5fa1cd..e1e1d9f 100644 --- a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx +++ b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx @@ -123,9 +123,7 @@ function getConflictLabel( return 'callback handled conflict with' } -function serializeConflictBehavior( - behavior: ConflictBehavior -): string { +function serializeConflictBehavior(behavior: ConflictBehavior): string { if (typeof behavior === 'string') { return behavior } @@ -296,7 +294,9 @@ function HotkeyDetails(props: {
conflictBehavior - {serializeConflictBehavior(conflictBehavior())} + + {serializeConflictBehavior(conflictBehavior())} +
hasFired @@ -509,7 +509,9 @@ function SequenceDetails(props: {
conflictBehavior - {serializeConflictBehavior(conflictBehavior())} + + {serializeConflictBehavior(conflictBehavior())} +
diff --git a/packages/hotkeys/src/manager.utils.ts b/packages/hotkeys/src/manager.utils.ts index 55587b8..27ef095 100644 --- a/packages/hotkeys/src/manager.utils.ts +++ b/packages/hotkeys/src/manager.utils.ts @@ -2,7 +2,7 @@ import type { ParsedHotkey } from './hotkey' type CustomConflictHandler = ( keyDisplay: string, - unregisterAnotherConflictingId: () => void + unregisterAnotherConflictingId: () => void, ) => void /** @@ -13,7 +13,12 @@ type CustomConflictHandler = ( * - `'replace'` - Unregister the existing registration and register the new one * - `'allow'` - Allow multiple registrations without warning */ -export type ConflictBehavior = 'warn' | 'error' | 'replace' | 'allow' | CustomConflictHandler +export type ConflictBehavior = + | 'warn' + | 'error' + | 'replace' + | 'allow' + | CustomConflictHandler /** * Default options for hotkey/sequence registration. @@ -170,8 +175,8 @@ export function handleConflict( } if (typeof conflictBehavior === 'function') { - conflictBehavior(keyDisplay, () => unregister(conflictingId)) - return + conflictBehavior(keyDisplay, () => unregister(conflictingId)) + return } // At this point, conflictBehavior must be 'replace'