diff --git a/.changeset/tangy-mangos-clap.md b/.changeset/tangy-mangos-clap.md new file mode 100644 index 00000000..c9832260 --- /dev/null +++ b/.changeset/tangy-mangos-clap.md @@ -0,0 +1,5 @@ +--- +'@tanstack/hotkeys': patch +--- + +`formatForDisplay` and `formatWithLabels` now order macOS modifier keys according to Apple's Human Interface Guidelines: Control → Option → Shift → Command. For example, `Mod+Shift+S` on macOS now renders as `⇧ ⌘ S` instead of `⌘ ⇧ S`. Windows and Linux output is unchanged. diff --git a/docs/framework/lit/guides/formatting-display.md b/docs/framework/lit/guides/formatting-display.md index 825cd66d..bb1d5045 100644 --- a/docs/framework/lit/guides/formatting-display.md +++ b/docs/framework/lit/guides/formatting-display.md @@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/lit-hotkeys' // On macOS (symbols separated by spaces): formatForDisplay('Mod+S') // "⌘ S" -formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z" +formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z" formatForDisplay('Control+Alt+D') // "⌃ ⌥ D" // On Windows/Linux: @@ -32,7 +32,7 @@ formatForDisplay('Mod+S', { }) ``` -On macOS, modifier **order** matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). +On macOS, modifier **order** follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). `platform` is used for both normalization and display. If you need to show the same [`ParsedHotkey`](../../../reference/interfaces/ParsedHotkey.md) under several platforms, first serialize with the platform it was parsed with, then format for each display platform: @@ -57,14 +57,14 @@ import { formatWithLabels } from '@tanstack/lit-hotkeys' // On macOS: formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S" -formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z" +formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z" // On Windows/Linux: formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S" formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z" ``` -Modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). +On Windows and Linux, modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). On macOS, modifiers follow Apple's Human Interface Guidelines order. ## Using Formatted Hotkeys in Lit @@ -87,7 +87,7 @@ class ShortcutBadge extends LitElement { Usage: ```html - + ``` ### Menu Items with Hotkeys @@ -180,14 +180,24 @@ class CommandPaletteItem extends LitElement { ## Platform Symbols Reference -On macOS, modifiers are displayed as symbols: +On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards): + +| Modifier | Mac Symbol | +|----------|-----------| +| Control | `⌃` | +| Option | `⌥` | +| Shift | `⇧` | +| Meta (Cmd) | `⌘` | + +On Windows and Linux, modifiers are displayed as text labels: + +| Modifier | Windows Label | Linux Label | +|----------|---------------|-------------| +| Meta | `Win` | `Super` | +| Control | `Ctrl` | `Ctrl` | +| Alt/Option | `Alt` | `Alt` | +| Shift | `Shift` | `Shift` | -| Modifier | Mac Symbol | Windows/Linux Label | -|----------|-----------|-------------------| -| Meta (Cmd) | `⌘` | `Win` / `Super` | -| Control | `⌃` | `Ctrl` | -| Alt/Option | `⌥` | `Alt` | -| Shift | `⇧` | `Shift` | Special keys also have display symbols: diff --git a/docs/framework/preact/guides/formatting-display.md b/docs/framework/preact/guides/formatting-display.md index f5da6630..7e2cfaa3 100644 --- a/docs/framework/preact/guides/formatting-display.md +++ b/docs/framework/preact/guides/formatting-display.md @@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/preact-hotkeys' // On macOS (symbols separated by spaces): formatForDisplay('Mod+S') // "⌘ S" -formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z" +formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z" formatForDisplay('Control+Alt+D') // "⌃ ⌥ D" // On Windows/Linux: @@ -32,7 +32,7 @@ formatForDisplay('Mod+S', { }) ``` -On macOS, modifier order matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). +On macOS, modifier order follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). ## `formatWithLabels` @@ -43,14 +43,14 @@ import { formatWithLabels } from '@tanstack/preact-hotkeys' // On macOS: formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S" -formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z" +formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z" // On Windows/Linux: formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S" formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z" ``` -Modifier order matches canonical normalization from the core package. +On Windows and Linux, modifier order matches canonical normalization from the core package. On macOS, modifiers follow Apple's Human Interface Guidelines order. ## Using Formatted Hotkeys in Preact @@ -65,7 +65,7 @@ function ShortcutBadge({ hotkey }: { hotkey: string }) { // Usage // Renders: ⌘ S (Mac) or Ctrl+S (Windows) - // Renders: ⌘ ⇧ P (Mac) or Ctrl+Shift+P (Windows) + // Renders: ⇧ ⌘ P (Mac) or Ctrl+Shift+P (Windows) ``` ### Menu Items with Hotkeys @@ -125,14 +125,23 @@ function CommandPaletteItem({ command }: { command: Command }) { ## Platform Symbols Reference -On macOS, modifiers are displayed as symbols: +On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards): -| Modifier | Mac Symbol | Windows/Linux Label | -|----------|-----------|-------------------| -| Meta (Cmd) | `⌘` | `Win` / `Super` | -| Control | `⌃` | `Ctrl` | -| Alt/Option | `⌥` | `Alt` | -| Shift | `⇧` | `Shift` | +| Modifier | Mac Symbol | +|----------|-----------| +| Control | `⌃` | +| Option | `⌥` | +| Shift | `⇧` | +| Meta (Cmd) | `⌘` | + +On Windows and Linux, modifiers are displayed as text labels: + +| Modifier | Windows Label | Linux Label | +|----------|---------------|-------------| +| Meta | `Win` | `Super` | +| Control | `Ctrl` | `Ctrl` | +| Alt/Option | `Alt` | `Alt` | +| Shift | `Shift` | `Shift` | Special keys also have display symbols: diff --git a/docs/framework/react/guides/formatting-display.md b/docs/framework/react/guides/formatting-display.md index 1772526d..fdeb1230 100644 --- a/docs/framework/react/guides/formatting-display.md +++ b/docs/framework/react/guides/formatting-display.md @@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/react-hotkeys' // On macOS (symbols separated by spaces): formatForDisplay('Mod+S') // "⌘ S" -formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z" +formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z" formatForDisplay('Control+Alt+D') // "⌃ ⌥ D" // On Windows/Linux: @@ -32,7 +32,7 @@ formatForDisplay('Mod+S', { }) ``` -On macOS, modifier **order** matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). +On macOS, modifier **order** follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`). `platform` is used for both normalization and display. If you need to show the same [`ParsedHotkey`](../../../reference/interfaces/ParsedHotkey.md) under several platforms, first serialize with the platform it was parsed with, then format for each display platform: @@ -57,14 +57,14 @@ import { formatWithLabels } from '@tanstack/react-hotkeys' // On macOS: formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S" -formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z" +formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z" // On Windows/Linux: formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S" formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z" ``` -Modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). +On Windows and Linux, modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). On macOS, modifiers follow Apple's Human Interface Guidelines order. ## Using Formatted Hotkeys in React @@ -79,7 +79,7 @@ function ShortcutBadge({ hotkey }: { hotkey: string }) { // Usage // Renders: ⌘ S (Mac) or Ctrl+S (Windows) - // Renders: ⌘ ⇧ P (Mac) or Ctrl+Shift+P (Windows) + // Renders: ⇧ ⌘ P (Mac) or Ctrl+Shift+P (Windows) ``` ### Menu Items with Hotkeys @@ -139,14 +139,23 @@ function CommandPaletteItem({ command }: { command: Command }) { ## Platform Symbols Reference -On macOS, modifiers are displayed as symbols: +On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards): -| Modifier | Mac Symbol | Windows/Linux Label | -|----------|-----------|-------------------| -| Meta (Cmd) | `⌘` | `Win` / `Super` | -| Control | `⌃` | `Ctrl` | -| Alt/Option | `⌥` | `Alt` | -| Shift | `⇧` | `Shift` | +| Modifier | Mac Symbol | +|----------|-----------| +| Control | `⌃` | +| Option | `⌥` | +| Shift | `⇧` | +| Meta (Cmd) | `⌘` | + +On Windows and Linux, modifiers are displayed as text labels: + +| Modifier | Windows Label | Linux Label | +|----------|---------------|-------------| +| Meta | `Win` | `Super` | +| Control | `Ctrl` | `Ctrl` | +| Alt/Option | `Alt` | `Alt` | +| Shift | `Shift` | `Shift` | Special keys also have display symbols: diff --git a/docs/framework/solid/guides/formatting-display.md b/docs/framework/solid/guides/formatting-display.md index 63f77b6c..cc73cc2d 100644 --- a/docs/framework/solid/guides/formatting-display.md +++ b/docs/framework/solid/guides/formatting-display.md @@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/solid-hotkeys' // On macOS (symbols separated by spaces): formatForDisplay('Mod+S') // "⌘ S" -formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z" +formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z" formatForDisplay('Control+Alt+D') // "⌃ ⌥ D" // On Windows/Linux: @@ -32,7 +32,7 @@ formatForDisplay('Mod+S', { }) ``` -On macOS, modifier order matches canonical normalization (same as `formatWithLabels`), with **spaces** between symbol segments. +On macOS, modifier order follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), with **spaces** between symbol segments. ## `formatWithLabels` @@ -43,7 +43,7 @@ import { formatWithLabels } from '@tanstack/solid-hotkeys' formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S" formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S" -formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z" +formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z" formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z" ``` @@ -107,12 +107,23 @@ function CommandPaletteItem(props: { command: Command }) { ## Platform Symbols Reference -| Modifier | Mac Symbol | Windows/Linux Label | -|----------|-----------|-------------------| -| Meta (Cmd) | `⌘` | `Win` / `Super` | -| Control | `⌃` | `Ctrl` | -| Alt/Option | `⌥` | `Alt` | -| Shift | `⇧` | `Shift` | +On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards): + +| Modifier | Mac Symbol | +|----------|-----------| +| Control | `⌃` | +| Option | `⌥` | +| Shift | `⇧` | +| Meta (Cmd) | `⌘` | + +On Windows and Linux, modifiers are displayed as text labels: + +| Modifier | Windows Label | Linux Label | +|----------|---------------|-------------| +| Meta | `Win` | `Super` | +| Control | `Ctrl` | `Ctrl` | +| Alt/Option | `Alt` | `Alt` | +| Shift | `Shift` | `Shift` | ## Parsing and Normalization diff --git a/docs/reference/functions/formatForDisplay.md b/docs/reference/functions/formatForDisplay.md index 2f6ed013..f9d00d78 100644 --- a/docs/reference/functions/formatForDisplay.md +++ b/docs/reference/functions/formatForDisplay.md @@ -9,11 +9,12 @@ title: formatForDisplay function formatForDisplay(hotkey, options): string; ``` -Defined in: [format.ts:92](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L92) +Defined in: [format.ts:93](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L93) Formats a hotkey for display in a user interface. -On macOS, uses symbols (⌘⇧S) in the same modifier order as [normalizeHotkeyFromParsed](normalizeHotkeyFromParsed.md). +On macOS, uses symbols (⇧⌘S) in modifier order per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards): +Control → Option → Shift → Command. On Windows/Linux, uses text (Ctrl+Shift+S) with `+` separators. The separator can be customized with `separatorToken`. @@ -41,7 +42,7 @@ A formatted string suitable for display ```ts formatForDisplay('Mod+Shift+S', { platform: 'mac' }) -// Returns: '⌘ ⇧ S' (symbols separated by spaces on macOS) +// Returns: '⇧ ⌘ S' (symbols separated by spaces on macOS) formatForDisplay('Mod+Shift+S', { platform: 'windows' }) // Returns: 'Ctrl+Shift+S' diff --git a/packages/hotkeys/src/format.ts b/packages/hotkeys/src/format.ts index 1ba03b90..f7eb7cd2 100644 --- a/packages/hotkeys/src/format.ts +++ b/packages/hotkeys/src/format.ts @@ -8,6 +8,7 @@ import { PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS, detectPlatform, + resolveModifier, } from './constants' import { isModifierKey, @@ -101,7 +102,11 @@ export function formatForDisplay( hotkey as RegisterableHotkey, platform, ) - return normalizedHotkey + + const formattedHotkey = + platform === 'mac' ? orderMacModifiers(normalizedHotkey) : normalizedHotkey + + return formattedHotkey .split('+') .map((segment) => { if (isModifierKey(segment)) { @@ -131,6 +136,27 @@ export function formatForDisplay( .join(separatorToken) } +function orderMacModifiers(hotkey: string) { + const modifiers: Array = [] + const keys: Array = [] + hotkey.split('+').forEach((segment) => { + if (isModifierKey(segment)) { + const modifier = + segment === 'Mod' ? resolveModifier(segment, 'mac') : segment + modifiers.push(modifier) + } else { + keys.push(segment) + } + }) + + modifiers.sort( + (a, b) => + MODIFIER_ORDER.indexOf(a as CanonicalModifier) - + MODIFIER_ORDER.indexOf(b as CanonicalModifier), + ) + return modifiers.concat(keys).join('+') +} + /** * @deprecated Use {@link formatForDisplay} instead with `useSymbols: false` option. */ diff --git a/packages/hotkeys/tests/format.test.ts b/packages/hotkeys/tests/format.test.ts index dc763f5c..bc531772 100644 --- a/packages/hotkeys/tests/format.test.ts +++ b/packages/hotkeys/tests/format.test.ts @@ -98,7 +98,7 @@ describe('formatForDisplay', () => { '⌃ ⇧ A', ) expect(formatForDisplay(hk('Command+Shift+S'), { platform: 'mac' })).toBe( - '⌘ ⇧ S', + '⇧ ⌘ S', ) }) @@ -108,24 +108,24 @@ describe('formatForDisplay', () => { platform: 'mac', separatorToken: '', }), - ).toBe('⌘⇧S') + ).toBe('⇧⌘S') expect( formatForDisplay('Mod+Shift+S', { platform: 'mac', separatorToken: ' + ', }), - ).toBe('⌘ + ⇧ + S') + ).toBe('⇧ + ⌘ + S') expect( formatForDisplay('Mod+Shift+S', { platform: 'mac', separatorToken: null, }), - ).toBe('⌘ ⇧ S') + ).toBe('⇧ ⌘ S') }) it('should resolve Mod to Command symbol', () => { expect(formatForDisplay('Mod+S', { platform: 'mac' })).toBe('⌘ S') - expect(formatForDisplay('Mod+Shift+S', { platform: 'mac' })).toBe('⌘ ⇧ S') + expect(formatForDisplay('Mod+Shift+S', { platform: 'mac' })).toBe('⇧ ⌘ S') }) it('should use symbols for special keys', () => { @@ -195,10 +195,10 @@ describe('formatForDisplay', () => { meta: true, modifiers: ['Shift', 'Meta'], } - expect(formatForDisplay(parsed, { platform: 'mac' })).toBe('⌘ ⇧ S') + expect(formatForDisplay(parsed, { platform: 'mac' })).toBe('⇧ ⌘ S') expect( formatForDisplay(parsed, { platform: 'mac', useSymbols: false }), - ).toBe('Cmd+Shift+S') + ).toBe('Shift+Cmd+S') }) }) @@ -248,16 +248,25 @@ describe('formatForDisplay', () => { it('should handle multiple modifiers in canonical order (Mod first)', () => { expect( formatForDisplay('Mod+Shift+S', { - platform: 'mac', + platform: 'windows', useSymbols: false, }), - ).toBe('Cmd+Shift+S') + ).toBe('Ctrl+Shift+S') + }) + it('when platform is mac, handling multiple modifiers should follow apple human interface guidelines', () => { + // Control → Option → Shift → Command expect( - formatForDisplay('Mod+Shift+S', { - platform: 'windows', + formatForDisplay('Meta+Shift+Alt+Control+A', { + platform: 'mac', useSymbols: false, }), - ).toBe('Ctrl+Shift+S') + ).toBe('Control+Option+Shift+Cmd+A') + expect( + formatForDisplay('Meta+Shift+Alt+Control+A', { + platform: 'mac', + useSymbols: true, + }), + ).toBe('⌃ ⌥ ⇧ ⌘ A') }) }) })