feat: add ⌘L shortcut to focus message input with hint
Show a muted "⌘L to focus" hint in the top-right of the message input when unfocused and empty on web. Registers Cmd+L (Mac) / Ctrl+L (non-Mac) bindings for the existing message-input.focus action.
This commit is contained in:
@@ -42,6 +42,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
|||||||
import { Shortcut } from "@/components/ui/shortcut";
|
import { Shortcut } from "@/components/ui/shortcut";
|
||||||
import { useWebElementScrollbar } from "@/components/use-web-scrollbar";
|
import { useWebElementScrollbar } from "@/components/use-web-scrollbar";
|
||||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||||
|
import { formatShortcut } from "@/utils/format-shortcut";
|
||||||
|
import { getShortcutOs } from "@/utils/shortcut-platform";
|
||||||
import type { MessageInputKeyboardActionKind } from "@/keyboard/actions";
|
import type { MessageInputKeyboardActionKind } from "@/keyboard/actions";
|
||||||
import {
|
import {
|
||||||
markScrollInvestigationEvent,
|
markScrollInvestigationEvent,
|
||||||
@@ -233,7 +235,9 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
const voiceMuteToggleKeys = useShortcutKeys("voice-mute-toggle");
|
const voiceMuteToggleKeys = useShortcutKeys("voice-mute-toggle");
|
||||||
const dictationToggleKeys = useShortcutKeys("dictation-toggle");
|
const dictationToggleKeys = useShortcutKeys("dictation-toggle");
|
||||||
const queueKeys = useShortcutKeys("message-input-queue");
|
const queueKeys = useShortcutKeys("message-input-queue");
|
||||||
|
const focusInputKeys = useShortcutKeys("focus-message-input");
|
||||||
const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT);
|
const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT);
|
||||||
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
const rootRef = useRef<View | null>(null);
|
const rootRef = useRef<View | null>(null);
|
||||||
const inputWrapperRef = useRef<View | null>(null);
|
const inputWrapperRef = useRef<View | null>(null);
|
||||||
const textInputRef = useRef<TextInput | (TextInput & { getNativeRef?: () => unknown }) | null>(
|
const textInputRef = useRef<TextInput | (TextInput & { getNativeRef?: () => unknown }) | null>(
|
||||||
@@ -997,10 +1001,12 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
accessibilityLabel="Message agent..."
|
accessibilityLabel="Message agent..."
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
isInputFocusedRef.current = true;
|
isInputFocusedRef.current = true;
|
||||||
|
setIsInputFocused(true);
|
||||||
onFocusChange?.(true);
|
onFocusChange?.(true);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
isInputFocusedRef.current = false;
|
isInputFocusedRef.current = false;
|
||||||
|
setIsInputFocused(false);
|
||||||
onFocusChange?.(false);
|
onFocusChange?.(false);
|
||||||
}}
|
}}
|
||||||
style={[
|
style={[
|
||||||
@@ -1025,6 +1031,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
autoFocus={isWeb && autoFocus}
|
autoFocus={isWeb && autoFocus}
|
||||||
/>
|
/>
|
||||||
{inputScrollbar}
|
{inputScrollbar}
|
||||||
|
{isWeb && !isInputFocused && !value && focusInputKeys ? (
|
||||||
|
<Text style={styles.focusHintText} pointerEvents="none">
|
||||||
|
{formatShortcut(focusInputKeys[0], getShortcutOs())} to focus
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Button row */}
|
{/* Button row */}
|
||||||
@@ -1270,6 +1281,14 @@ const styles = StyleSheet.create(((theme: any) => ({
|
|||||||
textInputScrollWrapper: {
|
textInputScrollWrapper: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
},
|
},
|
||||||
|
focusHintText: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
|
|||||||
@@ -791,6 +791,32 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
// --- Message input ---
|
// --- Message input ---
|
||||||
|
{
|
||||||
|
id: "message-input-focus-cmd-l-mac",
|
||||||
|
action: "message-input.action",
|
||||||
|
combo: "Cmd+L",
|
||||||
|
when: { mac: true, commandCenter: false },
|
||||||
|
payload: { type: "message-input", kind: "focus" },
|
||||||
|
help: {
|
||||||
|
id: "focus-message-input",
|
||||||
|
section: "agent-input",
|
||||||
|
label: "Focus message input",
|
||||||
|
keys: ["mod", "L"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "message-input-focus-ctrl-l-non-mac",
|
||||||
|
action: "message-input.action",
|
||||||
|
combo: "Ctrl+L",
|
||||||
|
when: { mac: false, commandCenter: false, terminal: false },
|
||||||
|
payload: { type: "message-input", kind: "focus" },
|
||||||
|
help: {
|
||||||
|
id: "focus-message-input",
|
||||||
|
section: "agent-input",
|
||||||
|
label: "Focus message input",
|
||||||
|
keys: ["mod", "L"],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "message-input-voice-toggle-cmd-shift-d-mac",
|
id: "message-input-voice-toggle-cmd-shift-d-mac",
|
||||||
action: "message-input.action",
|
action: "message-input.action",
|
||||||
|
|||||||
Reference in New Issue
Block a user