From 785066fe938e3ecee8e53f441e19cafdeebd58e5 Mon Sep 17 00:00:00 2001 From: Kade Angell Date: Tue, 17 Feb 2026 14:55:08 -0700 Subject: [PATCH 1/9] fix: add ignoreInputs support to SequenceManager and useHotkeySequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SequenceManager never checked the ignoreInputs option, so keyboard sequences like G+A and G+G would fire even when the user was typing in input, textarea, select, or contentEditable elements. Two issues were fixed: 1. SequenceManager (#handleKeyDown) now checks ignoreInputs before matching keys. A new #isInputElement() method mirrors the same logic from HotkeyManager. The ignoreInputs default is resolved at registration time using the first step of the sequence — Ctrl/Meta combos and Escape default to false (fire in inputs), while single keys and Shift/Alt combos default to true (ignored in inputs). 2. useHotkeySequence React hook was silently dropping the ignoreInputs option — it only forwarded timeout and platform to manager.register(). Now ignoreInputs is extracted, forwarded, and included in the effect dependency array. Added tests covering: single-key sequences ignored in input/textarea/ contentEditable by default, Mod sequences firing in inputs by default, explicit ignoreInputs: true/false overrides, and button-type inputs not being treated as text inputs. --- packages/hotkeys/src/sequence.ts | 60 ++++++ packages/hotkeys/tests/sequence.test.ts | 174 ++++++++++++++++++ .../react-hotkeys/src/useHotkeySequence.ts | 5 +- 3 files changed, 237 insertions(+), 2 deletions(-) diff --git a/packages/hotkeys/src/sequence.ts b/packages/hotkeys/src/sequence.ts index 32a30fb..7cc3232 100644 --- a/packages/hotkeys/src/sequence.ts +++ b/packages/hotkeys/src/sequence.ts @@ -43,6 +43,21 @@ function generateSequenceId(): string { return `sequence_${++sequenceIdCounter}` } +/** + * Computes the default ignoreInputs value for a sequence based on its first step. + * Uses the same logic as HotkeyManager: Ctrl/Meta combos and Escape fire in inputs; + * single keys and Shift/Alt combos are ignored. + */ +function getDefaultIgnoreInputsForSequence( + parsedSequence: Array, +): boolean { + const firstStep = parsedSequence[0] + if (!firstStep) return true + if (firstStep.ctrl || firstStep.meta) return false + if (firstStep.key === 'Escape') return false + return true +} + /** * Internal representation of a sequence registration. */ @@ -130,6 +145,10 @@ export class SequenceManager { parseHotkey(hotkey, platform), ) + const resolvedIgnoreInputs = + options.ignoreInputs ?? + getDefaultIgnoreInputsForSequence(parsedSequence) + const registration: SequenceRegistration = { id, sequence, @@ -142,6 +161,7 @@ export class SequenceManager { enabled: true, ...options, platform, + ignoreInputs: resolvedIgnoreInputs, }, currentIndex: 0, lastKeyTime: 0, @@ -194,6 +214,39 @@ export class SequenceManager { } } + /** + * Checks if an element is an input-like element that should be ignored. + */ + #isInputElement(element: EventTarget | null): boolean { + if (!element) { + return false + } + + if (element instanceof HTMLInputElement) { + const type = element.type.toLowerCase() + if (type === 'button' || type === 'submit' || type === 'reset') { + return false + } + return true + } + + if ( + element instanceof HTMLTextAreaElement || + element instanceof HTMLSelectElement + ) { + return true + } + + if (element instanceof HTMLElement) { + const contentEditable = element.contentEditable + if (contentEditable === 'true' || contentEditable === '') { + return true + } + } + + return false + } + /** * Handles keydown events for sequence matching. */ @@ -205,6 +258,13 @@ export class SequenceManager { continue } + // Check if we should ignore input elements + if (registration.options.ignoreInputs !== false) { + if (this.#isInputElement(event.target)) { + continue + } + } + const timeout = registration.options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT // Check if sequence has timed out diff --git a/packages/hotkeys/tests/sequence.test.ts b/packages/hotkeys/tests/sequence.test.ts index 98ecf0d..786708f 100644 --- a/packages/hotkeys/tests/sequence.test.ts +++ b/packages/hotkeys/tests/sequence.test.ts @@ -147,6 +147,180 @@ describe('SequenceManager', () => { }) }) + describe('ignoreInputs option', () => { + /** + * Helper to dispatch a keyboard event from a specific element + */ + function dispatchKeyFromElement( + element: HTMLElement, + key: string, + options: { + ctrlKey?: boolean + shiftKey?: boolean + altKey?: boolean + metaKey?: boolean + } = {}, + ): KeyboardEvent { + const event = new KeyboardEvent('keydown', { + key, + ctrlKey: options.ctrlKey ?? false, + shiftKey: options.shiftKey ?? false, + altKey: options.altKey ?? false, + metaKey: options.metaKey ?? false, + bubbles: true, + }) + Object.defineProperty(event, 'target', { + value: element, + writable: false, + configurable: true, + }) + Object.defineProperty(event, 'currentTarget', { + value: document, + writable: false, + configurable: true, + }) + document.dispatchEvent(event) + return event + } + + it('should ignore single-key sequences in input elements by default', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + const input = document.createElement('input') + document.body.appendChild(input) + + dispatchKeyFromElement(input, 'g') + dispatchKeyFromElement(input, 'g') + + expect(callback).not.toHaveBeenCalled() + + document.body.removeChild(input) + }) + + it('should ignore single-key sequences in textarea elements by default', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + const textarea = document.createElement('textarea') + document.body.appendChild(textarea) + + dispatchKeyFromElement(textarea, 'g') + dispatchKeyFromElement(textarea, 'g') + + expect(callback).not.toHaveBeenCalled() + + document.body.removeChild(textarea) + }) + + it('should ignore single-key sequences in contenteditable elements by default', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + const div = document.createElement('div') + div.contentEditable = 'true' + document.body.appendChild(div) + + dispatchKeyFromElement(div, 'g') + dispatchKeyFromElement(div, 'g') + + expect(callback).not.toHaveBeenCalled() + + document.body.removeChild(div) + }) + + it('should fire sequences starting with Mod key in inputs by default', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['Mod+K', 'S'], callback, { platform: 'mac' }) + + const input = document.createElement('input') + document.body.appendChild(input) + + dispatchKeyFromElement(input, 'k', { metaKey: true }) + dispatchKeyFromElement(input, 's') + + expect(callback).toHaveBeenCalledTimes(1) + + document.body.removeChild(input) + }) + + it('should respect explicit ignoreInputs: true even for Mod sequences', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['Mod+K', 'S'], callback, { + platform: 'mac', + ignoreInputs: true, + }) + + const input = document.createElement('input') + document.body.appendChild(input) + + dispatchKeyFromElement(input, 'k', { metaKey: true }) + dispatchKeyFromElement(input, 's') + + expect(callback).not.toHaveBeenCalled() + + document.body.removeChild(input) + }) + + it('should respect explicit ignoreInputs: false for single-key sequences', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback, { ignoreInputs: false }) + + const input = document.createElement('input') + document.body.appendChild(input) + + dispatchKeyFromElement(input, 'g') + dispatchKeyFromElement(input, 'g') + + expect(callback).toHaveBeenCalledTimes(1) + + document.body.removeChild(input) + }) + + it('should fire single-key sequences outside of input elements', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + // dispatch from a non-input element + dispatchKey('g') + dispatchKey('g') + + expect(callback).toHaveBeenCalledTimes(1) + }) + + it('should not ignore button-type inputs', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + const button = document.createElement('input') + button.type = 'button' + document.body.appendChild(button) + + dispatchKeyFromElement(button, 'g') + dispatchKeyFromElement(button, 'g') + + expect(callback).toHaveBeenCalledTimes(1) + + document.body.removeChild(button) + }) + }) + describe('longer sequences', () => { it('should match three-key sequences', () => { const manager = SequenceManager.getInstance() diff --git a/packages/react-hotkeys/src/useHotkeySequence.ts b/packages/react-hotkeys/src/useHotkeySequence.ts index 02b13ec..6dada72 100644 --- a/packages/react-hotkeys/src/useHotkeySequence.ts +++ b/packages/react-hotkeys/src/useHotkeySequence.ts @@ -60,7 +60,7 @@ export function useHotkeySequence( const { enabled = true, ...sequenceOptions } = mergedOptions // Extract options for stable dependencies - const { timeout, platform } = sequenceOptions + const { timeout, platform, ignoreInputs } = sequenceOptions // Use refs to keep callback stable const callbackRef = useRef(callback) @@ -80,6 +80,7 @@ export function useHotkeySequence( const registerOptions: SequenceOptions = { enabled: true } if (timeout !== undefined) registerOptions.timeout = timeout if (platform !== undefined) registerOptions.platform = platform + if (ignoreInputs !== undefined) registerOptions.ignoreInputs = ignoreInputs const unregister = manager.register( sequence, @@ -88,5 +89,5 @@ export function useHotkeySequence( ) return unregister - }, [enabled, sequence, sequenceKey, timeout, platform]) + }, [enabled, sequence, sequenceKey, timeout, platform, ignoreInputs]) } From a7bf5b370ea59eac51898fded4a12b380d87fa0b Mon Sep 17 00:00:00 2001 From: Kade Angell Date: Tue, 17 Feb 2026 15:18:12 -0700 Subject: [PATCH 2/9] changeset --- .changeset/red-pens-taste.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/red-pens-taste.md diff --git a/.changeset/red-pens-taste.md b/.changeset/red-pens-taste.md new file mode 100644 index 0000000..4a62ddb --- /dev/null +++ b/.changeset/red-pens-taste.md @@ -0,0 +1,6 @@ +--- +'@tanstack/react-hotkeys': minor +'@tanstack/hotkeys': minor +--- + +Support for the ignoreInputs flag in useHotkeySequence From 350971b072571a37f41eb950727aae23f594042e Mon Sep 17 00:00:00 2001 From: Kade Angell Date: Tue, 17 Feb 2026 15:20:06 -0700 Subject: [PATCH 3/9] changeset update: patch bump, not minor bump --- .changeset/red-pens-taste.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/red-pens-taste.md b/.changeset/red-pens-taste.md index 4a62ddb..6bfd6ca 100644 --- a/.changeset/red-pens-taste.md +++ b/.changeset/red-pens-taste.md @@ -1,6 +1,6 @@ --- -'@tanstack/react-hotkeys': minor -'@tanstack/hotkeys': minor +'@tanstack/react-hotkeys': patch +'@tanstack/hotkeys': patch --- Support for the ignoreInputs flag in useHotkeySequence From 558ff184e214b0941a979c2e216b1bc92bbd797c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:55:48 +0000 Subject: [PATCH 4/9] ci: apply automated fixes --- docs/reference/classes/SequenceManager.md | 14 +++++++------- docs/reference/functions/createSequenceMatcher.md | 2 +- docs/reference/functions/getSequenceManager.md | 2 +- packages/hotkeys/src/sequence.ts | 3 +-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/reference/classes/SequenceManager.md b/docs/reference/classes/SequenceManager.md index 118cb6c..70d0d52 100644 --- a/docs/reference/classes/SequenceManager.md +++ b/docs/reference/classes/SequenceManager.md @@ -5,7 +5,7 @@ title: SequenceManager # Class: SequenceManager -Defined in: [sequence.ts:79](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L79) +Defined in: [sequence.ts:94](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L94) Manages keyboard sequence matching for Vim-style shortcuts. @@ -35,7 +35,7 @@ unregister() destroy(): void; ``` -Defined in: [sequence.ts:300](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L300) +Defined in: [sequence.ts:359](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L359) Destroys the manager and removes all listeners. @@ -51,7 +51,7 @@ Destroys the manager and removes all listeners. getRegistrationCount(): number; ``` -Defined in: [sequence.ts:293](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L293) +Defined in: [sequence.ts:352](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L352) Gets the number of registered sequences. @@ -70,7 +70,7 @@ register( options): () => void; ``` -Defined in: [sequence.ts:118](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L118) +Defined in: [sequence.ts:133](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L133) Registers a hotkey sequence handler. @@ -114,7 +114,7 @@ A function to unregister the sequence resetAll(): void; ``` -Defined in: [sequence.ts:283](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L283) +Defined in: [sequence.ts:342](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L342) Resets all sequence progress. @@ -130,7 +130,7 @@ Resets all sequence progress. static getInstance(): SequenceManager; ``` -Defined in: [sequence.ts:93](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L93) +Defined in: [sequence.ts:108](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L108) Gets the singleton instance of SequenceManager. @@ -146,7 +146,7 @@ Gets the singleton instance of SequenceManager. static resetInstance(): void; ``` -Defined in: [sequence.ts:103](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L103) +Defined in: [sequence.ts:118](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L118) Resets the singleton instance. Useful for testing. diff --git a/docs/reference/functions/createSequenceMatcher.md b/docs/reference/functions/createSequenceMatcher.md index 035a860..4fd7be6 100644 --- a/docs/reference/functions/createSequenceMatcher.md +++ b/docs/reference/functions/createSequenceMatcher.md @@ -9,7 +9,7 @@ title: createSequenceMatcher function createSequenceMatcher(sequence, options): object; ``` -Defined in: [sequence.ts:332](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L332) +Defined in: [sequence.ts:391](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L391) Creates a simple sequence matcher for one-off use. diff --git a/docs/reference/functions/getSequenceManager.md b/docs/reference/functions/getSequenceManager.md index 05b5dbd..51bd40c 100644 --- a/docs/reference/functions/getSequenceManager.md +++ b/docs/reference/functions/getSequenceManager.md @@ -9,7 +9,7 @@ title: getSequenceManager function getSequenceManager(): SequenceManager; ``` -Defined in: [sequence.ts:310](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L310) +Defined in: [sequence.ts:369](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L369) Gets the singleton SequenceManager instance. Convenience function for accessing the manager. diff --git a/packages/hotkeys/src/sequence.ts b/packages/hotkeys/src/sequence.ts index 7cc3232..b4c613d 100644 --- a/packages/hotkeys/src/sequence.ts +++ b/packages/hotkeys/src/sequence.ts @@ -146,8 +146,7 @@ export class SequenceManager { ) const resolvedIgnoreInputs = - options.ignoreInputs ?? - getDefaultIgnoreInputsForSequence(parsedSequence) + options.ignoreInputs ?? getDefaultIgnoreInputsForSequence(parsedSequence) const registration: SequenceRegistration = { id, From 70faf2b4224d17414a489eed5f4ecb86524291a9 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Feb 2026 13:09:08 -0600 Subject: [PATCH 5/9] rewrite hotkey-sequence stuff --- .../reference/functions/useHotkeySequence.md | 2 +- .../interfaces/UseHotkeySequenceOptions.md | 19 +- .../functions/createHotkeySequence.md | 2 +- .../interfaces/CreateHotkeySequenceOptions.md | 4 +- docs/reference/classes/HotkeyManager.md | 20 +- docs/reference/classes/SequenceManager.md | 26 +- .../functions/createSequenceMatcher.md | 6 +- docs/reference/functions/formatForDisplay.md | 2 +- docs/reference/functions/formatHotkey.md | 2 +- .../functions/formatHotkeySequence.md | 35 + .../functions/formatKeyForDebuggingDisplay.md | 2 +- docs/reference/functions/formatWithLabels.md | 2 +- docs/reference/functions/getHotkeyManager.md | 2 +- .../reference/functions/getSequenceManager.md | 2 +- docs/reference/index.md | 2 + .../interfaces/FormatKeyDebuggingOptions.md | 6 +- docs/reference/interfaces/HotkeyOptions.md | 6 +- .../interfaces/HotkeyRegistration.md | 2 +- docs/reference/interfaces/SequenceOptions.md | 25 +- .../interfaces/SequenceRegistrationHandle.md | 85 ++ .../type-aliases/ConflictBehavior.md | 8 +- docs/reference/type-aliases/HotkeySequence.md | 2 +- .../react/useHotkeySequence/src/index.css | 24 + .../react/useHotkeySequence/src/index.tsx | 23 +- examples/solid/createHeldKeys/tsconfig.json | 3 +- .../solid/createHotkeyRecorder/tsconfig.json | 3 +- .../solid/createHotkeySequence/src/index.css | 24 + .../solid/createHotkeySequence/src/index.tsx | 21 +- .../solid/createHotkeySequence/tsconfig.json | 3 +- examples/solid/createKeyHold/tsconfig.json | 3 +- packages/hotkeys/src/format.ts | 16 + packages/hotkeys/src/hotkey-manager.ts | 174 +-- packages/hotkeys/src/index.ts | 2 +- packages/hotkeys/src/manager.utils.ts | 171 +++ .../src/{sequence.ts => sequence-manager.ts} | 312 +++-- packages/hotkeys/tests/format.test.ts | 12 + ...manager.test.ts => hotkey-manager.test.ts} | 102 +- ...tate.test.ts => key-state-tracker.test.ts} | 0 packages/hotkeys/tests/manager.utils.test.ts | 273 +++++ ...uence.test.ts => sequence-manager.test.ts} | 256 +++- .../react-hotkeys/src/useHotkeySequence.ts | 127 +- .../solid-hotkeys/src/createHotkeySequence.ts | 59 +- pnpm-lock.yaml | 1049 +++++++++-------- 43 files changed, 1952 insertions(+), 967 deletions(-) create mode 100644 docs/reference/functions/formatHotkeySequence.md create mode 100644 docs/reference/interfaces/SequenceRegistrationHandle.md create mode 100644 packages/hotkeys/src/manager.utils.ts rename packages/hotkeys/src/{sequence.ts => sequence-manager.ts} (54%) rename packages/hotkeys/tests/{manager.test.ts => hotkey-manager.test.ts} (94%) rename packages/hotkeys/tests/{key-state.test.ts => key-state-tracker.test.ts} (100%) create mode 100644 packages/hotkeys/tests/manager.utils.test.ts rename packages/hotkeys/tests/{sequence.test.ts => sequence-manager.test.ts} (59%) diff --git a/docs/framework/react/reference/functions/useHotkeySequence.md b/docs/framework/react/reference/functions/useHotkeySequence.md index 27323dd..11593f9 100644 --- a/docs/framework/react/reference/functions/useHotkeySequence.md +++ b/docs/framework/react/reference/functions/useHotkeySequence.md @@ -12,7 +12,7 @@ function useHotkeySequence( options): void; ``` -Defined in: [useHotkeySequence.ts:50](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L50) +Defined in: [useHotkeySequence.ts:61](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L61) React hook for registering a keyboard shortcut sequence (Vim-style). diff --git a/docs/framework/react/reference/interfaces/UseHotkeySequenceOptions.md b/docs/framework/react/reference/interfaces/UseHotkeySequenceOptions.md index 193015f..5fd6a61 100644 --- a/docs/framework/react/reference/interfaces/UseHotkeySequenceOptions.md +++ b/docs/framework/react/reference/interfaces/UseHotkeySequenceOptions.md @@ -5,20 +5,27 @@ title: UseHotkeySequenceOptions # Interface: UseHotkeySequenceOptions -Defined in: [useHotkeySequence.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L10) +Defined in: [useHotkeySequence.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L12) ## Extends -- `Omit`\<`SequenceOptions`, `"enabled"`\> +- `Omit`\<`SequenceOptions`, `"target"`\> ## Properties -### enabled? +### target? ```ts -optional enabled: boolean; +optional target: + | HTMLElement + | Document + | Window + | RefObject + | null; ``` -Defined in: [useHotkeySequence.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L15) +Defined in: [useHotkeySequence.ts:21](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/useHotkeySequence.ts#L21) -Whether the sequence is enabled. Defaults to true. +The DOM element to attach the event listener to. +Can be a React ref, direct DOM element, or null. +Defaults to document. diff --git a/docs/framework/solid/reference/functions/createHotkeySequence.md b/docs/framework/solid/reference/functions/createHotkeySequence.md index faafb2d..7fb9bb3 100644 --- a/docs/framework/solid/reference/functions/createHotkeySequence.md +++ b/docs/framework/solid/reference/functions/createHotkeySequence.md @@ -12,7 +12,7 @@ function createHotkeySequence( options): void; ``` -Defined in: [createHotkeySequence.ts:50](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L50) +Defined in: [createHotkeySequence.ts:51](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L51) SolidJS primitive for registering a keyboard shortcut sequence (Vim-style). diff --git a/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md index 433f554..f447e90 100644 --- a/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md @@ -5,7 +5,7 @@ title: CreateHotkeySequenceOptions # Interface: CreateHotkeySequenceOptions -Defined in: [createHotkeySequence.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L10) +Defined in: [createHotkeySequence.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L11) ## Extends @@ -19,6 +19,6 @@ Defined in: [createHotkeySequence.ts:10](https://github.com/TanStack/hotkeys/blo optional enabled: boolean; ``` -Defined in: [createHotkeySequence.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L15) +Defined in: [createHotkeySequence.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L16) Whether the sequence is enabled. Defaults to true. diff --git a/docs/reference/classes/HotkeyManager.md b/docs/reference/classes/HotkeyManager.md index 97da494..d88a568 100644 --- a/docs/reference/classes/HotkeyManager.md +++ b/docs/reference/classes/HotkeyManager.md @@ -5,7 +5,7 @@ title: HotkeyManager # Class: HotkeyManager -Defined in: [hotkey-manager.ts:166](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L166) +Defined in: [hotkey-manager.ts:140](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L140) Singleton manager for hotkey registrations. @@ -34,7 +34,7 @@ unregister() readonly registrations: Store>; ``` -Defined in: [hotkey-manager.ts:188](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L188) +Defined in: [hotkey-manager.ts:162](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L162) The TanStack Store containing all hotkey registrations. Use this to subscribe to registration changes or access current registrations. @@ -63,7 +63,7 @@ for (const [id, reg] of manager.registrations.state) { destroy(): void; ``` -Defined in: [hotkey-manager.ts:836](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L836) +Defined in: [hotkey-manager.ts:698](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L698) Destroys the manager and removes all listeners. @@ -79,7 +79,7 @@ Destroys the manager and removes all listeners. getRegistrationCount(): number; ``` -Defined in: [hotkey-manager.ts:807](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L807) +Defined in: [hotkey-manager.ts:669](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L669) Gets the number of registered hotkeys. @@ -95,7 +95,7 @@ Gets the number of registered hotkeys. isRegistered(hotkey, target?): boolean; ``` -Defined in: [hotkey-manager.ts:818](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L818) +Defined in: [hotkey-manager.ts:680](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L680) Checks if a specific hotkey is registered. @@ -111,7 +111,7 @@ The hotkey string to check Optional target element to match (if provided, both hotkey and target must match) -`Document` | `Window` | `HTMLElement` +`HTMLElement` | `Document` | `Window` #### Returns @@ -130,7 +130,7 @@ register( options): HotkeyRegistrationHandle; ``` -Defined in: [hotkey-manager.ts:251](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L251) +Defined in: [hotkey-manager.ts:225](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L225) Registers a hotkey handler and returns a handle for updating the registration. @@ -186,7 +186,7 @@ handle.unregister() triggerRegistration(id): boolean; ``` -Defined in: [hotkey-manager.ts:771](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L771) +Defined in: [hotkey-manager.ts:633](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L633) Triggers a registration's callback programmatically from devtools. Creates a synthetic KeyboardEvent and invokes the callback. @@ -213,7 +213,7 @@ True if the registration was found and triggered static getInstance(): HotkeyManager; ``` -Defined in: [hotkey-manager.ts:209](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L209) +Defined in: [hotkey-manager.ts:183](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L183) Gets the singleton instance of HotkeyManager. @@ -229,7 +229,7 @@ Gets the singleton instance of HotkeyManager. static resetInstance(): void; ``` -Defined in: [hotkey-manager.ts:219](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L219) +Defined in: [hotkey-manager.ts:193](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L193) Resets the singleton instance. Useful for testing. diff --git a/docs/reference/classes/SequenceManager.md b/docs/reference/classes/SequenceManager.md index 70d0d52..7c906f6 100644 --- a/docs/reference/classes/SequenceManager.md +++ b/docs/reference/classes/SequenceManager.md @@ -5,7 +5,7 @@ title: SequenceManager # Class: SequenceManager -Defined in: [sequence.ts:94](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L94) +Defined in: [sequence-manager.ts:119](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L119) Manages keyboard sequence matching for Vim-style shortcuts. @@ -35,7 +35,7 @@ unregister() destroy(): void; ``` -Defined in: [sequence.ts:359](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L359) +Defined in: [sequence-manager.ts:501](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L501) Destroys the manager and removes all listeners. @@ -51,7 +51,7 @@ Destroys the manager and removes all listeners. getRegistrationCount(): number; ``` -Defined in: [sequence.ts:352](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L352) +Defined in: [sequence-manager.ts:494](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L494) Gets the number of registered sequences. @@ -67,10 +67,10 @@ Gets the number of registered sequences. register( sequence, callback, - options): () => void; + options): SequenceRegistrationHandle; ``` -Defined in: [sequence.ts:133](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L133) +Defined in: [sequence-manager.ts:165](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L165) Registers a hotkey sequence handler. @@ -96,15 +96,9 @@ Options for the sequence behavior #### Returns -A function to unregister the sequence +[`SequenceRegistrationHandle`](../interfaces/SequenceRegistrationHandle.md) -```ts -(): void; -``` - -##### Returns - -`void` +A handle to update or unregister the sequence *** @@ -114,7 +108,7 @@ A function to unregister the sequence resetAll(): void; ``` -Defined in: [sequence.ts:342](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L342) +Defined in: [sequence-manager.ts:484](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L484) Resets all sequence progress. @@ -130,7 +124,7 @@ Resets all sequence progress. static getInstance(): SequenceManager; ``` -Defined in: [sequence.ts:108](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L108) +Defined in: [sequence-manager.ts:140](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L140) Gets the singleton instance of SequenceManager. @@ -146,7 +140,7 @@ Gets the singleton instance of SequenceManager. static resetInstance(): void; ``` -Defined in: [sequence.ts:118](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L118) +Defined in: [sequence-manager.ts:150](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L150) Resets the singleton instance. Useful for testing. diff --git a/docs/reference/functions/createSequenceMatcher.md b/docs/reference/functions/createSequenceMatcher.md index 4fd7be6..022d1df 100644 --- a/docs/reference/functions/createSequenceMatcher.md +++ b/docs/reference/functions/createSequenceMatcher.md @@ -9,10 +9,14 @@ title: createSequenceMatcher function createSequenceMatcher(sequence, options): object; ``` -Defined in: [sequence.ts:391](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L391) +Defined in: [sequence-manager.ts:539](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L539) Creates a simple sequence matcher for one-off use. +This is a low-level helper that does not support ignoreInputs, target, +or other HotkeyOptions. Callers must handle input filtering themselves +if attaching to document. + ## Parameters ### sequence diff --git a/docs/reference/functions/formatForDisplay.md b/docs/reference/functions/formatForDisplay.md index 51bab41..a518cb8 100644 --- a/docs/reference/functions/formatForDisplay.md +++ b/docs/reference/functions/formatForDisplay.md @@ -9,7 +9,7 @@ title: formatForDisplay function formatForDisplay(hotkey, options): string; ``` -Defined in: [format.ts:61](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L61) +Defined in: [format.ts:77](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L77) Formats a hotkey for display in a user interface. diff --git a/docs/reference/functions/formatHotkey.md b/docs/reference/functions/formatHotkey.md index 12d7953..700c75c 100644 --- a/docs/reference/functions/formatHotkey.md +++ b/docs/reference/functions/formatHotkey.md @@ -9,7 +9,7 @@ title: formatHotkey function formatHotkey(parsed): string; ``` -Defined in: [format.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L23) +Defined in: [format.ts:39](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L39) Converts a ParsedHotkey back to a hotkey string. diff --git a/docs/reference/functions/formatHotkeySequence.md b/docs/reference/functions/formatHotkeySequence.md new file mode 100644 index 0000000..192cb71 --- /dev/null +++ b/docs/reference/functions/formatHotkeySequence.md @@ -0,0 +1,35 @@ +--- +id: formatHotkeySequence +title: formatHotkeySequence +--- + +# Function: formatHotkeySequence() + +```ts +function formatHotkeySequence(sequence): string; +``` + +Defined in: [format.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L23) + +Converts a hotkey sequence array to a display string. + +## Parameters + +### sequence + +[`Hotkey`](../type-aliases/Hotkey.md)[] + +Array of hotkey strings that form the sequence + +## Returns + +`string` + +A space-separated string (e.g. ['G','G'] → 'G G') + +## Example + +```ts +formatHotkeySequence(['G', 'G']) // 'G G' +formatHotkeySequence(['D', 'I', 'W']) // 'D I W' +``` diff --git a/docs/reference/functions/formatKeyForDebuggingDisplay.md b/docs/reference/functions/formatKeyForDebuggingDisplay.md index 0a4051d..c17c092 100644 --- a/docs/reference/functions/formatKeyForDebuggingDisplay.md +++ b/docs/reference/functions/formatKeyForDebuggingDisplay.md @@ -9,7 +9,7 @@ title: formatKeyForDebuggingDisplay function formatKeyForDebuggingDisplay(key, options): string; ``` -Defined in: [format.ts:227](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L227) +Defined in: [format.ts:243](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L243) Formats a single key name for debugging/devtools display. diff --git a/docs/reference/functions/formatWithLabels.md b/docs/reference/functions/formatWithLabels.md index 5a05477..10470b3 100644 --- a/docs/reference/functions/formatWithLabels.md +++ b/docs/reference/functions/formatWithLabels.md @@ -9,7 +9,7 @@ title: formatWithLabels function formatWithLabels(hotkey, platform): string; ``` -Defined in: [format.ts:127](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L127) +Defined in: [format.ts:143](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L143) Formats a hotkey using platform-agnostic labels. Uses 'Cmd' on Mac and 'Ctrl' for Control, etc. diff --git a/docs/reference/functions/getHotkeyManager.md b/docs/reference/functions/getHotkeyManager.md index 25a57db..03969d3 100644 --- a/docs/reference/functions/getHotkeyManager.md +++ b/docs/reference/functions/getHotkeyManager.md @@ -9,7 +9,7 @@ title: getHotkeyManager function getHotkeyManager(): HotkeyManager; ``` -Defined in: [hotkey-manager.ts:852](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L852) +Defined in: [hotkey-manager.ts:714](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L714) Gets the singleton HotkeyManager instance. Convenience function for accessing the manager. diff --git a/docs/reference/functions/getSequenceManager.md b/docs/reference/functions/getSequenceManager.md index 51bd40c..df72f71 100644 --- a/docs/reference/functions/getSequenceManager.md +++ b/docs/reference/functions/getSequenceManager.md @@ -9,7 +9,7 @@ title: getSequenceManager function getSequenceManager(): SequenceManager; ``` -Defined in: [sequence.ts:369](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L369) +Defined in: [sequence-manager.ts:513](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L513) Gets the singleton SequenceManager instance. Convenience function for accessing the manager. diff --git a/docs/reference/index.md b/docs/reference/index.md index b296021..6588478 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -27,6 +27,7 @@ title: "@tanstack/hotkeys" - [ParsedHotkey](interfaces/ParsedHotkey.md) - [RawHotkey](interfaces/RawHotkey.md) - [SequenceOptions](interfaces/SequenceOptions.md) +- [SequenceRegistrationHandle](interfaces/SequenceRegistrationHandle.md) - [ValidationResult](interfaces/ValidationResult.md) ## Type Aliases @@ -74,6 +75,7 @@ title: "@tanstack/hotkeys" - [detectPlatform](functions/detectPlatform.md) - [formatForDisplay](functions/formatForDisplay.md) - [formatHotkey](functions/formatHotkey.md) +- [formatHotkeySequence](functions/formatHotkeySequence.md) - [formatKeyForDebuggingDisplay](functions/formatKeyForDebuggingDisplay.md) - [formatWithLabels](functions/formatWithLabels.md) - [getHotkeyManager](functions/getHotkeyManager.md) diff --git a/docs/reference/interfaces/FormatKeyDebuggingOptions.md b/docs/reference/interfaces/FormatKeyDebuggingOptions.md index 3cd4a82..cbe8a25 100644 --- a/docs/reference/interfaces/FormatKeyDebuggingOptions.md +++ b/docs/reference/interfaces/FormatKeyDebuggingOptions.md @@ -5,7 +5,7 @@ title: FormatKeyDebuggingOptions # Interface: FormatKeyDebuggingOptions -Defined in: [format.ts:171](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L171) +Defined in: [format.ts:187](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L187) Options for formatting a single key for debugging display. @@ -17,7 +17,7 @@ Options for formatting a single key for debugging display. optional platform: "mac" | "windows" | "linux"; ``` -Defined in: [format.ts:173](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L173) +Defined in: [format.ts:189](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L189) The target platform. Defaults to auto-detection. @@ -29,7 +29,7 @@ The target platform. Defaults to auto-detection. optional source: "key" | "code"; ``` -Defined in: [format.ts:182](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L182) +Defined in: [format.ts:198](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L198) Whether the input value comes from `event.key` or `event.code`. diff --git a/docs/reference/interfaces/HotkeyOptions.md b/docs/reference/interfaces/HotkeyOptions.md index 27891a5..d37e619 100644 --- a/docs/reference/interfaces/HotkeyOptions.md +++ b/docs/reference/interfaces/HotkeyOptions.md @@ -9,10 +9,6 @@ Defined in: [hotkey-manager.ts:27](https://github.com/TanStack/hotkeys/blob/main Options for registering a hotkey. -## Extended by - -- [`SequenceOptions`](SequenceOptions.md) - ## Properties ### conflictBehavior? @@ -114,7 +110,7 @@ Stop event propagation when the hotkey matches. Defaults to true ### target? ```ts -optional target: Document | Window | HTMLElement | null; +optional target: HTMLElement | Document | Window | null; ``` Defined in: [hotkey-manager.ts:45](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L45) diff --git a/docs/reference/interfaces/HotkeyRegistration.md b/docs/reference/interfaces/HotkeyRegistration.md index c06e2ae..85b11c2 100644 --- a/docs/reference/interfaces/HotkeyRegistration.md +++ b/docs/reference/interfaces/HotkeyRegistration.md @@ -86,7 +86,7 @@ The parsed hotkey ### target ```ts -target: Document | Window | HTMLElement; +target: HTMLElement | Document | Window; ``` Defined in: [hotkey-manager.ts:65](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L65) diff --git a/docs/reference/interfaces/SequenceOptions.md b/docs/reference/interfaces/SequenceOptions.md index ea35af7..af561eb 100644 --- a/docs/reference/interfaces/SequenceOptions.md +++ b/docs/reference/interfaces/SequenceOptions.md @@ -5,13 +5,14 @@ title: SequenceOptions # Interface: SequenceOptions -Defined in: [sequence.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L15) +Defined in: [sequence-manager.ts:26](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L26) Options for hotkey sequence matching. +Extends HotkeyOptions but excludes requireReset (not applicable to sequences). ## Extends -- [`HotkeyOptions`](HotkeyOptions.md) +- `Omit`\<[`HotkeyOptions`](HotkeyOptions.md), `"requireReset"`\> ## Properties @@ -111,22 +112,6 @@ Prevent the default browser action when the hotkey matches. Defaults to true *** -### requireReset? - -```ts -optional requireReset: boolean; -``` - -Defined in: [hotkey-manager.ts:41](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L41) - -If true, only trigger once until all keys are released. Default: false - -#### Inherited from - -[`HotkeyOptions`](HotkeyOptions.md).[`requireReset`](HotkeyOptions.md#requirereset) - -*** - ### stopPropagation? ```ts @@ -146,7 +131,7 @@ Stop event propagation when the hotkey matches. Defaults to true ### target? ```ts -optional target: Document | Window | HTMLElement | null; +optional target: HTMLElement | Document | Window | null; ``` Defined in: [hotkey-manager.ts:45](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L45) @@ -165,6 +150,6 @@ The DOM element to attach the event listener to. Defaults to document. optional timeout: number; ``` -Defined in: [sequence.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L17) +Defined in: [sequence-manager.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L28) Timeout between keys in milliseconds. Default: 1000 diff --git a/docs/reference/interfaces/SequenceRegistrationHandle.md b/docs/reference/interfaces/SequenceRegistrationHandle.md new file mode 100644 index 0000000..c09bd3d --- /dev/null +++ b/docs/reference/interfaces/SequenceRegistrationHandle.md @@ -0,0 +1,85 @@ +--- +id: SequenceRegistrationHandle +title: SequenceRegistrationHandle +--- + +# Interface: SequenceRegistrationHandle + +Defined in: [sequence-manager.ts:91](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L91) + +A handle returned from SequenceManager.register() that allows updating +the callback and options without re-registering the sequence. + +## Example + +```ts +const handle = manager.register(['G', 'G'], callback, options) + +handle.callback = newCallback +handle.setOptions({ timeout: 500 }) +handle.unregister() +``` + +## Properties + +### callback + +```ts +callback: HotkeyCallback; +``` + +Defined in: [sequence-manager.ts:94](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L94) + +*** + +### id + +```ts +readonly id: string; +``` + +Defined in: [sequence-manager.ts:92](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L92) + +*** + +### isActive + +```ts +readonly isActive: boolean; +``` + +Defined in: [sequence-manager.ts:93](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L93) + +*** + +### setOptions() + +```ts +setOptions: (options) => void; +``` + +Defined in: [sequence-manager.ts:95](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L95) + +#### Parameters + +##### options + +`Partial`\<[`SequenceOptions`](SequenceOptions.md)\> + +#### Returns + +`void` + +*** + +### unregister() + +```ts +unregister: () => void; +``` + +Defined in: [sequence-manager.ts:96](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L96) + +#### Returns + +`void` diff --git a/docs/reference/type-aliases/ConflictBehavior.md b/docs/reference/type-aliases/ConflictBehavior.md index b909b4b..50f06ca 100644 --- a/docs/reference/type-aliases/ConflictBehavior.md +++ b/docs/reference/type-aliases/ConflictBehavior.md @@ -9,11 +9,11 @@ title: ConflictBehavior type ConflictBehavior = "warn" | "error" | "replace" | "allow"; ``` -Defined in: [hotkey-manager.ts:22](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey-manager.ts#L22) +Defined in: [manager.utils.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/manager.utils.ts#L11) -Behavior when registering a hotkey that conflicts with an existing registration. +Behavior when registering a hotkey/sequence that conflicts with an existing registration. - `'warn'` - Log a warning to the console but allow both registrations (default) - `'error'` - Throw an error and prevent the new registration -- `'replace'` - Unregister the existing hotkey and register the new one -- `'allow'` - Allow multiple registrations of the same hotkey without warning +- `'replace'` - Unregister the existing registration and register the new one +- `'allow'` - Allow multiple registrations without warning diff --git a/docs/reference/type-aliases/HotkeySequence.md b/docs/reference/type-aliases/HotkeySequence.md index 2bd89ee..d3e23ab 100644 --- a/docs/reference/type-aliases/HotkeySequence.md +++ b/docs/reference/type-aliases/HotkeySequence.md @@ -9,7 +9,7 @@ title: HotkeySequence type HotkeySequence = Hotkey[]; ``` -Defined in: [sequence.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L30) +Defined in: [sequence-manager.ts:41](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L41) A sequence of hotkeys for Vim-style shortcuts. diff --git a/examples/react/useHotkeySequence/src/index.css b/examples/react/useHotkeySequence/src/index.css index 0fe2ade..69b749b 100644 --- a/examples/react/useHotkeySequence/src/index.css +++ b/examples/react/useHotkeySequence/src/index.css @@ -29,6 +29,7 @@ header p { max-width: 500px; margin: 0 auto; } + .demo-section { background: white; border-radius: 12px; @@ -133,3 +134,26 @@ button { button:hover { background: #0052a3; } + +.counter { + font-size: 18px; + font-weight: bold; + color: #0066cc; + margin: 12px 0; +} + +.demo-input { + width: 100%; + max-width: 400px; + padding: 12px 16px; + font-size: 14px; + border: 1px solid #ddd; + border-radius: 6px; + margin-top: 8px; +} + +.demo-input:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; + border-color: #0066cc; +} diff --git a/examples/react/useHotkeySequence/src/index.tsx b/examples/react/useHotkeySequence/src/index.tsx index e920da6..1d5b4a2 100644 --- a/examples/react/useHotkeySequence/src/index.tsx +++ b/examples/react/useHotkeySequence/src/index.tsx @@ -15,7 +15,6 @@ function App() { setHistory((h) => [...h.slice(-9), action]) } - // Vim-style navigation useHotkeySequence(['G', 'G'], () => addToHistory('gg → Go to top')) useHotkeySequence(['Shift+G'], () => addToHistory('G → Go to bottom')) useHotkeySequence(['D', 'D'], () => addToHistory('dd → Delete line')) @@ -25,7 +24,6 @@ function App() { addToHistory('ciw → Change inner word'), ) - // Custom sequences with different timeout useHotkeySequence( ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown'], () => addToHistory('↑↑↓↓ → Konami code (partial)'), @@ -38,7 +36,6 @@ function App() { { timeout: 1500 }, ) - // Letter sequences useHotkeySequence(['H', 'E', 'L', 'L', 'O'], () => addToHistory('hello → Hello World!'), ) @@ -143,6 +140,26 @@ function App() { )} +
+

Input handling

+

+ Sequences are not detected when typing in text inputs, textareas, + selects, or contenteditable elements. Button-type inputs ( + type="button", submit, reset) + still receive sequences. Focus the input below and try g{' '} + g or h + e + l + l + o — nothing will trigger. Click outside to try again. +

+ +
+

Usage

{`import { useHotkeySequence } from '@tanstack/react-hotkeys'
diff --git a/examples/solid/createHeldKeys/tsconfig.json b/examples/solid/createHeldKeys/tsconfig.json
index ec9b6aa..cf560b8 100644
--- a/examples/solid/createHeldKeys/tsconfig.json
+++ b/examples/solid/createHeldKeys/tsconfig.json
@@ -6,8 +6,9 @@
     "skipLibCheck": true,
     "moduleResolution": "Bundler",
     "jsx": "preserve",
+    "jsxImportSource": "solid-js",
     "strict": true,
-    "types": ["vite/client"]
+    "types": ["vite/client", "solid-js"]
   },
   "include": ["src", "vite.config.ts"]
 }
diff --git a/examples/solid/createHotkeyRecorder/tsconfig.json b/examples/solid/createHotkeyRecorder/tsconfig.json
index ec9b6aa..cf560b8 100644
--- a/examples/solid/createHotkeyRecorder/tsconfig.json
+++ b/examples/solid/createHotkeyRecorder/tsconfig.json
@@ -6,8 +6,9 @@
     "skipLibCheck": true,
     "moduleResolution": "Bundler",
     "jsx": "preserve",
+    "jsxImportSource": "solid-js",
     "strict": true,
-    "types": ["vite/client"]
+    "types": ["vite/client", "solid-js"]
   },
   "include": ["src", "vite.config.ts"]
 }
diff --git a/examples/solid/createHotkeySequence/src/index.css b/examples/solid/createHotkeySequence/src/index.css
index 3a0fd93..bfce1a5 100644
--- a/examples/solid/createHotkeySequence/src/index.css
+++ b/examples/solid/createHotkeySequence/src/index.css
@@ -25,6 +25,7 @@ header p {
   max-width: 500px;
   margin: 0 auto;
 }
+
 .demo-section {
   background: white;
   border-radius: 12px;
@@ -115,3 +116,26 @@ button {
   border-radius: 6px;
   cursor: pointer;
 }
+
+.counter {
+  font-size: 18px;
+  font-weight: bold;
+  color: #0066cc;
+  margin: 12px 0;
+}
+
+.demo-input {
+  width: 100%;
+  max-width: 400px;
+  padding: 12px 16px;
+  font-size: 14px;
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  margin-top: 8px;
+}
+
+.demo-input:focus {
+  outline: 2px solid #0066cc;
+  outline-offset: 2px;
+  border-color: #0066cc;
+}
diff --git a/examples/solid/createHotkeySequence/src/index.tsx b/examples/solid/createHotkeySequence/src/index.tsx
index f242634..9f5e707 100644
--- a/examples/solid/createHotkeySequence/src/index.tsx
+++ b/examples/solid/createHotkeySequence/src/index.tsx
@@ -13,7 +13,6 @@ import './index.css'
 function App() {
   const [lastSequence, setLastSequence] = createSignal(null)
   const [history, setHistory] = createSignal>([])
-
   const addToHistory = (action: string) => {
     setLastSequence(action)
     setHistory((h) => [...h.slice(-9), action])
@@ -143,6 +142,26 @@ function App() {
           
         
 
+        
+

Input handling

+

+ Sequences are not detected when typing in text inputs, textareas, + selects, or contenteditable elements. Button-type inputs ( + type="button", submit, reset) + still receive sequences. Focus the input below and try g{' '} + g or h + e + l + l + o — nothing will trigger. Click outside to try again. +

+ +
+

Usage

{`import { createHotkeySequence } from '@tanstack/solid-hotkeys'
diff --git a/examples/solid/createHotkeySequence/tsconfig.json b/examples/solid/createHotkeySequence/tsconfig.json
index ec9b6aa..cf560b8 100644
--- a/examples/solid/createHotkeySequence/tsconfig.json
+++ b/examples/solid/createHotkeySequence/tsconfig.json
@@ -6,8 +6,9 @@
     "skipLibCheck": true,
     "moduleResolution": "Bundler",
     "jsx": "preserve",
+    "jsxImportSource": "solid-js",
     "strict": true,
-    "types": ["vite/client"]
+    "types": ["vite/client", "solid-js"]
   },
   "include": ["src", "vite.config.ts"]
 }
diff --git a/examples/solid/createKeyHold/tsconfig.json b/examples/solid/createKeyHold/tsconfig.json
index ec9b6aa..cf560b8 100644
--- a/examples/solid/createKeyHold/tsconfig.json
+++ b/examples/solid/createKeyHold/tsconfig.json
@@ -6,8 +6,9 @@
     "skipLibCheck": true,
     "moduleResolution": "Bundler",
     "jsx": "preserve",
+    "jsxImportSource": "solid-js",
     "strict": true,
-    "types": ["vite/client"]
+    "types": ["vite/client", "solid-js"]
   },
   "include": ["src", "vite.config.ts"]
 }
diff --git a/packages/hotkeys/src/format.ts b/packages/hotkeys/src/format.ts
index 126503e..419544e 100644
--- a/packages/hotkeys/src/format.ts
+++ b/packages/hotkeys/src/format.ts
@@ -8,6 +8,22 @@ import {
 import { parseHotkey } from './parse'
 import type { FormatDisplayOptions, Hotkey, ParsedHotkey } from './hotkey'
 
+/**
+ * Converts a hotkey sequence array to a display string.
+ *
+ * @param sequence - Array of hotkey strings that form the sequence
+ * @returns A space-separated string (e.g. ['G','G'] → 'G G')
+ *
+ * @example
+ * ```ts
+ * formatHotkeySequence(['G', 'G'])      // 'G G'
+ * formatHotkeySequence(['D', 'I', 'W']) // 'D I W'
+ * ```
+ */
+export function formatHotkeySequence(sequence: Array): string {
+  return sequence.join(' ')
+}
+
 /**
  * Converts a ParsedHotkey back to a hotkey string.
  *
diff --git a/packages/hotkeys/src/hotkey-manager.ts b/packages/hotkeys/src/hotkey-manager.ts
index 983941c..bf9e685 100644
--- a/packages/hotkeys/src/hotkey-manager.ts
+++ b/packages/hotkeys/src/hotkey-manager.ts
@@ -3,6 +3,14 @@ import { detectPlatform, normalizeKeyName } from './constants'
 import { formatHotkey } from './format'
 import { parseHotkey, rawHotkeyToParsedHotkey } from './parse'
 import { matchesKeyboardEvent } from './match'
+import {
+  defaultHotkeyOptions,
+  getDefaultIgnoreInputs,
+  handleConflict,
+  isEventForTarget,
+  isInputElement,
+} from './manager.utils'
+import type { ConflictBehavior } from './manager.utils'
 import type {
   Hotkey,
   HotkeyCallback,
@@ -11,15 +19,7 @@ import type {
   RegisterableHotkey,
 } from './hotkey'
 
-/**
- * Behavior when registering a hotkey that conflicts with an existing registration.
- *
- * - `'warn'` - Log a warning to the console but allow both registrations (default)
- * - `'error'` - Throw an error and prevent the new registration
- * - `'replace'` - Unregister the existing hotkey and register the new one
- * - `'allow'` - Allow multiple registrations of the same hotkey without warning
- */
-export type ConflictBehavior = 'warn' | 'error' | 'replace' | 'allow'
+export type { ConflictBehavior }
 
 /**
  * Options for registering a hotkey.
@@ -109,22 +109,6 @@ export interface HotkeyRegistrationHandle {
   unregister: () => void
 }
 
-/**
- * Default options for hotkey registration.
- */
-const defaultHotkeyOptions: Omit<
-  Required,
-  'platform' | 'target'
-> = {
-  preventDefault: true,
-  stopPropagation: true,
-  eventType: 'keydown',
-  requireReset: false,
-  enabled: true,
-  ignoreInputs: true,
-  conflictBehavior: 'warn',
-}
-
 let registrationIdCounter = 0
 
 /**
@@ -134,16 +118,6 @@ function generateId(): string {
   return `hotkey_${++registrationIdCounter}`
 }
 
-/**
- * Computes the default ignoreInputs value based on the hotkey.
- * Ctrl/Meta shortcuts and Escape fire in inputs; single keys and Shift/Alt combos are ignored.
- */
-function getDefaultIgnoreInputs(parsedHotkey: ParsedHotkey): boolean {
-  if (parsedHotkey.ctrl || parsedHotkey.meta) return false // Mod+S, Ctrl+C, etc.
-  if (parsedHotkey.key === 'Escape') return false // Close modal, etc.
-  return true // Single keys, Shift+key, Alt+key
-}
-
 /**
  * Singleton manager for hotkey registrations.
  *
@@ -296,7 +270,12 @@ export class HotkeyManager {
     )
 
     if (conflictingRegistration) {
-      this.#handleConflict(conflictingRegistration, hotkeyStr, conflictBehavior)
+      handleConflict(
+        conflictingRegistration.id,
+        hotkeyStr,
+        conflictBehavior,
+        (id) => this.#unregister(id),
+      )
     }
 
     const resolvedIgnoreInputs =
@@ -304,6 +283,7 @@ export class HotkeyManager {
 
     const baseOptions = {
       ...defaultHotkeyOptions,
+      requireReset: false,
       ...options,
       platform,
     }
@@ -462,7 +442,7 @@ export class HotkeyManager {
       }
 
       // Check if event originated from or bubbled to this target
-      if (!this.#isEventForTarget(event, target)) {
+      if (!isEventForTarget(event, target)) {
         continue
       }
 
@@ -472,7 +452,7 @@ export class HotkeyManager {
 
       // Check if we should ignore input elements (defaults to true)
       if (registration.options.ignoreInputs !== false) {
-        if (this.#isInputElement(event.target)) {
+        if (isInputElement(event.target)) {
           // Don't ignore if the hotkey is explicitly scoped to this input element
           if (event.target !== registration.target) {
             continue
@@ -589,48 +569,6 @@ export class HotkeyManager {
     }
   }
 
-  /**
-   * Checks if an event is for the given target (originated from or bubbled to it).
-   */
-  #isEventForTarget(
-    event: KeyboardEvent,
-    target: HTMLElement | Document | Window,
-  ): boolean {
-    // For Document and Window, verify that our handler was indeed called for this target.
-    //
-    // Browser compatibility note:
-    // Per the DOM spec, event.currentTarget should equal the element the listener was
-    // attached to. However, some Chromium-based browsers (notably Brave) exhibit
-    // non-standard behavior where event.currentTarget is set to document.documentElement
-    // () instead of document when a listener is attached to document.
-    // This may be related to privacy/fingerprinting protections.
-    //
-    // To ensure cross-browser compatibility, we accept both the expected target
-    // and document.documentElement as valid currentTarget values.
-    // See: https://dom.spec.whatwg.org/#dom-event-currenttarget
-    if (target === document || target === window) {
-      return (
-        event.currentTarget === target ||
-        event.currentTarget === document.documentElement
-      )
-    }
-
-    // For HTMLElement, check if event originated from or bubbled to the element
-    if (target instanceof HTMLElement) {
-      // Check if the event's currentTarget is the target (capturing/bubbling)
-      if (event.currentTarget === target) {
-        return true
-      }
-
-      // Check if the event's target is a descendant of our target
-      if (event.target instanceof Node && target.contains(event.target)) {
-        return true
-      }
-    }
-
-    return false
-  }
-
   /**
    * Finds an existing registration with the same hotkey and target.
    */
@@ -646,82 +584,6 @@ export class HotkeyManager {
     return null
   }
 
-  /**
-   * Handles conflicts between hotkey registrations based on conflict behavior.
-   */
-  #handleConflict(
-    conflictingRegistration: HotkeyRegistration,
-    hotkey: Hotkey,
-    conflictBehavior: ConflictBehavior,
-  ): void {
-    if (conflictBehavior === 'allow') {
-      return
-    }
-
-    if (conflictBehavior === 'warn') {
-      console.warn(
-        `Hotkey '${hotkey}' is already registered. Multiple handlers will be triggered. ` +
-          `Use conflictBehavior: 'replace' to replace the existing handler, ` +
-          `or conflictBehavior: 'allow' to suppress this warning.`,
-      )
-      return
-    }
-
-    if (conflictBehavior === 'error') {
-      throw new Error(
-        `Hotkey '${hotkey}' is already registered. ` +
-          `Use conflictBehavior: 'replace' to replace the existing handler, ` +
-          `or conflictBehavior: 'allow' to allow multiple registrations.`,
-      )
-    }
-
-    // At this point, conflictBehavior must be 'replace'
-    this.#unregister(conflictingRegistration.id)
-  }
-
-  /**
-   * Checks if an element is an input-like element that should be ignored.
-   *
-   * This includes:
-   * - HTMLInputElement (all input types except button, submit, reset)
-   * - HTMLTextAreaElement
-   * - HTMLSelectElement
-   * - Elements with contentEditable enabled
-   *
-   * Button-type inputs (button, submit, reset) are excluded so hotkeys like
-   * Mod+S and Escape fire when the user has tabbed to a form button.
-   */
-  #isInputElement(element: EventTarget | null): boolean {
-    if (!element) {
-      return false
-    }
-
-    if (element instanceof HTMLInputElement) {
-      const type = element.type.toLowerCase()
-      if (type === 'button' || type === 'submit' || type === 'reset') {
-        return false
-      }
-      return true
-    }
-
-    if (
-      element instanceof HTMLTextAreaElement ||
-      element instanceof HTMLSelectElement
-    ) {
-      return true
-    }
-
-    // Check for contenteditable elements
-    if (element instanceof HTMLElement) {
-      const contentEditable = element.contentEditable
-      if (contentEditable === 'true' || contentEditable === '') {
-        return true
-      }
-    }
-
-    return false
-  }
-
   /**
    * Determines if a registration should be reset based on the keyup event.
    */
diff --git a/packages/hotkeys/src/index.ts b/packages/hotkeys/src/index.ts
index 361608d..2163910 100644
--- a/packages/hotkeys/src/index.ts
+++ b/packages/hotkeys/src/index.ts
@@ -6,5 +6,5 @@ export * from './key-state-tracker'
 export * from './match'
 export * from './parse'
 export * from './recorder'
-export * from './sequence'
+export * from './sequence-manager'
 export * from './validate'
diff --git a/packages/hotkeys/src/manager.utils.ts b/packages/hotkeys/src/manager.utils.ts
new file mode 100644
index 0000000..ec8790f
--- /dev/null
+++ b/packages/hotkeys/src/manager.utils.ts
@@ -0,0 +1,171 @@
+import type { ParsedHotkey } from './hotkey'
+
+/**
+ * Behavior when registering a hotkey/sequence that conflicts with an existing registration.
+ *
+ * - `'warn'` - Log a warning to the console but allow both registrations (default)
+ * - `'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
+ */
+export type ConflictBehavior = 'warn' | 'error' | 'replace' | 'allow'
+
+/**
+ * Default options for hotkey/sequence registration.
+ * Omitted: platform, target (resolved at registration), requireReset (HotkeyManager only).
+ */
+export const defaultHotkeyOptions = {
+  preventDefault: true,
+  stopPropagation: true,
+  eventType: 'keydown' as const,
+  enabled: true,
+  ignoreInputs: true,
+  conflictBehavior: 'warn' as ConflictBehavior,
+}
+
+/**
+ * Computes the default ignoreInputs value based on the hotkey.
+ * Ctrl/Meta shortcuts and Escape fire in inputs; single keys and Shift/Alt combos are ignored.
+ */
+export function getDefaultIgnoreInputs(parsedHotkey: ParsedHotkey): boolean {
+  if (parsedHotkey.ctrl || parsedHotkey.meta) return false // Mod+S, Ctrl+C, etc.
+  if (parsedHotkey.key === 'Escape') return false // Close modal, etc.
+  return true // Single keys, Shift+key, Alt+key
+}
+
+/**
+ * Checks if an element is an input-like element that should be ignored for hotkeys.
+ *
+ * This includes:
+ * - HTMLInputElement (all input types except button, submit, reset)
+ * - HTMLTextAreaElement
+ * - HTMLSelectElement
+ * - Elements with contentEditable enabled
+ *
+ * Button-type inputs (button, submit, reset) are excluded so hotkeys like
+ * Mod+S and Escape fire when the user has tabbed to a form button.
+ */
+export function isInputElement(element: EventTarget | null): boolean {
+  if (!element) {
+    return false
+  }
+
+  if (element instanceof HTMLInputElement) {
+    const type = element.type.toLowerCase()
+    if (type === 'button' || type === 'submit' || type === 'reset') {
+      return false
+    }
+    return true
+  }
+
+  if (
+    element instanceof HTMLTextAreaElement ||
+    element instanceof HTMLSelectElement
+  ) {
+    return true
+  }
+
+  // Check for contenteditable elements
+  if (element instanceof HTMLElement) {
+    const contentEditable = element.contentEditable
+    if (contentEditable === 'true' || contentEditable === '') {
+      return true
+    }
+  }
+
+  return false
+}
+
+/**
+ * Checks if an event is for the given target (originated from or bubbled to it).
+ *
+ * For document/window targets, also accepts document.documentElement as currentTarget
+ * to handle Brave and other browsers where currentTarget may be documentElement
+ * instead of document when listeners are attached to document.
+ */
+export function isEventForTarget(
+  event: KeyboardEvent,
+  target: HTMLElement | Document | Window,
+): boolean {
+  // For Document and Window, verify that our handler was indeed called for this target.
+  //
+  // Browser compatibility note:
+  // Per the DOM spec, event.currentTarget should equal the element the listener was
+  // attached to. However, some Chromium-based browsers (notably Brave) exhibit
+  // non-standard behavior where event.currentTarget is set to document.documentElement
+  // () instead of document when a listener is attached to document.
+  // This may be related to privacy/fingerprinting protections.
+  //
+  // To ensure cross-browser compatibility, we accept both the expected target
+  // and document.documentElement as valid currentTarget values.
+  // See: https://dom.spec.whatwg.org/#dom-event-currenttarget
+  if (target === document || target === window) {
+    return (
+      event.currentTarget === target ||
+      event.currentTarget === document.documentElement
+    )
+  }
+
+  // For Window, accept window, document, or document.documentElement (browser quirks)
+  if (target === window) {
+    return (
+      event.currentTarget === window ||
+      event.currentTarget === document ||
+      event.currentTarget === document.documentElement
+    )
+  }
+
+  // For HTMLElement, check if event originated from or bubbled to the element
+  if (target instanceof HTMLElement) {
+    // Check if the event's currentTarget is the target (capturing/bubbling)
+    if (event.currentTarget === target) {
+      return true
+    }
+
+    // Check if the event's target is a descendant of our target
+    if (event.target instanceof Node && target.contains(event.target)) {
+      return true
+    }
+  }
+
+  return false
+}
+
+/**
+ * Handles conflicts between registrations based on conflict behavior.
+ *
+ * @param conflictingId - The ID of the conflicting registration
+ * @param keyDisplay - Display string for the conflicting key/sequence (for error messages)
+ * @param conflictBehavior - How to handle the conflict
+ * @param unregister - Function to unregister by ID
+ */
+export function handleConflict(
+  conflictingId: string,
+  keyDisplay: string,
+  conflictBehavior: ConflictBehavior,
+  unregister: (id: string) => void,
+): void {
+  if (conflictBehavior === 'allow') {
+    return
+  }
+
+  if (conflictBehavior === 'warn') {
+    console.warn(
+      `'${keyDisplay}' is already registered. Multiple handlers will be triggered. ` +
+        `Use conflictBehavior: 'replace' to replace the existing handler, ` +
+        `or conflictBehavior: 'allow' to suppress this warning.`,
+    )
+    return
+  }
+
+  if (conflictBehavior === 'error') {
+    throw new Error(
+      `'${keyDisplay}' is already registered. ` +
+        `Use conflictBehavior: 'replace' to replace the existing handler, ` +
+        `or conflictBehavior: 'allow' to allow multiple registrations.`,
+    )
+  }
+
+  // At this point, conflictBehavior must be 'replace'
+  unregister(conflictingId)
+}
diff --git a/packages/hotkeys/src/sequence.ts b/packages/hotkeys/src/sequence-manager.ts
similarity index 54%
rename from packages/hotkeys/src/sequence.ts
rename to packages/hotkeys/src/sequence-manager.ts
index b4c613d..21f6edd 100644
--- a/packages/hotkeys/src/sequence.ts
+++ b/packages/hotkeys/src/sequence-manager.ts
@@ -1,6 +1,14 @@
+import { formatHotkeySequence } from './format'
 import { detectPlatform } from './constants'
 import { parseHotkey } from './parse'
 import { matchesKeyboardEvent } from './match'
+import {
+  defaultHotkeyOptions,
+  getDefaultIgnoreInputs,
+  handleConflict,
+  isEventForTarget,
+  isInputElement,
+} from './manager.utils'
 import type { HotkeyOptions } from './hotkey-manager'
 import type {
   Hotkey,
@@ -9,10 +17,13 @@ import type {
   ParsedHotkey,
 } from './hotkey'
 
+type Target = HTMLElement | Document | Window
+
 /**
  * Options for hotkey sequence matching.
+ * Extends HotkeyOptions but excludes requireReset (not applicable to sequences).
  */
-export interface SequenceOptions extends HotkeyOptions {
+export interface SequenceOptions extends Omit {
   /** Timeout between keys in milliseconds. Default: 1000 */
   timeout?: number
 }
@@ -44,18 +55,10 @@ function generateSequenceId(): string {
 }
 
 /**
- * Computes the default ignoreInputs value for a sequence based on its first step.
- * Uses the same logic as HotkeyManager: Ctrl/Meta combos and Escape fire in inputs;
- * single keys and Shift/Alt combos are ignored.
+ * Returns a canonical string for sequence conflict comparison.
  */
-function getDefaultIgnoreInputsForSequence(
-  parsedSequence: Array,
-): boolean {
-  const firstStep = parsedSequence[0]
-  if (!firstStep) return true
-  if (firstStep.ctrl || firstStep.meta) return false
-  if (firstStep.key === 'Escape') return false
-  return true
+function sequenceKey(sequence: HotkeySequence): string {
+  return sequence.join('|')
 }
 
 /**
@@ -67,10 +70,32 @@ interface SequenceRegistration {
   parsedSequence: Array
   callback: HotkeyCallback
   options: SequenceOptions
+  target: Target
   currentIndex: number
   lastKeyTime: number
 }
 
+/**
+ * A handle returned from SequenceManager.register() that allows updating
+ * the callback and options without re-registering the sequence.
+ *
+ * @example
+ * ```ts
+ * const handle = manager.register(['G', 'G'], callback, options)
+ *
+ * handle.callback = newCallback
+ * handle.setOptions({ timeout: 500 })
+ * handle.unregister()
+ * ```
+ */
+export interface SequenceRegistrationHandle {
+  readonly id: string
+  readonly isActive: boolean
+  callback: HotkeyCallback
+  setOptions: (options: Partial) => void
+  unregister: () => void
+}
+
 /**
  * Manages keyboard sequence matching for Vim-style shortcuts.
  *
@@ -95,7 +120,14 @@ export class SequenceManager {
   static #instance: SequenceManager | null = null
 
   #registrations: Map = new Map()
-  #keydownListener: ((event: KeyboardEvent) => void) | null = null
+  #targetListeners: Map<
+    Target,
+    {
+      keydown: (event: KeyboardEvent) => void
+      keyup: (event: KeyboardEvent) => void
+    }
+  > = new Map()
+  #targetRegistrations: Map> = new Map()
   #platform: 'mac' | 'windows' | 'linux'
 
   private constructor() {
@@ -128,13 +160,13 @@ export class SequenceManager {
    * @param sequence - Array of hotkey strings that form the sequence
    * @param callback - Function to call when the sequence is completed
    * @param options - Options for the sequence behavior
-   * @returns A function to unregister the sequence
+   * @returns A handle to update or unregister the sequence
    */
   register(
     sequence: HotkeySequence,
     callback: HotkeyCallback,
     options: SequenceOptions = {},
-  ): () => void {
+  ): SequenceRegistrationHandle {
     if (sequence.length === 0) {
       throw new Error('Sequence must contain at least one hotkey')
     }
@@ -145,125 +177,224 @@ export class SequenceManager {
       parseHotkey(hotkey, platform),
     )
 
+    // Resolve target: default to document if not provided or null
+    const target: Target =
+      options.target ??
+      (typeof document !== 'undefined' ? document : ({} as Document))
+
+    // Resolve conflict behavior
+    const conflictBehavior = options.conflictBehavior ?? 'warn'
+
+    // Check for existing registrations with the same sequence and target
+    const conflictingRegistration = this.#findConflictingSequence(
+      sequence,
+      target,
+    )
+
+    if (conflictingRegistration) {
+      handleConflict(
+        conflictingRegistration.id,
+        formatHotkeySequence(sequence),
+        conflictBehavior,
+        (regId) => this.#unregister(regId),
+      )
+    }
+
+    const firstStep = parsedSequence[0]!
     const resolvedIgnoreInputs =
-      options.ignoreInputs ?? getDefaultIgnoreInputsForSequence(parsedSequence)
+      options.ignoreInputs ?? getDefaultIgnoreInputs(firstStep)
+
+    const baseOptions = {
+      ...defaultHotkeyOptions,
+      timeout: DEFAULT_SEQUENCE_TIMEOUT,
+      ...options,
+      platform,
+      ignoreInputs: resolvedIgnoreInputs,
+    }
 
     const registration: SequenceRegistration = {
       id,
       sequence,
       parsedSequence,
       callback,
-      options: {
-        timeout: DEFAULT_SEQUENCE_TIMEOUT,
-        preventDefault: true,
-        stopPropagation: true,
-        enabled: true,
-        ...options,
-        platform,
-        ignoreInputs: resolvedIgnoreInputs,
-      },
+      options: baseOptions,
+      target,
       currentIndex: 0,
       lastKeyTime: 0,
     }
 
     this.#registrations.set(id, registration)
-    this.#ensureListener()
 
-    return () => {
-      this.#unregister(id)
+    // Track registration for this target
+    if (!this.#targetRegistrations.has(target)) {
+      this.#targetRegistrations.set(target, new Set())
     }
+    this.#targetRegistrations.get(target)!.add(id)
+
+    // Ensure listeners are attached for this target
+    this.#ensureListenersForTarget(target)
+
+    const manager = this
+    const handle: SequenceRegistrationHandle = {
+      get id() {
+        return id
+      },
+      get isActive() {
+        return manager.#registrations.has(id)
+      },
+      get callback() {
+        const reg = manager.#registrations.get(id)
+        return reg?.callback ?? callback
+      },
+      set callback(newCallback: HotkeyCallback) {
+        const reg = manager.#registrations.get(id)
+        if (reg) {
+          reg.callback = newCallback
+        }
+      },
+      setOptions: (newOptions: Partial) => {
+        const reg = manager.#registrations.get(id)
+        if (reg) {
+          reg.options = { ...reg.options, ...newOptions }
+        }
+      },
+      unregister: () => {
+        manager.#unregister(id)
+      },
+    }
+
+    return handle
   }
 
   /**
    * Unregisters a sequence by its registration ID.
    */
   #unregister(id: string): void {
+    const registration = this.#registrations.get(id)
+    if (!registration) {
+      return
+    }
+
+    const target = registration.target
+
     this.#registrations.delete(id)
 
-    if (this.#registrations.size === 0) {
-      this.#removeListener()
+    // Remove from target registrations tracking
+    const targetRegs = this.#targetRegistrations.get(target)
+    if (targetRegs) {
+      targetRegs.delete(id)
+      if (targetRegs.size === 0) {
+        this.#removeListenersForTarget(target)
+      }
     }
   }
 
   /**
-   * Ensures the keydown listener is attached.
+   * Ensures event listeners are attached for a specific target.
    */
-  #ensureListener(): void {
+  #ensureListenersForTarget(target: Target): void {
     if (typeof document === 'undefined') {
       return // SSR safety
     }
 
-    if (!this.#keydownListener) {
-      this.#keydownListener = this.#handleKeyDown.bind(this)
-      document.addEventListener('keydown', this.#keydownListener)
+    if (this.#targetListeners.has(target)) {
+      return
     }
+
+    const keydownHandler = this.#createTargetKeyDownHandler(target)
+    const keyupHandler = this.#createTargetKeyUpHandler(target)
+
+    target.addEventListener('keydown', keydownHandler as EventListener)
+    target.addEventListener('keyup', keyupHandler as EventListener)
+
+    this.#targetListeners.set(target, {
+      keydown: keydownHandler,
+      keyup: keyupHandler,
+    })
   }
 
   /**
-   * Removes the keydown listener.
+   * Removes event listeners for a specific target.
    */
-  #removeListener(): void {
+  #removeListenersForTarget(target: Target): void {
     if (typeof document === 'undefined') {
       return
     }
 
-    if (this.#keydownListener) {
-      document.removeEventListener('keydown', this.#keydownListener)
-      this.#keydownListener = null
+    const listeners = this.#targetListeners.get(target)
+    if (!listeners) {
+      return
     }
+
+    target.removeEventListener('keydown', listeners.keydown as EventListener)
+    target.removeEventListener('keyup', listeners.keyup as EventListener)
+
+    this.#targetListeners.delete(target)
+    this.#targetRegistrations.delete(target)
   }
 
   /**
-   * Checks if an element is an input-like element that should be ignored.
+   * Creates a keydown handler for a specific target.
    */
-  #isInputElement(element: EventTarget | null): boolean {
-    if (!element) {
-      return false
-    }
-
-    if (element instanceof HTMLInputElement) {
-      const type = element.type.toLowerCase()
-      if (type === 'button' || type === 'submit' || type === 'reset') {
-        return false
-      }
-      return true
-    }
-
-    if (
-      element instanceof HTMLTextAreaElement ||
-      element instanceof HTMLSelectElement
-    ) {
-      return true
+  #createTargetKeyDownHandler(target: Target): (event: KeyboardEvent) => void {
+    return (event: KeyboardEvent) => {
+      this.#processTargetEvent(event, target, 'keydown')
     }
+  }
 
-    if (element instanceof HTMLElement) {
-      const contentEditable = element.contentEditable
-      if (contentEditable === 'true' || contentEditable === '') {
-        return true
-      }
+  /**
+   * Creates a keyup handler for a specific target.
+   */
+  #createTargetKeyUpHandler(target: Target): (event: KeyboardEvent) => void {
+    return (event: KeyboardEvent) => {
+      this.#processTargetEvent(event, target, 'keyup')
     }
-
-    return false
   }
 
   /**
-   * Handles keydown events for sequence matching.
+   * Processes keyboard events for a specific target and event type.
    */
-  #handleKeyDown(event: KeyboardEvent): void {
+  #processTargetEvent(
+    event: KeyboardEvent,
+    target: Target,
+    eventType: 'keydown' | 'keyup',
+  ): void {
+    const targetRegs = this.#targetRegistrations.get(target)
+    if (!targetRegs) {
+      return
+    }
+
     const now = Date.now()
 
-    for (const registration of this.#registrations.values()) {
+    for (const id of targetRegs) {
+      const registration = this.#registrations.get(id)
+      if (!registration) {
+        continue
+      }
+
+      if (!isEventForTarget(event, target)) {
+        continue
+      }
+
       if (!registration.options.enabled) {
         continue
       }
 
-      // Check if we should ignore input elements
+      // Check if we should ignore input elements (defaults to true)
       if (registration.options.ignoreInputs !== false) {
-        if (this.#isInputElement(event.target)) {
-          continue
+        if (isInputElement(event.target)) {
+          // Don't ignore if the sequence is explicitly scoped to this input element
+          if (event.target !== registration.target) {
+            continue
+          }
         }
       }
 
+      // Only process registrations that listen for this event type
+      if (registration.options.eventType !== eventType) {
+        continue
+      }
+
       const timeout = registration.options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT
 
       // Check if sequence has timed out
@@ -271,7 +402,6 @@ export class SequenceManager {
         registration.currentIndex > 0 &&
         now - registration.lastKeyTime > timeout
       ) {
-        // Reset the sequence
         registration.currentIndex = 0
       }
 
@@ -281,7 +411,6 @@ export class SequenceManager {
         continue
       }
 
-      // Check if current key matches the expected key in sequence
       if (
         matchesKeyboardEvent(
           event,
@@ -292,9 +421,7 @@ export class SequenceManager {
         registration.lastKeyTime = now
         registration.currentIndex++
 
-        // Check if sequence is complete
         if (registration.currentIndex >= registration.parsedSequence.length) {
-          // Sequence complete!
           if (registration.options.preventDefault) {
             event.preventDefault()
           }
@@ -312,12 +439,9 @@ export class SequenceManager {
 
           registration.callback(event, context)
 
-          // Reset for next sequence
           registration.currentIndex = 0
         }
       } else if (registration.currentIndex > 0) {
-        // Key didn't match and we were in the middle of a sequence
-        // Check if it matches the start of the sequence (for overlapping sequences)
         const firstHotkey = registration.parsedSequence[0]!
         if (
           matchesKeyboardEvent(
@@ -329,13 +453,31 @@ export class SequenceManager {
           registration.currentIndex = 1
           registration.lastKeyTime = now
         } else {
-          // Reset the sequence
           registration.currentIndex = 0
         }
       }
     }
   }
 
+  /**
+   * Finds an existing registration with the same sequence and target.
+   */
+  #findConflictingSequence(
+    sequence: HotkeySequence,
+    target: Target,
+  ): SequenceRegistration | null {
+    const key = sequenceKey(sequence)
+    for (const registration of this.#registrations.values()) {
+      if (
+        sequenceKey(registration.sequence) === key &&
+        registration.target === target
+      ) {
+        return registration
+      }
+    }
+    return null
+  }
+
   /**
    * Resets all sequence progress.
    */
@@ -357,7 +499,9 @@ export class SequenceManager {
    * Destroys the manager and removes all listeners.
    */
   destroy(): void {
-    this.#removeListener()
+    for (const target of this.#targetListeners.keys()) {
+      this.#removeListenersForTarget(target)
+    }
     this.#registrations.clear()
   }
 }
@@ -373,6 +517,10 @@ export function getSequenceManager(): SequenceManager {
 /**
  * Creates a simple sequence matcher for one-off use.
  *
+ * This is a low-level helper that does not support ignoreInputs, target,
+ * or other HotkeyOptions. Callers must handle input filtering themselves
+ * if attaching to document.
+ *
  * @param sequence - The sequence of hotkeys to match
  * @param options - Options including timeout
  * @returns An object with match() and reset() methods
@@ -407,7 +555,6 @@ export function createSequenceMatcher(
     match(event: KeyboardEvent): boolean {
       const now = Date.now()
 
-      // Check timeout
       if (currentIndex > 0 && now - lastKeyTime > timeout) {
         currentIndex = 0
       }
@@ -426,7 +573,6 @@ export function createSequenceMatcher(
           return true
         }
       } else if (currentIndex > 0) {
-        // Check if it matches start of sequence
         if (matchesKeyboardEvent(event, parsedSequence[0]!, platform)) {
           currentIndex = 1
           lastKeyTime = now
diff --git a/packages/hotkeys/tests/format.test.ts b/packages/hotkeys/tests/format.test.ts
index 4fb0591..2c6f178 100644
--- a/packages/hotkeys/tests/format.test.ts
+++ b/packages/hotkeys/tests/format.test.ts
@@ -2,11 +2,23 @@ import { describe, expect, it } from 'vitest'
 import {
   formatForDisplay,
   formatHotkey,
+  formatHotkeySequence,
   formatKeyForDebuggingDisplay,
   formatWithLabels,
 } from '../src/format'
 import type { ParsedHotkey } from '../src/hotkey'
 
+describe('formatHotkeySequence', () => {
+  it('should join sequence with spaces', () => {
+    expect(formatHotkeySequence(['G', 'G'])).toBe('G G')
+    expect(formatHotkeySequence(['D', 'I', 'W'])).toBe('D I W')
+  })
+
+  it('should handle single-key sequence', () => {
+    expect(formatHotkeySequence(['Escape'])).toBe('Escape')
+  })
+})
+
 describe('formatHotkey', () => {
   it('should format a simple key', () => {
     const parsed: ParsedHotkey = {
diff --git a/packages/hotkeys/tests/manager.test.ts b/packages/hotkeys/tests/hotkey-manager.test.ts
similarity index 94%
rename from packages/hotkeys/tests/manager.test.ts
rename to packages/hotkeys/tests/hotkey-manager.test.ts
index d2ebf9e..f57a85c 100644
--- a/packages/hotkeys/tests/manager.test.ts
+++ b/packages/hotkeys/tests/hotkey-manager.test.ts
@@ -334,66 +334,66 @@ describe('HotkeyManager', () => {
       document.dispatchEvent(keyupEvent)
       expect(callback).toHaveBeenCalled()
     })
+  })
 
-    describe('Brave browser compatibility (currentTarget)', () => {
-      /**
-       * Creates an event proxy that simulates Brave's non-standard behavior where
-       * event.currentTarget is document.documentElement instead of document when
-       * a listener is attached to document.
-       */
-      function createBraveLikeEvent(
-        type: 'keydown' | 'keyup',
-        key: string,
-        options: {
-          ctrlKey?: boolean
-          shiftKey?: boolean
-          altKey?: boolean
-          metaKey?: boolean
-        } = {},
-      ): KeyboardEvent {
-        const event = createKeyboardEvent(type, key, options)
-        return new Proxy(event, {
-          get(target, prop) {
-            if (prop === 'currentTarget') {
-              return document.documentElement
-            }
-            return Reflect.get(target, prop)
-          },
-        }) as KeyboardEvent
-      }
+  describe('Brave browser compatibility (currentTarget)', () => {
+    /**
+     * Creates an event proxy that simulates Brave's non-standard behavior where
+     * event.currentTarget is document.documentElement instead of document when
+     * a listener is attached to document.
+     */
+    function createBraveLikeEvent(
+      type: 'keydown' | 'keyup',
+      key: string,
+      options: {
+        ctrlKey?: boolean
+        shiftKey?: boolean
+        altKey?: boolean
+        metaKey?: boolean
+      } = {},
+    ): KeyboardEvent {
+      const event = createKeyboardEvent(type, key, options)
+      return new Proxy(event, {
+        get(target, prop) {
+          if (prop === 'currentTarget') {
+            return document.documentElement
+          }
+          return Reflect.get(target, prop)
+        },
+      }) as KeyboardEvent
+    }
 
-      it('should fire hotkeys when currentTarget is document.documentElement (document target)', () => {
-        const manager = HotkeyManager.getInstance()
-        const callback = vi.fn()
+    it('should fire hotkeys when currentTarget is document.documentElement (document target)', () => {
+      const manager = HotkeyManager.getInstance()
+      const callback = vi.fn()
 
-        manager.register('Mod+S', callback, { platform: 'mac' })
+      manager.register('Mod+S', callback, { platform: 'mac' })
 
-        const event = createBraveLikeEvent('keydown', 's', { metaKey: true })
-        document.dispatchEvent(event)
+      const event = createBraveLikeEvent('keydown', 's', { metaKey: true })
+      document.dispatchEvent(event)
 
-        expect(callback).toHaveBeenCalledTimes(1)
-        expect(callback).toHaveBeenCalledWith(
-          event,
-          expect.objectContaining({
-            hotkey: 'Mod+S',
-          }),
-        )
-      })
+      expect(callback).toHaveBeenCalledTimes(1)
+      expect(callback).toHaveBeenCalledWith(
+        event,
+        expect.objectContaining({
+          hotkey: 'Mod+S',
+        }),
+      )
+    })
 
-      it('should fire hotkeys when currentTarget is document.documentElement (window target)', () => {
-        const manager = HotkeyManager.getInstance()
-        const callback = vi.fn()
+    it('should fire hotkeys when currentTarget is document.documentElement (window target)', () => {
+      const manager = HotkeyManager.getInstance()
+      const callback = vi.fn()
 
-        manager.register('Escape', callback, {
-          platform: 'mac',
-          target: window,
-        })
+      manager.register('Escape', callback, {
+        platform: 'mac',
+        target: window,
+      })
 
-        const event = createBraveLikeEvent('keydown', 'Escape')
-        window.dispatchEvent(event)
+      const event = createBraveLikeEvent('keydown', 'Escape')
+      window.dispatchEvent(event)
 
-        expect(callback).toHaveBeenCalledTimes(1)
-      })
+      expect(callback).toHaveBeenCalledTimes(1)
     })
   })
 
diff --git a/packages/hotkeys/tests/key-state.test.ts b/packages/hotkeys/tests/key-state-tracker.test.ts
similarity index 100%
rename from packages/hotkeys/tests/key-state.test.ts
rename to packages/hotkeys/tests/key-state-tracker.test.ts
diff --git a/packages/hotkeys/tests/manager.utils.test.ts b/packages/hotkeys/tests/manager.utils.test.ts
new file mode 100644
index 0000000..11b74f7
--- /dev/null
+++ b/packages/hotkeys/tests/manager.utils.test.ts
@@ -0,0 +1,273 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import {
+  defaultHotkeyOptions,
+  getDefaultIgnoreInputs,
+  handleConflict,
+  isEventForTarget,
+  isInputElement,
+} from '../src/manager.utils'
+
+describe('manager.utils', () => {
+  describe('isInputElement', () => {
+    it('should return false for null', () => {
+      expect(isInputElement(null)).toBe(false)
+    })
+
+    it('should return true for text input elements', () => {
+      const input = document.createElement('input')
+      input.type = 'text'
+      expect(isInputElement(input)).toBe(true)
+    })
+
+    it('should return true for various input types', () => {
+      const types = ['text', 'email', 'number', 'password', 'search', 'tel']
+      for (const type of types) {
+        const input = document.createElement('input')
+        input.type = type
+        expect(isInputElement(input)).toBe(true)
+      }
+    })
+
+    it('should return false for button-type inputs', () => {
+      const buttonTypes = ['button', 'submit', 'reset']
+      for (const type of buttonTypes) {
+        const input = document.createElement('input')
+        input.type = type
+        expect(isInputElement(input)).toBe(false)
+      }
+    })
+
+    it('should return true for textarea', () => {
+      const textarea = document.createElement('textarea')
+      expect(isInputElement(textarea)).toBe(true)
+    })
+
+    it('should return true for select', () => {
+      const select = document.createElement('select')
+      expect(isInputElement(select)).toBe(true)
+    })
+
+    it('should return true for contenteditable elements', () => {
+      const div = document.createElement('div')
+      div.contentEditable = 'true'
+      expect(isInputElement(div)).toBe(true)
+    })
+
+    it('should return false for contenteditable false', () => {
+      const div = document.createElement('div')
+      div.contentEditable = 'false'
+      expect(isInputElement(div)).toBe(false)
+    })
+
+    it('should return false for regular div', () => {
+      const div = document.createElement('div')
+      expect(isInputElement(div)).toBe(false)
+    })
+  })
+
+  describe('getDefaultIgnoreInputs', () => {
+    it('should return false for Ctrl hotkeys', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'S',
+          ctrl: true,
+          shift: false,
+          alt: false,
+          meta: false,
+          modifiers: ['Control'],
+        }),
+      ).toBe(false)
+    })
+
+    it('should return false for Meta hotkeys', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'S',
+          ctrl: false,
+          shift: false,
+          alt: false,
+          meta: true,
+          modifiers: ['Meta'],
+        }),
+      ).toBe(false)
+    })
+
+    it('should return false for Escape', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'Escape',
+          ctrl: false,
+          shift: false,
+          alt: false,
+          meta: false,
+          modifiers: [],
+        }),
+      ).toBe(false)
+    })
+
+    it('should return true for single keys', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'G',
+          ctrl: false,
+          shift: false,
+          alt: false,
+          meta: false,
+          modifiers: [],
+        }),
+      ).toBe(true)
+    })
+
+    it('should return true for Shift combinations', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'S',
+          ctrl: false,
+          shift: true,
+          alt: false,
+          meta: false,
+          modifiers: ['Shift'],
+        }),
+      ).toBe(true)
+    })
+
+    it('should return true for Alt combinations', () => {
+      expect(
+        getDefaultIgnoreInputs({
+          key: 'A',
+          ctrl: false,
+          shift: false,
+          alt: true,
+          meta: false,
+          modifiers: ['Alt'],
+        }),
+      ).toBe(true)
+    })
+  })
+
+  describe('defaultHotkeyOptions', () => {
+    it('should have expected default values', () => {
+      expect(defaultHotkeyOptions).toEqual({
+        preventDefault: true,
+        stopPropagation: true,
+        eventType: 'keydown',
+        enabled: true,
+        ignoreInputs: true,
+        conflictBehavior: 'warn',
+      })
+    })
+  })
+
+  describe('isEventForTarget', () => {
+    it('should return true when event currentTarget matches document target', () => {
+      const event = new KeyboardEvent('keydown', { key: 'a', bubbles: true })
+      Object.defineProperty(event, 'currentTarget', {
+        value: document,
+        writable: false,
+        configurable: true,
+      })
+      document.dispatchEvent(event)
+      expect(isEventForTarget(event, document)).toBe(true)
+    })
+
+    it('should return true when event currentTarget matches window target', () => {
+      const event = new KeyboardEvent('keydown', { key: 'a', bubbles: true })
+      Object.defineProperty(event, 'currentTarget', {
+        value: window,
+        writable: false,
+        configurable: true,
+      })
+      expect(isEventForTarget(event, window)).toBe(true)
+    })
+
+    it('should return true when target contains event target', () => {
+      const div = document.createElement('div')
+      const span = document.createElement('span')
+      div.appendChild(span)
+      document.body.appendChild(div)
+
+      const event = new KeyboardEvent('keydown', { key: 'a', bubbles: true })
+      span.dispatchEvent(event)
+      Object.defineProperty(event, 'target', {
+        value: span,
+        writable: false,
+        configurable: true,
+      })
+      Object.defineProperty(event, 'currentTarget', {
+        value: div,
+        writable: false,
+        configurable: true,
+      })
+
+      expect(isEventForTarget(event, div)).toBe(true)
+
+      document.body.removeChild(div)
+    })
+
+    it('should return true when currentTarget matches element target', () => {
+      const div = document.createElement('div')
+      document.body.appendChild(div)
+
+      const event = new KeyboardEvent('keydown', { key: 'a', bubbles: true })
+      Object.defineProperty(event, 'target', {
+        value: div,
+        writable: false,
+        configurable: true,
+      })
+      Object.defineProperty(event, 'currentTarget', {
+        value: div,
+        writable: false,
+        configurable: true,
+      })
+
+      expect(isEventForTarget(event, div)).toBe(true)
+
+      document.body.removeChild(div)
+    })
+  })
+
+  describe('handleConflict', () => {
+    beforeEach(() => {
+      vi.restoreAllMocks()
+    })
+
+    it('should do nothing when conflictBehavior is allow', () => {
+      const unregister = vi.fn()
+      expect(() =>
+        handleConflict('id-1', 'Mod+S', 'allow', unregister),
+      ).not.toThrow()
+      expect(unregister).not.toHaveBeenCalled()
+    })
+
+    it('should warn when conflictBehavior is warn', () => {
+      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+      const unregister = vi.fn()
+
+      handleConflict('id-1', 'Mod+S', 'warn', unregister)
+
+      expect(warnSpy).toHaveBeenCalledWith(
+        expect.stringContaining('already registered'),
+      )
+      expect(unregister).not.toHaveBeenCalled()
+
+      warnSpy.mockRestore()
+    })
+
+    it('should throw when conflictBehavior is error', () => {
+      const unregister = vi.fn()
+
+      expect(() =>
+        handleConflict('id-1', 'Mod+S', 'error', unregister),
+      ).toThrow(/already registered/)
+      expect(unregister).not.toHaveBeenCalled()
+    })
+
+    it('should call unregister when conflictBehavior is replace', () => {
+      const unregister = vi.fn()
+
+      handleConflict('id-1', 'Mod+S', 'replace', unregister)
+
+      expect(unregister).toHaveBeenCalledWith('id-1')
+    })
+  })
+})
diff --git a/packages/hotkeys/tests/sequence.test.ts b/packages/hotkeys/tests/sequence-manager.test.ts
similarity index 59%
rename from packages/hotkeys/tests/sequence.test.ts
rename to packages/hotkeys/tests/sequence-manager.test.ts
index 786708f..6cc49e0 100644
--- a/packages/hotkeys/tests/sequence.test.ts
+++ b/packages/hotkeys/tests/sequence-manager.test.ts
@@ -1,15 +1,51 @@
 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { SequenceManager, createSequenceMatcher } from '../src/sequence'
+import {
+  SequenceManager,
+  createSequenceMatcher,
+  type SequenceOptions,
+} from '../src/sequence-manager'
 
 /**
  * Helper to create and dispatch a KeyboardEvent
  */
-function dispatchKey(key: string): KeyboardEvent {
-  const event = new KeyboardEvent('keydown', { key, bubbles: true })
+function dispatchKey(
+  key: string,
+  options: { eventType?: 'keydown' | 'keyup' } = {},
+): KeyboardEvent {
+  const eventType = options.eventType ?? 'keydown'
+  const event = new KeyboardEvent(eventType, { key, bubbles: true })
   document.dispatchEvent(event)
   return event
 }
 
+/**
+ * Helper to dispatch a keyboard event from a specific element.
+ * Dispatches on the element so listeners attached to it receive the event.
+ */
+function dispatchKeyFromElement(
+  element: HTMLElement,
+  key: string,
+  options: {
+    eventType?: 'keydown' | 'keyup'
+    ctrlKey?: boolean
+    shiftKey?: boolean
+    altKey?: boolean
+    metaKey?: boolean
+  } = {},
+): KeyboardEvent {
+  const eventType = options.eventType ?? 'keydown'
+  const event = new KeyboardEvent(eventType, {
+    key,
+    ctrlKey: options.ctrlKey ?? false,
+    shiftKey: options.shiftKey ?? false,
+    altKey: options.altKey ?? false,
+    metaKey: options.metaKey ?? false,
+    bubbles: true,
+  })
+  element.dispatchEvent(event)
+  return event
+}
+
 describe('SequenceManager', () => {
   beforeEach(() => {
     SequenceManager.resetInstance()
@@ -43,13 +79,34 @@ describe('SequenceManager', () => {
       const manager = SequenceManager.getInstance()
       const callback = vi.fn()
 
-      const unregister = manager.register(['G', 'G'], callback)
+      const handle = manager.register(['G', 'G'], callback)
       expect(manager.getRegistrationCount()).toBe(1)
 
-      unregister()
+      handle.unregister()
       expect(manager.getRegistrationCount()).toBe(0)
     })
 
+    it('should return handle with callback and setOptions', () => {
+      const manager = SequenceManager.getInstance()
+      const callback1 = vi.fn()
+      const callback2 = vi.fn()
+
+      const handle = manager.register(['G', 'G'], callback1)
+
+      expect(handle.id).toBeDefined()
+      expect(handle.isActive).toBe(true)
+
+      handle.callback = callback2
+      dispatchKey('g')
+      dispatchKey('g')
+      expect(callback2).toHaveBeenCalledTimes(1)
+      expect(callback1).not.toHaveBeenCalled()
+
+      handle.setOptions({ timeout: 2000 })
+      handle.unregister()
+      expect(handle.isActive).toBe(false)
+    })
+
     it('should throw for empty sequence', () => {
       const manager = SequenceManager.getInstance()
       expect(() => manager.register([], vi.fn())).toThrow()
@@ -148,41 +205,6 @@ describe('SequenceManager', () => {
   })
 
   describe('ignoreInputs option', () => {
-    /**
-     * Helper to dispatch a keyboard event from a specific element
-     */
-    function dispatchKeyFromElement(
-      element: HTMLElement,
-      key: string,
-      options: {
-        ctrlKey?: boolean
-        shiftKey?: boolean
-        altKey?: boolean
-        metaKey?: boolean
-      } = {},
-    ): KeyboardEvent {
-      const event = new KeyboardEvent('keydown', {
-        key,
-        ctrlKey: options.ctrlKey ?? false,
-        shiftKey: options.shiftKey ?? false,
-        altKey: options.altKey ?? false,
-        metaKey: options.metaKey ?? false,
-        bubbles: true,
-      })
-      Object.defineProperty(event, 'target', {
-        value: element,
-        writable: false,
-        configurable: true,
-      })
-      Object.defineProperty(event, 'currentTarget', {
-        value: document,
-        writable: false,
-        configurable: true,
-      })
-      document.dispatchEvent(event)
-      return event
-    }
-
     it('should ignore single-key sequences in input elements by default', () => {
       const manager = SequenceManager.getInstance()
       const callback = vi.fn()
@@ -319,6 +341,160 @@ describe('SequenceManager', () => {
 
       document.body.removeChild(button)
     })
+
+    it('should fire sequence when target is the input element itself', () => {
+      const manager = SequenceManager.getInstance()
+      const callback = vi.fn()
+
+      const input = document.createElement('input')
+      document.body.appendChild(input)
+
+      manager.register(['G', 'G'], callback, { target: input })
+
+      dispatchKeyFromElement(input, 'g')
+      dispatchKeyFromElement(input, 'g')
+
+      expect(callback).toHaveBeenCalledTimes(1)
+
+      document.body.removeChild(input)
+    })
+  })
+
+  describe('target option', () => {
+    it('should fire sequence only for events inside target element', () => {
+      const manager = SequenceManager.getInstance()
+      const callback = vi.fn()
+
+      const div = document.createElement('div')
+      document.body.appendChild(div)
+
+      manager.register(['G', 'G'], callback, { target: div })
+
+      dispatchKeyFromElement(div, 'g')
+      dispatchKeyFromElement(div, 'g')
+
+      expect(callback).toHaveBeenCalledTimes(1)
+
+      document.body.removeChild(div)
+    })
+
+    it('should not fire sequence for events outside target element', () => {
+      const manager = SequenceManager.getInstance()
+      const callback = vi.fn()
+
+      const divA = document.createElement('div')
+      const divB = document.createElement('div')
+      document.body.appendChild(divA)
+      document.body.appendChild(divB)
+
+      manager.register(['G', 'G'], callback, { target: divA })
+
+      dispatchKeyFromElement(divB, 'g')
+      dispatchKeyFromElement(divB, 'g')
+
+      expect(callback).not.toHaveBeenCalled()
+
+      document.body.removeChild(divA)
+      document.body.removeChild(divB)
+    })
+  })
+
+  describe('eventType option', () => {
+    it('should support eventType keyup sequences', () => {
+      const manager = SequenceManager.getInstance()
+      const callback = vi.fn()
+
+      manager.register(['G', 'G'], callback, { eventType: 'keyup' })
+
+      dispatchKey('g', { eventType: 'keydown' })
+      dispatchKey('g', { eventType: 'keyup' })
+      expect(callback).not.toHaveBeenCalled()
+
+      dispatchKey('g', { eventType: 'keydown' })
+      dispatchKey('g', { eventType: 'keyup' })
+      expect(callback).toHaveBeenCalledTimes(1)
+    })
+  })
+
+  describe('conflictBehavior option', () => {
+    it('should warn by default when same sequence is registered twice', () => {
+      const manager = SequenceManager.getInstance()
+      const callback1 = vi.fn()
+      const callback2 = vi.fn()
+      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+
+      manager.register(['G', 'G'], callback1)
+      manager.register(['G', 'G'], callback2)
+
+      dispatchKey('g')
+      dispatchKey('g')
+
+      expect(callback1).toHaveBeenCalledTimes(1)
+      expect(callback2).toHaveBeenCalledTimes(1)
+      expect(warnSpy).toHaveBeenCalled()
+
+      warnSpy.mockRestore()
+    })
+
+    it('should throw with conflictBehavior error', () => {
+      const manager = SequenceManager.getInstance()
+      const callback = vi.fn()
+
+      manager.register(['G', 'G'], callback)
+      expect(() =>
+        manager.register(['G', 'G'], callback, { conflictBehavior: 'error' }),
+      ).toThrow(/already registered/)
+
+      dispatchKey('g')
+      dispatchKey('g')
+      expect(callback).toHaveBeenCalledTimes(1)
+    })
+
+    it('should replace with conflictBehavior replace', () => {
+      const manager = SequenceManager.getInstance()
+      const callback1 = vi.fn()
+      const callback2 = vi.fn()
+
+      manager.register(['G', 'G'], callback1, { conflictBehavior: 'replace' })
+      manager.register(['G', 'G'], callback2, { conflictBehavior: 'replace' })
+
+      dispatchKey('g')
+      dispatchKey('g')
+
+      expect(callback1).not.toHaveBeenCalled()
+      expect(callback2).toHaveBeenCalledTimes(1)
+    })
+
+    it('should allow multiple with conflictBehavior allow', () => {
+      const manager = SequenceManager.getInstance()
+      const callback1 = vi.fn()
+      const callback2 = vi.fn()
+      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+
+      manager.register(['G', 'G'], callback1, { conflictBehavior: 'allow' })
+      manager.register(['G', 'G'], callback2, { conflictBehavior: 'allow' })
+
+      dispatchKey('g')
+      dispatchKey('g')
+
+      expect(callback1).toHaveBeenCalledTimes(1)
+      expect(callback2).toHaveBeenCalledTimes(1)
+      expect(warnSpy).not.toHaveBeenCalled()
+
+      warnSpy.mockRestore()
+    })
+  })
+
+  describe('SequenceOptions type', () => {
+    it('should not include requireReset (compile-time check)', () => {
+      // requireReset is excluded from SequenceOptions - this should type-error if someone adds it back
+      const options: SequenceOptions = {
+        timeout: 500,
+        // @ts-expect-error - requireReset is not in SequenceOptions
+        requireReset: true,
+      }
+      expect(options.timeout).toBe(500)
+    })
   })
 
   describe('longer sequences', () => {
diff --git a/packages/react-hotkeys/src/useHotkeySequence.ts b/packages/react-hotkeys/src/useHotkeySequence.ts
index 6dada72..6e5dc62 100644
--- a/packages/react-hotkeys/src/useHotkeySequence.ts
+++ b/packages/react-hotkeys/src/useHotkeySequence.ts
@@ -1,18 +1,29 @@
 import { useEffect, useRef } from 'react'
-import { getSequenceManager } from '@tanstack/hotkeys'
+import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
 import { useDefaultHotkeysOptions } from './HotkeysProvider'
 import type {
   HotkeyCallback,
+  HotkeyCallbackContext,
   HotkeySequence,
   SequenceOptions,
+  SequenceRegistrationHandle,
 } from '@tanstack/hotkeys'
 
 export interface UseHotkeySequenceOptions extends Omit<
   SequenceOptions,
-  'enabled'
+  'target'
 > {
-  /** Whether the sequence is enabled. Defaults to true. */
-  enabled?: boolean
+  /**
+   * The DOM element to attach the event listener to.
+   * Can be a React ref, direct DOM element, or null.
+   * Defaults to document.
+   */
+  target?:
+    | React.RefObject
+    | HTMLElement
+    | Document
+    | Window
+    | null
 }
 
 /**
@@ -57,37 +68,105 @@ export function useHotkeySequence(
     ...options,
   } as UseHotkeySequenceOptions
 
-  const { enabled = true, ...sequenceOptions } = mergedOptions
+  const manager = getSequenceManager()
 
-  // Extract options for stable dependencies
-  const { timeout, platform, ignoreInputs } = sequenceOptions
+  // Stable ref for registration handle
+  const registrationRef = useRef(null)
 
-  // Use refs to keep callback stable
+  // Refs to capture current values for use in effect without adding dependencies
   const callbackRef = useRef(callback)
+  const optionsRef = useRef(mergedOptions)
+  const managerRef = useRef(manager)
+
+  // Update refs on every render
   callbackRef.current = callback
+  optionsRef.current = mergedOptions
+  managerRef.current = manager
+
+  // Track previous target and sequence to detect changes requiring re-registration
+  const prevTargetRef = useRef(null)
+  const prevSequenceRef = useRef(null)
+
+  // Normalize to hotkey sequence string (join with spaces)
+  const hotkeySequenceString = formatHotkeySequence(sequence)
 
-  // Serialize sequence for dependency comparison
-  const sequenceKey = sequence.join('|')
+  // Extract options without target (target is handled separately)
+  const { target: _target, ...optionsWithoutTarget } = mergedOptions
 
   useEffect(() => {
-    if (!enabled || sequence.length === 0) {
+    if (!(mergedOptions.enabled ?? true)) {
+      return
+    }
+    if (sequence.length === 0) {
+      return
+    }
+
+    // Resolve target inside the effect so refs are already attached after mount
+    const resolvedTarget = isRef(optionsRef.current.target)
+      ? optionsRef.current.target.current
+      : (optionsRef.current.target ??
+        (typeof document !== 'undefined' ? document : null))
+
+    // Skip if no valid target (SSR or ref still null)
+    if (!resolvedTarget) {
       return
     }
 
-    const manager = getSequenceManager()
+    // Check if we need to re-register (target or sequence changed)
+    const targetChanged =
+      prevTargetRef.current !== null && prevTargetRef.current !== resolvedTarget
+    const sequenceChanged =
+      prevSequenceRef.current !== null &&
+      prevSequenceRef.current !== hotkeySequenceString
 
-    // Build options object conditionally to avoid overwriting manager defaults with undefined
-    const registerOptions: SequenceOptions = { enabled: true }
-    if (timeout !== undefined) registerOptions.timeout = timeout
-    if (platform !== undefined) registerOptions.platform = platform
-    if (ignoreInputs !== undefined) registerOptions.ignoreInputs = ignoreInputs
+    // If we have an active registration and target/sequence changed, unregister first
+    if (
+      registrationRef.current?.isActive &&
+      (targetChanged || sequenceChanged)
+    ) {
+      registrationRef.current.unregister()
+      registrationRef.current = null
+    }
+
+    // Register if needed (no active registration)
+    if (!registrationRef.current || !registrationRef.current.isActive) {
+      registrationRef.current = managerRef.current.register(
+        sequence,
+        (event, context) => callbackRef.current(event, context),
+        {
+          ...optionsRef.current,
+          target: resolvedTarget,
+          enabled: true,
+        },
+      )
+    }
 
-    const unregister = manager.register(
-      sequence,
-      (event, context) => callbackRef.current(event, context),
-      registerOptions,
-    )
+    // Update tracking refs
+    prevTargetRef.current = resolvedTarget
+    prevSequenceRef.current = hotkeySequenceString
+
+    // Cleanup on unmount
+    return () => {
+      if (registrationRef.current?.isActive) {
+        registrationRef.current.unregister()
+        registrationRef.current = null
+      }
+    }
+  }, [hotkeySequenceString, options.enabled, mergedOptions.enabled, sequence])
 
-    return unregister
-  }, [enabled, sequence, sequenceKey, timeout, platform, ignoreInputs])
+  // Sync callback and options on EVERY render (outside useEffect)
+  if (registrationRef.current?.isActive) {
+    registrationRef.current.callback = (
+      event: KeyboardEvent,
+      context: HotkeyCallbackContext,
+    ) => callbackRef.current(event, context)
+    registrationRef.current.setOptions(optionsWithoutTarget)
+  }
+}
+
+/**
+ * Type guard to check if a value is a React ref-like object.
+ */
+function isRef(value: unknown): value is React.RefObject {
+  return value !== null && typeof value === 'object' && 'current' in value
 }
diff --git a/packages/solid-hotkeys/src/createHotkeySequence.ts b/packages/solid-hotkeys/src/createHotkeySequence.ts
index 40530eb..e884e8f 100644
--- a/packages/solid-hotkeys/src/createHotkeySequence.ts
+++ b/packages/solid-hotkeys/src/createHotkeySequence.ts
@@ -5,6 +5,7 @@ import type {
   HotkeyCallback,
   HotkeySequence,
   SequenceOptions,
+  SequenceRegistrationHandle,
 } from '@tanstack/hotkeys'
 
 export interface CreateHotkeySequenceOptions extends Omit<
@@ -55,6 +56,9 @@ export function createHotkeySequence(
     | (() => CreateHotkeySequenceOptions) = {},
 ): void {
   const defaultOptions = useDefaultHotkeysOptions()
+  const manager = getSequenceManager()
+
+  let registration: SequenceRegistrationHandle | null = null
 
   createEffect(() => {
     // Resolve reactive values
@@ -67,27 +71,54 @@ export function createHotkeySequence(
       ...resolvedOptions,
     } as CreateHotkeySequenceOptions
 
-    const { enabled = true, ...sequenceOptions } = mergedOptions
+    const {
+      enabled = true,
+      target: _target,
+      ...optionsWithoutTarget
+    } = mergedOptions
 
     if (!enabled || resolvedSequence.length === 0) {
       return
     }
 
-    const manager = getSequenceManager()
+    // Resolve target: when explicitly provided (even as null), use it and skip if null.
+    // When not provided, default to document. Matches createHotkey.
+    const resolvedTarget =
+      'target' in mergedOptions
+        ? (mergedOptions.target ?? null)
+        : typeof document !== 'undefined'
+          ? document
+          : null
 
-    // Build options object conditionally to avoid overwriting manager defaults with undefined
-    const registerOptions: SequenceOptions = { enabled: true }
-    if (sequenceOptions.timeout !== undefined)
-      registerOptions.timeout = sequenceOptions.timeout
-    if (sequenceOptions.platform !== undefined)
-      registerOptions.platform = sequenceOptions.platform
+    if (!resolvedTarget) {
+      return
+    }
 
-    const unregister = manager.register(
-      resolvedSequence,
-      callback,
-      registerOptions,
-    )
+    // Unregister previous registration if it exists
+    if (registration?.isActive) {
+      registration.unregister()
+      registration = null
+    }
+
+    // Register the sequence
+    registration = manager.register(resolvedSequence, callback, {
+      ...optionsWithoutTarget,
+      target: resolvedTarget,
+      enabled: true,
+    })
+
+    // Sync callback and options on every effect run
+    if (registration.isActive) {
+      registration.callback = callback
+      registration.setOptions(optionsWithoutTarget)
+    }
 
-    onCleanup(unregister)
+    // Cleanup on disposal
+    onCleanup(() => {
+      if (registration?.isActive) {
+        registration.unregister()
+        registration = null
+      }
+    })
   })
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1edc526..4c884a5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -22,7 +22,7 @@ importers:
         version: 1.2.0
       '@tanstack/eslint-config':
         specifier: 0.4.0
-        version: 0.4.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+        version: 0.4.0(@typescript-eslint/utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@tanstack/typedoc-config':
         specifier: 0.3.3
         version: 0.3.3(typescript@5.9.3)
@@ -34,16 +34,16 @@ importers:
         version: 25.3.0
       eslint:
         specifier: ^9.32.2
-        version: 9.39.2(jiti@2.6.1)
+        version: 9.39.3(jiti@2.6.1)
       eslint-plugin-unused-imports:
         specifier: ^4.4.1
-        version: 4.4.1(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
+        version: 4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))
       happy-dom:
         specifier: ^20.7.0
         version: 20.7.0
       knip:
         specifier: ^5.84.1
-        version: 5.84.1(@types/node@25.3.0)(typescript@5.9.3)
+        version: 5.85.0(@types/node@25.3.0)(typescript@5.9.3)
       markdown-link-extractor:
         specifier: ^4.0.3
         version: 4.0.3
@@ -58,7 +58,7 @@ importers:
         version: 3.8.1
       prettier-plugin-svelte:
         specifier: ^3.5.0
-        version: 3.5.0(prettier@3.8.1)(svelte@5.50.3)
+        version: 3.5.0(prettier@3.8.1)(svelte@5.53.1)
       publint:
         specifier: ^0.3.17
         version: 0.3.17
@@ -73,7 +73,7 @@ importers:
         version: 0.2.15
       tsdown:
         specifier: ^0.20.3
-        version: 0.20.3(oxc-resolver@11.17.1)(publint@0.3.17)(typescript@5.9.3)
+        version: 0.20.3(oxc-resolver@11.18.0)(publint@0.3.17)(typescript@5.9.3)
       typescript:
         specifier: 5.9.3
         version: 5.9.3
@@ -424,7 +424,7 @@ importers:
     devDependencies:
       '@eslint-react/eslint-plugin':
         specifier: ^2.13.0
-        version: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+        version: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@testing-library/react':
         specifier: ^16.3.2
         version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -436,10 +436,10 @@ importers:
         version: 5.1.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))
       eslint-plugin-react-compiler:
         specifier: 19.1.0-rc.2
-        version: 19.1.0-rc.2(eslint@9.39.2(jiti@2.6.1))
+        version: 19.1.0-rc.2(eslint@9.39.3(jiti@2.6.1))
       eslint-plugin-react-hooks:
         specifier: ^7.0.1
-        version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
+        version: 7.0.1(eslint@9.39.3(jiti@2.6.1))
       react:
         specifier: ^19.2.4
         version: 19.2.4
@@ -464,7 +464,7 @@ importers:
     devDependencies:
       '@eslint-react/eslint-plugin':
         specifier: ^2.13.0
-        version: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+        version: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@types/react':
         specifier: ^19.2.14
         version: 19.2.14
@@ -473,10 +473,10 @@ importers:
         version: 5.1.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))
       eslint-plugin-react-compiler:
         specifier: 19.1.0-rc.2
-        version: 19.1.0-rc.2(eslint@9.39.2(jiti@2.6.1))
+        version: 19.1.0-rc.2(eslint@9.39.3(jiti@2.6.1))
       eslint-plugin-react-hooks:
         specifier: ^7.0.1
-        version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
+        version: 7.0.1(eslint@9.39.3(jiti@2.6.1))
       react:
         specifier: ^19.2.4
         version: 19.2.4
@@ -602,8 +602,8 @@ packages:
     resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-string-parser@8.0.0-rc.1':
-    resolution: {integrity: sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw==}
+  '@babel/helper-string-parser@8.0.0-rc.2':
+    resolution: {integrity: sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==}
     engines: {node: ^20.19.0 || >=22.12.0}
 
   '@babel/helper-validator-identifier@7.28.5':
@@ -974,8 +974,8 @@ packages:
       eslint:
         optional: true
 
-  '@eslint/js@9.39.2':
-    resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
+  '@eslint/js@9.39.3':
+    resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@eslint/object-schema@2.1.7':
@@ -1026,10 +1026,6 @@ packages:
     resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==}
     engines: {node: 20 || >=22}
 
-  '@isaacs/cliui@9.0.0':
-    resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
-    engines: {node: '>=18'}
-
   '@jest/diff-sequences@30.0.1':
     resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==}
     engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -1142,111 +1138,111 @@ packages:
   '@oxc-project/types@0.112.0':
     resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==}
 
-  '@oxc-resolver/binding-android-arm-eabi@11.17.1':
-    resolution: {integrity: sha512-+VuZyMYYaap5uDAU1xDU3Kul0FekLqpBS8kI5JozlWfYQKnc/HsZg2gHPkQrj0SC9lt74WMNCfOzZZJlYXSdEQ==}
+  '@oxc-resolver/binding-android-arm-eabi@11.18.0':
+    resolution: {integrity: sha512-EhwJNzbfLwQQIeyak3n08EB3UHknMnjy1dFyL98r3xlorje2uzHOT2vkB5nB1zqtTtzT31uSot3oGZFfODbGUg==}
     cpu: [arm]
     os: [android]
 
-  '@oxc-resolver/binding-android-arm64@11.17.1':
-    resolution: {integrity: sha512-YlDDTjvOEKhom/cRSVsXsMVeXVIAM9PJ/x2mfe08rfuS0iIEfJd8PngKbEIhG72WPxleUa+vkEZj9ncmC14z3Q==}
+  '@oxc-resolver/binding-android-arm64@11.18.0':
+    resolution: {integrity: sha512-esOPsT9S9B6vEMMp1qR9Yz5UepQXljoWRJYoyp7GV/4SYQOSTpN0+V2fTruxbMmzqLK+fjCEU2x3SVhc96LQLQ==}
     cpu: [arm64]
     os: [android]
 
-  '@oxc-resolver/binding-darwin-arm64@11.17.1':
-    resolution: {integrity: sha512-HOYYLSY4JDk14YkXaz/ApgJYhgDP4KsG8EZpgpOxdszGW9HmIMMY/vXqVKYW74dSH+GQkIXYxBrEh3nv+XODVg==}
+  '@oxc-resolver/binding-darwin-arm64@11.18.0':
+    resolution: {integrity: sha512-iJknScn8fRLRhGR6VHG31bzOoyLihSDmsJHRjHwRUL0yF1MkLlvzmZ+liKl9MGl+WZkZHaOFT5T1jNlLSWTowQ==}
     cpu: [arm64]
     os: [darwin]
 
-  '@oxc-resolver/binding-darwin-x64@11.17.1':
-    resolution: {integrity: sha512-JHPJbsa5HvPq2/RIdtGlqfaG9zV2WmgvHrKTYmlW0L5esqtKCBuetFudXTBzkNcyD69kSZLzH92AzTr6vFHMFg==}
+  '@oxc-resolver/binding-darwin-x64@11.18.0':
+    resolution: {integrity: sha512-3rMweF2GQLzkaUoWgFKy1fRtk0dpj4JDqucoZLJN9IZG+TC+RZg7QMwG5WKMvmEjzdYmOTw1L1XqZDVXF2ksaQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@oxc-resolver/binding-freebsd-x64@11.17.1':
-    resolution: {integrity: sha512-UD1FRC8j8xZstFXYsXwQkNmmg7vUbee006IqxokwDUUA+xEgKZDpLhBEiVKM08Urb+bn7Q0gn6M1pyNR0ng5mg==}
+  '@oxc-resolver/binding-freebsd-x64@11.18.0':
+    resolution: {integrity: sha512-TfXsFby4QvpGwmUP66+X+XXQsycddZe9ZUUu/vHhq2XGI1EkparCSzjpYW1Nz5fFncbI5oLymQLln/qR+qxyOw==}
     cpu: [x64]
     os: [freebsd]
 
-  '@oxc-resolver/binding-linux-arm-gnueabihf@11.17.1':
-    resolution: {integrity: sha512-wFWC1wyf2ROFWTxK5x0Enm++DSof3EBQ/ypyAesMDLiYxOOASDoMOZG1ylWUnlKaCt5W7eNOWOzABpdfFf/ssA==}
+  '@oxc-resolver/binding-linux-arm-gnueabihf@11.18.0':
+    resolution: {integrity: sha512-WolOILquy9DJsHcfFMHeA5EjTCI9A7JoERFJru4UI2zKZcnfNPo5GApzYwiloscEp/s+fALPmyRntswUns0qHg==}
     cpu: [arm]
     os: [linux]
 
-  '@oxc-resolver/binding-linux-arm-musleabihf@11.17.1':
-    resolution: {integrity: sha512-k/hUif0GEBk/csSqCfTPXb8AAVs1NNWCa/skBghvNbTtORcWfOVqJ3mM+2pE189+enRm4UnryLREu5ysI0kXEQ==}
+  '@oxc-resolver/binding-linux-arm-musleabihf@11.18.0':
+    resolution: {integrity: sha512-r+5nHJyPdiBqOGTYAFyuq5RtuAQbm4y69GYWNG/uup9Cqr7RG9Ak0YZgGEbkQsc+XBs00ougu/D1+w3UAYIWHA==}
     cpu: [arm]
     os: [linux]
 
-  '@oxc-resolver/binding-linux-arm64-gnu@11.17.1':
-    resolution: {integrity: sha512-Cwm6A071ww60QouJ9LoHAwBgEoZzHQ0Qaqk2E7WLfBdiQN9mLXIDhnrpn04hlRElRPhLiu/dtg+o5PPLvaINXQ==}
+  '@oxc-resolver/binding-linux-arm64-gnu@11.18.0':
+    resolution: {integrity: sha512-bUzg6QxljqMLLwsxYajAQEHW1LYRLdKOg/aykt14PSqUUOmfnOJjPdSLTiHIZCluVzPCQxv1LjoyRcoTAXfQaQ==}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
-  '@oxc-resolver/binding-linux-arm64-musl@11.17.1':
-    resolution: {integrity: sha512-+hwlE2v3m0r3sk93SchJL1uyaKcPjf+NGO/TD2DZUDo+chXx7FfaEj0nUMewigSt7oZ2sQN9Z4NJOtUa75HE5Q==}
+  '@oxc-resolver/binding-linux-arm64-musl@11.18.0':
+    resolution: {integrity: sha512-l43GVwls5+YR8WXOIez5x7Pp/MfhdkMOZOOjFUSWC/9qMnSLX1kd95j9oxDrkWdD321JdHTyd4eau5KQPxZM9w==}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
-  '@oxc-resolver/binding-linux-ppc64-gnu@11.17.1':
-    resolution: {integrity: sha512-bO+rsaE5Ox8cFyeL5Ct5tzot1TnQpFa/Wmu5k+hqBYSH2dNVDGoi0NizBN5QV8kOIC6O5MZr81UG4yW/2FyDTA==}
+  '@oxc-resolver/binding-linux-ppc64-gnu@11.18.0':
+    resolution: {integrity: sha512-ayj7TweYWi/azxWmRpUZGz41kKNvfkXam20UrFhaQDrSNGNqefQRODxhJn0iv6jt4qChh7TUxDIoavR6ftRsjw==}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
 
-  '@oxc-resolver/binding-linux-riscv64-gnu@11.17.1':
-    resolution: {integrity: sha512-B/P+hxKQ1oX4YstI9Lyh4PGzqB87Ddqj/A4iyRBbPdXTcxa+WW3oRLx1CsJKLmHPdDk461Hmbghq1Bm3pl+8Aw==}
+  '@oxc-resolver/binding-linux-riscv64-gnu@11.18.0':
+    resolution: {integrity: sha512-2Jz7jpq6BBNlBBup3usZB6sZWEZOBbjWn++/bKC2lpAT+sTEwdTonnf3rNcb+XY7+v53jYB9pM8LEKVXZfr8BA==}
     cpu: [riscv64]
     os: [linux]
     libc: [glibc]
 
-  '@oxc-resolver/binding-linux-riscv64-musl@11.17.1':
-    resolution: {integrity: sha512-ulp2H3bFXzd/th2maH+QNKj5qgOhJ3v9Yspdf1svTw3CDOuuTl6sRKsWQ7MUw0vnkSNvQndtflBwVXgzZvURsQ==}
+  '@oxc-resolver/binding-linux-riscv64-musl@11.18.0':
+    resolution: {integrity: sha512-omw8/ISOc6ubR247iEMma4/JRfbY2I+nGJC59oKBhCIEZoyqEg/NmDSBc4ToMH+AsZDucqQUDOCku3k7pBiEag==}
     cpu: [riscv64]
     os: [linux]
     libc: [musl]
 
-  '@oxc-resolver/binding-linux-s390x-gnu@11.17.1':
-    resolution: {integrity: sha512-LAXYVe3rKk09Zo9YKF2ZLBcH8sz8Oj+JIyiUxiHtq0hiYLMsN6dOpCf2hzQEjPAmsSEA/hdC1PVKeXo+oma8mQ==}
+  '@oxc-resolver/binding-linux-s390x-gnu@11.18.0':
+    resolution: {integrity: sha512-uFipBXaS+honSL5r5G/rlvVrkffUjpKwD3S/aIiwp64bylK3+RztgV+mM1blk+OT5gBRG864auhH6jCfrOo3ZA==}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
 
-  '@oxc-resolver/binding-linux-x64-gnu@11.17.1':
-    resolution: {integrity: sha512-3RAhxipMKE8RCSPn7O//sj440i+cYTgYbapLeOoDvQEt6R1QcJjTsFgI4iz99FhVj3YbPxlZmcLB5VW+ipyRTA==}
+  '@oxc-resolver/binding-linux-x64-gnu@11.18.0':
+    resolution: {integrity: sha512-bY4uMIoKRv8Ine3UiKLFPWRZ+fPCDamTHZFf5pNOjlfmTJIANtJo0mzWDUdFZLYhVgQdegrDL9etZbTMR8qieg==}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
-  '@oxc-resolver/binding-linux-x64-musl@11.17.1':
-    resolution: {integrity: sha512-wpjMEubGU8r9VjZTLdZR3aPHaBqTl8Jl8F4DBbgNoZ+yhkhQD1/MGvY70v2TLnAI6kAHSvcqgfvaqKDa2iWsPQ==}
+  '@oxc-resolver/binding-linux-x64-musl@11.18.0':
+    resolution: {integrity: sha512-40IicL/aitfNOWur06x7Do41WcqFJ9VUNAciFjZCXzF6wR2i6uVsi6N19ecqgSRoLYFCAoRYi9F50QteIxCwKQ==}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
-  '@oxc-resolver/binding-openharmony-arm64@11.17.1':
-    resolution: {integrity: sha512-XIE4w17RYAVIgx+9Gs3deTREq5tsmalbatYOOBGNdH7n0DfTE600c7wYXsp7ANc3BPDXsInnOzXDEPCvO1F6cg==}
+  '@oxc-resolver/binding-openharmony-arm64@11.18.0':
+    resolution: {integrity: sha512-DJIzYjUnSJtz4Trs/J9TnzivtPcUKn9AeL3YjHlM5+RvK27ZL9xISs3gg2VAo2nWU7ThuadC1jSYkWaZyONMwg==}
     cpu: [arm64]
     os: [openharmony]
 
-  '@oxc-resolver/binding-wasm32-wasi@11.17.1':
-    resolution: {integrity: sha512-Lqi5BlHX3zS4bpSOkIbOKVf7DIk6Gvmdifr2OuOI58eUUyP944M8/OyaB09cNpPy9Vukj7nmmhOzj8pwLgAkIg==}
+  '@oxc-resolver/binding-wasm32-wasi@11.18.0':
+    resolution: {integrity: sha512-57+R8Ioqc8g9k80WovoupOoyIOfLEceHTizkUcwOXspXLhiZ67ScM7Q8OuvhDoRRSZzH6yI0qML3WZwMFR3s7g==}
     engines: {node: '>=14.0.0'}
     cpu: [wasm32]
 
-  '@oxc-resolver/binding-win32-arm64-msvc@11.17.1':
-    resolution: {integrity: sha512-l6lTcLBQVj1HNquFpXSsrkCIM8X5Hlng5YNQJrg00z/KyovvDV5l3OFhoRyZ+aLBQ74zUnMRaJZC7xcBnHyeNg==}
+  '@oxc-resolver/binding-win32-arm64-msvc@11.18.0':
+    resolution: {integrity: sha512-t9Oa4BPptJqVlHTT1cV1frs+LY/vjsKhHI6ltj2EwoGM1TykJ0WW43UlQaU4SC8N+oTY8JRbAywVMNkfqjSu9w==}
     cpu: [arm64]
     os: [win32]
 
-  '@oxc-resolver/binding-win32-ia32-msvc@11.17.1':
-    resolution: {integrity: sha512-VTzVtfnCCsU/6GgvursWoyZrhe3Gj/RyXzDWmh4/U1Y3IW0u1FZbp+hCIlBL16pRPbDc5YvXVtCOnA41QOrOoQ==}
+  '@oxc-resolver/binding-win32-ia32-msvc@11.18.0':
+    resolution: {integrity: sha512-4maf/f6ea5IEtIXqGwSw38srRtVHTre9iKShG4gjzat7c3Iq6B1OppXMj8gNmTuM4n8Xh1hQM9z2hBELccJr1g==}
     cpu: [ia32]
     os: [win32]
 
-  '@oxc-resolver/binding-win32-x64-msvc@11.17.1':
-    resolution: {integrity: sha512-jRPVU+6/12baj87q2+UGRh30FBVBzqKdJ7rP/mSqiL1kpNQB9yZ1j0+m3sru1m+C8hiFK7lBFwjUtYUBI7+UpQ==}
+  '@oxc-resolver/binding-win32-x64-msvc@11.18.0':
+    resolution: {integrity: sha512-EhW8Su3AEACSw5HfzKMmyCtV0oArNrVViPdeOfvVYL9TrkL+/4c8fWHFTBtxUMUyCjhSG5xYNdwty1D/TAgL0Q==}
     cpu: [x64]
     os: [win32]
 
@@ -1341,141 +1337,141 @@ packages:
   '@rolldown/pluginutils@1.0.0-rc.3':
     resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==}
 
-  '@rollup/rollup-android-arm-eabi@4.57.1':
-    resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==}
+  '@rollup/rollup-android-arm-eabi@4.58.0':
+    resolution: {integrity: sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.57.1':
-    resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==}
+  '@rollup/rollup-android-arm64@4.58.0':
+    resolution: {integrity: sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.57.1':
-    resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==}
+  '@rollup/rollup-darwin-arm64@4.58.0':
+    resolution: {integrity: sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.57.1':
-    resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==}
+  '@rollup/rollup-darwin-x64@4.58.0':
+    resolution: {integrity: sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-freebsd-arm64@4.57.1':
-    resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==}
+  '@rollup/rollup-freebsd-arm64@4.58.0':
+    resolution: {integrity: sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==}
     cpu: [arm64]
     os: [freebsd]
 
-  '@rollup/rollup-freebsd-x64@4.57.1':
-    resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==}
+  '@rollup/rollup-freebsd-x64@4.58.0':
+    resolution: {integrity: sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==}
     cpu: [x64]
     os: [freebsd]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.57.1':
-    resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.58.0':
+    resolution: {integrity: sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==}
     cpu: [arm]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.57.1':
-    resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
+  '@rollup/rollup-linux-arm-musleabihf@4.58.0':
+    resolution: {integrity: sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==}
     cpu: [arm]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-linux-arm64-gnu@4.57.1':
-    resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
+  '@rollup/rollup-linux-arm64-gnu@4.58.0':
+    resolution: {integrity: sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-arm64-musl@4.57.1':
-    resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
+  '@rollup/rollup-linux-arm64-musl@4.58.0':
+    resolution: {integrity: sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-linux-loong64-gnu@4.57.1':
-    resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
+  '@rollup/rollup-linux-loong64-gnu@4.58.0':
+    resolution: {integrity: sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==}
     cpu: [loong64]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-loong64-musl@4.57.1':
-    resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
+  '@rollup/rollup-linux-loong64-musl@4.58.0':
+    resolution: {integrity: sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==}
     cpu: [loong64]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-linux-ppc64-gnu@4.57.1':
-    resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
+  '@rollup/rollup-linux-ppc64-gnu@4.58.0':
+    resolution: {integrity: sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-ppc64-musl@4.57.1':
-    resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
+  '@rollup/rollup-linux-ppc64-musl@4.58.0':
+    resolution: {integrity: sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==}
     cpu: [ppc64]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.57.1':
-    resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
+  '@rollup/rollup-linux-riscv64-gnu@4.58.0':
+    resolution: {integrity: sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==}
     cpu: [riscv64]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-riscv64-musl@4.57.1':
-    resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
+  '@rollup/rollup-linux-riscv64-musl@4.58.0':
+    resolution: {integrity: sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==}
     cpu: [riscv64]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-linux-s390x-gnu@4.57.1':
-    resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
+  '@rollup/rollup-linux-s390x-gnu@4.58.0':
+    resolution: {integrity: sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-x64-gnu@4.57.1':
-    resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
+  '@rollup/rollup-linux-x64-gnu@4.58.0':
+    resolution: {integrity: sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
-  '@rollup/rollup-linux-x64-musl@4.57.1':
-    resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
+  '@rollup/rollup-linux-x64-musl@4.58.0':
+    resolution: {integrity: sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
-  '@rollup/rollup-openbsd-x64@4.57.1':
-    resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
+  '@rollup/rollup-openbsd-x64@4.58.0':
+    resolution: {integrity: sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==}
     cpu: [x64]
     os: [openbsd]
 
-  '@rollup/rollup-openharmony-arm64@4.57.1':
-    resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==}
+  '@rollup/rollup-openharmony-arm64@4.58.0':
+    resolution: {integrity: sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==}
     cpu: [arm64]
     os: [openharmony]
 
-  '@rollup/rollup-win32-arm64-msvc@4.57.1':
-    resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==}
+  '@rollup/rollup-win32-arm64-msvc@4.58.0':
+    resolution: {integrity: sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.57.1':
-    resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==}
+  '@rollup/rollup-win32-ia32-msvc@4.58.0':
+    resolution: {integrity: sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-gnu@4.57.1':
-    resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==}
+  '@rollup/rollup-win32-x64-gnu@4.58.0':
+    resolution: {integrity: sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==}
     cpu: [x64]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.57.1':
-    resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==}
+  '@rollup/rollup-win32-x64-msvc@4.58.0':
+    resolution: {integrity: sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==}
     cpu: [x64]
     os: [win32]
 
@@ -1557,11 +1553,11 @@ packages:
   '@standard-schema/spec@1.1.0':
     resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
 
-  '@stylistic/eslint-plugin@5.8.0':
-    resolution: {integrity: sha512-WNPVF/FfBAjyi3OA7gok8swRiImNLKI4dmV3iK/GC/0xSJR7eCzBFsw9hLZVgb1+MYNLy7aDsjohxN1hA/FIfQ==}
+  '@stylistic/eslint-plugin@5.9.0':
+    resolution: {integrity: sha512-FqqSkvDMYJReydrMhlugc71M76yLLQWNfmGq+SIlLa7N3kHp8Qq8i2PyWrVNAfjOyOIY+xv9XaaYwvVW7vroMA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: '>=9.0.0'
+      eslint: ^9.0.0 || ^10.0.0
 
   '@sveltejs/acorn-typescript@1.0.9':
     resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==}
@@ -1706,6 +1702,9 @@ packages:
   '@types/deep-eql@4.0.2':
     resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
 
+  '@types/esrecurse@4.3.1':
+    resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
+
   '@types/estree@1.0.8':
     resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
 
@@ -1732,6 +1731,9 @@ packages:
   '@types/react@19.2.14':
     resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
 
+  '@types/trusted-types@2.0.7':
+    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
   '@types/unist@3.0.3':
     resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
 
@@ -1741,63 +1743,63 @@ packages:
   '@types/ws@8.18.1':
     resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
 
-  '@typescript-eslint/eslint-plugin@8.55.0':
-    resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==}
+  '@typescript-eslint/eslint-plugin@8.56.0':
+    resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      '@typescript-eslint/parser': ^8.55.0
-      eslint: ^8.57.0 || ^9.0.0
+      '@typescript-eslint/parser': ^8.56.0
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/parser@8.55.0':
-    resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==}
+  '@typescript-eslint/parser@8.56.0':
+    resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.57.0 || ^9.0.0
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/project-service@8.55.0':
-    resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==}
+  '@typescript-eslint/project-service@8.56.0':
+    resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/scope-manager@8.55.0':
-    resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==}
+  '@typescript-eslint/scope-manager@8.56.0':
+    resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/tsconfig-utils@8.55.0':
-    resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==}
+  '@typescript-eslint/tsconfig-utils@8.56.0':
+    resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/type-utils@8.55.0':
-    resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==}
+  '@typescript-eslint/type-utils@8.56.0':
+    resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.57.0 || ^9.0.0
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/types@8.55.0':
-    resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==}
+  '@typescript-eslint/types@8.56.0':
+    resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/typescript-estree@8.55.0':
-    resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==}
+  '@typescript-eslint/typescript-estree@8.56.0':
+    resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/utils@8.55.0':
-    resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==}
+  '@typescript-eslint/utils@8.56.0':
+    resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.57.0 || ^9.0.0
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
-  '@typescript-eslint/visitor-keys@8.55.0':
-    resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==}
+  '@typescript-eslint/visitor-keys@8.56.0':
+    resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -1954,13 +1956,13 @@ packages:
     peerDependencies:
       acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
 
-  acorn@8.15.0:
-    resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+  acorn@8.16.0:
+    resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  ajv@6.12.6:
-    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+  ajv@6.14.0:
+    resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
 
   ansi-colors@4.1.3:
     resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@@ -2020,8 +2022,8 @@ packages:
     resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
     engines: {node: '>= 0.4'}
 
-  babel-plugin-jsx-dom-expressions@0.40.3:
-    resolution: {integrity: sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==}
+  babel-plugin-jsx-dom-expressions@0.40.5:
+    resolution: {integrity: sha512-8TFKemVLDYezqqv4mWz+PhRrkryTzivTGu0twyLrOkVZ0P63COx2Y04eVsUjFlwSOXui1z3P3Pn209dokWnirg==}
     peerDependencies:
       '@babel/core': ^7.20.12
 
@@ -2037,15 +2039,16 @@ packages:
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
-  balanced-match@4.0.2:
-    resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
+  balanced-match@4.0.3:
+    resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==}
     engines: {node: 20 || >=22}
 
   base64-js@1.5.1:
     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
 
-  baseline-browser-mapping@2.9.19:
-    resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
+  baseline-browser-mapping@2.10.0:
+    resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
+    engines: {node: '>=6.0.0'}
     hasBin: true
 
   better-path-resolve@1.0.0:
@@ -2102,8 +2105,8 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
-  caniuse-lite@1.0.30001769:
-    resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
+  caniuse-lite@1.0.30001770:
+    resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==}
 
   chai@6.2.2:
     resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
@@ -2225,8 +2228,8 @@ packages:
     resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
     engines: {node: '>=8'}
 
-  devalue@5.6.2:
-    resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
+  devalue@5.6.3:
+    resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==}
 
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
@@ -2281,8 +2284,8 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true
 
-  electron-to-chromium@1.5.286:
-    resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
+  electron-to-chromium@1.5.302:
+    resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==}
 
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -2464,6 +2467,10 @@ packages:
     resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  eslint-scope@9.1.1:
+    resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
+    engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
   eslint-visitor-keys@3.4.3:
     resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2472,8 +2479,12 @@ packages:
     resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  eslint@9.39.2:
-    resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
+  eslint-visitor-keys@5.0.1:
+    resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+    engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+  eslint@9.39.3:
+    resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
     peerDependencies:
@@ -2489,6 +2500,10 @@ packages:
     resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  espree@11.1.1:
+    resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
+    engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
   esprima@4.0.1:
     resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
     engines: {node: '>=4'}
@@ -2820,10 +2835,6 @@ packages:
   isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
 
-  jackspeak@4.2.3:
-    resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
-    engines: {node: 20 || >=22}
-
   jake@10.9.4:
     resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
     engines: {node: '>=10'}
@@ -2876,8 +2887,8 @@ packages:
   keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
 
-  knip@5.84.1:
-    resolution: {integrity: sha512-F1+yACEsSapAwmQLzfD4i9uPsnI82P4p5ABpNQ9pcc4fpQtjHEX34XDtNl5863I4O6SCECpymylcWDHI3ouhQQ==}
+  knip@5.85.0:
+    resolution: {integrity: sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==}
     engines: {node: '>=18.18.0'}
     hasBin: true
     peerDependencies:
@@ -2940,8 +2951,8 @@ packages:
   markdown-link-extractor@4.0.3:
     resolution: {integrity: sha512-aEltJiQ4/oC0h6Jbw/uuATGSHZPkcH8DIunNH1A0e+GSFkvZ6BbBkdvBTVfIV8r6HapCU3yTd0eFdi3ZeM1eAQ==}
 
-  marked@17.0.2:
-    resolution: {integrity: sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==}
+  marked@17.0.3:
+    resolution: {integrity: sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==}
     engines: {node: '>= 20'}
     hasBin: true
 
@@ -2984,9 +2995,9 @@ packages:
     resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
     engines: {node: 20 || >=22}
 
-  minimatch@10.2.0:
-    resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
-    engines: {node: 20 || >=22}
+  minimatch@10.2.2:
+    resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==}
+    engines: {node: 18 || 20 || >=22}
 
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -3089,8 +3100,8 @@ packages:
   outdent@0.5.0:
     resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
 
-  oxc-resolver@11.17.1:
-    resolution: {integrity: sha512-pyRXK9kH81zKlirHufkFhOFBZRks8iAMLwPH8gU7lvKFiuzUH9L8MxDEllazwOb8fjXMcWjY1PMDfMJ2/yh5cw==}
+  oxc-resolver@11.18.0:
+    resolution: {integrity: sha512-Fv/b05AfhpYoCDvsog6tgsDm2yIwIeJafpMFLncNwKHRYu+Y1xQu5Q/rgUn7xBfuhNgjtPO7C0jCf7p2fLDj1g==}
 
   p-filter@2.1.0:
     resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==}
@@ -3313,8 +3324,8 @@ packages:
     engines: {node: ^20.19.0 || >=22.12.0}
     hasBin: true
 
-  rollup@4.57.1:
-    resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
+  rollup@4.58.0:
+    resolution: {integrity: sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -3495,8 +3506,8 @@ packages:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
 
-  svelte@5.50.3:
-    resolution: {integrity: sha512-5JCO8P/cFlwyfi1LeZ9uppMZvuaHWygyZmqxyKOIqbV3PoHKaddvV1C6njL/InpDXplNYZnAVEbn8mLslycBxQ==}
+  svelte@5.53.1:
+    resolution: {integrity: sha512-WzxFHZhhD23Qzu7JCYdvm1rxvRSzdt9HtHO8TScMBX51bLRFTcJmATVqjqXG+6Ln6hrViGCo9DzwOhAasxwC/w==}
     engines: {node: '>=18'}
 
   tapable@2.3.0:
@@ -3609,11 +3620,11 @@ packages:
     peerDependencies:
       typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x
 
-  typescript-eslint@8.55.0:
-    resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==}
+  typescript-eslint@8.56.0:
+    resolution: {integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.57.0 || ^9.0.0
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
   typescript@5.9.3:
@@ -3624,14 +3635,14 @@ packages:
   uc.micro@2.1.0:
     resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
 
-  unconfig-core@7.4.2:
-    resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==}
+  unconfig-core@7.5.0:
+    resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==}
 
   undici-types@7.18.2:
     resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
 
-  undici@7.21.0:
-    resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==}
+  undici@7.22.0:
+    resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
     engines: {node: '>=20.18.1'}
 
   universalify@0.1.2:
@@ -3997,7 +4008,7 @@ snapshots:
 
   '@babel/helper-string-parser@7.27.1': {}
 
-  '@babel/helper-string-parser@8.0.0-rc.1': {}
+  '@babel/helper-string-parser@8.0.0-rc.2': {}
 
   '@babel/helper-validator-identifier@7.28.5': {}
 
@@ -4068,7 +4079,7 @@ snapshots:
 
   '@babel/types@8.0.0-rc.1':
     dependencies:
-      '@babel/helper-string-parser': 8.0.0-rc.1
+      '@babel/helper-string-parser': 8.0.0-rc.2
       '@babel/helper-validator-identifier': 8.0.0-rc.1
 
   '@changesets/apply-release-plan@7.0.14':
@@ -4313,35 +4324,35 @@ snapshots:
   '@esbuild/win32-x64@0.27.3':
     optional: true
 
-  '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))':
+  '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@2.6.1))':
     dependencies:
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.12.2': {}
 
-  '@eslint-react/ast@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@eslint-react/ast@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
       '@eslint-react/eff': 2.13.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       string-ts: 2.3.1
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint-react/core@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@eslint-react/core@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
@@ -4349,46 +4360,46 @@ snapshots:
 
   '@eslint-react/eff@2.13.0': {}
 
-  '@eslint-react/eslint-plugin@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@eslint-react/eslint-plugin@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
-      eslint-plugin-react-dom: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint-plugin-react-hooks-extra: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint-plugin-react-naming-convention: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint-plugin-react-rsc: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint-plugin-react-web-api: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint-plugin-react-x: 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
+      eslint-plugin-react-dom: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint-plugin-react-hooks-extra: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint-plugin-react-naming-convention: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint-plugin-react-rsc: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint-plugin-react-web-api: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint-plugin-react-x: 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       ts-api-utils: 2.4.0(typescript@5.9.3)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint-react/shared@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@eslint-react/shared@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
       '@eslint-react/eff': 2.13.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
       zod: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint-react/var@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@eslint-react/var@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
@@ -4412,7 +4423,7 @@ snapshots:
 
   '@eslint/eslintrc@3.3.3':
     dependencies:
-      ajv: 6.12.6
+      ajv: 6.14.0
       debug: 4.4.3
       espree: 10.4.0
       globals: 14.0.0
@@ -4424,11 +4435,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@10.0.1(eslint@9.39.2(jiti@2.6.1))':
+  '@eslint/js@10.0.1(eslint@9.39.3(jiti@2.6.1))':
     optionalDependencies:
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
 
-  '@eslint/js@9.39.2': {}
+  '@eslint/js@9.39.3': {}
 
   '@eslint/object-schema@2.1.7': {}
 
@@ -4471,8 +4482,6 @@ snapshots:
     dependencies:
       '@isaacs/balanced-match': 4.0.1
 
-  '@isaacs/cliui@9.0.0': {}
-
   '@jest/diff-sequences@30.0.1': {}
 
   '@jest/get-type@30.1.0': {}
@@ -4580,66 +4589,66 @@ snapshots:
 
   '@oxc-project/types@0.112.0': {}
 
-  '@oxc-resolver/binding-android-arm-eabi@11.17.1':
+  '@oxc-resolver/binding-android-arm-eabi@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-android-arm64@11.17.1':
+  '@oxc-resolver/binding-android-arm64@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-darwin-arm64@11.17.1':
+  '@oxc-resolver/binding-darwin-arm64@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-darwin-x64@11.17.1':
+  '@oxc-resolver/binding-darwin-x64@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-freebsd-x64@11.17.1':
+  '@oxc-resolver/binding-freebsd-x64@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-arm-gnueabihf@11.17.1':
+  '@oxc-resolver/binding-linux-arm-gnueabihf@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-arm-musleabihf@11.17.1':
+  '@oxc-resolver/binding-linux-arm-musleabihf@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-arm64-gnu@11.17.1':
+  '@oxc-resolver/binding-linux-arm64-gnu@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-arm64-musl@11.17.1':
+  '@oxc-resolver/binding-linux-arm64-musl@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-ppc64-gnu@11.17.1':
+  '@oxc-resolver/binding-linux-ppc64-gnu@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-riscv64-gnu@11.17.1':
+  '@oxc-resolver/binding-linux-riscv64-gnu@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-riscv64-musl@11.17.1':
+  '@oxc-resolver/binding-linux-riscv64-musl@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-s390x-gnu@11.17.1':
+  '@oxc-resolver/binding-linux-s390x-gnu@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-x64-gnu@11.17.1':
+  '@oxc-resolver/binding-linux-x64-gnu@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-linux-x64-musl@11.17.1':
+  '@oxc-resolver/binding-linux-x64-musl@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-openharmony-arm64@11.17.1':
+  '@oxc-resolver/binding-openharmony-arm64@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-wasm32-wasi@11.17.1':
+  '@oxc-resolver/binding-wasm32-wasi@11.18.0':
     dependencies:
       '@napi-rs/wasm-runtime': 1.1.1
     optional: true
 
-  '@oxc-resolver/binding-win32-arm64-msvc@11.17.1':
+  '@oxc-resolver/binding-win32-arm64-msvc@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-win32-ia32-msvc@11.17.1':
+  '@oxc-resolver/binding-win32-ia32-msvc@11.18.0':
     optional: true
 
-  '@oxc-resolver/binding-win32-x64-msvc@11.17.1':
+  '@oxc-resolver/binding-win32-x64-msvc@11.18.0':
     optional: true
 
   '@publint/pack@0.1.4': {}
@@ -4691,79 +4700,79 @@ snapshots:
 
   '@rolldown/pluginutils@1.0.0-rc.3': {}
 
-  '@rollup/rollup-android-arm-eabi@4.57.1':
+  '@rollup/rollup-android-arm-eabi@4.58.0':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.57.1':
+  '@rollup/rollup-android-arm64@4.58.0':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.57.1':
+  '@rollup/rollup-darwin-arm64@4.58.0':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.57.1':
+  '@rollup/rollup-darwin-x64@4.58.0':
     optional: true
 
-  '@rollup/rollup-freebsd-arm64@4.57.1':
+  '@rollup/rollup-freebsd-arm64@4.58.0':
     optional: true
 
-  '@rollup/rollup-freebsd-x64@4.57.1':
+  '@rollup/rollup-freebsd-x64@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.57.1':
+  '@rollup/rollup-linux-arm-gnueabihf@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.57.1':
+  '@rollup/rollup-linux-arm-musleabihf@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.57.1':
+  '@rollup/rollup-linux-arm64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.57.1':
+  '@rollup/rollup-linux-arm64-musl@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-loong64-gnu@4.57.1':
+  '@rollup/rollup-linux-loong64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-loong64-musl@4.57.1':
+  '@rollup/rollup-linux-loong64-musl@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-ppc64-gnu@4.57.1':
+  '@rollup/rollup-linux-ppc64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-ppc64-musl@4.57.1':
+  '@rollup/rollup-linux-ppc64-musl@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.57.1':
+  '@rollup/rollup-linux-riscv64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-musl@4.57.1':
+  '@rollup/rollup-linux-riscv64-musl@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.57.1':
+  '@rollup/rollup-linux-s390x-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.57.1':
+  '@rollup/rollup-linux-x64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.57.1':
+  '@rollup/rollup-linux-x64-musl@4.58.0':
     optional: true
 
-  '@rollup/rollup-openbsd-x64@4.57.1':
+  '@rollup/rollup-openbsd-x64@4.58.0':
     optional: true
 
-  '@rollup/rollup-openharmony-arm64@4.57.1':
+  '@rollup/rollup-openharmony-arm64@4.58.0':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.57.1':
+  '@rollup/rollup-win32-arm64-msvc@4.58.0':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.57.1':
+  '@rollup/rollup-win32-ia32-msvc@4.58.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-gnu@4.57.1':
+  '@rollup/rollup-win32-x64-gnu@4.58.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.57.1':
+  '@rollup/rollup-win32-x64-msvc@4.58.0':
     optional: true
 
   '@shikijs/engine-oniguruma@3.22.0':
@@ -4845,19 +4854,19 @@ snapshots:
 
   '@standard-schema/spec@1.1.0': {}
 
-  '@stylistic/eslint-plugin@5.8.0(eslint@9.39.2(jiti@2.6.1))':
+  '@stylistic/eslint-plugin@5.9.0(eslint@9.39.3(jiti@2.6.1))':
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
-      '@typescript-eslint/types': 8.55.0
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
+      '@typescript-eslint/types': 8.56.0
+      eslint: 9.39.3(jiti@2.6.1)
       eslint-visitor-keys: 4.2.1
       espree: 10.4.0
       estraverse: 5.3.0
       picomatch: 4.0.3
 
-  '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)':
+  '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)':
     dependencies:
-      acorn: 8.15.0
+      acorn: 8.16.0
 
   '@svitejs/changesets-changelog-github-compact@1.2.0':
     dependencies:
@@ -4913,16 +4922,16 @@ snapshots:
       - csstype
       - utf-8-validate
 
-  '@tanstack/eslint-config@0.4.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@tanstack/eslint-config@0.4.0(@typescript-eslint/utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@eslint/js': 10.0.1(eslint@9.39.2(jiti@2.6.1))
-      '@stylistic/eslint-plugin': 5.8.0(eslint@9.39.2(jiti@2.6.1))
-      eslint: 9.39.2(jiti@2.6.1)
-      eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
-      eslint-plugin-n: 17.24.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint/js': 10.0.1(eslint@9.39.3(jiti@2.6.1))
+      '@stylistic/eslint-plugin': 5.9.0(eslint@9.39.3(jiti@2.6.1))
+      eslint: 9.39.3(jiti@2.6.1)
+      eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))
+      eslint-plugin-n: 17.24.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       globals: 17.3.0
-      typescript-eslint: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      vue-eslint-parser: 10.4.0(eslint@9.39.2(jiti@2.6.1))
+      typescript-eslint: 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      vue-eslint-parser: 10.4.0(eslint@9.39.3(jiti@2.6.1))
     transitivePeerDependencies:
       - '@typescript-eslint/utils'
       - eslint-import-resolver-node
@@ -5042,6 +5051,8 @@ snapshots:
 
   '@types/deep-eql@4.0.2': {}
 
+  '@types/esrecurse@4.3.1': {}
+
   '@types/estree@1.0.8': {}
 
   '@types/hast@3.0.4':
@@ -5066,6 +5077,8 @@ snapshots:
     dependencies:
       csstype: 3.2.3
 
+  '@types/trusted-types@2.0.7': {}
+
   '@types/unist@3.0.3': {}
 
   '@types/whatwg-mimetype@3.0.2': {}
@@ -5074,15 +5087,15 @@ snapshots:
     dependencies:
       '@types/node': 25.3.0
 
-  '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.2
-      '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/visitor-keys': 8.55.0
-      eslint: 9.39.2(jiti@2.6.1)
+      '@typescript-eslint/parser': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/visitor-keys': 8.56.0
+      eslint: 9.39.3(jiti@2.6.1)
       ignore: 7.0.5
       natural-compare: 1.4.0
       ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -5090,56 +5103,56 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/visitor-keys': 8.55.0
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/visitor-keys': 8.56.0
       debug: 4.4.3
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)':
+  '@typescript-eslint/project-service@8.56.0(typescript@5.9.3)':
     dependencies:
-      '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
+      '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
       debug: 4.4.3
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@8.55.0':
+  '@typescript-eslint/scope-manager@8.56.0':
     dependencies:
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/visitor-keys': 8.55.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/visitor-keys': 8.56.0
 
-  '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)':
+  '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)':
     dependencies:
       typescript: 5.9.3
 
-  '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@typescript-eslint/type-utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       debug: 4.4.3
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-api-utils: 2.4.0(typescript@5.9.3)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@8.55.0': {}
+  '@typescript-eslint/types@8.56.0': {}
 
-  '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)':
+  '@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)':
     dependencies:
-      '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/visitor-keys': 8.55.0
+      '@typescript-eslint/project-service': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/visitor-keys': 8.56.0
       debug: 4.4.3
       minimatch: 9.0.5
       semver: 7.7.4
@@ -5149,21 +5162,21 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+  '@typescript-eslint/utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/visitor-keys@8.55.0':
+  '@typescript-eslint/visitor-keys@8.56.0':
     dependencies:
-      '@typescript-eslint/types': 8.55.0
-      eslint-visitor-keys: 4.2.1
+      '@typescript-eslint/types': 8.56.0
+      eslint-visitor-keys: 5.0.1
 
   '@unrs/resolver-binding-android-arm-eabi@1.11.1':
     optional: true
@@ -5286,13 +5299,13 @@ snapshots:
     dependencies:
       argparse: 2.0.1
 
-  acorn-jsx@5.3.2(acorn@8.15.0):
+  acorn-jsx@5.3.2(acorn@8.16.0):
     dependencies:
-      acorn: 8.15.0
+      acorn: 8.16.0
 
-  acorn@8.15.0: {}
+  acorn@8.16.0: {}
 
-  ajv@6.12.6:
+  ajv@6.14.0:
     dependencies:
       fast-deep-equal: 3.1.3
       fast-json-stable-stringify: 2.1.0
@@ -5347,7 +5360,7 @@ snapshots:
 
   axobject-query@4.1.0: {}
 
-  babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.29.0):
+  babel-plugin-jsx-dom-expressions@0.40.5(@babel/core@7.29.0):
     dependencies:
       '@babel/core': 7.29.0
       '@babel/helper-module-imports': 7.18.6
@@ -5359,19 +5372,17 @@ snapshots:
   babel-preset-solid@1.9.10(@babel/core@7.29.0)(solid-js@1.9.11):
     dependencies:
       '@babel/core': 7.29.0
-      babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.29.0)
+      babel-plugin-jsx-dom-expressions: 0.40.5(@babel/core@7.29.0)
     optionalDependencies:
       solid-js: 1.9.11
 
   balanced-match@1.0.2: {}
 
-  balanced-match@4.0.2:
-    dependencies:
-      jackspeak: 4.2.3
+  balanced-match@4.0.3: {}
 
   base64-js@1.5.1: {}
 
-  baseline-browser-mapping@2.9.19: {}
+  baseline-browser-mapping@2.10.0: {}
 
   better-path-resolve@1.0.0:
     dependencies:
@@ -5400,7 +5411,7 @@ snapshots:
 
   brace-expansion@5.0.2:
     dependencies:
-      balanced-match: 4.0.2
+      balanced-match: 4.0.3
 
   braces@3.0.3:
     dependencies:
@@ -5408,9 +5419,9 @@ snapshots:
 
   browserslist@4.28.1:
     dependencies:
-      baseline-browser-mapping: 2.9.19
-      caniuse-lite: 1.0.30001769
-      electron-to-chromium: 1.5.286
+      baseline-browser-mapping: 2.10.0
+      caniuse-lite: 1.0.30001770
+      electron-to-chromium: 1.5.302
       node-releases: 2.0.27
       update-browserslist-db: 1.2.3(browserslist@4.28.1)
 
@@ -5430,7 +5441,7 @@ snapshots:
 
   callsites@3.1.0: {}
 
-  caniuse-lite@1.0.30001769: {}
+  caniuse-lite@1.0.30001770: {}
 
   chai@6.2.2: {}
 
@@ -5461,7 +5472,7 @@ snapshots:
       parse5: 7.3.0
       parse5-htmlparser2-tree-adapter: 7.1.0
       parse5-parser-stream: 7.1.2
-      undici: 7.21.0
+      undici: 7.22.0
       whatwg-mimetype: 4.0.0
 
   ci-info@3.9.0: {}
@@ -5542,7 +5553,7 @@ snapshots:
 
   detect-indent@6.1.0: {}
 
-  devalue@5.6.2: {}
+  devalue@5.6.3: {}
 
   dir-glob@3.0.1:
     dependencies:
@@ -5578,9 +5589,9 @@ snapshots:
 
   dotenv@16.6.1: {}
 
-  dts-resolver@2.1.3(oxc-resolver@11.17.1):
+  dts-resolver@2.1.3(oxc-resolver@11.18.0):
     optionalDependencies:
-      oxc-resolver: 11.17.1
+      oxc-resolver: 11.18.0
 
   dunder-proto@1.0.1:
     dependencies:
@@ -5592,7 +5603,7 @@ snapshots:
     dependencies:
       jake: 10.9.4
 
-  electron-to-chromium@1.5.286: {}
+  electron-to-chromium@1.5.302: {}
 
   emoji-regex@8.0.0: {}
 
@@ -5679,9 +5690,9 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
-  eslint-compat-utils@0.5.1(eslint@9.39.2(jiti@2.6.1)):
+  eslint-compat-utils@0.5.1(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       semver: 7.7.4
 
   eslint-import-context@0.1.9(unrs-resolver@1.11.1):
@@ -5691,36 +5702,36 @@ snapshots:
     optionalDependencies:
       unrs-resolver: 1.11.1
 
-  eslint-plugin-es-x@7.8.0(eslint@9.39.2(jiti@2.6.1)):
+  eslint-plugin-es-x@7.8.0(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
       '@eslint-community/regexpp': 4.12.2
-      eslint: 9.39.2(jiti@2.6.1)
-      eslint-compat-utils: 0.5.1(eslint@9.39.2(jiti@2.6.1))
+      eslint: 9.39.3(jiti@2.6.1)
+      eslint-compat-utils: 0.5.1(eslint@9.39.3(jiti@2.6.1))
 
-  eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
+  eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
-      '@typescript-eslint/types': 8.55.0
+      '@typescript-eslint/types': 8.56.0
       comment-parser: 1.4.5
       debug: 4.4.3
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
       is-glob: 4.0.3
-      minimatch: 10.2.0
+      minimatch: 10.2.2
       semver: 7.7.4
       stable-hash-x: 0.2.0
       unrs-resolver: 1.11.1
     optionalDependencies:
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-n@17.24.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-n@17.24.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
       enhanced-resolve: 5.19.0
-      eslint: 9.39.2(jiti@2.6.1)
-      eslint-plugin-es-x: 7.8.0(eslint@9.39.2(jiti@2.6.1))
+      eslint: 9.39.3(jiti@2.6.1)
+      eslint-plugin-es-x: 7.8.0(eslint@9.39.3(jiti@2.6.1))
       get-tsconfig: 4.13.6
       globals: 15.15.0
       globrex: 0.1.2
@@ -5730,162 +5741,171 @@ snapshots:
     transitivePeerDependencies:
       - typescript
 
-  eslint-plugin-react-compiler@19.1.0-rc.2(eslint@9.39.2(jiti@2.6.1)):
+  eslint-plugin-react-compiler@19.1.0-rc.2(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
       '@babel/core': 7.29.0
       '@babel/parser': 7.29.0
       '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.29.0)
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       hermes-parser: 0.25.1
       zod: 3.25.76
       zod-validation-error: 3.5.4(zod@3.25.76)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-dom@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-dom@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/core': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/core': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       compare-versions: 6.1.1
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-hooks-extra@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-hooks-extra@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/core': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/core': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)):
+  eslint-plugin-react-hooks@7.0.1(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
       '@babel/core': 7.29.0
       '@babel/parser': 7.29.0
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       hermes-parser: 0.25.1
       zod: 4.3.6
       zod-validation-error: 4.0.2(zod@4.3.6)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-naming-convention@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-naming-convention@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/core': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/core': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       compare-versions: 6.1.1
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       string-ts: 2.3.1
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-rsc@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-rsc@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-web-api@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-web-api@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/core': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/core': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       birecord: 0.1.1
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react-x@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  eslint-plugin-react-x@2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@eslint-react/ast': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/core': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/ast': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/core': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       '@eslint-react/eff': 2.13.0
-      '@eslint-react/shared': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@eslint-react/var': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/scope-manager': 8.55.0
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/types': 8.55.0
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/shared': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@eslint-react/var': 2.13.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/scope-manager': 8.56.0
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/types': 8.56.0
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       compare-versions: 6.1.1
-      eslint: 9.39.2(jiti@2.6.1)
-      is-immutable-type: 5.0.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
+      is-immutable-type: 5.0.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
       ts-api-utils: 2.4.0(typescript@5.9.3)
       ts-pattern: 5.9.0
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
+  eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
-      eslint: 9.39.2(jiti@2.6.1)
+      eslint: 9.39.3(jiti@2.6.1)
     optionalDependencies:
-      '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
 
   eslint-scope@8.4.0:
     dependencies:
       esrecurse: 4.3.0
       estraverse: 5.3.0
 
+  eslint-scope@9.1.1:
+    dependencies:
+      '@types/esrecurse': 4.3.1
+      '@types/estree': 1.0.8
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
   eslint-visitor-keys@3.4.3: {}
 
   eslint-visitor-keys@4.2.1: {}
 
-  eslint@9.39.2(jiti@2.6.1):
+  eslint-visitor-keys@5.0.1: {}
+
+  eslint@9.39.3(jiti@2.6.1):
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
       '@eslint-community/regexpp': 4.12.2
       '@eslint/config-array': 0.21.1
       '@eslint/config-helpers': 0.4.2
       '@eslint/core': 0.17.0
       '@eslint/eslintrc': 3.3.3
-      '@eslint/js': 9.39.2
+      '@eslint/js': 9.39.3
       '@eslint/plugin-kit': 0.4.1
       '@humanfs/node': 0.16.7
       '@humanwhocodes/module-importer': 1.0.1
       '@humanwhocodes/retry': 0.4.3
       '@types/estree': 1.0.8
-      ajv: 6.12.6
+      ajv: 6.14.0
       chalk: 4.1.2
       cross-spawn: 7.0.6
       debug: 4.4.3
@@ -5916,10 +5936,16 @@ snapshots:
 
   espree@10.4.0:
     dependencies:
-      acorn: 8.15.0
-      acorn-jsx: 5.3.2(acorn@8.15.0)
+      acorn: 8.16.0
+      acorn-jsx: 5.3.2(acorn@8.16.0)
       eslint-visitor-keys: 4.2.1
 
+  espree@11.1.1:
+    dependencies:
+      acorn: 8.16.0
+      acorn-jsx: 5.3.2(acorn@8.16.0)
+      eslint-visitor-keys: 5.0.1
+
   esprima@4.0.1: {}
 
   esquery@1.7.0:
@@ -6187,10 +6213,10 @@ snapshots:
     dependencies:
       is-extglob: 2.1.1
 
-  is-immutable-type@5.0.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  is-immutable-type@5.0.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       ts-api-utils: 2.4.0(typescript@5.9.3)
       ts-declaration-location: 1.0.7(typescript@5.9.3)
       typescript: 5.9.3
@@ -6221,10 +6247,6 @@ snapshots:
 
   isexe@2.0.0: {}
 
-  jackspeak@4.2.3:
-    dependencies:
-      '@isaacs/cliui': 9.0.0
-
   jake@10.9.4:
     dependencies:
       async: 3.2.6
@@ -6271,7 +6293,7 @@ snapshots:
     dependencies:
       json-buffer: 3.0.1
 
-  knip@5.84.1(@types/node@25.3.0)(typescript@5.9.3):
+  knip@5.85.0(@types/node@25.3.0)(typescript@5.9.3):
     dependencies:
       '@nodelib/fs.walk': 1.2.8
       '@types/node': 25.3.0
@@ -6280,7 +6302,7 @@ snapshots:
       jiti: 2.6.1
       js-yaml: 4.1.1
       minimist: 1.2.8
-      oxc-resolver: 11.17.1
+      oxc-resolver: 11.18.0
       picocolors: 1.1.1
       picomatch: 4.0.3
       smol-toml: 1.6.0
@@ -6344,9 +6366,9 @@ snapshots:
   markdown-link-extractor@4.0.3:
     dependencies:
       html-link-extractor: 1.0.5
-      marked: 17.0.2
+      marked: 17.0.3
 
-  marked@17.0.2: {}
+  marked@17.0.3: {}
 
   math-intrinsics@1.1.0: {}
 
@@ -6377,7 +6399,7 @@ snapshots:
     dependencies:
       '@isaacs/brace-expansion': 5.0.1
 
-  minimatch@10.2.0:
+  minimatch@10.2.2:
     dependencies:
       brace-expansion: 5.0.2
 
@@ -6517,28 +6539,28 @@ snapshots:
 
   outdent@0.5.0: {}
 
-  oxc-resolver@11.17.1:
+  oxc-resolver@11.18.0:
     optionalDependencies:
-      '@oxc-resolver/binding-android-arm-eabi': 11.17.1
-      '@oxc-resolver/binding-android-arm64': 11.17.1
-      '@oxc-resolver/binding-darwin-arm64': 11.17.1
-      '@oxc-resolver/binding-darwin-x64': 11.17.1
-      '@oxc-resolver/binding-freebsd-x64': 11.17.1
-      '@oxc-resolver/binding-linux-arm-gnueabihf': 11.17.1
-      '@oxc-resolver/binding-linux-arm-musleabihf': 11.17.1
-      '@oxc-resolver/binding-linux-arm64-gnu': 11.17.1
-      '@oxc-resolver/binding-linux-arm64-musl': 11.17.1
-      '@oxc-resolver/binding-linux-ppc64-gnu': 11.17.1
-      '@oxc-resolver/binding-linux-riscv64-gnu': 11.17.1
-      '@oxc-resolver/binding-linux-riscv64-musl': 11.17.1
-      '@oxc-resolver/binding-linux-s390x-gnu': 11.17.1
-      '@oxc-resolver/binding-linux-x64-gnu': 11.17.1
-      '@oxc-resolver/binding-linux-x64-musl': 11.17.1
-      '@oxc-resolver/binding-openharmony-arm64': 11.17.1
-      '@oxc-resolver/binding-wasm32-wasi': 11.17.1
-      '@oxc-resolver/binding-win32-arm64-msvc': 11.17.1
-      '@oxc-resolver/binding-win32-ia32-msvc': 11.17.1
-      '@oxc-resolver/binding-win32-x64-msvc': 11.17.1
+      '@oxc-resolver/binding-android-arm-eabi': 11.18.0
+      '@oxc-resolver/binding-android-arm64': 11.18.0
+      '@oxc-resolver/binding-darwin-arm64': 11.18.0
+      '@oxc-resolver/binding-darwin-x64': 11.18.0
+      '@oxc-resolver/binding-freebsd-x64': 11.18.0
+      '@oxc-resolver/binding-linux-arm-gnueabihf': 11.18.0
+      '@oxc-resolver/binding-linux-arm-musleabihf': 11.18.0
+      '@oxc-resolver/binding-linux-arm64-gnu': 11.18.0
+      '@oxc-resolver/binding-linux-arm64-musl': 11.18.0
+      '@oxc-resolver/binding-linux-ppc64-gnu': 11.18.0
+      '@oxc-resolver/binding-linux-riscv64-gnu': 11.18.0
+      '@oxc-resolver/binding-linux-riscv64-musl': 11.18.0
+      '@oxc-resolver/binding-linux-s390x-gnu': 11.18.0
+      '@oxc-resolver/binding-linux-x64-gnu': 11.18.0
+      '@oxc-resolver/binding-linux-x64-musl': 11.18.0
+      '@oxc-resolver/binding-openharmony-arm64': 11.18.0
+      '@oxc-resolver/binding-wasm32-wasi': 11.18.0
+      '@oxc-resolver/binding-win32-arm64-msvc': 11.18.0
+      '@oxc-resolver/binding-win32-ia32-msvc': 11.18.0
+      '@oxc-resolver/binding-win32-x64-msvc': 11.18.0
 
   p-filter@2.1.0:
     dependencies:
@@ -6613,10 +6635,10 @@ snapshots:
 
   premove@4.0.0: {}
 
-  prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.50.3):
+  prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.1):
     dependencies:
       prettier: 3.8.1
-      svelte: 5.50.3
+      svelte: 5.53.1
 
   prettier@2.8.8: {}
 
@@ -6701,7 +6723,7 @@ snapshots:
 
   reusify@1.1.0: {}
 
-  rolldown-plugin-dts@0.22.1(oxc-resolver@11.17.1)(rolldown@1.0.0-rc.3)(typescript@5.9.3):
+  rolldown-plugin-dts@0.22.1(oxc-resolver@11.18.0)(rolldown@1.0.0-rc.3)(typescript@5.9.3):
     dependencies:
       '@babel/generator': 8.0.0-rc.1
       '@babel/helper-validator-identifier': 8.0.0-rc.1
@@ -6709,7 +6731,7 @@ snapshots:
       '@babel/types': 8.0.0-rc.1
       ast-kit: 3.0.0-beta.1
       birpc: 4.0.0
-      dts-resolver: 2.1.3(oxc-resolver@11.17.1)
+      dts-resolver: 2.1.3(oxc-resolver@11.18.0)
       get-tsconfig: 4.13.6
       obug: 2.1.1
       rolldown: 1.0.0-rc.3
@@ -6737,35 +6759,35 @@ snapshots:
       '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3
       '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3
 
-  rollup@4.57.1:
+  rollup@4.58.0:
     dependencies:
       '@types/estree': 1.0.8
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.57.1
-      '@rollup/rollup-android-arm64': 4.57.1
-      '@rollup/rollup-darwin-arm64': 4.57.1
-      '@rollup/rollup-darwin-x64': 4.57.1
-      '@rollup/rollup-freebsd-arm64': 4.57.1
-      '@rollup/rollup-freebsd-x64': 4.57.1
-      '@rollup/rollup-linux-arm-gnueabihf': 4.57.1
-      '@rollup/rollup-linux-arm-musleabihf': 4.57.1
-      '@rollup/rollup-linux-arm64-gnu': 4.57.1
-      '@rollup/rollup-linux-arm64-musl': 4.57.1
-      '@rollup/rollup-linux-loong64-gnu': 4.57.1
-      '@rollup/rollup-linux-loong64-musl': 4.57.1
-      '@rollup/rollup-linux-ppc64-gnu': 4.57.1
-      '@rollup/rollup-linux-ppc64-musl': 4.57.1
-      '@rollup/rollup-linux-riscv64-gnu': 4.57.1
-      '@rollup/rollup-linux-riscv64-musl': 4.57.1
-      '@rollup/rollup-linux-s390x-gnu': 4.57.1
-      '@rollup/rollup-linux-x64-gnu': 4.57.1
-      '@rollup/rollup-linux-x64-musl': 4.57.1
-      '@rollup/rollup-openbsd-x64': 4.57.1
-      '@rollup/rollup-openharmony-arm64': 4.57.1
-      '@rollup/rollup-win32-arm64-msvc': 4.57.1
-      '@rollup/rollup-win32-ia32-msvc': 4.57.1
-      '@rollup/rollup-win32-x64-gnu': 4.57.1
-      '@rollup/rollup-win32-x64-msvc': 4.57.1
+      '@rollup/rollup-android-arm-eabi': 4.58.0
+      '@rollup/rollup-android-arm64': 4.58.0
+      '@rollup/rollup-darwin-arm64': 4.58.0
+      '@rollup/rollup-darwin-x64': 4.58.0
+      '@rollup/rollup-freebsd-arm64': 4.58.0
+      '@rollup/rollup-freebsd-x64': 4.58.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.58.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.58.0
+      '@rollup/rollup-linux-arm64-gnu': 4.58.0
+      '@rollup/rollup-linux-arm64-musl': 4.58.0
+      '@rollup/rollup-linux-loong64-gnu': 4.58.0
+      '@rollup/rollup-linux-loong64-musl': 4.58.0
+      '@rollup/rollup-linux-ppc64-gnu': 4.58.0
+      '@rollup/rollup-linux-ppc64-musl': 4.58.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.58.0
+      '@rollup/rollup-linux-riscv64-musl': 4.58.0
+      '@rollup/rollup-linux-s390x-gnu': 4.58.0
+      '@rollup/rollup-linux-x64-gnu': 4.58.0
+      '@rollup/rollup-linux-x64-musl': 4.58.0
+      '@rollup/rollup-openbsd-x64': 4.58.0
+      '@rollup/rollup-openharmony-arm64': 4.58.0
+      '@rollup/rollup-win32-arm64-msvc': 4.58.0
+      '@rollup/rollup-win32-ia32-msvc': 4.58.0
+      '@rollup/rollup-win32-x64-gnu': 4.58.0
+      '@rollup/rollup-win32-x64-msvc': 4.58.0
       fsevents: 2.3.3
 
   run-parallel@1.2.0:
@@ -6913,17 +6935,18 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
-  svelte@5.50.3:
+  svelte@5.53.1:
     dependencies:
       '@jridgewell/remapping': 2.3.5
       '@jridgewell/sourcemap-codec': 1.5.5
-      '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0)
+      '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0)
       '@types/estree': 1.0.8
-      acorn: 8.15.0
+      '@types/trusted-types': 2.0.7
+      acorn: 8.16.0
       aria-query: 5.3.2
       axobject-query: 4.1.0
       clsx: 2.1.1
-      devalue: 5.6.2
+      devalue: 5.6.3
       esm-env: 1.2.2
       esrap: 2.2.3
       is-reference: 3.0.3
@@ -6981,7 +7004,7 @@ snapshots:
       minimist: 1.2.8
       strip-bom: 3.0.0
 
-  tsdown@0.20.3(oxc-resolver@11.17.1)(publint@0.3.17)(typescript@5.9.3):
+  tsdown@0.20.3(oxc-resolver@11.18.0)(publint@0.3.17)(typescript@5.9.3):
     dependencies:
       ansis: 4.2.0
       cac: 6.7.14
@@ -6992,12 +7015,12 @@ snapshots:
       obug: 2.1.1
       picomatch: 4.0.3
       rolldown: 1.0.0-rc.3
-      rolldown-plugin-dts: 0.22.1(oxc-resolver@11.17.1)(rolldown@1.0.0-rc.3)(typescript@5.9.3)
+      rolldown-plugin-dts: 0.22.1(oxc-resolver@11.18.0)(rolldown@1.0.0-rc.3)(typescript@5.9.3)
       semver: 7.7.4
       tinyexec: 1.0.2
       tinyglobby: 0.2.15
       tree-kill: 1.2.2
-      unconfig-core: 7.4.2
+      unconfig-core: 7.5.0
       unrun: 0.2.27
     optionalDependencies:
       publint: 0.3.17
@@ -7033,13 +7056,13 @@ snapshots:
       typescript: 5.9.3
       yaml: 2.8.2
 
-  typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+  typescript-eslint@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3):
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
-      eslint: 9.39.2(jiti@2.6.1)
+      '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/parser': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
+      eslint: 9.39.3(jiti@2.6.1)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
@@ -7048,14 +7071,14 @@ snapshots:
 
   uc.micro@2.1.0: {}
 
-  unconfig-core@7.4.2:
+  unconfig-core@7.5.0:
     dependencies:
       '@quansync/fs': 1.0.0
       quansync: 1.0.0
 
   undici-types@7.18.2: {}
 
-  undici@7.21.0: {}
+  undici@7.22.0: {}
 
   universalify@0.1.2: {}
 
@@ -7124,7 +7147,7 @@ snapshots:
       fdir: 6.5.0(picomatch@4.0.3)
       picomatch: 4.0.3
       postcss: 8.5.6
-      rollup: 4.57.1
+      rollup: 4.58.0
       tinyglobby: 0.2.15
     optionalDependencies:
       '@types/node': 25.3.0
@@ -7174,13 +7197,13 @@ snapshots:
       - tsx
       - yaml
 
-  vue-eslint-parser@10.4.0(eslint@9.39.2(jiti@2.6.1)):
+  vue-eslint-parser@10.4.0(eslint@9.39.3(jiti@2.6.1)):
     dependencies:
       debug: 4.4.3
-      eslint: 9.39.2(jiti@2.6.1)
-      eslint-scope: 8.4.0
-      eslint-visitor-keys: 4.2.1
-      espree: 10.4.0
+      eslint: 9.39.3(jiti@2.6.1)
+      eslint-scope: 9.1.1
+      eslint-visitor-keys: 5.0.1
+      espree: 11.1.1
       esquery: 1.7.0
       semver: 7.7.4
     transitivePeerDependencies:

From 84ff019e3e11da5ba425ec833797b7ea5f835ff9 Mon Sep 17 00:00:00 2001
From: Kevin Van Cott 
Date: Sat, 21 Feb 2026 13:34:19 -0600
Subject: [PATCH 6/9] add sequences to devtools

---
 docs/reference/classes/SequenceManager.md     |  62 +-
 .../functions/createSequenceMatcher.md        |   2 +-
 .../reference/functions/getSequenceManager.md |   2 +-
 docs/reference/index.md                       |   1 +
 docs/reference/interfaces/SequenceOptions.md  |   4 +-
 .../interfaces/SequenceRegistrationHandle.md  |  12 +-
 .../interfaces/SequenceRegistrationView.md    |  61 ++
 docs/reference/type-aliases/HotkeySequence.md |   2 +-
 .../src/HotkeysContextProvider.tsx            |  34 +-
 .../src/components/ActionButtons.tsx          |  23 +-
 .../src/components/DetailsPanel.tsx           | 704 +++++++++++++-----
 .../src/components/HotkeyList.tsx             | 197 ++++-
 .../hotkeys-devtools/src/components/Shell.tsx |   5 +-
 packages/hotkeys/src/sequence-manager.ts      |  97 +++
 14 files changed, 959 insertions(+), 247 deletions(-)
 create mode 100644 docs/reference/interfaces/SequenceRegistrationView.md

diff --git a/docs/reference/classes/SequenceManager.md b/docs/reference/classes/SequenceManager.md
index 7c906f6..5f2fe40 100644
--- a/docs/reference/classes/SequenceManager.md
+++ b/docs/reference/classes/SequenceManager.md
@@ -5,27 +5,20 @@ title: SequenceManager
 
 # Class: SequenceManager
 
-Defined in: [sequence-manager.ts:119](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L119)
+Defined in: [sequence-manager.ts:148](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L148)
 
-Manages keyboard sequence matching for Vim-style shortcuts.
+## Properties
 
-This class allows registering multi-key sequences like 'g g' or 'd d'
-that trigger callbacks when the full sequence is pressed within
-a configurable timeout.
-
-## Example
+### registrations
 
 ```ts
-const matcher = SequenceManager.getInstance()
+readonly registrations: Store>;
+```
 
-// Register 'g g' to go to top
-const unregister = matcher.register(['G', 'G'], (event, context) => {
-  scrollToTop()
-}, { timeout: 500 })
+Defined in: [sequence-manager.ts:155](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L155)
 
-// Later, to unregister:
-unregister()
-```
+The TanStack Store containing sequence registration views for devtools.
+Subscribe to this to observe registration changes.
 
 ## Methods
 
@@ -35,7 +28,7 @@ unregister()
 destroy(): void;
 ```
 
-Defined in: [sequence-manager.ts:501](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L501)
+Defined in: [sequence-manager.ts:597](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L597)
 
 Destroys the manager and removes all listeners.
 
@@ -51,7 +44,7 @@ Destroys the manager and removes all listeners.
 getRegistrationCount(): number;
 ```
 
-Defined in: [sequence-manager.ts:494](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L494)
+Defined in: [sequence-manager.ts:590](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L590)
 
 Gets the number of registered sequences.
 
@@ -70,7 +63,7 @@ register(
    options): SequenceRegistrationHandle;
 ```
 
-Defined in: [sequence-manager.ts:165](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L165)
+Defined in: [sequence-manager.ts:201](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L201)
 
 Registers a hotkey sequence handler.
 
@@ -108,7 +101,7 @@ A handle to update or unregister the sequence
 resetAll(): void;
 ```
 
-Defined in: [sequence-manager.ts:484](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L484)
+Defined in: [sequence-manager.ts:532](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L532)
 
 Resets all sequence progress.
 
@@ -118,13 +111,40 @@ Resets all sequence progress.
 
 ***
 
+### triggerSequence()
+
+```ts
+triggerSequence(id): boolean;
+```
+
+Defined in: [sequence-manager.ts:546](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L546)
+
+Triggers a sequence's callback programmatically from devtools.
+Creates a synthetic KeyboardEvent from the last key in the sequence.
+
+#### Parameters
+
+##### id
+
+`string`
+
+The registration ID to trigger
+
+#### Returns
+
+`boolean`
+
+True if the registration was found and triggered
+
+***
+
 ### getInstance()
 
 ```ts
 static getInstance(): SequenceManager;
 ```
 
-Defined in: [sequence-manager.ts:140](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L140)
+Defined in: [sequence-manager.ts:176](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L176)
 
 Gets the singleton instance of SequenceManager.
 
@@ -140,7 +160,7 @@ Gets the singleton instance of SequenceManager.
 static resetInstance(): void;
 ```
 
-Defined in: [sequence-manager.ts:150](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L150)
+Defined in: [sequence-manager.ts:186](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L186)
 
 Resets the singleton instance. Useful for testing.
 
diff --git a/docs/reference/functions/createSequenceMatcher.md b/docs/reference/functions/createSequenceMatcher.md
index 022d1df..6968e30 100644
--- a/docs/reference/functions/createSequenceMatcher.md
+++ b/docs/reference/functions/createSequenceMatcher.md
@@ -9,7 +9,7 @@ title: createSequenceMatcher
 function createSequenceMatcher(sequence, options): object;
 ```
 
-Defined in: [sequence-manager.ts:539](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L539)
+Defined in: [sequence-manager.ts:636](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L636)
 
 Creates a simple sequence matcher for one-off use.
 
diff --git a/docs/reference/functions/getSequenceManager.md b/docs/reference/functions/getSequenceManager.md
index df72f71..fa13098 100644
--- a/docs/reference/functions/getSequenceManager.md
+++ b/docs/reference/functions/getSequenceManager.md
@@ -9,7 +9,7 @@ title: getSequenceManager
 function getSequenceManager(): SequenceManager;
 ```
 
-Defined in: [sequence-manager.ts:513](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L513)
+Defined in: [sequence-manager.ts:610](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L610)
 
 Gets the singleton SequenceManager instance.
 Convenience function for accessing the manager.
diff --git a/docs/reference/index.md b/docs/reference/index.md
index 6588478..32a5c07 100644
--- a/docs/reference/index.md
+++ b/docs/reference/index.md
@@ -28,6 +28,7 @@ title: "@tanstack/hotkeys"
 - [RawHotkey](interfaces/RawHotkey.md)
 - [SequenceOptions](interfaces/SequenceOptions.md)
 - [SequenceRegistrationHandle](interfaces/SequenceRegistrationHandle.md)
+- [SequenceRegistrationView](interfaces/SequenceRegistrationView.md)
 - [ValidationResult](interfaces/ValidationResult.md)
 
 ## Type Aliases
diff --git a/docs/reference/interfaces/SequenceOptions.md b/docs/reference/interfaces/SequenceOptions.md
index af561eb..72672b6 100644
--- a/docs/reference/interfaces/SequenceOptions.md
+++ b/docs/reference/interfaces/SequenceOptions.md
@@ -5,7 +5,7 @@ title: SequenceOptions
 
 # Interface: SequenceOptions
 
-Defined in: [sequence-manager.ts:26](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L26)
+Defined in: [sequence-manager.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L27)
 
 Options for hotkey sequence matching.
 Extends HotkeyOptions but excludes requireReset (not applicable to sequences).
@@ -150,6 +150,6 @@ The DOM element to attach the event listener to. Defaults to document.
 optional timeout: number;
 ```
 
-Defined in: [sequence-manager.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L28)
+Defined in: [sequence-manager.ts:29](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L29)
 
 Timeout between keys in milliseconds. Default: 1000
diff --git a/docs/reference/interfaces/SequenceRegistrationHandle.md b/docs/reference/interfaces/SequenceRegistrationHandle.md
index c09bd3d..b800ac8 100644
--- a/docs/reference/interfaces/SequenceRegistrationHandle.md
+++ b/docs/reference/interfaces/SequenceRegistrationHandle.md
@@ -5,7 +5,7 @@ title: SequenceRegistrationHandle
 
 # Interface: SequenceRegistrationHandle
 
-Defined in: [sequence-manager.ts:91](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L91)
+Defined in: [sequence-manager.ts:105](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L105)
 
 A handle returned from SequenceManager.register() that allows updating
 the callback and options without re-registering the sequence.
@@ -28,7 +28,7 @@ handle.unregister()
 callback: HotkeyCallback;
 ```
 
-Defined in: [sequence-manager.ts:94](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L94)
+Defined in: [sequence-manager.ts:108](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L108)
 
 ***
 
@@ -38,7 +38,7 @@ Defined in: [sequence-manager.ts:94](https://github.com/TanStack/hotkeys/blob/ma
 readonly id: string;
 ```
 
-Defined in: [sequence-manager.ts:92](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L92)
+Defined in: [sequence-manager.ts:106](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L106)
 
 ***
 
@@ -48,7 +48,7 @@ Defined in: [sequence-manager.ts:92](https://github.com/TanStack/hotkeys/blob/ma
 readonly isActive: boolean;
 ```
 
-Defined in: [sequence-manager.ts:93](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L93)
+Defined in: [sequence-manager.ts:107](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L107)
 
 ***
 
@@ -58,7 +58,7 @@ Defined in: [sequence-manager.ts:93](https://github.com/TanStack/hotkeys/blob/ma
 setOptions: (options) => void;
 ```
 
-Defined in: [sequence-manager.ts:95](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L95)
+Defined in: [sequence-manager.ts:109](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L109)
 
 #### Parameters
 
@@ -78,7 +78,7 @@ Defined in: [sequence-manager.ts:95](https://github.com/TanStack/hotkeys/blob/ma
 unregister: () => void;
 ```
 
-Defined in: [sequence-manager.ts:96](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L96)
+Defined in: [sequence-manager.ts:110](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L110)
 
 #### Returns
 
diff --git a/docs/reference/interfaces/SequenceRegistrationView.md b/docs/reference/interfaces/SequenceRegistrationView.md
new file mode 100644
index 0000000..d5178bc
--- /dev/null
+++ b/docs/reference/interfaces/SequenceRegistrationView.md
@@ -0,0 +1,61 @@
+---
+id: SequenceRegistrationView
+title: SequenceRegistrationView
+---
+
+# Interface: SequenceRegistrationView
+
+Defined in: [sequence-manager.ts:69](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L69)
+
+View of a sequence registration for devtools display.
+Excludes internal matching state (currentIndex, lastKeyTime).
+
+## Properties
+
+### id
+
+```ts
+id: string;
+```
+
+Defined in: [sequence-manager.ts:70](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L70)
+
+***
+
+### options
+
+```ts
+options: SequenceOptions;
+```
+
+Defined in: [sequence-manager.ts:72](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L72)
+
+***
+
+### sequence
+
+```ts
+sequence: HotkeySequence;
+```
+
+Defined in: [sequence-manager.ts:71](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L71)
+
+***
+
+### target
+
+```ts
+target: Target;
+```
+
+Defined in: [sequence-manager.ts:73](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L73)
+
+***
+
+### triggerCount
+
+```ts
+triggerCount: number;
+```
+
+Defined in: [sequence-manager.ts:74](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L74)
diff --git a/docs/reference/type-aliases/HotkeySequence.md b/docs/reference/type-aliases/HotkeySequence.md
index d3e23ab..12fc382 100644
--- a/docs/reference/type-aliases/HotkeySequence.md
+++ b/docs/reference/type-aliases/HotkeySequence.md
@@ -9,7 +9,7 @@ title: HotkeySequence
 type HotkeySequence = Hotkey[];
 ```
 
-Defined in: [sequence-manager.ts:41](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L41)
+Defined in: [sequence-manager.ts:42](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L42)
 
 A sequence of hotkeys for Vim-style shortcuts.
 
diff --git a/packages/hotkeys-devtools/src/HotkeysContextProvider.tsx b/packages/hotkeys-devtools/src/HotkeysContextProvider.tsx
index a9e649d..237985c 100644
--- a/packages/hotkeys-devtools/src/HotkeysContextProvider.tsx
+++ b/packages/hotkeys-devtools/src/HotkeysContextProvider.tsx
@@ -5,30 +5,43 @@ import {
   onCleanup,
   useContext,
 } from 'solid-js'
-import { HotkeyManager, KeyStateTracker } from '@tanstack/hotkeys'
+import {
+  HotkeyManager,
+  KeyStateTracker,
+  SequenceManager,
+} from '@tanstack/hotkeys'
 import type { Accessor } from 'solid-js'
-import type { HotkeyRegistration } from '@tanstack/hotkeys'
+import type {
+  HotkeyRegistration,
+  SequenceRegistrationView,
+} from '@tanstack/hotkeys'
 
 interface HotkeysDevtoolsContextType {
   registrations: Accessor>
+  sequenceRegistrations: Accessor>
   heldKeys: Accessor>
   heldCodes: Accessor>
 }
 
 const HotkeysDevtoolsContext = createContext({
   registrations: () => [],
+  sequenceRegistrations: () => [],
   heldKeys: () => [],
   heldCodes: () => ({}),
 })
 
 export function HotkeysContextProvider(props: { children: any }) {
   const manager = HotkeyManager.getInstance()
+  const sequenceManager = SequenceManager.getInstance()
   const tracker = KeyStateTracker.getInstance()
 
   // Create local signals that will be updated by subscriptions
   const [registrations, setRegistrations] = createSignal<
     Array
   >(Array.from(manager.registrations.state.values()))
+  const [sequenceRegistrations, setSequenceRegistrations] = createSignal<
+    Array
+  >(Array.from(sequenceManager.registrations.state.values()))
   const [heldKeys, setHeldKeys] = createSignal>(
     tracker.store.state.heldKeys,
   )
@@ -44,6 +57,16 @@ export function HotkeysContextProvider(props: { children: any }) {
     onCleanup(() => unsubscribe())
   })
 
+  // Subscribe to SequenceManager registrations store
+  createEffect(() => {
+    const unsubscribe = sequenceManager.registrations.subscribe(() => {
+      setSequenceRegistrations(
+        Array.from(sequenceManager.registrations.state.values()),
+      )
+    }).unsubscribe
+    onCleanup(() => unsubscribe())
+  })
+
   // Subscribe to KeyStateTracker store
   createEffect(() => {
     const unsubscribe = tracker.store.subscribe(() => {
@@ -55,7 +78,12 @@ export function HotkeysContextProvider(props: { children: any }) {
 
   return (
     
       {props.children}
     
diff --git a/packages/hotkeys-devtools/src/components/ActionButtons.tsx b/packages/hotkeys-devtools/src/components/ActionButtons.tsx
index 13b1c1e..9f09e75 100644
--- a/packages/hotkeys-devtools/src/components/ActionButtons.tsx
+++ b/packages/hotkeys-devtools/src/components/ActionButtons.tsx
@@ -1,17 +1,30 @@
-import { HotkeyManager } from '@tanstack/hotkeys'
+import { HotkeyManager, SequenceManager } from '@tanstack/hotkeys'
 import { useStyles } from '../styles/use-styles'
-import type { HotkeyRegistration } from '@tanstack/hotkeys'
+import type {
+  HotkeyRegistration,
+  SequenceRegistrationView,
+} from '@tanstack/hotkeys'
 
 type ActionButtonsProps = {
-  registration: HotkeyRegistration
+  registration: HotkeyRegistration | SequenceRegistrationView
+}
+
+function isSequenceRegistration(
+  reg: HotkeyRegistration | SequenceRegistrationView,
+): reg is SequenceRegistrationView {
+  return 'sequence' in reg && Array.isArray(reg.sequence)
 }
 
 export function ActionButtons(props: ActionButtonsProps) {
   const styles = useStyles()
 
   const handleTrigger = () => {
-    const manager = HotkeyManager.getInstance()
-    manager.triggerRegistration(props.registration.id)
+    const reg = props.registration
+    if (isSequenceRegistration(reg)) {
+      SequenceManager.getInstance().triggerSequence(reg.id)
+    } else {
+      HotkeyManager.getInstance().triggerRegistration(reg.id)
+    }
   }
 
   return (
diff --git a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
index 869f7dd..38411e3 100644
--- a/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
+++ b/packages/hotkeys-devtools/src/components/DetailsPanel.tsx
@@ -1,12 +1,30 @@
 import { For, Show, createMemo } from 'solid-js'
-import { formatForDisplay } from '@tanstack/hotkeys'
+import { formatForDisplay, formatHotkeySequence } from '@tanstack/hotkeys'
 import { useStyles } from '../styles/use-styles'
 import { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
 import { ActionButtons } from './ActionButtons'
-import type { ConflictBehavior, HotkeyRegistration } from '@tanstack/hotkeys'
+import type {
+  ConflictBehavior,
+  HotkeyRegistration,
+  HotkeySequence,
+  SequenceRegistrationView,
+} from '@tanstack/hotkeys'
+
+function sequenceKey(sequence: Array): string {
+  return sequence.join('|')
+}
+
+function isSequenceRegistration(
+  reg: HotkeyRegistration | SequenceRegistrationView,
+): reg is SequenceRegistrationView {
+  return 'sequence' in reg && Array.isArray(reg.sequence)
+}
 
 type DetailsPanelProps = {
-  selectedRegistration: () => HotkeyRegistration | null
+  selectedRegistration: () =>
+    | HotkeyRegistration
+    | SequenceRegistrationView
+    | null
 }
 
 function getTargetDescription(target: HTMLElement | Document | Window): string {
@@ -53,6 +71,32 @@ function findScopeConflicts(
   )
 }
 
+function findSequenceTargetConflicts(
+  registration: SequenceRegistrationView,
+  all: Array,
+): Array {
+  return all.filter(
+    (other) =>
+      other.id !== registration.id &&
+      sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
+      other.options.eventType === registration.options.eventType &&
+      other.target === registration.target,
+  )
+}
+
+function findSequenceScopeConflicts(
+  registration: SequenceRegistrationView,
+  all: Array,
+): Array {
+  return all.filter(
+    (other) =>
+      other.id !== registration.id &&
+      sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
+      other.options.eventType === registration.options.eventType &&
+      other.target !== registration.target,
+  )
+}
+
 function getConflictItemStyle(
   behavior: ConflictBehavior,
   isSameTarget: boolean,
@@ -78,6 +122,427 @@ function getConflictLabel(
   return 'warning'
 }
 
+function HotkeyDetails(props: {
+  registration: HotkeyRegistration
+  state: ReturnType
+  getTargetDescription: (t: HTMLElement | Document | Window) => string
+  findTargetConflicts: (
+    r: HotkeyRegistration,
+    all: Array,
+  ) => Array
+  findScopeConflicts: (
+    r: HotkeyRegistration,
+    all: Array,
+  ) => Array
+  getConflictItemStyle: (
+    b: ConflictBehavior,
+    same: boolean,
+  ) =>
+    | 'conflictItem'
+    | 'conflictItemAllow'
+    | 'conflictItemError'
+    | 'conflictItemScope'
+  getConflictLabel: (b: ConflictBehavior, same: boolean) => string
+  formatForDisplay: (hotkey: string) => string
+  styles: ReturnType
+}) {
+  const reg = () => props.registration
+  const parsed = () => reg().parsedHotkey
+  const targetConflicts = createMemo(() =>
+    props.findTargetConflicts(reg(), props.state.registrations()),
+  )
+  const scopeConflicts = createMemo(() =>
+    props.findScopeConflicts(reg(), props.state.registrations()),
+  )
+  const allConflicts = createMemo(() => [
+    ...targetConflicts(),
+    ...scopeConflicts(),
+  ])
+  const conflictBehavior = (): ConflictBehavior =>
+    reg().options.conflictBehavior ?? 'warn'
+
+  const keyParts = createMemo(() => {
+    const parts: Array = []
+    const p = parsed()
+    if (p.ctrl) parts.push('Ctrl')
+    if (p.shift) parts.push('Shift')
+    if (p.alt) parts.push('Alt')
+    if (p.meta) parts.push('Meta')
+    parts.push(p.key)
+    return parts
+  })
+
+  const styles = props.styles
+
+  return (
+    <>
+      
+
+ {props.formatForDisplay(reg().hotkey)} +
+
+
ID
+
{reg().id}
+
Raw
+
{reg().hotkey}
+
Target
+
+ {props.getTargetDescription(reg().target)} +
+
+
+ +
+
+
Key Breakdown
+
+ + {(part, i) => ( + <> + 0}> + + + + {part} + + )} + +
+
+ +
+
Actions
+ +
+ +
+
Options
+
+
+ enabled + + {String(reg().options.enabled !== false)} + +
+
+ eventType + + {reg().options.eventType ?? 'keydown'} + +
+
+ preventDefault + + {String(!!reg().options.preventDefault)} + +
+
+ stopPropagation + + {String(!!reg().options.stopPropagation)} + +
+
+ ignoreInputs + + {String(reg().options.ignoreInputs !== false)} + +
+
+ requireReset + + {String(!!reg().options.requireReset)} + +
+
+ conflictBehavior + {conflictBehavior()} +
+
+ hasFired + + {String(reg().hasFired)} + +
+
+
+ + 0}> +
+
+ Conflicts ({allConflicts().length}) +
+
+ + {(conflict) => { + const itemStyle = () => + props.getConflictItemStyle(conflictBehavior(), true) + const label = () => + props.getConflictLabel(conflictBehavior(), true) + return ( +
+ {label()} {conflict.id}:{' '} + {props.formatForDisplay(conflict.hotkey)} ( + {conflict.options.eventType ?? 'keydown'}) on{' '} + {props.getTargetDescription(conflict.target)} +
+ ) + }} +
+ + {(conflict) => ( +
+ scope {conflict.id}:{' '} + {props.formatForDisplay(conflict.hotkey)} ( + {conflict.options.eventType ?? 'keydown'}) on{' '} + {props.getTargetDescription(conflict.target)} +
+ )} +
+
+
+
+
+ + ) +} + +function SequenceDetails(props: { + registration: SequenceRegistrationView + state: ReturnType + getTargetDescription: (t: HTMLElement | Document | Window) => string + findSequenceTargetConflicts: ( + r: SequenceRegistrationView, + all: Array, + ) => Array + findSequenceScopeConflicts: ( + r: SequenceRegistrationView, + all: Array, + ) => Array + getConflictItemStyle: ( + b: ConflictBehavior, + same: boolean, + ) => + | 'conflictItem' + | 'conflictItemAllow' + | 'conflictItemError' + | 'conflictItemScope' + getConflictLabel: (b: ConflictBehavior, same: boolean) => string + formatHotkeySequence: (seq: HotkeySequence) => string + formatForDisplay: (hotkey: string) => string + styles: ReturnType +}) { + const reg = () => props.registration + const targetConflicts = createMemo(() => + props.findSequenceTargetConflicts( + reg(), + props.state.sequenceRegistrations(), + ), + ) + const scopeConflicts = createMemo(() => + props.findSequenceScopeConflicts( + reg(), + props.state.sequenceRegistrations(), + ), + ) + const allConflicts = createMemo(() => [ + ...targetConflicts(), + ...scopeConflicts(), + ]) + const conflictBehavior = (): ConflictBehavior => + reg().options.conflictBehavior ?? 'warn' + + const styles = props.styles + + return ( + <> +
+
+ {props.formatHotkeySequence(reg().sequence)} +
+
+
ID
+
{reg().id}
+
Sequence
+
+ {props.formatHotkeySequence(reg().sequence)} +
+
Target
+
+ {props.getTargetDescription(reg().target)} +
+
+
+ +
+
+
Key Breakdown
+
+ + {(step, i) => ( + <> + 0}> + + + + {props.formatForDisplay(step)} + + + )} + +
+
+ +
+
Actions
+ +
+ +
+
Options
+
+
+ enabled + + {String(reg().options.enabled !== false)} + +
+
+ eventType + + {reg().options.eventType ?? 'keydown'} + +
+
+ timeout + + {reg().options.timeout ?? 1000}ms + +
+
+ preventDefault + + {String(!!reg().options.preventDefault)} + +
+
+ stopPropagation + + {String(!!reg().options.stopPropagation)} + +
+
+ ignoreInputs + + {String(reg().options.ignoreInputs !== false)} + +
+
+ conflictBehavior + {conflictBehavior()} +
+
+
+ + 0}> +
+
+ Conflicts ({allConflicts().length}) +
+
+ + {(conflict) => { + const itemStyle = () => + props.getConflictItemStyle(conflictBehavior(), true) + const label = () => + props.getConflictLabel(conflictBehavior(), true) + return ( +
+ {label()} {conflict.id}:{' '} + {props.formatHotkeySequence(conflict.sequence)} ( + {conflict.options.eventType ?? 'keydown'}) on{' '} + {props.getTargetDescription(conflict.target)} +
+ ) + }} +
+ + {(conflict) => ( +
+ scope {conflict.id}:{' '} + {props.formatHotkeySequence(conflict.sequence)} ( + {conflict.options.eventType ?? 'keydown'}) on{' '} + {props.getTargetDescription(conflict.target)} +
+ )} +
+
+
+
+
+ + ) +} + export function DetailsPanel(props: DetailsPanelProps) { const styles = useStyles() const state = useHotkeysDevtoolsState() @@ -88,210 +553,41 @@ export function DetailsPanel(props: DetailsPanelProps) { when={props.selectedRegistration()} fallback={
- Select a hotkey from the list to view its details + Select a hotkey or sequence from the list to view its details
} > - {(reg) => { - const parsed = () => reg().parsedHotkey - const targetConflicts = createMemo(() => - findTargetConflicts(reg(), state.registrations()), - ) - const scopeConflicts = createMemo(() => - findScopeConflicts(reg(), state.registrations()), - ) - const allConflicts = createMemo(() => [ - ...targetConflicts(), - ...scopeConflicts(), - ]) - const conflictBehavior = (): ConflictBehavior => - reg().options.conflictBehavior ?? 'warn' - - // Build key parts for visual breakdown - const keyParts = createMemo(() => { - const parts: Array = [] - const p = parsed() - if (p.ctrl) parts.push('Ctrl') - if (p.shift) parts.push('Shift') - if (p.alt) parts.push('Alt') - if (p.meta) parts.push('Meta') - parts.push(p.key) - return parts - }) - - return ( - <> -
-
- {formatForDisplay(reg().hotkey)} -
-
-
ID
-
{reg().id}
-
Raw
-
{reg().hotkey}
-
Target
-
- {getTargetDescription(reg().target)} -
-
-
- -
- {/* Key Breakdown */} -
-
Key Breakdown
-
- - {(part, i) => ( - <> - 0}> - + - - {part} - - )} - -
-
- - {/* Actions */} -
-
Actions
- -
- - {/* Options */} -
-
Options
-
-
- enabled - - {String(reg().options.enabled !== false)} - -
-
- eventType - - {reg().options.eventType ?? 'keydown'} - -
-
- preventDefault - - {String(!!reg().options.preventDefault)} - -
-
- stopPropagation - - {String(!!reg().options.stopPropagation)} - -
-
- ignoreInputs - - {String(reg().options.ignoreInputs !== false)} - -
-
- requireReset - - {String(!!reg().options.requireReset)} - -
-
- conflictBehavior - - {conflictBehavior()} - -
-
- hasFired - - {String(reg().hasFired)} - -
-
-
- - {/* Conflicts */} - 0}> -
-
- Conflicts ({allConflicts().length}) -
-
- - {(conflict) => { - const itemStyle = () => - getConflictItemStyle(conflictBehavior(), true) - const label = () => - getConflictLabel(conflictBehavior(), true) - return ( -
- {label()} {conflict.id}:{' '} - {formatForDisplay(conflict.hotkey)} ( - {conflict.options.eventType ?? 'keydown'}) on{' '} - {getTargetDescription(conflict.target)} -
- ) - }} -
- - {(conflict) => ( -
- scope {conflict.id}:{' '} - {formatForDisplay(conflict.hotkey)} ( - {conflict.options.eventType ?? 'keydown'}) on{' '} - {getTargetDescription(conflict.target)} -
- )} -
-
-
-
-
- - ) - }} + {(reg) => ( + + } + > + + + )} ) diff --git a/packages/hotkeys-devtools/src/components/HotkeyList.tsx b/packages/hotkeys-devtools/src/components/HotkeyList.tsx index 7b8098f..0bb976b 100644 --- a/packages/hotkeys-devtools/src/components/HotkeyList.tsx +++ b/packages/hotkeys-devtools/src/components/HotkeyList.tsx @@ -1,9 +1,17 @@ import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js' import clsx from 'clsx' -import { formatForDisplay } from '@tanstack/hotkeys' +import { formatForDisplay, formatHotkeySequence } from '@tanstack/hotkeys' import { useStyles } from '../styles/use-styles' import { useHotkeysDevtoolsState } from '../HotkeysContextProvider' -import type { ConflictBehavior, HotkeyRegistration } from '@tanstack/hotkeys' +import type { + ConflictBehavior, + HotkeyRegistration, + SequenceRegistrationView, +} from '@tanstack/hotkeys' + +function sequenceKey(sequence: Array): string { + return sequence.join('|') +} type HotkeyListProps = { selectedId: () => string | null @@ -78,11 +86,38 @@ function findScopeConflicts( ) } +function findSequenceTargetConflicts( + registration: SequenceRegistrationView, + all: Array, +): Array { + return all.filter( + (other) => + other.id !== registration.id && + sequenceKey(other.sequence) === sequenceKey(registration.sequence) && + other.options.eventType === registration.options.eventType && + other.target === registration.target, + ) +} + +function findSequenceScopeConflicts( + registration: SequenceRegistrationView, + all: Array, +): Array { + return all.filter( + (other) => + other.id !== registration.id && + sequenceKey(other.sequence) === sequenceKey(registration.sequence) && + other.options.eventType === registration.options.eventType && + other.target !== registration.target, + ) +} + export function HotkeyList(props: HotkeyListProps) { const styles = useStyles() const state = useHotkeysDevtoolsState() const registrations = createMemo(() => state.registrations()) + const sequenceRegistrations = createMemo(() => state.sequenceRegistrations()) return ( <> @@ -243,6 +278,164 @@ export function HotkeyList(props: HotkeyListProps) { }} + +
+ Sequences ({sequenceRegistrations().length}) +
+
+ + {(reg) => { + const targetConflicts = () => + findSequenceTargetConflicts(reg, sequenceRegistrations()) + const scopeConflicts = () => + findSequenceScopeConflicts(reg, sequenceRegistrations()) + + const hasTargetConflict = () => targetConflicts().length > 0 + const hasScopeConflict = () => scopeConflicts().length > 0 + + const conflictBehavior = (): ConflictBehavior => + reg.options.conflictBehavior ?? 'warn' + + const targetConflictBadge = () => { + const behavior = conflictBehavior() + const c = targetConflicts() + if (behavior === 'allow') { + return { + style: 'badgeAllow' as const, + label: '~', + tooltip: `Allowed: ${c.length} other binding${c.length > 1 ? 's' : ''} on same sequence and target (conflictBehavior: allow)`, + } + } + if (behavior === 'error') { + return { + style: 'badgeError' as const, + label: '!', + tooltip: `Error: ${c.length} conflicting binding${c.length > 1 ? 's' : ''} on same sequence and target (conflictBehavior: error)`, + } + } + return { + style: 'badgeConflict' as const, + label: '!', + tooltip: `Warning: ${c.length} other binding${c.length > 1 ? 's' : ''} on same sequence and target`, + } + } + + const scopeConflictTooltip = () => { + const c = scopeConflicts() + return `Info: ${c.length} binding${c.length > 1 ? 's' : ''} with same sequence on different target${c.length > 1 ? 's' : ''}` + } + + const enabled = () => reg.options.enabled !== false + + const triggerCount = () => + sequenceRegistrations().find((r) => r.id === reg.id) + ?.triggerCount ?? reg.triggerCount + + const [prevCount, setPrevCount] = createSignal(reg.triggerCount) + const [pulsing, setPulsing] = createSignal(false) + + createEffect( + on(triggerCount, (current) => { + if (current > prevCount()) { + setPulsing(true) + setTimeout(() => setPulsing(false), 600) + } + setPrevCount(current) + }), + ) + + return ( +
props.setSelectedId(reg.id)} + > + + {formatHotkeySequence(reg.sequence)} + + 0}> + x{triggerCount()} + +
+ {hasTargetConflict() && ( + + {targetConflictBadge().label} + + {targetConflictBadge().tooltip} + + + )} + {hasScopeConflict() && ( + + i + + {scopeConflictTooltip()} + + + )} + + {enabled() ? 'on' : 'off'} + + {enabled() + ? 'Sequence is enabled' + : 'Sequence is disabled'} + + + + {reg.options.eventType ?? 'keydown'} + + Fires on {reg.options.eventType ?? 'keydown'} event + + + + {getTargetLabel(reg.target)} + + {getTargetTooltip(reg.target)} + + +
+
+ ) + }} +
+
) } diff --git a/packages/hotkeys-devtools/src/components/Shell.tsx b/packages/hotkeys-devtools/src/components/Shell.tsx index efdb5ac..7b4b0c9 100644 --- a/packages/hotkeys-devtools/src/components/Shell.tsx +++ b/packages/hotkeys-devtools/src/components/Shell.tsx @@ -16,7 +16,10 @@ export function Shell() { const selectedRegistration = createMemo(() => { const id = selectedId() if (!id) return null - return state.registrations().find((r) => r.id === id) ?? null + const hotkey = state.registrations().find((r) => r.id === id) + if (hotkey) return hotkey + const sequence = state.sequenceRegistrations().find((r) => r.id === id) + return sequence ?? null }) let dragStartX = 0 diff --git a/packages/hotkeys/src/sequence-manager.ts b/packages/hotkeys/src/sequence-manager.ts index 21f6edd..88bf893 100644 --- a/packages/hotkeys/src/sequence-manager.ts +++ b/packages/hotkeys/src/sequence-manager.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { formatHotkeySequence } from './format' import { detectPlatform } from './constants' import { parseHotkey } from './parse' @@ -61,6 +62,18 @@ function sequenceKey(sequence: HotkeySequence): string { return sequence.join('|') } +/** + * View of a sequence registration for devtools display. + * Excludes internal matching state (currentIndex, lastKeyTime). + */ +export interface SequenceRegistrationView { + id: string + sequence: HotkeySequence + options: SequenceOptions + target: Target + triggerCount: number +} + /** * Internal representation of a sequence registration. */ @@ -73,6 +86,7 @@ interface SequenceRegistration { target: Target currentIndex: number lastKeyTime: number + triggerCount: number } /** @@ -116,9 +130,31 @@ export interface SequenceRegistrationHandle { * unregister() * ``` */ +/** + * Builds a devtools view from an internal registration. + */ +function toRegistrationView( + reg: SequenceRegistration, +): SequenceRegistrationView { + return { + id: reg.id, + sequence: reg.sequence, + options: reg.options, + target: reg.target, + triggerCount: reg.triggerCount, + } +} + export class SequenceManager { static #instance: SequenceManager | null = null + /** + * The TanStack Store containing sequence registration views for devtools. + * Subscribe to this to observe registration changes. + */ + readonly registrations: Store> = + new Store(new Map()) + #registrations: Map = new Map() #targetListeners: Map< Target, @@ -221,9 +257,13 @@ export class SequenceManager { target, currentIndex: 0, lastKeyTime: 0, + triggerCount: 0, } this.#registrations.set(id, registration) + this.registrations.setState((prev) => + new Map(prev).set(id, toRegistrationView(registration)), + ) // Track registration for this target if (!this.#targetRegistrations.has(target)) { @@ -256,6 +296,9 @@ export class SequenceManager { const reg = manager.#registrations.get(id) if (reg) { reg.options = { ...reg.options, ...newOptions } + manager.registrations.setState((prev) => + new Map(prev).set(id, toRegistrationView(reg)), + ) } }, unregister: () => { @@ -278,6 +321,11 @@ export class SequenceManager { const target = registration.target this.#registrations.delete(id) + this.registrations.setState((prev) => { + const next = new Map(prev) + next.delete(id) + return next + }) // Remove from target registrations tracking const targetRegs = this.#targetRegistrations.get(target) @@ -488,6 +536,54 @@ export class SequenceManager { } } + /** + * Triggers a sequence's callback programmatically from devtools. + * Creates a synthetic KeyboardEvent from the last key in the sequence. + * + * @param id - The registration ID to trigger + * @returns True if the registration was found and triggered + */ + triggerSequence(id: string): boolean { + const registration = this.#registrations.get(id) + if (!registration) { + return false + } + + const lastParsed = + registration.parsedSequence[registration.parsedSequence.length - 1] + if (!lastParsed) { + return false + } + + const syntheticEvent = new KeyboardEvent( + registration.options.eventType ?? 'keydown', + { + key: lastParsed.key, + ctrlKey: lastParsed.ctrl, + shiftKey: lastParsed.shift, + altKey: lastParsed.alt, + metaKey: lastParsed.meta, + bubbles: true, + cancelable: true, + }, + ) + + registration.triggerCount++ + + this.registrations.setState((prev) => + new Map(prev).set(id, toRegistrationView(registration)), + ) + + const context: HotkeyCallbackContext = { + hotkey: registration.sequence.join(' ') as Hotkey, + parsedHotkey: lastParsed, + } + + registration.callback(syntheticEvent, context) + + return true + } + /** * Gets the number of registered sequences. */ @@ -503,6 +599,7 @@ export class SequenceManager { this.#removeListenersForTarget(target) } this.#registrations.clear() + this.registrations.setState(() => new Map()) } } From c284a3a9217c50fa2d9e29e14dc80da8ad64859c Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Feb 2026 15:04:26 -0600 Subject: [PATCH 7/9] update changeset --- .changeset/red-pens-taste.md | 10 ++++++--- .../react-hotkeys/src/useHotkeySequence.ts | 6 +---- .../solid-hotkeys/src/createHotkeySequence.ts | 22 ++++++++----------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/.changeset/red-pens-taste.md b/.changeset/red-pens-taste.md index 6bfd6ca..22ef5d1 100644 --- a/.changeset/red-pens-taste.md +++ b/.changeset/red-pens-taste.md @@ -1,6 +1,10 @@ --- -'@tanstack/react-hotkeys': patch -'@tanstack/hotkeys': patch +'@tanstack/hotkeys': minor +'@tanstack/react-hotkeys': minor +'@tanstack/solid-hotkeys': minor +'@tanstack/hotkeys-devtools': minor +'@tanstack/solid-hotkeys-devtools': minor +'@tanstack/react-hotkeys-devtools': minor --- -Support for the ignoreInputs flag in useHotkeySequence +feat: overhaul sequence-manager and hooks to be in feature parity with hotkey-manager. diff --git a/packages/react-hotkeys/src/useHotkeySequence.ts b/packages/react-hotkeys/src/useHotkeySequence.ts index 6e5dc62..b8d52e8 100644 --- a/packages/react-hotkeys/src/useHotkeySequence.ts +++ b/packages/react-hotkeys/src/useHotkeySequence.ts @@ -94,9 +94,6 @@ export function useHotkeySequence( const { target: _target, ...optionsWithoutTarget } = mergedOptions useEffect(() => { - if (!(mergedOptions.enabled ?? true)) { - return - } if (sequence.length === 0) { return } @@ -136,7 +133,6 @@ export function useHotkeySequence( { ...optionsRef.current, target: resolvedTarget, - enabled: true, }, ) } @@ -152,7 +148,7 @@ export function useHotkeySequence( registrationRef.current = null } } - }, [hotkeySequenceString, options.enabled, mergedOptions.enabled, sequence]) + }, [hotkeySequenceString, mergedOptions.enabled, sequence]) // Sync callback and options on EVERY render (outside useEffect) if (registrationRef.current?.isActive) { diff --git a/packages/solid-hotkeys/src/createHotkeySequence.ts b/packages/solid-hotkeys/src/createHotkeySequence.ts index e884e8f..fd1eaf7 100644 --- a/packages/solid-hotkeys/src/createHotkeySequence.ts +++ b/packages/solid-hotkeys/src/createHotkeySequence.ts @@ -10,10 +10,13 @@ import type { export interface CreateHotkeySequenceOptions extends Omit< SequenceOptions, - 'enabled' + 'target' > { - /** Whether the sequence is enabled. Defaults to true. */ - enabled?: boolean + /** + * The DOM element to attach the event listener to. + * Can be a direct DOM element, an accessor, or null. Defaults to document. + */ + target?: HTMLElement | Document | Window | null } /** @@ -71,13 +74,7 @@ export function createHotkeySequence( ...resolvedOptions, } as CreateHotkeySequenceOptions - const { - enabled = true, - target: _target, - ...optionsWithoutTarget - } = mergedOptions - - if (!enabled || resolvedSequence.length === 0) { + if (resolvedSequence.length === 0) { return } @@ -102,15 +99,14 @@ export function createHotkeySequence( // Register the sequence registration = manager.register(resolvedSequence, callback, { - ...optionsWithoutTarget, + ...mergedOptions, target: resolvedTarget, - enabled: true, }) // Sync callback and options on every effect run if (registration.isActive) { registration.callback = callback - registration.setOptions(optionsWithoutTarget) + registration.setOptions(mergedOptions) } // Cleanup on disposal From 539f6f692c0dca9f01261f8144ef6fb7abb307bc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:05:25 +0000 Subject: [PATCH 8/9] ci: apply automated fixes --- .../solid/reference/functions/createHotkeySequence.md | 2 +- .../interfaces/CreateHotkeySequenceOptions.md | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/framework/solid/reference/functions/createHotkeySequence.md b/docs/framework/solid/reference/functions/createHotkeySequence.md index 7fb9bb3..8d72c6e 100644 --- a/docs/framework/solid/reference/functions/createHotkeySequence.md +++ b/docs/framework/solid/reference/functions/createHotkeySequence.md @@ -12,7 +12,7 @@ function createHotkeySequence( options): void; ``` -Defined in: [createHotkeySequence.ts:51](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L51) +Defined in: [createHotkeySequence.ts:54](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L54) SolidJS primitive for registering a keyboard shortcut sequence (Vim-style). diff --git a/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md index f447e90..d3a3913 100644 --- a/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md @@ -9,16 +9,17 @@ Defined in: [createHotkeySequence.ts:11](https://github.com/TanStack/hotkeys/blo ## Extends -- `Omit`\<`SequenceOptions`, `"enabled"`\> +- `Omit`\<`SequenceOptions`, `"target"`\> ## Properties -### enabled? +### target? ```ts -optional enabled: boolean; +optional target: HTMLElement | Document | Window | null; ``` -Defined in: [createHotkeySequence.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L16) +Defined in: [createHotkeySequence.ts:19](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L19) -Whether the sequence is enabled. Defaults to true. +The DOM element to attach the event listener to. +Can be a direct DOM element, an accessor, or null. Defaults to document. From 430c4f438510a9c2cf9dc626d7799fe47604f128 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Feb 2026 15:13:45 -0600 Subject: [PATCH 9/9] fix solid target override --- packages/solid-hotkeys/src/createHotkeySequence.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/solid-hotkeys/src/createHotkeySequence.ts b/packages/solid-hotkeys/src/createHotkeySequence.ts index fd1eaf7..07b3fc0 100644 --- a/packages/solid-hotkeys/src/createHotkeySequence.ts +++ b/packages/solid-hotkeys/src/createHotkeySequence.ts @@ -74,6 +74,9 @@ export function createHotkeySequence( ...resolvedOptions, } as CreateHotkeySequenceOptions + // Extract options without target (target is handled separately) + const { target: _target, ...optionsWithoutTarget } = mergedOptions + if (resolvedSequence.length === 0) { return } @@ -106,7 +109,7 @@ export function createHotkeySequence( // Sync callback and options on every effect run if (registration.isActive) { registration.callback = callback - registration.setOptions(mergedOptions) + registration.setOptions(optionsWithoutTarget) } // Cleanup on disposal