diff --git a/.changeset/add-question-mark-key.md b/.changeset/add-question-mark-key.md new file mode 100644 index 0000000..8fb6989 --- /dev/null +++ b/.changeset/add-question-mark-key.md @@ -0,0 +1,9 @@ +--- +'@tanstack/hotkeys': patch +--- + +Use `event.code` for punctuation key matching to handle Shift-affected keys + +Punctuation keys like `/` produce different characters when Shift is pressed (`?`), causing `event.key` to mismatch. The matcher now falls back to `event.code` (e.g., `Slash`, `Comma`) to reliably identify the physical key, matching the existing approach for letters and digits. + +Shifted punctuation in hotkey strings is also normalized automatically: `Mod+?` becomes `Mod+Shift+/`. diff --git a/docs/reference/functions/convertToModFormat.md b/docs/reference/functions/convertToModFormat.md index 4501ce3..c850cec 100644 --- a/docs/reference/functions/convertToModFormat.md +++ b/docs/reference/functions/convertToModFormat.md @@ -9,7 +9,7 @@ title: convertToModFormat function convertToModFormat(hotkey, platform): Hotkey; ``` -Defined in: [parse.ts:345](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L345) +Defined in: [parse.ts:353](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L353) Converts a hotkey string to use 'Mod' format for portability. diff --git a/docs/reference/functions/createHotkeyHandler.md b/docs/reference/functions/createHotkeyHandler.md index 07142d7..b2b9a1f 100644 --- a/docs/reference/functions/createHotkeyHandler.md +++ b/docs/reference/functions/createHotkeyHandler.md @@ -12,7 +12,7 @@ function createHotkeyHandler( options): (event) => void; ``` -Defined in: [match.ts:122](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L122) +Defined in: [match.ts:137](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L137) Creates a keyboard event handler that calls the callback when the hotkey matches. diff --git a/docs/reference/functions/createMultiHotkeyHandler.md b/docs/reference/functions/createMultiHotkeyHandler.md index 5426f39..e86c3f1 100644 --- a/docs/reference/functions/createMultiHotkeyHandler.md +++ b/docs/reference/functions/createMultiHotkeyHandler.md @@ -9,7 +9,7 @@ title: createMultiHotkeyHandler function createMultiHotkeyHandler(handlers, options): (event) => void; ``` -Defined in: [match.ts:173](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L173) +Defined in: [match.ts:188](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L188) Creates a handler that matches multiple hotkeys. diff --git a/docs/reference/functions/hasNonModifierKey.md b/docs/reference/functions/hasNonModifierKey.md index 6a17ed1..7d5f52a 100644 --- a/docs/reference/functions/hasNonModifierKey.md +++ b/docs/reference/functions/hasNonModifierKey.md @@ -9,7 +9,7 @@ title: hasNonModifierKey function hasNonModifierKey(hotkey, platform): boolean; ``` -Defined in: [parse.ts:314](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L314) +Defined in: [parse.ts:322](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L322) Checks if a hotkey or ParsedHotkey contains at least one non-modifier key. diff --git a/docs/reference/functions/isModifier.md b/docs/reference/functions/isModifier.md index a4eef4f..45651ae 100644 --- a/docs/reference/functions/isModifier.md +++ b/docs/reference/functions/isModifier.md @@ -9,7 +9,7 @@ title: isModifier function isModifier(key): boolean; ``` -Defined in: [parse.ts:186](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L186) +Defined in: [parse.ts:194](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L194) Checks if a string represents a modifier key. diff --git a/docs/reference/functions/isModifierKey.md b/docs/reference/functions/isModifierKey.md index 715c5c3..ba42555 100644 --- a/docs/reference/functions/isModifierKey.md +++ b/docs/reference/functions/isModifierKey.md @@ -9,7 +9,7 @@ title: isModifierKey function isModifierKey(event): boolean; ``` -Defined in: [parse.ts:284](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L284) +Defined in: [parse.ts:292](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L292) Checks if a KeyboardEvent represents a modifier-only key press. diff --git a/docs/reference/functions/keyboardEventToHotkey.md b/docs/reference/functions/keyboardEventToHotkey.md index 72529cc..5531058 100644 --- a/docs/reference/functions/keyboardEventToHotkey.md +++ b/docs/reference/functions/keyboardEventToHotkey.md @@ -9,7 +9,7 @@ title: keyboardEventToHotkey function keyboardEventToHotkey(event): Hotkey; ``` -Defined in: [parse.ts:248](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L248) +Defined in: [parse.ts:256](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L256) Converts a KeyboardEvent directly to a hotkey string. diff --git a/docs/reference/functions/matchesKeyboardEvent.md b/docs/reference/functions/matchesKeyboardEvent.md index 58c4682..1d4e18a 100644 --- a/docs/reference/functions/matchesKeyboardEvent.md +++ b/docs/reference/functions/matchesKeyboardEvent.md @@ -12,13 +12,14 @@ function matchesKeyboardEvent( platform): boolean; ``` -Defined in: [match.ts:32](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L32) +Defined in: [match.ts:37](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L37) Checks if a KeyboardEvent matches a hotkey. Uses the `key` property from KeyboardEvent for matching, with a fallback to `code` -for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters -(e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively. +for letter keys (A-Z), digit keys (0-9), and punctuation keys when `key` produces +unexpected characters (e.g., macOS Option+letter, Shift+number, or Shift+punctuation). +Letter keys are matched case-insensitively. ## Parameters diff --git a/docs/reference/functions/normalizeHotkey.md b/docs/reference/functions/normalizeHotkey.md index 26b04e5..28da7c7 100644 --- a/docs/reference/functions/normalizeHotkey.md +++ b/docs/reference/functions/normalizeHotkey.md @@ -9,7 +9,7 @@ title: normalizeHotkey function normalizeHotkey(hotkey, platform): string; ``` -Defined in: [parse.ts:160](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L160) +Defined in: [parse.ts:168](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L168) Normalizes a hotkey string to its canonical form. diff --git a/docs/reference/functions/normalizeKeyName.md b/docs/reference/functions/normalizeKeyName.md index d561d46..d5fd538 100644 --- a/docs/reference/functions/normalizeKeyName.md +++ b/docs/reference/functions/normalizeKeyName.md @@ -9,7 +9,7 @@ title: normalizeKeyName function normalizeKeyName(key): string; ``` -Defined in: [constants.ts:422](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L422) +Defined in: [constants.ts:476](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L476) Normalizes a key name to its canonical form. diff --git a/docs/reference/functions/parseHotkey.md b/docs/reference/functions/parseHotkey.md index ba041a7..5dd538e 100644 --- a/docs/reference/functions/parseHotkey.md +++ b/docs/reference/functions/parseHotkey.md @@ -9,7 +9,7 @@ title: parseHotkey function parseHotkey(hotkey, platform): ParsedHotkey; ``` -Defined in: [parse.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L30) +Defined in: [parse.ts:31](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L31) Parses a hotkey string into its component parts. diff --git a/docs/reference/functions/parseKeyboardEvent.md b/docs/reference/functions/parseKeyboardEvent.md index 6c9bcb1..77c7f58 100644 --- a/docs/reference/functions/parseKeyboardEvent.md +++ b/docs/reference/functions/parseKeyboardEvent.md @@ -9,7 +9,7 @@ title: parseKeyboardEvent function parseKeyboardEvent(event): ParsedHotkey; ``` -Defined in: [parse.ts:208](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L208) +Defined in: [parse.ts:216](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L216) Parses a KeyboardEvent into a ParsedHotkey object. diff --git a/docs/reference/functions/rawHotkeyToParsedHotkey.md b/docs/reference/functions/rawHotkeyToParsedHotkey.md index f31912e..fb55a3a 100644 --- a/docs/reference/functions/rawHotkeyToParsedHotkey.md +++ b/docs/reference/functions/rawHotkeyToParsedHotkey.md @@ -9,7 +9,7 @@ title: rawHotkeyToParsedHotkey function rawHotkeyToParsedHotkey(raw, platform): ParsedHotkey; ``` -Defined in: [parse.ts:98](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L98) +Defined in: [parse.ts:106](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/parse.ts#L106) Converts a RawHotkey object to a ParsedHotkey. Optional modifier booleans default to false; modifiers array is derived from them. diff --git a/docs/reference/index.md b/docs/reference/index.md index b296021..c031f88 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -60,7 +60,9 @@ title: "@tanstack/hotkeys" - [MODIFIER\_ORDER](variables/MODIFIER_ORDER.md) - [NAVIGATION\_KEYS](variables/NAVIGATION_KEYS.md) - [NUMBER\_KEYS](variables/NUMBER_KEYS.md) +- [PUNCTUATION\_CODE\_TO\_KEY](variables/PUNCTUATION_CODE_TO_KEY.md) - [PUNCTUATION\_KEYS](variables/PUNCTUATION_KEYS.md) +- [SHIFTED\_KEY\_MAP](variables/SHIFTED_KEY_MAP.md) - [STANDARD\_MODIFIER\_LABELS](variables/STANDARD_MODIFIER_LABELS.md) ## Functions diff --git a/docs/reference/interfaces/CreateHotkeyHandlerOptions.md b/docs/reference/interfaces/CreateHotkeyHandlerOptions.md index a7b824d..6632d96 100644 --- a/docs/reference/interfaces/CreateHotkeyHandlerOptions.md +++ b/docs/reference/interfaces/CreateHotkeyHandlerOptions.md @@ -5,7 +5,7 @@ title: CreateHotkeyHandlerOptions # Interface: CreateHotkeyHandlerOptions -Defined in: [match.ts:95](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L95) +Defined in: [match.ts:110](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L110) Options for creating a hotkey handler. @@ -17,7 +17,7 @@ Options for creating a hotkey handler. optional platform: "mac" | "windows" | "linux"; ``` -Defined in: [match.ts:101](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L101) +Defined in: [match.ts:116](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L116) The target platform for resolving 'Mod' @@ -29,7 +29,7 @@ The target platform for resolving 'Mod' optional preventDefault: boolean; ``` -Defined in: [match.ts:97](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L97) +Defined in: [match.ts:112](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L112) Prevent the default browser action when the hotkey matches. Defaults to true @@ -41,6 +41,6 @@ Prevent the default browser action when the hotkey matches. Defaults to true optional stopPropagation: boolean; ``` -Defined in: [match.ts:99](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L99) +Defined in: [match.ts:114](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/match.ts#L114) Stop event propagation when the hotkey matches. Defaults to true diff --git a/docs/reference/interfaces/FormatDisplayOptions.md b/docs/reference/interfaces/FormatDisplayOptions.md index c233eef..d5da75e 100644 --- a/docs/reference/interfaces/FormatDisplayOptions.md +++ b/docs/reference/interfaces/FormatDisplayOptions.md @@ -5,7 +5,7 @@ title: FormatDisplayOptions # Interface: FormatDisplayOptions -Defined in: [hotkey.ts:367](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L367) +Defined in: [hotkey.ts:365](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L365) Options for formatting hotkeys for display. @@ -17,6 +17,6 @@ Options for formatting hotkeys for display. optional platform: "mac" | "windows" | "linux"; ``` -Defined in: [hotkey.ts:369](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L369) +Defined in: [hotkey.ts:367](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L367) The target platform. Defaults to auto-detection. diff --git a/docs/reference/interfaces/HotkeyCallbackContext.md b/docs/reference/interfaces/HotkeyCallbackContext.md index 9790013..08eba9c 100644 --- a/docs/reference/interfaces/HotkeyCallbackContext.md +++ b/docs/reference/interfaces/HotkeyCallbackContext.md @@ -5,7 +5,7 @@ title: HotkeyCallbackContext # Interface: HotkeyCallbackContext -Defined in: [hotkey.ts:387](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L387) +Defined in: [hotkey.ts:385](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L385) Context passed to hotkey callbacks along with the keyboard event. @@ -17,7 +17,7 @@ Context passed to hotkey callbacks along with the keyboard event. hotkey: Hotkey; ``` -Defined in: [hotkey.ts:389](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L389) +Defined in: [hotkey.ts:387](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L387) The original hotkey string that was registered @@ -29,6 +29,6 @@ The original hotkey string that was registered parsedHotkey: ParsedHotkey; ``` -Defined in: [hotkey.ts:391](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L391) +Defined in: [hotkey.ts:389](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L389) The parsed representation of the hotkey diff --git a/docs/reference/interfaces/ParsedHotkey.md b/docs/reference/interfaces/ParsedHotkey.md index a142d43..950a8d1 100644 --- a/docs/reference/interfaces/ParsedHotkey.md +++ b/docs/reference/interfaces/ParsedHotkey.md @@ -5,7 +5,7 @@ title: ParsedHotkey # Interface: ParsedHotkey -Defined in: [hotkey.ts:308](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L308) +Defined in: [hotkey.ts:306](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L306) A parsed representation of a hotkey string. @@ -33,7 +33,7 @@ useHotkey(parsed, handler) // Works even if userInput isn't in Hotkey type alt: boolean; ``` -Defined in: [hotkey.ts:316](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L316) +Defined in: [hotkey.ts:314](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L314) Whether the Alt key is required @@ -45,7 +45,7 @@ Whether the Alt key is required ctrl: boolean; ``` -Defined in: [hotkey.ts:312](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L312) +Defined in: [hotkey.ts:310](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L310) Whether the Control key is required @@ -57,7 +57,7 @@ Whether the Control key is required key: Key | string & object; ``` -Defined in: [hotkey.ts:310](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L310) +Defined in: [hotkey.ts:308](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L308) The non-modifier key (e.g., 'S', 'Escape', 'F1', '/', '['). Can be any string for flexibility. @@ -69,7 +69,7 @@ The non-modifier key (e.g., 'S', 'Escape', 'F1', '/', '['). Can be any string fo meta: boolean; ``` -Defined in: [hotkey.ts:318](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L318) +Defined in: [hotkey.ts:316](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L316) Whether the Meta (Command) key is required @@ -81,7 +81,7 @@ Whether the Meta (Command) key is required modifiers: CanonicalModifier[]; ``` -Defined in: [hotkey.ts:320](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L320) +Defined in: [hotkey.ts:318](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L318) List of canonical modifier names that are required, in canonical order @@ -93,6 +93,6 @@ List of canonical modifier names that are required, in canonical order shift: boolean; ``` -Defined in: [hotkey.ts:314](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L314) +Defined in: [hotkey.ts:312](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L312) Whether the Shift key is required diff --git a/docs/reference/interfaces/RawHotkey.md b/docs/reference/interfaces/RawHotkey.md index a0255a8..3f13baf 100644 --- a/docs/reference/interfaces/RawHotkey.md +++ b/docs/reference/interfaces/RawHotkey.md @@ -5,7 +5,7 @@ title: RawHotkey # Interface: RawHotkey -Defined in: [hotkey.ts:343](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L343) +Defined in: [hotkey.ts:341](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L341) A raw hotkey object for programmatic registration. @@ -35,7 +35,7 @@ useHotkey({ key: 'S', mod: true, shift: true }, handler) // Mod+Shift+S optional alt: boolean; ``` -Defined in: [hotkey.ts:353](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L353) +Defined in: [hotkey.ts:351](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L351) Whether the Alt key is required. Defaults to false. @@ -47,7 +47,7 @@ Whether the Alt key is required. Defaults to false. optional ctrl: boolean; ``` -Defined in: [hotkey.ts:349](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L349) +Defined in: [hotkey.ts:347](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L347) Whether the Control key is required. Defaults to false. @@ -59,7 +59,7 @@ Whether the Control key is required. Defaults to false. key: Key | string & object; ``` -Defined in: [hotkey.ts:345](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L345) +Defined in: [hotkey.ts:343](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L343) The non-modifier key (e.g., 'S', 'Escape', 'F1'). @@ -71,7 +71,7 @@ The non-modifier key (e.g., 'S', 'Escape', 'F1'). optional meta: boolean; ``` -Defined in: [hotkey.ts:355](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L355) +Defined in: [hotkey.ts:353](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L353) Whether the Meta (Command) key is required. Defaults to false. @@ -83,7 +83,7 @@ Whether the Meta (Command) key is required. Defaults to false. optional mod: boolean; ``` -Defined in: [hotkey.ts:347](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L347) +Defined in: [hotkey.ts:345](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L345) Platform-adaptive modifier: Command on macOS, Control on Windows/Linux. Defaults to false. @@ -95,6 +95,6 @@ Platform-adaptive modifier: Command on macOS, Control on Windows/Linux. Defaults optional shift: boolean; ``` -Defined in: [hotkey.ts:351](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L351) +Defined in: [hotkey.ts:349](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L349) Whether the Shift key is required. Defaults to false. diff --git a/docs/reference/interfaces/ValidationResult.md b/docs/reference/interfaces/ValidationResult.md index 32c2345..157402e 100644 --- a/docs/reference/interfaces/ValidationResult.md +++ b/docs/reference/interfaces/ValidationResult.md @@ -5,7 +5,7 @@ title: ValidationResult # Interface: ValidationResult -Defined in: [hotkey.ts:375](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L375) +Defined in: [hotkey.ts:373](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L373) Result of validating a hotkey string. @@ -17,7 +17,7 @@ Result of validating a hotkey string. errors: string[]; ``` -Defined in: [hotkey.ts:381](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L381) +Defined in: [hotkey.ts:379](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L379) Error messages about invalid syntax @@ -29,7 +29,7 @@ Error messages about invalid syntax valid: boolean; ``` -Defined in: [hotkey.ts:377](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L377) +Defined in: [hotkey.ts:375](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L375) Whether the hotkey is valid (can still have warnings) @@ -41,6 +41,6 @@ Whether the hotkey is valid (can still have warnings) warnings: string[]; ``` -Defined in: [hotkey.ts:379](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L379) +Defined in: [hotkey.ts:377](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L377) Warning messages about potential issues diff --git a/docs/reference/type-aliases/HeldKey.md b/docs/reference/type-aliases/HeldKey.md index ada5d42..747d2cc 100644 --- a/docs/reference/type-aliases/HeldKey.md +++ b/docs/reference/type-aliases/HeldKey.md @@ -9,7 +9,7 @@ title: HeldKey type HeldKey = CanonicalModifier | Key; ``` -Defined in: [hotkey.ts:154](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L154) +Defined in: [hotkey.ts:156](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L156) Keys that can be tracked as "held" (pressed down). Includes both modifier keys and regular keys. diff --git a/docs/reference/type-aliases/Hotkey.md b/docs/reference/type-aliases/Hotkey.md index 6a8c5ad..1741250 100644 --- a/docs/reference/type-aliases/Hotkey.md +++ b/docs/reference/type-aliases/Hotkey.md @@ -14,7 +14,7 @@ type Hotkey = | FourModifierHotkey; ``` -Defined in: [hotkey.ts:283](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L283) +Defined in: [hotkey.ts:281](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L281) A type-safe hotkey string. diff --git a/docs/reference/type-aliases/HotkeyCallback.md b/docs/reference/type-aliases/HotkeyCallback.md index 728df70..7c80d97 100644 --- a/docs/reference/type-aliases/HotkeyCallback.md +++ b/docs/reference/type-aliases/HotkeyCallback.md @@ -9,7 +9,7 @@ title: HotkeyCallback type HotkeyCallback = (event, context) => void; ``` -Defined in: [hotkey.ts:408](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L408) +Defined in: [hotkey.ts:406](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L406) Callback function type for hotkey handlers. diff --git a/docs/reference/type-aliases/Key.md b/docs/reference/type-aliases/Key.md index 6ea2748..59b4669 100644 --- a/docs/reference/type-aliases/Key.md +++ b/docs/reference/type-aliases/Key.md @@ -9,6 +9,6 @@ title: Key type Key = NonPunctuationKey | PunctuationKey; ``` -Defined in: [hotkey.ts:148](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L148) +Defined in: [hotkey.ts:150](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L150) All supported non-modifier keys. diff --git a/docs/reference/type-aliases/PunctuationKey.md b/docs/reference/type-aliases/PunctuationKey.md index da6c872..97cda43 100644 --- a/docs/reference/type-aliases/PunctuationKey.md +++ b/docs/reference/type-aliases/PunctuationKey.md @@ -6,7 +6,7 @@ title: PunctuationKey # Type Alias: PunctuationKey ```ts -type PunctuationKey = "/" | "[" | "]" | "\" | "=" | "-" | "," | "." | "`"; +type PunctuationKey = "/" | "[" | "]" | "\" | "=" | "-" | "," | "." | "`" | ";" | "'"; ``` Defined in: [hotkey.ts:117](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L117) diff --git a/docs/reference/type-aliases/RegisterableHotkey.md b/docs/reference/type-aliases/RegisterableHotkey.md index ca996bb..db8b2ff 100644 --- a/docs/reference/type-aliases/RegisterableHotkey.md +++ b/docs/reference/type-aliases/RegisterableHotkey.md @@ -9,7 +9,7 @@ title: RegisterableHotkey type RegisterableHotkey = Hotkey | RawHotkey; ``` -Defined in: [hotkey.ts:362](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L362) +Defined in: [hotkey.ts:360](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/hotkey.ts#L360) A hotkey that can be passed to `HotkeyManager.register()` and `useHotkey()`. Either a type-safe string (`Hotkey`) or a raw object (`RawHotkey`). diff --git a/docs/reference/variables/ALL_KEYS.md b/docs/reference/variables/ALL_KEYS.md index 818f62c..f640407 100644 --- a/docs/reference/variables/ALL_KEYS.md +++ b/docs/reference/variables/ALL_KEYS.md @@ -15,7 +15,7 @@ const ALL_KEYS: Set< | PunctuationKey>; ``` -Defined in: [constants.ts:317](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L317) +Defined in: [constants.ts:371](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L371) Set of all valid non-modifier keys. diff --git a/docs/reference/variables/KEY_DISPLAY_SYMBOLS.md b/docs/reference/variables/KEY_DISPLAY_SYMBOLS.md index 73ac054..5687c4b 100644 --- a/docs/reference/variables/KEY_DISPLAY_SYMBOLS.md +++ b/docs/reference/variables/KEY_DISPLAY_SYMBOLS.md @@ -9,7 +9,7 @@ title: KEY_DISPLAY_SYMBOLS const KEY_DISPLAY_SYMBOLS: Record; ``` -Defined in: [constants.ts:505](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L505) +Defined in: [constants.ts:559](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L559) Special key symbols for display formatting. diff --git a/docs/reference/variables/MAC_MODIFIER_SYMBOLS.md b/docs/reference/variables/MAC_MODIFIER_SYMBOLS.md index 472c857..fb9a1c9 100644 --- a/docs/reference/variables/MAC_MODIFIER_SYMBOLS.md +++ b/docs/reference/variables/MAC_MODIFIER_SYMBOLS.md @@ -9,7 +9,7 @@ title: MAC_MODIFIER_SYMBOLS const MAC_MODIFIER_SYMBOLS: Record; ``` -Defined in: [constants.ts:461](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L461) +Defined in: [constants.ts:515](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L515) Modifier key symbols for macOS display. diff --git a/docs/reference/variables/PUNCTUATION_CODE_TO_KEY.md b/docs/reference/variables/PUNCTUATION_CODE_TO_KEY.md new file mode 100644 index 0000000..80d2874 --- /dev/null +++ b/docs/reference/variables/PUNCTUATION_CODE_TO_KEY.md @@ -0,0 +1,23 @@ +--- +id: PUNCTUATION_CODE_TO_KEY +title: PUNCTUATION_CODE_TO_KEY +--- + +# Variable: PUNCTUATION\_CODE\_TO\_KEY + +```ts +const PUNCTUATION_CODE_TO_KEY: Record; +``` + +Defined in: [constants.ts:320](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L320) + +Maps `KeyboardEvent.code` values to their corresponding unshifted punctuation key. + +Used by the hotkey matcher as a fallback when `event.key` doesn't match +due to Shift changing the produced character (e.g., `Shift+/` produces `?` +in `event.key` but `event.code` remains `Slash`). + +This is analogous to the existing `event.code` fallbacks for letters (`KeyA`→`A`) +and digits (`Digit4`→`4`), extended to cover punctuation keys. + +Based on the US QWERTY layout, which is the standard for `event.code` values. diff --git a/docs/reference/variables/PUNCTUATION_KEYS.md b/docs/reference/variables/PUNCTUATION_KEYS.md index 9087000..ba6d51c 100644 --- a/docs/reference/variables/PUNCTUATION_KEYS.md +++ b/docs/reference/variables/PUNCTUATION_KEYS.md @@ -9,15 +9,20 @@ title: PUNCTUATION_KEYS const PUNCTUATION_KEYS: Set; ``` -Defined in: [constants.ts:291](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L291) +Defined in: [constants.ts:294](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L294) Set of all valid punctuation keys commonly used in keyboard shortcuts. -These are the literal characters as they appear in `KeyboardEvent.key` (layout-dependent, -typically US keyboard layout). Common shortcuts include: +These are the unshifted (base) characters as they appear in `KeyboardEvent.key` +on a US keyboard layout. Common shortcuts include: - `Mod+/` - Toggle comment - `Mod+[` / `Mod+]` - Indent/outdent - `Mod+=` / `Mod+-` - Zoom in/out -Note: Punctuation keys are affected by Shift (Shift+',' → '<' on US layout), -so they're excluded from Shift-based hotkey combinations to avoid layout-dependent behavior. +Shifted variants (e.g., `?` from `Shift+/`) are not listed separately. +Instead, use `Mod+Shift+/` to register the shifted form. The matcher uses +`event.code` to reliably identify the physical key regardless of shift state. + +## See + +[PUNCTUATION\_CODE\_TO\_KEY](PUNCTUATION_CODE_TO_KEY.md) for the event.code → key mapping used in matching diff --git a/docs/reference/variables/SHIFTED_KEY_MAP.md b/docs/reference/variables/SHIFTED_KEY_MAP.md new file mode 100644 index 0000000..2b9f883 --- /dev/null +++ b/docs/reference/variables/SHIFTED_KEY_MAP.md @@ -0,0 +1,20 @@ +--- +id: SHIFTED_KEY_MAP +title: SHIFTED_KEY_MAP +--- + +# Variable: SHIFTED\_KEY\_MAP + +```ts +const SHIFTED_KEY_MAP: Record; +``` + +Defined in: [constants.ts:343](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L343) + +Maps shifted punctuation characters to their base (unshifted) key. + +Used by the parser to normalize shifted punctuation in hotkey strings. +For example, writing `Mod+?` is automatically normalized to `Mod+Shift+/`, +since `?` is just the shifted form of `/` on a US keyboard. + +Based on the US QWERTY layout. diff --git a/docs/reference/variables/STANDARD_MODIFIER_LABELS.md b/docs/reference/variables/STANDARD_MODIFIER_LABELS.md index a6c1303..8b38470 100644 --- a/docs/reference/variables/STANDARD_MODIFIER_LABELS.md +++ b/docs/reference/variables/STANDARD_MODIFIER_LABELS.md @@ -9,7 +9,7 @@ title: STANDARD_MODIFIER_LABELS const STANDARD_MODIFIER_LABELS: Record; ``` -Defined in: [constants.ts:483](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L483) +Defined in: [constants.ts:537](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/constants.ts#L537) Modifier key labels for Windows/Linux display. diff --git a/packages/hotkeys/src/constants.ts b/packages/hotkeys/src/constants.ts index ae6631d..e5ee9c8 100644 --- a/packages/hotkeys/src/constants.ts +++ b/packages/hotkeys/src/constants.ts @@ -279,14 +279,17 @@ export const EDITING_KEYS = new Set([ /** * Set of all valid punctuation keys commonly used in keyboard shortcuts. * - * These are the literal characters as they appear in `KeyboardEvent.key` (layout-dependent, - * typically US keyboard layout). Common shortcuts include: + * These are the unshifted (base) characters as they appear in `KeyboardEvent.key` + * on a US keyboard layout. Common shortcuts include: * - `Mod+/` - Toggle comment * - `Mod+[` / `Mod+]` - Indent/outdent * - `Mod+=` / `Mod+-` - Zoom in/out * - * Note: Punctuation keys are affected by Shift (Shift+',' → '<' on US layout), - * so they're excluded from Shift-based hotkey combinations to avoid layout-dependent behavior. + * Shifted variants (e.g., `?` from `Shift+/`) are not listed separately. + * Instead, use `Mod+Shift+/` to register the shifted form. The matcher uses + * `event.code` to reliably identify the physical key regardless of shift state. + * + * @see {@link PUNCTUATION_CODE_TO_KEY} for the event.code → key mapping used in matching */ export const PUNCTUATION_KEYS = new Set([ '/', @@ -298,8 +301,59 @@ export const PUNCTUATION_KEYS = new Set([ ',', '.', '`', + ';', + "'", ]) +/** + * Maps `KeyboardEvent.code` values to their corresponding unshifted punctuation key. + * + * Used by the hotkey matcher as a fallback when `event.key` doesn't match + * due to Shift changing the produced character (e.g., `Shift+/` produces `?` + * in `event.key` but `event.code` remains `Slash`). + * + * This is analogous to the existing `event.code` fallbacks for letters (`KeyA`→`A`) + * and digits (`Digit4`→`4`), extended to cover punctuation keys. + * + * Based on the US QWERTY layout, which is the standard for `event.code` values. + */ +export const PUNCTUATION_CODE_TO_KEY: Record = { + Slash: '/', + BracketLeft: '[', + BracketRight: ']', + Backslash: '\\', + Equal: '=', + Minus: '-', + Comma: ',', + Period: '.', + Backquote: '`', + Semicolon: ';', + Quote: "'", +} + +/** + * Maps shifted punctuation characters to their base (unshifted) key. + * + * Used by the parser to normalize shifted punctuation in hotkey strings. + * For example, writing `Mod+?` is automatically normalized to `Mod+Shift+/`, + * since `?` is just the shifted form of `/` on a US keyboard. + * + * Based on the US QWERTY layout. + */ +export const SHIFTED_KEY_MAP: Record = { + '?': '/', + '{': '[', + '}': ']', + '|': '\\', + '+': '=', + _: '-', + '<': ',', + '>': '.', + '~': '`', + ':': ';', + '"': "'", +} + /** * Set of all valid non-modifier keys. * diff --git a/packages/hotkeys/src/hotkey.ts b/packages/hotkeys/src/hotkey.ts index 756a9c9..949638d 100644 --- a/packages/hotkeys/src/hotkey.ts +++ b/packages/hotkeys/src/hotkey.ts @@ -124,6 +124,8 @@ export type PunctuationKey = | ',' | '.' | '`' + | ';' + | "'" /** * Keys that don't change their value when Shift is pressed. @@ -160,7 +162,6 @@ export type HeldKey = CanonicalModifier | Key /** * Single modifier + key combinations. * Uses canonical modifiers (4) + Mod (1) = 5 modifiers. - * Shift combinations exclude PunctuationKey to avoid layout-dependent issues. * * The `Mod` modifier is platform-adaptive: * - **macOS**: Resolves to `Meta` (Command key ⌘) @@ -172,13 +173,12 @@ export type HeldKey = CanonicalModifier | Key type SingleModifierHotkey = | `Control+${Key}` | `Alt+${Key}` - | `Shift+${NonPunctuationKey}` + | `Shift+${Key}` | `Meta+${Key}` | `Mod+${Key}` /** * Two modifier + key combinations. - * Shift combinations exclude Numbers and PunctuationKeys to avoid layout-dependent issues. * * **Platform-adaptive `Mod` combinations:** * - `Mod+Alt` and `Mod+Shift` are included (safe on all platforms) @@ -188,17 +188,16 @@ type SingleModifierHotkey = */ type TwoModifierHotkey = | `Control+Alt+${Key}` - | `Control+Shift+${NonPunctuationKey}` + | `Control+Shift+${Key}` | `Control+Meta+${Key}` - | `Alt+Shift+${NonPunctuationKey}` + | `Alt+Shift+${Key}` | `Alt+Meta+${Key}` - | `Shift+Meta+${NonPunctuationKey}` + | `Shift+Meta+${Key}` | `Mod+Alt+${Key}` - | `Mod+Shift+${NonPunctuationKey}` + | `Mod+Shift+${Key}` /** * Three modifier + key combinations. - * Shift combinations exclude Numbers and PunctuationKeys to avoid layout-dependent issues. * * **Platform-adaptive `Mod` combinations:** * - `Mod+Alt+Shift` is included (safe on all platforms) @@ -207,15 +206,14 @@ type TwoModifierHotkey = * - `Mod+Shift+Meta` duplicates `Meta` on macOS (Mod = Meta) */ type ThreeModifierHotkey = - | `Control+Alt+Shift+${NonPunctuationKey}` + | `Control+Alt+Shift+${Key}` | `Control+Alt+Meta+${Key}` - | `Control+Shift+Meta+${NonPunctuationKey}` - | `Alt+Shift+Meta+${NonPunctuationKey}` - | `Mod+Alt+Shift+${NonPunctuationKey}` + | `Control+Shift+Meta+${Key}` + | `Alt+Shift+Meta+${Key}` + | `Mod+Alt+Shift+${Key}` /** * Four modifier + key combinations. - * Shift combinations exclude Numbers and PunctuationKeys to avoid layout-dependent issues. * * Only the canonical `Control+Alt+Shift+Meta` combination is included. * @@ -226,7 +224,7 @@ type ThreeModifierHotkey = * - `Mod+Control+Alt+Shift` → duplicates `Control` on Windows/Linux * - `Mod+Alt+Shift+Meta` → duplicates `Meta` on macOS */ -type FourModifierHotkey = `Control+Alt+Shift+Meta+${NonPunctuationKey}` +type FourModifierHotkey = `Control+Alt+Shift+Meta+${Key}` /** * A type-safe hotkey string. diff --git a/packages/hotkeys/src/match.ts b/packages/hotkeys/src/match.ts index cfc7972..3536f1a 100644 --- a/packages/hotkeys/src/match.ts +++ b/packages/hotkeys/src/match.ts @@ -1,4 +1,8 @@ -import { detectPlatform, normalizeKeyName } from './constants' +import { + PUNCTUATION_CODE_TO_KEY, + detectPlatform, + normalizeKeyName, +} from './constants' import { parseHotkey } from './parse' import type { Hotkey, @@ -11,8 +15,9 @@ import type { * Checks if a KeyboardEvent matches a hotkey. * * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code` - * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters - * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively. + * for letter keys (A-Z), digit keys (0-9), and punctuation keys when `key` produces + * unexpected characters (e.g., macOS Option+letter, Shift+number, or Shift+punctuation). + * Letter keys are matched case-insensitively. * * @param event - The KeyboardEvent to check * @param hotkey - The hotkey string or ParsedHotkey to match against @@ -82,6 +87,16 @@ export function matchesKeyboardEvent( } } + // Fallback to event.code for punctuation keys when event.key doesn't match + // This handles Shift-affected keys like Shift+/ producing '?' in event.key + // while event.code remains 'Slash' + if (event.code) { + const baseKey = PUNCTUATION_CODE_TO_KEY[event.code] + if (baseKey !== undefined && baseKey === hotkeyKey) { + return true + } + } + return false } diff --git a/packages/hotkeys/src/parse.ts b/packages/hotkeys/src/parse.ts index 287d3f9..487767b 100644 --- a/packages/hotkeys/src/parse.ts +++ b/packages/hotkeys/src/parse.ts @@ -1,6 +1,7 @@ import { MODIFIER_ALIASES, MODIFIER_ORDER, + SHIFTED_KEY_MAP, detectPlatform, normalizeKeyName, resolveModifier, @@ -64,6 +65,13 @@ export function parseHotkey( key = normalizeKeyName(parts[parts.length - 1]!.trim()) } + // Normalize shifted punctuation: e.g., '?' → '/' with Shift added + const baseKey = SHIFTED_KEY_MAP[key] + if (baseKey !== undefined) { + key = baseKey + modifiers.add('Shift') + } + return { key, ctrl: modifiers.has('Control'), diff --git a/packages/hotkeys/tests/match.test.ts b/packages/hotkeys/tests/match.test.ts index a37421a..3b30444 100644 --- a/packages/hotkeys/tests/match.test.ts +++ b/packages/hotkeys/tests/match.test.ts @@ -4,7 +4,7 @@ import { createMultiHotkeyHandler, matchesKeyboardEvent, } from '../src/match' -import { Hotkey } from '../src' +import type { Hotkey } from '../src' /** * Helper to create a mock KeyboardEvent @@ -160,7 +160,7 @@ describe('matchesKeyboardEvent', () => { shift: false, alt: false, meta: true, - modifiers: ['Meta'] as ('Control' | 'Shift' | 'Alt' | 'Meta')[], + modifiers: ['Meta'] as Array<'Control' | 'Shift' | 'Alt' | 'Meta'>, } expect(matchesKeyboardEvent(event, parsed)).toBe(true) }) @@ -334,6 +334,124 @@ describe('matchesKeyboardEvent', () => { } }) }) + + describe('event.code fallback for punctuation keys', () => { + it('should match Shift+/ when event.key is ? (Shift-affected punctuation)', () => { + const event = createKeyboardEvent('?', { + shiftKey: true, + metaKey: true, + code: 'Slash', + }) + expect(matchesKeyboardEvent(event, 'Mod+Shift+/', 'mac')).toBe(true) + }) + + it('should still match / without Shift', () => { + const event = createKeyboardEvent('/', { + metaKey: true, + code: 'Slash', + }) + expect(matchesKeyboardEvent(event, 'Mod+/', 'mac')).toBe(true) + }) + + it('should not match Mod+/ when Shift is pressed (modifier mismatch)', () => { + const event = createKeyboardEvent('?', { + shiftKey: true, + metaKey: true, + code: 'Slash', + }) + expect(matchesKeyboardEvent(event, 'Mod+/', 'mac')).toBe(false) + }) + + it('should match Shift+, when event.key is < (shifted comma)', () => { + const event = createKeyboardEvent('<', { + shiftKey: true, + code: 'Comma', + }) + expect(matchesKeyboardEvent(event, 'Shift+,')).toBe(true) + }) + + it('should match Shift+. when event.key is > (shifted period)', () => { + const event = createKeyboardEvent('>', { + shiftKey: true, + code: 'Period', + }) + expect(matchesKeyboardEvent(event, 'Shift+.')).toBe(true) + }) + + it('should match Shift+= when event.key is + (shifted equal)', () => { + const event = createKeyboardEvent('+', { + shiftKey: true, + code: 'Equal', + }) + expect(matchesKeyboardEvent(event, 'Shift+=')).toBe(true) + }) + + it('should match Shift+` when event.key is ~ (shifted backquote)', () => { + const event = createKeyboardEvent('~', { + shiftKey: true, + code: 'Backquote', + }) + expect(matchesKeyboardEvent(event, 'Shift+`')).toBe(true) + }) + + it('should match Shift+[ when event.key is { (shifted bracket)', () => { + const event = createKeyboardEvent('{', { + shiftKey: true, + code: 'BracketLeft', + }) + expect(matchesKeyboardEvent(event, 'Shift+[')).toBe(true) + }) + + it('should match Shift+] when event.key is } (shifted bracket)', () => { + const event = createKeyboardEvent('}', { + shiftKey: true, + code: 'BracketRight', + }) + expect(matchesKeyboardEvent(event, 'Shift+]')).toBe(true) + }) + + it('should match Shift+\\ when event.key is | (shifted backslash)', () => { + const event = createKeyboardEvent('|', { + shiftKey: true, + code: 'Backslash', + }) + expect(matchesKeyboardEvent(event, 'Shift+\\')).toBe(true) + }) + + it('should match Shift+- when event.key is _ (shifted minus)', () => { + const event = createKeyboardEvent('_', { + shiftKey: true, + code: 'Minus', + }) + expect(matchesKeyboardEvent(event, 'Shift+-')).toBe(true) + }) + + it('should work with multiple modifiers', () => { + const event = createKeyboardEvent('?', { + shiftKey: true, + ctrlKey: true, + code: 'Slash', + }) + expect(matchesKeyboardEvent(event, 'Control+Shift+/')).toBe(true) + }) + + it('should not match when event.code is missing', () => { + const event = createKeyboardEvent('?', { + shiftKey: true, + metaKey: true, + code: undefined, + }) + expect(matchesKeyboardEvent(event, 'Mod+Shift+/', 'mac')).toBe(false) + }) + + it('should not match when event.code maps to a different key', () => { + const event = createKeyboardEvent('?', { + shiftKey: true, + code: 'Slash', + }) + expect(matchesKeyboardEvent(event, 'Shift+,')).toBe(false) + }) + }) }) describe('createHotkeyHandler', () => { diff --git a/packages/hotkeys/tests/parse.test.ts b/packages/hotkeys/tests/parse.test.ts index ac6f1a0..4e04bf9 100644 --- a/packages/hotkeys/tests/parse.test.ts +++ b/packages/hotkeys/tests/parse.test.ts @@ -158,6 +158,66 @@ describe('parseHotkey', () => { }) }) + describe('shifted punctuation normalization', () => { + it('should normalize ? to Shift+/', () => { + const result = parseHotkey('?') + expect(result.key).toBe('/') + expect(result.shift).toBe(true) + expect(result.modifiers).toContain('Shift') + }) + + it('should normalize Mod+? to Mod+Shift+/', () => { + const result = parseHotkey('Mod+?', 'mac') + expect(result.key).toBe('/') + expect(result.shift).toBe(true) + expect(result.meta).toBe(true) + expect(result.modifiers).toEqual(['Shift', 'Meta']) + }) + + it('should normalize { to Shift+[', () => { + const result = parseHotkey('{') + expect(result.key).toBe('[') + expect(result.shift).toBe(true) + }) + + it('should normalize } to Shift+]', () => { + const result = parseHotkey('}') + expect(result.key).toBe(']') + expect(result.shift).toBe(true) + }) + + it('should normalize | to Shift+\\', () => { + const result = parseHotkey('|') + expect(result.key).toBe('\\') + expect(result.shift).toBe(true) + }) + + it('should normalize ~ to Shift+`', () => { + const result = parseHotkey('~') + expect(result.key).toBe('`') + expect(result.shift).toBe(true) + }) + + it('should normalize < to Shift+,', () => { + const result = parseHotkey('<') + expect(result.key).toBe(',') + expect(result.shift).toBe(true) + }) + + it('should normalize > to Shift+.', () => { + const result = parseHotkey('>') + expect(result.key).toBe('.') + expect(result.shift).toBe(true) + }) + + it('should not duplicate Shift when already present', () => { + const result = parseHotkey('Shift+?') + expect(result.key).toBe('/') + expect(result.shift).toBe(true) + expect(result.modifiers.filter((m) => m === 'Shift')).toHaveLength(1) + }) + }) + describe('case insensitivity', () => { it('should handle lowercase modifiers', () => { const result = parseHotkey('ctrl+shift+a')