@@ -47,6 +47,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useWebElementScrollbar } from "@/components/use-web-scrollbar";
|
||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||
import { useIosHardwareKeyboardSubmit } from "@/hooks/use-ios-hardware-keyboard-submit";
|
||||
import { formatShortcut } from "@/utils/format-shortcut";
|
||||
import { getShortcutOs } from "@/utils/shortcut-platform";
|
||||
import type { MessageInputKeyboardActionKind } from "@/keyboard/actions";
|
||||
@@ -1598,6 +1599,10 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
defaultSendBehavior,
|
||||
isAgentRunning,
|
||||
});
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: isInputFocused && !isSendButtonDisabled,
|
||||
onSubmit: handleDefaultSendAction,
|
||||
});
|
||||
const submitAccessibilityLabel = resolveSubmitAccessibilityLabel({
|
||||
submitButtonAccessibilityLabel,
|
||||
canPressLoadingButton,
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useIosHardwareKeyboardSubmit } from "./use-ios-hardware-keyboard-submit";
|
||||
import {
|
||||
emitHardwareKeyboardSubmitForTest,
|
||||
resetHardwareKeyboardSubmitForTest,
|
||||
getHardwareKeyboardSubmitEnabledForTest,
|
||||
setHardwareKeyboardSubmitEnabled,
|
||||
} from "@/native/ios-hardware-keyboard-submit";
|
||||
|
||||
describe("useIosHardwareKeyboardSubmit", () => {
|
||||
beforeEach(() => {
|
||||
resetHardwareKeyboardSubmitForTest();
|
||||
});
|
||||
|
||||
it("submits when the focused composer receives a hardware keyboard submit event", () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
renderHook(() =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: true,
|
||||
onSubmit,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
emitHardwareKeyboardSubmitForTest();
|
||||
});
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not submit when the composer is blurred", () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
renderHook(() =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: false,
|
||||
onSubmit,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
emitHardwareKeyboardSubmitForTest();
|
||||
});
|
||||
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
expect(getHardwareKeyboardSubmitEnabledForTest()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not submit while the default send action is disabled", () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
renderHook(() =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: false,
|
||||
onSubmit,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
emitHardwareKeyboardSubmitForTest();
|
||||
});
|
||||
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not disable native hardware submit when mounted disabled", () => {
|
||||
setHardwareKeyboardSubmitEnabled(true);
|
||||
|
||||
renderHook(() =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: false,
|
||||
onSubmit: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(getHardwareKeyboardSubmitEnabledForTest()).toBe(true);
|
||||
});
|
||||
|
||||
it("disables native hardware submit when focus moves away", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const { rerender } = renderHook(
|
||||
({ isEnabled }) =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled,
|
||||
onSubmit,
|
||||
}),
|
||||
{ initialProps: { isEnabled: true } },
|
||||
);
|
||||
|
||||
expect(getHardwareKeyboardSubmitEnabledForTest()).toBe(true);
|
||||
|
||||
rerender({ isEnabled: false });
|
||||
|
||||
expect(getHardwareKeyboardSubmitEnabledForTest()).toBe(false);
|
||||
});
|
||||
|
||||
it("unsubscribes on unmount", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const { unmount } = renderHook(() =>
|
||||
useIosHardwareKeyboardSubmit({
|
||||
isEnabled: true,
|
||||
onSubmit,
|
||||
}),
|
||||
);
|
||||
|
||||
unmount();
|
||||
|
||||
act(() => {
|
||||
emitHardwareKeyboardSubmitForTest();
|
||||
});
|
||||
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
addHardwareKeyboardSubmitListener,
|
||||
setHardwareKeyboardSubmitEnabled,
|
||||
} from "@/native/ios-hardware-keyboard-submit";
|
||||
|
||||
interface UseIosHardwareKeyboardSubmitInput {
|
||||
isEnabled: boolean;
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export function useIosHardwareKeyboardSubmit(input: UseIosHardwareKeyboardSubmitInput) {
|
||||
const onSubmitRef = useRef(input.onSubmit);
|
||||
|
||||
useEffect(() => {
|
||||
onSubmitRef.current = input.onSubmit;
|
||||
}, [input.onSubmit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!input.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscription = addHardwareKeyboardSubmitListener(() => {
|
||||
onSubmitRef.current();
|
||||
});
|
||||
setHardwareKeyboardSubmitEnabled(true);
|
||||
|
||||
return () => {
|
||||
setHardwareKeyboardSubmitEnabled(false);
|
||||
subscription.remove();
|
||||
};
|
||||
}, [input.isEnabled]);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { requireNativeModule, type EventSubscription } from "expo-modules-core";
|
||||
|
||||
type HardwareKeyboardSubmitHandler = () => void;
|
||||
|
||||
interface PaseoHardwareKeyboardModule {
|
||||
setHardwareKeyboardSubmitEnabled(enabled: boolean): void;
|
||||
addListener(
|
||||
eventName: "onHardwareKeyboardSubmit",
|
||||
handler: HardwareKeyboardSubmitHandler,
|
||||
): EventSubscription;
|
||||
}
|
||||
|
||||
const module = requireNativeModule<PaseoHardwareKeyboardModule>("PaseoHardwareKeyboard");
|
||||
|
||||
export function setHardwareKeyboardSubmitEnabled(enabled: boolean) {
|
||||
module.setHardwareKeyboardSubmitEnabled(enabled);
|
||||
}
|
||||
|
||||
export function addHardwareKeyboardSubmitListener(handler: HardwareKeyboardSubmitHandler) {
|
||||
return module.addListener("onHardwareKeyboardSubmit", handler);
|
||||
}
|
||||
|
||||
export function emitHardwareKeyboardSubmitForTest() {}
|
||||
|
||||
export function resetHardwareKeyboardSubmitForTest() {}
|
||||
|
||||
export function getHardwareKeyboardSubmitEnabledForTest() {
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { EventSubscription } from "expo-modules-core";
|
||||
|
||||
type HardwareKeyboardSubmitHandler = () => void;
|
||||
|
||||
const testHandlers = new Set<HardwareKeyboardSubmitHandler>();
|
||||
let isEnabledForTest = false;
|
||||
|
||||
export function setHardwareKeyboardSubmitEnabled(enabled: boolean) {
|
||||
isEnabledForTest = enabled;
|
||||
}
|
||||
|
||||
export function addHardwareKeyboardSubmitListener(
|
||||
handler: HardwareKeyboardSubmitHandler,
|
||||
): EventSubscription {
|
||||
testHandlers.add(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
testHandlers.delete(handler);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function emitHardwareKeyboardSubmitForTest() {
|
||||
testHandlers.forEach((handler) => handler());
|
||||
}
|
||||
|
||||
export function resetHardwareKeyboardSubmitForTest() {
|
||||
testHandlers.clear();
|
||||
isEnabledForTest = false;
|
||||
}
|
||||
|
||||
export function getHardwareKeyboardSubmitEnabledForTest() {
|
||||
return isEnabledForTest;
|
||||
}
|
||||
Reference in New Issue
Block a user