diff --git a/.changeset/red-pens-taste.md b/.changeset/red-pens-taste.md new file mode 100644 index 0000000..22ef5d1 --- /dev/null +++ b/.changeset/red-pens-taste.md @@ -0,0 +1,10 @@ +--- +'@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 +--- + +feat: overhaul sequence-manager and hooks to be in feature parity with hotkey-manager. 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..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:50](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L50) +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 433f554..d3a3913 100644 --- a/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/solid/reference/interfaces/CreateHotkeySequenceOptions.md @@ -5,20 +5,21 @@ 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 -- `Omit`\<`SequenceOptions`, `"enabled"`\> +- `Omit`\<`SequenceOptions`, `"target"`\> ## Properties -### enabled? +### target? ```ts -optional enabled: boolean; +optional target: HTMLElement | Document | Window | null; ``` -Defined in: [createHotkeySequence.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/solid-hotkeys/src/createHotkeySequence.ts#L15) +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. 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 118cb6c..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.ts:79](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L79) +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.ts:300](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L300) +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.ts:293](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L293) +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. @@ -67,10 +60,10 @@ Gets the number of registered sequences. register( sequence, callback, - options): () => void; + options): SequenceRegistrationHandle; ``` -Defined in: [sequence.ts:118](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L118) +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. @@ -96,31 +89,52 @@ Options for the sequence behavior #### Returns -A function to unregister the sequence +[`SequenceRegistrationHandle`](../interfaces/SequenceRegistrationHandle.md) + +A handle to update or unregister the sequence + +*** + +### resetAll() ```ts -(): void; +resetAll(): void; ``` -##### Returns +Defined in: [sequence-manager.ts:532](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L532) + +Resets all sequence progress. + +#### Returns `void` *** -### resetAll() +### triggerSequence() ```ts -resetAll(): void; +triggerSequence(id): boolean; ``` -Defined in: [sequence.ts:283](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L283) +Defined in: [sequence-manager.ts:546](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L546) -Resets all sequence progress. +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 -`void` +`boolean` + +True if the registration was found and triggered *** @@ -130,7 +144,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-manager.ts:176](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L176) Gets the singleton instance of SequenceManager. @@ -146,7 +160,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-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 035a860..6968e30 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:332](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L332) +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. +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 05b5dbd..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.ts:310](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L310) +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 b296021..32a5c07 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -27,6 +27,8 @@ title: "@tanstack/hotkeys" - [ParsedHotkey](interfaces/ParsedHotkey.md) - [RawHotkey](interfaces/RawHotkey.md) - [SequenceOptions](interfaces/SequenceOptions.md) +- [SequenceRegistrationHandle](interfaces/SequenceRegistrationHandle.md) +- [SequenceRegistrationView](interfaces/SequenceRegistrationView.md) - [ValidationResult](interfaces/ValidationResult.md) ## Type Aliases @@ -74,6 +76,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..72672b6 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: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). ## 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: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 new file mode 100644 index 0000000..b800ac8 --- /dev/null +++ b/docs/reference/interfaces/SequenceRegistrationHandle.md @@ -0,0 +1,85 @@ +--- +id: SequenceRegistrationHandle +title: SequenceRegistrationHandle +--- + +# Interface: SequenceRegistrationHandle + +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. + +## 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:108](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L108) + +*** + +### id + +```ts +readonly id: string; +``` + +Defined in: [sequence-manager.ts:106](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L106) + +*** + +### isActive + +```ts +readonly isActive: boolean; +``` + +Defined in: [sequence-manager.ts:107](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L107) + +*** + +### setOptions() + +```ts +setOptions: (options) => void; +``` + +Defined in: [sequence-manager.ts:109](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L109) + +#### Parameters + +##### options + +`Partial`\<[`SequenceOptions`](SequenceOptions.md)\> + +#### Returns + +`void` + +*** + +### unregister() + +```ts +unregister: () => void; +``` + +Defined in: [sequence-manager.ts:110](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence-manager.ts#L110) + +#### Returns + +`void` 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/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..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.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/sequence.ts#L30) +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/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-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/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-manager.ts b/packages/hotkeys/src/sequence-manager.ts new file mode 100644 index 0000000..88bf893 --- /dev/null +++ b/packages/hotkeys/src/sequence-manager.ts @@ -0,0 +1,693 @@ +import { Store } from '@tanstack/store' +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, + HotkeyCallback, + HotkeyCallbackContext, + 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 Omit { + /** Timeout between keys in milliseconds. Default: 1000 */ + timeout?: number +} + +/** + * A sequence of hotkeys for Vim-style shortcuts. + * + * @example + * ```ts + * const gotoTop: HotkeySequence = ['G', 'G'] // gg + * const deleteLine: HotkeySequence = ['D', 'D'] // dd + * const deleteWord: HotkeySequence = ['D', 'I', 'W'] // diw + * ``` + */ +export type HotkeySequence = Array + +/** + * Default timeout between keys in a sequence (in milliseconds). + */ +const DEFAULT_SEQUENCE_TIMEOUT = 1000 + +let sequenceIdCounter = 0 + +/** + * Generates a unique ID for sequence registrations. + */ +function generateSequenceId(): string { + return `sequence_${++sequenceIdCounter}` +} + +/** + * Returns a canonical string for sequence conflict comparison. + */ +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. + */ +interface SequenceRegistration { + id: string + sequence: HotkeySequence + parsedSequence: Array + callback: HotkeyCallback + options: SequenceOptions + target: Target + currentIndex: number + lastKeyTime: number + triggerCount: 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. + * + * 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 + * ```ts + * const matcher = SequenceManager.getInstance() + * + * // Register 'g g' to go to top + * const unregister = matcher.register(['G', 'G'], (event, context) => { + * scrollToTop() + * }, { timeout: 500 }) + * + * // Later, to unregister: + * 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, + { + keydown: (event: KeyboardEvent) => void + keyup: (event: KeyboardEvent) => void + } + > = new Map() + #targetRegistrations: Map> = new Map() + #platform: 'mac' | 'windows' | 'linux' + + private constructor() { + this.#platform = detectPlatform() + } + + /** + * Gets the singleton instance of SequenceManager. + */ + static getInstance(): SequenceManager { + if (!SequenceManager.#instance) { + SequenceManager.#instance = new SequenceManager() + } + return SequenceManager.#instance + } + + /** + * Resets the singleton instance. Useful for testing. + */ + static resetInstance(): void { + if (SequenceManager.#instance) { + SequenceManager.#instance.destroy() + SequenceManager.#instance = null + } + } + + /** + * Registers a hotkey sequence handler. + * + * @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 handle to update or unregister the sequence + */ + register( + sequence: HotkeySequence, + callback: HotkeyCallback, + options: SequenceOptions = {}, + ): SequenceRegistrationHandle { + if (sequence.length === 0) { + throw new Error('Sequence must contain at least one hotkey') + } + + const id = generateSequenceId() + const platform = options.platform ?? this.#platform + const parsedSequence = sequence.map((hotkey) => + 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 ?? getDefaultIgnoreInputs(firstStep) + + const baseOptions = { + ...defaultHotkeyOptions, + timeout: DEFAULT_SEQUENCE_TIMEOUT, + ...options, + platform, + ignoreInputs: resolvedIgnoreInputs, + } + + const registration: SequenceRegistration = { + id, + sequence, + parsedSequence, + callback, + options: baseOptions, + 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)) { + 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 } + manager.registrations.setState((prev) => + new Map(prev).set(id, toRegistrationView(reg)), + ) + } + }, + 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) + 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) + if (targetRegs) { + targetRegs.delete(id) + if (targetRegs.size === 0) { + this.#removeListenersForTarget(target) + } + } + } + + /** + * Ensures event listeners are attached for a specific target. + */ + #ensureListenersForTarget(target: Target): void { + if (typeof document === 'undefined') { + return // SSR safety + } + + 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 event listeners for a specific target. + */ + #removeListenersForTarget(target: Target): void { + if (typeof document === 'undefined') { + return + } + + 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) + } + + /** + * Creates a keydown handler for a specific target. + */ + #createTargetKeyDownHandler(target: Target): (event: KeyboardEvent) => void { + return (event: KeyboardEvent) => { + this.#processTargetEvent(event, target, 'keydown') + } + } + + /** + * Creates a keyup handler for a specific target. + */ + #createTargetKeyUpHandler(target: Target): (event: KeyboardEvent) => void { + return (event: KeyboardEvent) => { + this.#processTargetEvent(event, target, 'keyup') + } + } + + /** + * Processes keyboard events for a specific target and event type. + */ + #processTargetEvent( + event: KeyboardEvent, + target: Target, + eventType: 'keydown' | 'keyup', + ): void { + const targetRegs = this.#targetRegistrations.get(target) + if (!targetRegs) { + return + } + + const now = Date.now() + + 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 (defaults to true) + if (registration.options.ignoreInputs !== false) { + 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 + if ( + registration.currentIndex > 0 && + now - registration.lastKeyTime > timeout + ) { + registration.currentIndex = 0 + } + + const expectedHotkey = + registration.parsedSequence[registration.currentIndex] + if (!expectedHotkey) { + continue + } + + if ( + matchesKeyboardEvent( + event, + expectedHotkey, + registration.options.platform, + ) + ) { + registration.lastKeyTime = now + registration.currentIndex++ + + if (registration.currentIndex >= registration.parsedSequence.length) { + if (registration.options.preventDefault) { + event.preventDefault() + } + if (registration.options.stopPropagation) { + event.stopPropagation() + } + + const context: HotkeyCallbackContext = { + hotkey: registration.sequence.join(' ') as Hotkey, + parsedHotkey: + registration.parsedSequence[ + registration.parsedSequence.length - 1 + ]!, + } + + registration.callback(event, context) + + registration.currentIndex = 0 + } + } else if (registration.currentIndex > 0) { + const firstHotkey = registration.parsedSequence[0]! + if ( + matchesKeyboardEvent( + event, + firstHotkey, + registration.options.platform, + ) + ) { + registration.currentIndex = 1 + registration.lastKeyTime = now + } else { + 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. + */ + resetAll(): void { + for (const registration of this.#registrations.values()) { + registration.currentIndex = 0 + registration.lastKeyTime = 0 + } + } + + /** + * 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. + */ + getRegistrationCount(): number { + return this.#registrations.size + } + + /** + * Destroys the manager and removes all listeners. + */ + destroy(): void { + for (const target of this.#targetListeners.keys()) { + this.#removeListenersForTarget(target) + } + this.#registrations.clear() + this.registrations.setState(() => new Map()) + } +} + +/** + * Gets the singleton SequenceManager instance. + * Convenience function for accessing the manager. + */ +export function getSequenceManager(): SequenceManager { + return SequenceManager.getInstance() +} + +/** + * 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 + * + * @example + * ```ts + * const matcher = createSequenceMatcher(['G', 'G'], { timeout: 500 }) + * + * document.addEventListener('keydown', (event) => { + * if (matcher.match(event)) { + * console.log('Sequence matched!') + * } + * }) + * ``` + */ +export function createSequenceMatcher( + sequence: HotkeySequence, + options: { timeout?: number; platform?: 'mac' | 'windows' | 'linux' } = {}, +): { + match: (event: KeyboardEvent) => boolean + reset: () => void + getProgress: () => number +} { + const platform = options.platform ?? detectPlatform() + const timeout = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT + const parsedSequence = sequence.map((hotkey) => parseHotkey(hotkey, platform)) + + let currentIndex = 0 + let lastKeyTime = 0 + + return { + match(event: KeyboardEvent): boolean { + const now = Date.now() + + if (currentIndex > 0 && now - lastKeyTime > timeout) { + currentIndex = 0 + } + + const expected = parsedSequence[currentIndex] + if (!expected) { + return false + } + + if (matchesKeyboardEvent(event, expected, platform)) { + lastKeyTime = now + currentIndex++ + + if (currentIndex >= parsedSequence.length) { + currentIndex = 0 + return true + } + } else if (currentIndex > 0) { + if (matchesKeyboardEvent(event, parsedSequence[0]!, platform)) { + currentIndex = 1 + lastKeyTime = now + } else { + currentIndex = 0 + } + } + + return false + }, + + reset(): void { + currentIndex = 0 + lastKeyTime = 0 + }, + + getProgress(): number { + return currentIndex + }, + } +} diff --git a/packages/hotkeys/src/sequence.ts b/packages/hotkeys/src/sequence.ts deleted file mode 100644 index 32a30fb..0000000 --- a/packages/hotkeys/src/sequence.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { detectPlatform } from './constants' -import { parseHotkey } from './parse' -import { matchesKeyboardEvent } from './match' -import type { HotkeyOptions } from './hotkey-manager' -import type { - Hotkey, - HotkeyCallback, - HotkeyCallbackContext, - ParsedHotkey, -} from './hotkey' - -/** - * Options for hotkey sequence matching. - */ -export interface SequenceOptions extends HotkeyOptions { - /** Timeout between keys in milliseconds. Default: 1000 */ - timeout?: number -} - -/** - * A sequence of hotkeys for Vim-style shortcuts. - * - * @example - * ```ts - * const gotoTop: HotkeySequence = ['G', 'G'] // gg - * const deleteLine: HotkeySequence = ['D', 'D'] // dd - * const deleteWord: HotkeySequence = ['D', 'I', 'W'] // diw - * ``` - */ -export type HotkeySequence = Array - -/** - * Default timeout between keys in a sequence (in milliseconds). - */ -const DEFAULT_SEQUENCE_TIMEOUT = 1000 - -let sequenceIdCounter = 0 - -/** - * Generates a unique ID for sequence registrations. - */ -function generateSequenceId(): string { - return `sequence_${++sequenceIdCounter}` -} - -/** - * Internal representation of a sequence registration. - */ -interface SequenceRegistration { - id: string - sequence: HotkeySequence - parsedSequence: Array - callback: HotkeyCallback - options: SequenceOptions - currentIndex: number - lastKeyTime: number -} - -/** - * Manages keyboard sequence matching for Vim-style shortcuts. - * - * 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 - * ```ts - * const matcher = SequenceManager.getInstance() - * - * // Register 'g g' to go to top - * const unregister = matcher.register(['G', 'G'], (event, context) => { - * scrollToTop() - * }, { timeout: 500 }) - * - * // Later, to unregister: - * unregister() - * ``` - */ -export class SequenceManager { - static #instance: SequenceManager | null = null - - #registrations: Map = new Map() - #keydownListener: ((event: KeyboardEvent) => void) | null = null - #platform: 'mac' | 'windows' | 'linux' - - private constructor() { - this.#platform = detectPlatform() - } - - /** - * Gets the singleton instance of SequenceManager. - */ - static getInstance(): SequenceManager { - if (!SequenceManager.#instance) { - SequenceManager.#instance = new SequenceManager() - } - return SequenceManager.#instance - } - - /** - * Resets the singleton instance. Useful for testing. - */ - static resetInstance(): void { - if (SequenceManager.#instance) { - SequenceManager.#instance.destroy() - SequenceManager.#instance = null - } - } - - /** - * Registers a hotkey sequence handler. - * - * @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 - */ - register( - sequence: HotkeySequence, - callback: HotkeyCallback, - options: SequenceOptions = {}, - ): () => void { - if (sequence.length === 0) { - throw new Error('Sequence must contain at least one hotkey') - } - - const id = generateSequenceId() - const platform = options.platform ?? this.#platform - const parsedSequence = sequence.map((hotkey) => - parseHotkey(hotkey, platform), - ) - - const registration: SequenceRegistration = { - id, - sequence, - parsedSequence, - callback, - options: { - timeout: DEFAULT_SEQUENCE_TIMEOUT, - preventDefault: true, - stopPropagation: true, - enabled: true, - ...options, - platform, - }, - currentIndex: 0, - lastKeyTime: 0, - } - - this.#registrations.set(id, registration) - this.#ensureListener() - - return () => { - this.#unregister(id) - } - } - - /** - * Unregisters a sequence by its registration ID. - */ - #unregister(id: string): void { - this.#registrations.delete(id) - - if (this.#registrations.size === 0) { - this.#removeListener() - } - } - - /** - * Ensures the keydown listener is attached. - */ - #ensureListener(): void { - if (typeof document === 'undefined') { - return // SSR safety - } - - if (!this.#keydownListener) { - this.#keydownListener = this.#handleKeyDown.bind(this) - document.addEventListener('keydown', this.#keydownListener) - } - } - - /** - * Removes the keydown listener. - */ - #removeListener(): void { - if (typeof document === 'undefined') { - return - } - - if (this.#keydownListener) { - document.removeEventListener('keydown', this.#keydownListener) - this.#keydownListener = null - } - } - - /** - * Handles keydown events for sequence matching. - */ - #handleKeyDown(event: KeyboardEvent): void { - const now = Date.now() - - for (const registration of this.#registrations.values()) { - if (!registration.options.enabled) { - continue - } - - const timeout = registration.options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT - - // Check if sequence has timed out - if ( - registration.currentIndex > 0 && - now - registration.lastKeyTime > timeout - ) { - // Reset the sequence - registration.currentIndex = 0 - } - - const expectedHotkey = - registration.parsedSequence[registration.currentIndex] - if (!expectedHotkey) { - continue - } - - // Check if current key matches the expected key in sequence - if ( - matchesKeyboardEvent( - event, - expectedHotkey, - registration.options.platform, - ) - ) { - registration.lastKeyTime = now - registration.currentIndex++ - - // Check if sequence is complete - if (registration.currentIndex >= registration.parsedSequence.length) { - // Sequence complete! - if (registration.options.preventDefault) { - event.preventDefault() - } - if (registration.options.stopPropagation) { - event.stopPropagation() - } - - const context: HotkeyCallbackContext = { - hotkey: registration.sequence.join(' ') as Hotkey, - parsedHotkey: - registration.parsedSequence[ - registration.parsedSequence.length - 1 - ]!, - } - - 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( - event, - firstHotkey, - registration.options.platform, - ) - ) { - registration.currentIndex = 1 - registration.lastKeyTime = now - } else { - // Reset the sequence - registration.currentIndex = 0 - } - } - } - } - - /** - * Resets all sequence progress. - */ - resetAll(): void { - for (const registration of this.#registrations.values()) { - registration.currentIndex = 0 - registration.lastKeyTime = 0 - } - } - - /** - * Gets the number of registered sequences. - */ - getRegistrationCount(): number { - return this.#registrations.size - } - - /** - * Destroys the manager and removes all listeners. - */ - destroy(): void { - this.#removeListener() - this.#registrations.clear() - } -} - -/** - * Gets the singleton SequenceManager instance. - * Convenience function for accessing the manager. - */ -export function getSequenceManager(): SequenceManager { - return SequenceManager.getInstance() -} - -/** - * Creates a simple sequence matcher for one-off use. - * - * @param sequence - The sequence of hotkeys to match - * @param options - Options including timeout - * @returns An object with match() and reset() methods - * - * @example - * ```ts - * const matcher = createSequenceMatcher(['G', 'G'], { timeout: 500 }) - * - * document.addEventListener('keydown', (event) => { - * if (matcher.match(event)) { - * console.log('Sequence matched!') - * } - * }) - * ``` - */ -export function createSequenceMatcher( - sequence: HotkeySequence, - options: { timeout?: number; platform?: 'mac' | 'windows' | 'linux' } = {}, -): { - match: (event: KeyboardEvent) => boolean - reset: () => void - getProgress: () => number -} { - const platform = options.platform ?? detectPlatform() - const timeout = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT - const parsedSequence = sequence.map((hotkey) => parseHotkey(hotkey, platform)) - - let currentIndex = 0 - let lastKeyTime = 0 - - return { - match(event: KeyboardEvent): boolean { - const now = Date.now() - - // Check timeout - if (currentIndex > 0 && now - lastKeyTime > timeout) { - currentIndex = 0 - } - - const expected = parsedSequence[currentIndex] - if (!expected) { - return false - } - - if (matchesKeyboardEvent(event, expected, platform)) { - lastKeyTime = now - currentIndex++ - - if (currentIndex >= parsedSequence.length) { - currentIndex = 0 - return true - } - } else if (currentIndex > 0) { - // Check if it matches start of sequence - if (matchesKeyboardEvent(event, parsedSequence[0]!, platform)) { - currentIndex = 1 - lastKeyTime = now - } else { - currentIndex = 0 - } - } - - return false - }, - - reset(): void { - currentIndex = 0 - lastKeyTime = 0 - }, - - getProgress(): number { - return currentIndex - }, - } -} 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-manager.test.ts b/packages/hotkeys/tests/sequence-manager.test.ts new file mode 100644 index 0000000..6cc49e0 --- /dev/null +++ b/packages/hotkeys/tests/sequence-manager.test.ts @@ -0,0 +1,569 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + SequenceManager, + createSequenceMatcher, + type SequenceOptions, +} from '../src/sequence-manager' + +/** + * Helper to create and dispatch a KeyboardEvent + */ +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() + vi.useFakeTimers() + }) + + afterEach(() => { + SequenceManager.resetInstance() + vi.useRealTimers() + }) + + describe('singleton pattern', () => { + it('should return the same instance', () => { + const instance1 = SequenceManager.getInstance() + const instance2 = SequenceManager.getInstance() + expect(instance1).toBe(instance2) + }) + }) + + describe('sequence registration', () => { + it('should register a sequence', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + expect(manager.getRegistrationCount()).toBe(1) + }) + + it('should unregister a sequence', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + const handle = manager.register(['G', 'G'], callback) + expect(manager.getRegistrationCount()).toBe(1) + + 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() + }) + }) + + describe('sequence matching', () => { + it('should trigger callback when sequence is completed', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + dispatchKey('g') + expect(callback).not.toHaveBeenCalled() + + dispatchKey('g') + expect(callback).toHaveBeenCalledTimes(1) + }) + + it('should pass context to callback', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['D', 'D'], callback) + + dispatchKey('d') + dispatchKey('d') + + expect(callback).toHaveBeenCalledWith( + expect.any(KeyboardEvent), + expect.objectContaining({ + hotkey: 'D D', + }), + ) + }) + + it('should reset on wrong key', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + dispatchKey('g') + dispatchKey('x') // Wrong key + dispatchKey('g') // Start over + expect(callback).not.toHaveBeenCalled() + + dispatchKey('g') + expect(callback).toHaveBeenCalledTimes(1) + }) + + it('should restart sequence if first key of sequence is pressed', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback) + + dispatchKey('g') + dispatchKey('g') + expect(callback).toHaveBeenCalledTimes(1) + + // Press g g again + dispatchKey('g') + dispatchKey('g') + expect(callback).toHaveBeenCalledTimes(2) + }) + }) + + describe('timeout', () => { + it('should reset sequence after timeout', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback, { timeout: 500 }) + + dispatchKey('g') + vi.advanceTimersByTime(600) // Exceed timeout + dispatchKey('g') + + expect(callback).not.toHaveBeenCalled() + }) + + it('should complete sequence within timeout', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['G', 'G'], callback, { timeout: 500 }) + + dispatchKey('g') + vi.advanceTimersByTime(400) // Within timeout + dispatchKey('g') + + expect(callback).toHaveBeenCalledTimes(1) + }) + }) + + describe('ignoreInputs option', () => { + 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) + }) + + 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', () => { + it('should match three-key sequences', () => { + const manager = SequenceManager.getInstance() + const callback = vi.fn() + + manager.register(['D', 'I', 'W'], callback) + + dispatchKey('d') + dispatchKey('i') + expect(callback).not.toHaveBeenCalled() + + dispatchKey('w') + expect(callback).toHaveBeenCalledTimes(1) + }) + }) +}) + +describe('createSequenceMatcher', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should match sequence', () => { + const matcher = createSequenceMatcher(['G', 'G']) + + const event1 = new KeyboardEvent('keydown', { key: 'g' }) + expect(matcher.match(event1)).toBe(false) + expect(matcher.getProgress()).toBe(1) + + const event2 = new KeyboardEvent('keydown', { key: 'g' }) + expect(matcher.match(event2)).toBe(true) + expect(matcher.getProgress()).toBe(0) // Reset after match + }) + + it('should reset on wrong key', () => { + const matcher = createSequenceMatcher(['G', 'G']) + + matcher.match(new KeyboardEvent('keydown', { key: 'g' })) + expect(matcher.getProgress()).toBe(1) + + matcher.match(new KeyboardEvent('keydown', { key: 'x' })) + expect(matcher.getProgress()).toBe(0) + }) + + it('should reset manually', () => { + const matcher = createSequenceMatcher(['G', 'G']) + + matcher.match(new KeyboardEvent('keydown', { key: 'g' })) + expect(matcher.getProgress()).toBe(1) + + matcher.reset() + expect(matcher.getProgress()).toBe(0) + }) + + it('should respect timeout', () => { + const matcher = createSequenceMatcher(['G', 'G'], { timeout: 500 }) + + matcher.match(new KeyboardEvent('keydown', { key: 'g' })) + vi.advanceTimersByTime(600) + + expect(matcher.match(new KeyboardEvent('keydown', { key: 'g' }))).toBe( + false, + ) + expect(matcher.getProgress()).toBe(1) // Started new sequence + }) +}) diff --git a/packages/hotkeys/tests/sequence.test.ts b/packages/hotkeys/tests/sequence.test.ts deleted file mode 100644 index 98ecf0d..0000000 --- a/packages/hotkeys/tests/sequence.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { SequenceManager, createSequenceMatcher } from '../src/sequence' - -/** - * Helper to create and dispatch a KeyboardEvent - */ -function dispatchKey(key: string): KeyboardEvent { - const event = new KeyboardEvent('keydown', { key, bubbles: true }) - document.dispatchEvent(event) - return event -} - -describe('SequenceManager', () => { - beforeEach(() => { - SequenceManager.resetInstance() - vi.useFakeTimers() - }) - - afterEach(() => { - SequenceManager.resetInstance() - vi.useRealTimers() - }) - - describe('singleton pattern', () => { - it('should return the same instance', () => { - const instance1 = SequenceManager.getInstance() - const instance2 = SequenceManager.getInstance() - expect(instance1).toBe(instance2) - }) - }) - - describe('sequence registration', () => { - it('should register a sequence', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback) - - expect(manager.getRegistrationCount()).toBe(1) - }) - - it('should unregister a sequence', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - const unregister = manager.register(['G', 'G'], callback) - expect(manager.getRegistrationCount()).toBe(1) - - unregister() - expect(manager.getRegistrationCount()).toBe(0) - }) - - it('should throw for empty sequence', () => { - const manager = SequenceManager.getInstance() - expect(() => manager.register([], vi.fn())).toThrow() - }) - }) - - describe('sequence matching', () => { - it('should trigger callback when sequence is completed', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback) - - dispatchKey('g') - expect(callback).not.toHaveBeenCalled() - - dispatchKey('g') - expect(callback).toHaveBeenCalledTimes(1) - }) - - it('should pass context to callback', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['D', 'D'], callback) - - dispatchKey('d') - dispatchKey('d') - - expect(callback).toHaveBeenCalledWith( - expect.any(KeyboardEvent), - expect.objectContaining({ - hotkey: 'D D', - }), - ) - }) - - it('should reset on wrong key', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback) - - dispatchKey('g') - dispatchKey('x') // Wrong key - dispatchKey('g') // Start over - expect(callback).not.toHaveBeenCalled() - - dispatchKey('g') - expect(callback).toHaveBeenCalledTimes(1) - }) - - it('should restart sequence if first key of sequence is pressed', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback) - - dispatchKey('g') - dispatchKey('g') - expect(callback).toHaveBeenCalledTimes(1) - - // Press g g again - dispatchKey('g') - dispatchKey('g') - expect(callback).toHaveBeenCalledTimes(2) - }) - }) - - describe('timeout', () => { - it('should reset sequence after timeout', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback, { timeout: 500 }) - - dispatchKey('g') - vi.advanceTimersByTime(600) // Exceed timeout - dispatchKey('g') - - expect(callback).not.toHaveBeenCalled() - }) - - it('should complete sequence within timeout', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['G', 'G'], callback, { timeout: 500 }) - - dispatchKey('g') - vi.advanceTimersByTime(400) // Within timeout - dispatchKey('g') - - expect(callback).toHaveBeenCalledTimes(1) - }) - }) - - describe('longer sequences', () => { - it('should match three-key sequences', () => { - const manager = SequenceManager.getInstance() - const callback = vi.fn() - - manager.register(['D', 'I', 'W'], callback) - - dispatchKey('d') - dispatchKey('i') - expect(callback).not.toHaveBeenCalled() - - dispatchKey('w') - expect(callback).toHaveBeenCalledTimes(1) - }) - }) -}) - -describe('createSequenceMatcher', () => { - beforeEach(() => { - vi.useFakeTimers() - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('should match sequence', () => { - const matcher = createSequenceMatcher(['G', 'G']) - - const event1 = new KeyboardEvent('keydown', { key: 'g' }) - expect(matcher.match(event1)).toBe(false) - expect(matcher.getProgress()).toBe(1) - - const event2 = new KeyboardEvent('keydown', { key: 'g' }) - expect(matcher.match(event2)).toBe(true) - expect(matcher.getProgress()).toBe(0) // Reset after match - }) - - it('should reset on wrong key', () => { - const matcher = createSequenceMatcher(['G', 'G']) - - matcher.match(new KeyboardEvent('keydown', { key: 'g' })) - expect(matcher.getProgress()).toBe(1) - - matcher.match(new KeyboardEvent('keydown', { key: 'x' })) - expect(matcher.getProgress()).toBe(0) - }) - - it('should reset manually', () => { - const matcher = createSequenceMatcher(['G', 'G']) - - matcher.match(new KeyboardEvent('keydown', { key: 'g' })) - expect(matcher.getProgress()).toBe(1) - - matcher.reset() - expect(matcher.getProgress()).toBe(0) - }) - - it('should respect timeout', () => { - const matcher = createSequenceMatcher(['G', 'G'], { timeout: 500 }) - - matcher.match(new KeyboardEvent('keydown', { key: 'g' })) - vi.advanceTimersByTime(600) - - expect(matcher.match(new KeyboardEvent('keydown', { key: 'g' }))).toBe( - false, - ) - expect(matcher.getProgress()).toBe(1) // Started new sequence - }) -}) diff --git a/packages/react-hotkeys/src/useHotkeySequence.ts b/packages/react-hotkeys/src/useHotkeySequence.ts index 02b13ec..b8d52e8 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,36 +68,101 @@ export function useHotkeySequence( ...options, } as UseHotkeySequenceOptions - const { enabled = true, ...sequenceOptions } = mergedOptions + const manager = getSequenceManager() - // Extract options for stable dependencies - const { timeout, platform } = 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) - // Serialize sequence for dependency comparison - const sequenceKey = sequence.join('|') + // Normalize to hotkey sequence string (join with spaces) + const hotkeySequenceString = formatHotkeySequence(sequence) + + // Extract options without target (target is handled separately) + const { target: _target, ...optionsWithoutTarget } = mergedOptions useEffect(() => { - if (!enabled || sequence.length === 0) { + 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 + + // 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, + }, + ) + } + + // Update tracking refs + prevTargetRef.current = resolvedTarget + 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 + // Cleanup on unmount + return () => { + if (registrationRef.current?.isActive) { + registrationRef.current.unregister() + registrationRef.current = null + } + } + }, [hotkeySequenceString, mergedOptions.enabled, sequence]) - const unregister = manager.register( - sequence, - (event, context) => callbackRef.current(event, context), - registerOptions, - ) + // 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) + } +} - return unregister - }, [enabled, sequence, sequenceKey, timeout, platform]) +/** + * 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..07b3fc0 100644 --- a/packages/solid-hotkeys/src/createHotkeySequence.ts +++ b/packages/solid-hotkeys/src/createHotkeySequence.ts @@ -5,14 +5,18 @@ import type { HotkeyCallback, HotkeySequence, SequenceOptions, + SequenceRegistrationHandle, } from '@tanstack/hotkeys' 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 } /** @@ -55,6 +59,9 @@ export function createHotkeySequence( | (() => CreateHotkeySequenceOptions) = {}, ): void { const defaultOptions = useDefaultHotkeysOptions() + const manager = getSequenceManager() + + let registration: SequenceRegistrationHandle | null = null createEffect(() => { // Resolve reactive values @@ -67,27 +74,50 @@ export function createHotkeySequence( ...resolvedOptions, } as CreateHotkeySequenceOptions - const { enabled = true, ...sequenceOptions } = mergedOptions + // Extract options without target (target is handled separately) + const { target: _target, ...optionsWithoutTarget } = mergedOptions - if (!enabled || resolvedSequence.length === 0) { + if (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, { + ...mergedOptions, + target: resolvedTarget, + }) + + // 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: