'use client'; import { useEffect } from 'react'; import type { UseSpeechRecognitionReturn } from '../types'; export interface UsePushToTalkOptions { /** Key to hold. Combine modifiers with `+`, e.g. `'alt'`, `'mod+alt'`. */ key: string; /** Disable the binding without unmounting. */ enabled?: boolean; } const MOD_KEYS = new Set(['shift', 'ctrl', 'alt', 'meta', 'mod']); /** `mod` resolves to ⌘ on Apple platforms, Ctrl elsewhere. */ function isApplePlatform(): boolean { if (typeof navigator === 'undefined') return false; const p = (navigator as { userAgentData?: { platform?: string } }).userAgentData ?.platform ?? navigator.platform; return /mac|iphone|ipad|ipod/i.test(p ?? ''); } function parseChord(chord: string): { mods: Set; main: string | null } { const parts = chord .toLowerCase() .split('+') .map((s) => s.trim()); const modAlias = isApplePlatform() ? 'meta' : 'ctrl'; const mods = new Set(); let main: string | null = null; for (const part of parts) { if (MOD_KEYS.has(part)) { mods.add(part === 'mod' ? modAlias : part); } else if (part) { main = part; } } return { mods, main }; } function matches(e: KeyboardEvent, mods: Set, main: string | null): boolean { if (mods.has('shift') !== e.shiftKey) return false; if (mods.has('ctrl') !== e.ctrlKey) return false; if (mods.has('alt') !== e.altKey) return false; if (mods.has('meta') !== e.metaKey) return false; if (main && e.key.toLowerCase() !== main) return false; return true; } /** * Key-up matcher — modifier state is unreliable on `keyup` (releasing * the modifier itself drops the flag before the main key's keyup), so * we only check the main key here. Pure-modifier chords match when the * released key is one of the chord's modifiers. */ function matchesRelease( e: KeyboardEvent, mods: Set, main: string | null, ): boolean { const key = e.key.toLowerCase(); if (main) return key === main; // Pure-modifier chord: stop when any chord modifier is released. return ( (mods.has('shift') && key === 'shift') || (mods.has('ctrl') && key === 'control') || (mods.has('alt') && key === 'alt') || (mods.has('meta') && key === 'meta') ); } /** * Hold-to-talk wiring. Press → `start()`, release → `stop()`. Ignores * repeats and skips keydown inside `` / `