@tanstack/hotkeys 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ //#region src/_server.ts
2
+ const SERVER_AGENT = /^(?:Node\.js\/|Deno\/|Bun\/|Cloudflare-Workers)/;
3
+ /** Whether this runs on a server: no `navigator`, or a server runtime's. */
4
+ function isServerRuntime() {
5
+ return typeof navigator === "undefined" || SERVER_AGENT.test(String(navigator.userAgent));
6
+ }
7
+ let warned = false;
8
+ /**
9
+ * Warns once, in development, when a label's platform is detected during
10
+ * server rendering. The server then renders 'linux' labels, which differ for
11
+ * users on macOS and Windows after hydration.
12
+ */
13
+ function warnServerPlatform() {
14
+ if (process.env.NODE_ENV !== "production" && !warned && isServerRuntime()) {
15
+ warned = true;
16
+ console.warn("[TanStack Hotkeys] A hotkey label was formatted during server rendering without a platform, so it uses 'linux'. Mac and Windows users get a different label after hydration. Read the platform from the request with `detectPlatform(userAgent)`, set it as the `platform` of your hotkeys provider or scope, and format labels with your adapter's formatter, such as `useFormatForDisplay`. Or render the labels only in the browser. See the server rendering section of the Displaying Hotkeys guide.");
17
+ }
18
+ }
19
+
20
+ //#endregion
21
+ export { isServerRuntime, warnServerPlatform };
package/dist/adapter.d.ts CHANGED
@@ -2,8 +2,9 @@ import { Store, createStore } from "./_store.js";
2
2
  import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
3
3
  import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
4
4
  import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
5
- import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
5
+ import { HotkeyPlatform } from "./platform.js";
6
6
  import { definedOptions, withDefaults } from "./_registration.js";
7
+ import { createAppHotkeysStore } from "@tanstack/hotkeys";
7
8
  //#region src/adapter.d.ts
8
9
  /**
9
10
  * A hotkey and its callback, with optional per-binding options. Chords accept
@@ -147,11 +148,20 @@ export interface HotkeysProviderScopeProps {
147
148
  /**
148
149
  * Merges a provider's default options over its parent's. A provider with a
149
150
  * `scope` sets `hotkey.scope`, so hotkeys registered inside it use that scope.
151
+ * A provider's `platform` is inherited, and is the default platform for its
152
+ * hotkeys and recorders; `hotkey.platform` and `hotkeyRecorder.platform` win.
150
153
  */
151
154
  export declare function mergeHotkeysProviderDefaults<T extends {
155
+ platform?: HotkeyPlatform;
152
156
  hotkey?: object;
153
157
  hotkeyRecorder?: object;
154
158
  }>(parent: T | undefined, own: T | undefined, scope: string | undefined): T;
159
+ /**
160
+ * The platform to render with during server rendering and hydration when no
161
+ * provider sets one, for a `useSyncExternalStore` server snapshot. Warns once
162
+ * in development on the server.
163
+ */
164
+ export declare function getServerPlatformSnapshot(): HotkeyPlatform;
155
165
  /**
156
166
  * A key for a provider's scope activation, or `null` when the scope should not
157
167
  * be active. Equal keys mean the same activation, so adapters reactivate only
package/dist/adapter.js CHANGED
@@ -1,10 +1,7 @@
1
- import { detectPlatform } from "./platform.js";
2
- import { isHotkeySequence, normalizeHotkey, parseHotkey } from "./parse.js";
1
+ import { warnServerPlatform } from "./_server.js";
3
2
  import { chordHasKey, defaultHotkeyOptions, definedOptions, withDefaults } from "./_registration.js";
4
3
  import { createStore } from "./_store.js";
5
- import { getHotkeyManager } from "./hotkey-manager.js";
6
- import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
7
- import { getAppHotkeyRegistrationOptions, isAppHotkey, resolveAppHotkey } from "./app-hotkeys.js";
4
+ import { createAppHotkeysStore, detectPlatform, getAppHotkeyRegistrationOptions, getHotkeyManager, isAppHotkey, isHotkeySequence, normalizeHotkey, parseHotkey, resolveAppHotkey } from "@tanstack/hotkeys";
8
5
 
9
6
  //#region src/adapter.ts
10
7
  /** Reconciles adapter registrations while retaining handles for option updates. */
@@ -126,17 +123,31 @@ function subscribePublishedAppHotkeys(onChange) {
126
123
  /**
127
124
  * Merges a provider's default options over its parent's. A provider with a
128
125
  * `scope` sets `hotkey.scope`, so hotkeys registered inside it use that scope.
126
+ * A provider's `platform` is inherited, and is the default platform for its
127
+ * hotkeys and recorders; `hotkey.platform` and `hotkeyRecorder.platform` win.
129
128
  */
130
129
  function mergeHotkeysProviderDefaults(parent, own, scope) {
130
+ const parentPlatform = { platform: parent?.platform };
131
+ const ownPlatform = { platform: own?.platform };
131
132
  return {
133
+ ...definedOptions(parentPlatform, ownPlatform),
132
134
  hotkey: {
133
- ...definedOptions(parent?.hotkey, own?.hotkey),
135
+ ...definedOptions(parentPlatform, parent?.hotkey, ownPlatform, own?.hotkey),
134
136
  ...scope && { scope }
135
137
  },
136
- hotkeyRecorder: definedOptions(parent?.hotkeyRecorder, own?.hotkeyRecorder)
138
+ hotkeyRecorder: definedOptions(parentPlatform, parent?.hotkeyRecorder, ownPlatform, own?.hotkeyRecorder)
137
139
  };
138
140
  }
139
141
  /**
142
+ * The platform to render with during server rendering and hydration when no
143
+ * provider sets one, for a `useSyncExternalStore` server snapshot. Warns once
144
+ * in development on the server.
145
+ */
146
+ function getServerPlatformSnapshot() {
147
+ warnServerPlatform();
148
+ return "linux";
149
+ }
150
+ /**
140
151
  * A key for a provider's scope activation, or `null` when the scope should not
141
152
  * be active. Equal keys mean the same activation, so adapters reactivate only
142
153
  * when the key changes, not when an inline `exclusive` object is recreated.
@@ -163,4 +174,4 @@ function activateHotkeysProviderScope(key) {
163
174
  }
164
175
 
165
176
  //#endregion
166
- export { activateHotkeysProviderScope, createAppHotkeysStore, createHotkeyBindings, createStore, definedOptions, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys, withDefaults };
177
+ export { activateHotkeysProviderScope, createAppHotkeysStore, createHotkeyBindings, createStore, definedOptions, getHotkeysProviderScopeKey, getServerPlatformSnapshot, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys, withDefaults };
package/dist/format.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { MODIFIER_ORDER, normalizeKeyName } from "./constants.js";
2
2
  import { KEY_DISPLAY_SYMBOLS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS } from "./display-labels.js";
3
+ import { warnServerPlatform } from "./_server.js";
3
4
  import { detectPlatform } from "./platform.js";
4
5
  import { assertLogicalKey, codeToLogicalKey, modifierAlias, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
5
6
  import { normalizeHotkey } from "./parse.js";
@@ -23,16 +24,21 @@ function formatForDisplay(hotkey, options = {}) {
23
24
  const parts = formatChordParts(hotkey, options);
24
25
  return options.parts ? parts : parts.join(chordSeparator(options));
25
26
  }
27
+ /** Detects the platform for a label, warning in development on a server. */
28
+ function detectDisplayPlatform() {
29
+ warnServerPlatform();
30
+ return detectPlatform();
31
+ }
26
32
  function modifierSymbolsEnabled(options) {
27
33
  const symbols = options.useSymbols ?? true;
28
34
  return typeof symbols === "boolean" ? symbols : symbols.modifiers ?? true;
29
35
  }
30
36
  function chordSeparator(options) {
31
- const platform = options.platform ?? detectPlatform();
37
+ const platform = options.platform ?? detectDisplayPlatform();
32
38
  return options.separatorToken ?? (platform === "mac" && modifierSymbolsEnabled(options) ? " " : "+");
33
39
  }
34
40
  function formatChordParts(hotkey, options) {
35
- const platform = options.platform ?? detectPlatform();
41
+ const platform = options.platform ?? detectDisplayPlatform();
36
42
  const symbols = options.useSymbols ?? true;
37
43
  const modifierSymbols = modifierSymbolsEnabled(options);
38
44
  const keySymbols = typeof symbols === "boolean" ? symbols : symbols.keys ?? true;
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { CanonicalModifier, EditingKey, FunctionKey, IndividualKey, Key, LetterK
2
2
  import { ConflictBehavior, FourModifierHotkeyChord, Hotkey, HotkeyCallback, HotkeyCallbackContext, HotkeyCallbackContextFor, HotkeyChord, HotkeyChordCallbackContext, HotkeyMeta, HotkeySequence, HotkeySequenceCallbackContext, ParsedHotkeyChord, ParsedModifiers, RawHotkeyChord, RawModifiers, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence, SingleModifierHotkeyChord, ThreeModifierHotkeyChord, TwoModifierHotkeyChord } from "./hotkey.types.js";
3
3
  import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyChordRegistration, HotkeyChordRegistrationView, HotkeyManager, HotkeyOptions, HotkeyOptionsFor, HotkeyRegistration, HotkeyRegistrationHandle, HotkeyRegistrationStatus, HotkeyRegistrationView, HotkeyScopesState, HotkeySequenceOptions, HotkeySequenceRegistration, HotkeySequenceRegistrationView, Target, getHotkeyManager, toHotkeyRegistrationView } from "./hotkey-manager.js";
4
4
  import { AppHotkey, AppHotkeyChordOptions, AppHotkeyDefaultKind, AppHotkeyId, AppHotkeyInput, AppHotkeyListItem, AppHotkeyOptions, AppHotkeySequenceOptions, AppHotkeyStatus, AppHotkeys, AppHotkeysState, AppHotkeysStore, AppHotkeysStoreOptions, CustomHotkeys, DefinedAppHotkeys, Register, RegisteredAppHotkeys, defineAppHotkeys, getAppHotkeyRegistrationOptions, isAppHotkey, isCustomHotkey, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey } from "./app-hotkeys.js";
5
+ import { HotkeyPlatform, detectPlatform, resolveModifier } from "./platform.js";
5
6
  import { ALL_KEYS, EDITING_KEYS, FUNCTION_KEYS, LETTER_KEYS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, NAVIGATION_KEYS, NUMBER_KEYS, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, isSingleLetterKey, normalizeKeyName } from "./constants.js";
6
7
  import { KEY_DISPLAY_SYMBOLS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS } from "./display-labels.js";
7
- import { detectPlatform, resolveModifier } from "./platform.js";
8
8
  import { DisplayHotkey, DisplayHotkeyChord, FormatDisplayOptions, formatForDisplay, formatHotkey } from "./format.js";
9
9
  import { hasNonModifierKey, isHotkeySequence, isModifierKey, normalizeHotkey, parseHotkey, parseKeyboardEvent, simplifyHotkey } from "./parse.js";
10
10
  import { ValidatableHotkey, ValidationResult, assertValidHotkey, checkHotkey, validateHotkey } from "./validate.js";
@@ -13,4 +13,5 @@ import { HeldModifierOptions, matchesHeldModifiers } from "./hint.js";
13
13
  import { HotkeyConflict, HotkeyConflictOptions, findHotkeyConflicts } from "./conflicts.js";
14
14
  import { KeyStateTracker, KeyStateTrackerState, getKeyStateTracker } from "./key-state-tracker.js";
15
15
  import { HotkeyChordRecorderOptions, HotkeyChordRecorderValidationContext, HotkeyRecorder, HotkeyRecorderCommitKeys, HotkeyRecorderOptions, HotkeyRecorderState, HotkeyRecorderValidationContext, HotkeySequenceRecorderOptions, HotkeySequenceRecorderValidationContext, RecorderKeyMode, RecorderOptions, RecorderRejection } from "./hotkey-recorder.js";
16
- export { ALL_KEYS, ActivateHotkeyScopeOptions, AppHotkey, AppHotkeyChordOptions, AppHotkeyDefaultKind, AppHotkeyId, AppHotkeyInput, AppHotkeyListItem, AppHotkeyOptions, AppHotkeySequenceOptions, AppHotkeyStatus, AppHotkeys, AppHotkeysState, AppHotkeysStore, AppHotkeysStoreOptions, CanonicalModifier, ConflictBehavior, CreateHotkeyChordHandlerOptions, CustomHotkeys, DEFAULT_SEQUENCE_TIMEOUT, DefinedAppHotkeys, DisplayHotkey, DisplayHotkeyChord, EDITING_KEYS, EditingKey, FUNCTION_KEYS, FormatDisplayOptions, FourModifierHotkeyChord, FunctionKey, HeldModifierOptions, Hotkey, HotkeyCallback, HotkeyCallbackContext, HotkeyCallbackContextFor, HotkeyChord, HotkeyChordCallbackContext, HotkeyChordOptions, HotkeyChordRecorderOptions, HotkeyChordRecorderValidationContext, HotkeyChordRegistration, HotkeyChordRegistrationView, HotkeyConflict, HotkeyConflictOptions, HotkeyManager, HotkeyMeta, HotkeyOptions, HotkeyOptionsFor, HotkeyRecorder, HotkeyRecorderCommitKeys, HotkeyRecorderOptions, HotkeyRecorderState, HotkeyRecorderValidationContext, HotkeyRegistration, HotkeyRegistrationHandle, HotkeyRegistrationStatus, HotkeyRegistrationView, HotkeyScopesState, HotkeySequence, HotkeySequenceCallbackContext, HotkeySequenceOptions, HotkeySequenceRecorderOptions, HotkeySequenceRecorderValidationContext, HotkeySequenceRegistration, HotkeySequenceRegistrationView, IndividualKey, KEY_DISPLAY_SYMBOLS, Key, KeyStateTracker, KeyStateTrackerState, KeyboardEventMatch, LETTER_KEYS, LINUX_MODIFIER_LABELS, LetterKey, LogicalKey, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, Modifier, MultiHotkeyChordHandler, NAVIGATION_KEYS, NUMBER_KEYS, NamedKey, NavigationKey, NonPunctuationKey, NormalizedKeyboardEvent, NumberKey, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, ParsedHotkeyChord, ParsedModifiers, PhysicalKey, PhysicalKeyCode, PunctuationKey, RawHotkeyChord, RawModifiers, RecorderKeyMode, RecorderOptions, RecorderRejection, Register, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence, RegisteredAppHotkeys, SingleModifierHotkeyChord, Target, ThreeModifierHotkeyChord, TwoModifierHotkeyChord, ValidatableHotkey, ValidationResult, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
16
+ import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
17
+ export { ALL_KEYS, ActivateHotkeyScopeOptions, AppHotkey, AppHotkeyChordOptions, AppHotkeyDefaultKind, AppHotkeyId, AppHotkeyInput, AppHotkeyListItem, AppHotkeyOptions, AppHotkeySequenceOptions, AppHotkeyStatus, AppHotkeys, AppHotkeysState, AppHotkeysStore, AppHotkeysStoreOptions, CanonicalModifier, ConflictBehavior, CreateHotkeyChordHandlerOptions, CustomHotkeys, DEFAULT_SEQUENCE_TIMEOUT, DefinedAppHotkeys, DisplayHotkey, DisplayHotkeyChord, EDITING_KEYS, EditingKey, FUNCTION_KEYS, FormatDisplayOptions, FourModifierHotkeyChord, FunctionKey, HeldModifierOptions, Hotkey, HotkeyCallback, HotkeyCallbackContext, HotkeyCallbackContextFor, HotkeyChord, HotkeyChordCallbackContext, HotkeyChordOptions, HotkeyChordRecorderOptions, HotkeyChordRecorderValidationContext, HotkeyChordRegistration, HotkeyChordRegistrationView, HotkeyConflict, HotkeyConflictOptions, HotkeyManager, HotkeyMeta, HotkeyOptions, HotkeyOptionsFor, HotkeyPlatform, HotkeyRecorder, HotkeyRecorderCommitKeys, HotkeyRecorderOptions, HotkeyRecorderState, HotkeyRecorderValidationContext, HotkeyRegistration, HotkeyRegistrationHandle, HotkeyRegistrationStatus, HotkeyRegistrationView, HotkeyScopesState, HotkeySequence, HotkeySequenceCallbackContext, HotkeySequenceOptions, HotkeySequenceRecorderOptions, HotkeySequenceRecorderValidationContext, HotkeySequenceRegistration, HotkeySequenceRegistrationView, IndividualKey, KEY_DISPLAY_SYMBOLS, Key, KeyStateTracker, KeyStateTrackerState, KeyboardEventMatch, LETTER_KEYS, LINUX_MODIFIER_LABELS, LetterKey, LogicalKey, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, Modifier, MultiHotkeyChordHandler, NAVIGATION_KEYS, NUMBER_KEYS, NamedKey, NavigationKey, NonPunctuationKey, NormalizedKeyboardEvent, NumberKey, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, ParsedHotkeyChord, ParsedModifiers, PhysicalKey, PhysicalKeyCode, PunctuationKey, RawHotkeyChord, RawModifiers, RecorderKeyMode, RecorderOptions, RecorderRejection, Register, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence, RegisteredAppHotkeys, SingleModifierHotkeyChord, Target, ThreeModifierHotkeyChord, TwoModifierHotkeyChord, ValidatableHotkey, ValidationResult, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createAppHotkeysStore, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
package/dist/index.js CHANGED
@@ -8,8 +8,9 @@ import { DEFAULT_SEQUENCE_TIMEOUT, areHotkeysEqual, createHotkeyChordHandler, cr
8
8
  import { matchesHeldModifiers } from "./hint.js";
9
9
  import { HotkeyManager, getHotkeyManager, toHotkeyRegistrationView } from "./hotkey-manager.js";
10
10
  import { findHotkeyConflicts } from "./conflicts.js";
11
+ import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
11
12
  import { AppHotkeysStore, defineAppHotkeys, getAppHotkeyRegistrationOptions, isAppHotkey, isCustomHotkey, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey } from "./app-hotkeys.js";
12
13
  import { KeyStateTracker, getKeyStateTracker } from "./key-state-tracker.js";
13
14
  import { HotkeyRecorder } from "./hotkey-recorder.js";
14
15
 
15
- export { ALL_KEYS, AppHotkeysStore, DEFAULT_SEQUENCE_TIMEOUT, EDITING_KEYS, FUNCTION_KEYS, HotkeyManager, HotkeyRecorder, KEY_DISPLAY_SYMBOLS, KeyStateTracker, LETTER_KEYS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, NAVIGATION_KEYS, NUMBER_KEYS, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
16
+ export { ALL_KEYS, AppHotkeysStore, DEFAULT_SEQUENCE_TIMEOUT, EDITING_KEYS, FUNCTION_KEYS, HotkeyManager, HotkeyRecorder, KEY_DISPLAY_SYMBOLS, KeyStateTracker, LETTER_KEYS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, NAVIGATION_KEYS, NUMBER_KEYS, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createAppHotkeysStore, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
package/dist/match.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { HotkeyCallback, HotkeyChord, HotkeyChordCallbackContext, ParsedHotkeyChord, RegisterableHotkey, RegisterableHotkeySequence } from "./hotkey.types.js";
2
+ import { HotkeyPlatform } from "./platform.js";
2
3
  //#region src/match.d.ts
3
4
  export interface KeyboardEventMatch {
4
5
  matched: boolean;
@@ -96,7 +97,7 @@ export declare function createMultiHotkeyChordHandler(handlers: MultiHotkeyChord
96
97
  * Aliases resolve on the supplied platform. A physical code and a logical key
97
98
  * remain distinct even when one keyboard event could match both.
98
99
  */
99
- export declare function areHotkeysEqual(left: RegisterableHotkey, right: RegisterableHotkey, platform?: "mac" | "windows" | "linux"): boolean;
100
+ export declare function areHotkeysEqual(left: RegisterableHotkey, right: RegisterableHotkey, platform?: HotkeyPlatform): boolean;
100
101
  /** Default timeout between sequence steps (in milliseconds). */
101
102
  export declare const DEFAULT_SEQUENCE_TIMEOUT = 1000;
102
103
  /**
@@ -1,21 +1,33 @@
1
1
  import { CanonicalModifier } from "./key.types.js";
2
2
  //#region src/platform.d.ts
3
+ /** A platform for resolving `Mod` and formatting labels. */
4
+ export type HotkeyPlatform = 'mac' | 'windows' | 'linux';
3
5
  /**
4
- * Detects the current platform based on browser navigator properties.
6
+ * Detects a platform from a user agent string, or from the browser's
7
+ * `navigator` when called without one.
5
8
  *
6
9
  * Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
7
- * Control elsewhere) and for platform-specific hotkey formatting.
10
+ * Control elsewhere) and for platform-specific hotkey formatting. During server
11
+ * rendering, pass the request's `User-Agent` header to detect the user's
12
+ * platform instead of the server's.
8
13
  *
14
+ * @param userAgent - A user agent string, such as a request's `User-Agent` header.
15
+ * When omitted or `null` (a missing header), reads `navigator`.
9
16
  * @returns The detected platform: 'mac', 'windows', or 'linux'
10
- * @remarks Defaults to 'linux' in SSR environments where navigator is undefined
17
+ * @remarks Without a user agent, returns 'linux' on a server runtime (no
18
+ * `navigator`, or Node, Deno, Bun, or Cloudflare Workers), where `navigator`
19
+ * describes the server.
11
20
  *
12
21
  * @example
13
22
  * ```ts
14
23
  * const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
15
24
  * const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
25
+ *
26
+ * // On the server
27
+ * detectPlatform(request.headers.get('user-agent'))
16
28
  * ```
17
29
  */
18
- export declare function detectPlatform(): 'mac' | 'windows' | 'linux';
30
+ export declare function detectPlatform(userAgent?: string | null): HotkeyPlatform;
19
31
  /**
20
32
  * Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
21
33
  *
package/dist/platform.js CHANGED
@@ -1,25 +1,42 @@
1
+ import { isServerRuntime } from "./_server.js";
2
+
1
3
  //#region src/platform.ts
2
4
  /**
3
- * Detects the current platform based on browser navigator properties.
5
+ * Detects a platform from a user agent string, or from the browser's
6
+ * `navigator` when called without one.
4
7
  *
5
8
  * Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
6
- * Control elsewhere) and for platform-specific hotkey formatting.
9
+ * Control elsewhere) and for platform-specific hotkey formatting. During server
10
+ * rendering, pass the request's `User-Agent` header to detect the user's
11
+ * platform instead of the server's.
7
12
  *
13
+ * @param userAgent - A user agent string, such as a request's `User-Agent` header.
14
+ * When omitted or `null` (a missing header), reads `navigator`.
8
15
  * @returns The detected platform: 'mac', 'windows', or 'linux'
9
- * @remarks Defaults to 'linux' in SSR environments where navigator is undefined
16
+ * @remarks Without a user agent, returns 'linux' on a server runtime (no
17
+ * `navigator`, or Node, Deno, Bun, or Cloudflare Workers), where `navigator`
18
+ * describes the server.
10
19
  *
11
20
  * @example
12
21
  * ```ts
13
22
  * const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
14
23
  * const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
24
+ *
25
+ * // On the server
26
+ * detectPlatform(request.headers.get('user-agent'))
15
27
  * ```
16
28
  */
17
- function detectPlatform() {
18
- if (typeof navigator === "undefined") return "linux";
19
- const platform = navigator.platform?.toLowerCase() ?? "";
20
- const userAgent = navigator.userAgent?.toLowerCase() ?? "";
21
- if (platform.includes("mac") || userAgent.includes("mac")) return "mac";
22
- if (platform.includes("win") || userAgent.includes("win")) return "windows";
29
+ function detectPlatform(userAgent) {
30
+ let platform = "";
31
+ let agent = userAgent;
32
+ if (agent == null) {
33
+ if (isServerRuntime()) return "linux";
34
+ platform = navigator.platform?.toLowerCase() ?? "";
35
+ agent = navigator.userAgent ?? "";
36
+ }
37
+ agent = agent.toLowerCase();
38
+ if (platform.includes("mac") || agent.includes("mac")) return "mac";
39
+ if (platform.includes("win") || agent.includes("win")) return "windows";
23
40
  return "linux";
24
41
  }
25
42
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/hotkeys",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Type-safe, framework-agnostic keyboard hotkey management for the browser",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@ Use the first mechanism that fits:
50
50
  1. **Lifetime.** A hotkey registered in a component exists while that component is mounted. A dialog's `Escape` belongs in the dialog.
51
51
  2. **`enabled`.** A condition inside a mounted component.
52
52
  3. **Scope.** A group that turns on and off together, such as a dialog, pane, or tab. An `exclusive` scope suspends every other scope, including global hotkeys, while it is active.
53
- 4. **`target`.** Only while focus is inside one element, which must be focusable (`tabindex="0"`).
53
+ 4. **`target`.** Only while focus is on one element or inside it. If nothing there can take focus, give the element `tabindex="0"`.
54
54
 
55
55
  When nesting scopes, stacking dialogs, or keeping some hotkeys working during an exclusive scope, read [scopes](references/scopes.md).
56
56
 
@@ -66,6 +66,7 @@ When nesting scopes, stacking dialogs, or keeping some hotkeys working during an
66
66
  - **Single-character shortcuts need an off switch.** For WCAG 2.1.4, `K`, `?`, and `['G', 'H']` need a user setting passed as `enabled`, or an app hotkey users can remap.
67
67
  - **react-hotkeys-hook names don't carry over.** `useHotkeys` and `HotkeysProvider` share names, not signatures: there's no dependency array, `preventDefault` is on by default, and sequences are arrays. Remove the old library, so two libraries don't handle the same keys.
68
68
  - **Install only the adapter.** A separately installed `@tanstack/hotkeys` at another version is a second copy, and scopes don't apply across copies.
69
+ - **Server-rendered labels need the user's platform.** On a server, `formatForDisplay` renders Linux labels, because the server can't see the user's OS. Read the request's `User-Agent` header, pass `detectPlatform(userAgent)` as the platform of the adapter's provider or scope, and format labels with the adapter's formatter, such as `useFormatForDisplay`. Those pages then depend on the user agent, so keep them out of shared caches or add the platform to the cache key.
69
70
 
70
71
  When writing tests for hotkeys, read [testing](references/testing.md).
71
72