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..c473ba7 100644
--- a/docs/reference/type-aliases/ConflictBehavior.md
+++ b/docs/reference/type-aliases/ConflictBehavior.md
@@ -6,10 +6,10 @@ title: ConflictBehavior
# Type Alias: ConflictBehavior
```ts
-type ConflictBehavior = "warn" | "error" | "replace" | "allow";
+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.
diff --git a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
index 38411e3..e1e1d9f 100644
--- a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
+++ b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
@@ -119,7 +119,16 @@ 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 +294,9 @@ function HotkeyDetails(props: {
conflictBehavior
- {conflictBehavior()}
+
+ {serializeConflictBehavior(conflictBehavior())}
+
hasFired
@@ -498,7 +509,9 @@ 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..27ef095 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,12 @@ 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 +174,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')
+ })
})
})